np2 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- np2/__init__.py +215 -0
- np2/__main__.py +2 -0
- np2/data/commands.json +1207 -0
- np2/data/definitions.json +877 -0
- np2/data/source-command-sheet.txt +601 -0
- np2/data/topics.json +650 -0
- np2/data/unit1.txt +4197 -0
- np2/data/unit2.txt +4348 -0
- np2/data/unit3.txt +3627 -0
- np2/data/unit4.txt +4557 -0
- np2/data/unit5.txt +3956 -0
- np2/data/units.json +42 -0
- np2-1.0.0.dist-info/METADATA +150 -0
- np2-1.0.0.dist-info/RECORD +16 -0
- np2-1.0.0.dist-info/WHEEL +4 -0
- np2-1.0.0.dist-info/entry_points.txt +2 -0
np2/__init__.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Offline IMAP reference. Start with import np2; np2.show()."""
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import pydoc
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__all__ = ["show", "commands", "units", "unit", "topics", "notes", "search", "main"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _json(name):
|
|
13
|
+
return json.loads(files(__package__).joinpath("data", name + ".json").read_text(encoding="utf-8"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _output(text, pager=False, return_text=False):
|
|
17
|
+
if return_text:
|
|
18
|
+
return text
|
|
19
|
+
if pager and sys.stdout.isatty():
|
|
20
|
+
pydoc.pager(text)
|
|
21
|
+
else:
|
|
22
|
+
print(text)
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _unit_number(value):
|
|
27
|
+
if isinstance(value, bool) or str(value) not in {"1", "2", "3", "4", "5"}:
|
|
28
|
+
raise ValueError("Unit must be an integer from 1 to 5.")
|
|
29
|
+
return int(value)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _match(query, *values):
|
|
33
|
+
return query.casefold() in " ".join(str(v) for v in values).casefold()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def commands(group=None, query=None, *, pager=False, return_text=False):
|
|
37
|
+
"""Display command examples and their use cases. Never execute examples."""
|
|
38
|
+
rows = _json("commands")
|
|
39
|
+
groups = list(dict.fromkeys(r["group"] for r in rows))
|
|
40
|
+
aliases = {"pip": "python", "npm": "react", "drf": "django", "postgres": "sql", "aws": "cloud", "ssh": "cloud"}
|
|
41
|
+
if group:
|
|
42
|
+
group = aliases.get(str(group).casefold(), str(group).casefold())
|
|
43
|
+
if group not in groups:
|
|
44
|
+
raise ValueError("Unknown group. Choose: " + ", ".join(groups))
|
|
45
|
+
rows = [r for r in rows if r["group"] == group]
|
|
46
|
+
if query:
|
|
47
|
+
rows = [r for r in rows if _match(query, r["group"], r["command"], r["use"])]
|
|
48
|
+
lines = ["SECTION 1 - COMMANDS AND USE CASES", "Examples only; replace <placeholders>. Commands are never executed.",
|
|
49
|
+
"Shell commands, SQL, ORM expressions and Dockerfile instructions are labelled separately."]
|
|
50
|
+
previous = None
|
|
51
|
+
for r in rows:
|
|
52
|
+
if r["group"] != previous:
|
|
53
|
+
previous = r["group"]
|
|
54
|
+
lines += ["", "[" + previous.upper() + "]"]
|
|
55
|
+
lines += [r["command"], " Use: " + r["use"]]
|
|
56
|
+
if not rows:
|
|
57
|
+
lines.append("No matching commands.")
|
|
58
|
+
return _output("\n".join(lines), pager, return_text)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def units(number=None, query=None, *, pager=False, return_text=False):
|
|
62
|
+
"""Display all concise definitions, optionally filtered to a unit or phrase."""
|
|
63
|
+
number = _unit_number(number) if number is not None else None
|
|
64
|
+
rows = _json("definitions")
|
|
65
|
+
titles = {r["unit"]: r["title"] for r in _json("units")}
|
|
66
|
+
rows = [r for r in rows if (number is None or r["unit"] == number) and
|
|
67
|
+
(not query or _match(query, r["term"], r["definition"]))]
|
|
68
|
+
lines = ["SECTION 2 - UNIT-WISE DEFINITIONS", "Concise revision glossary; use notes() for full source explanations and code."]
|
|
69
|
+
previous = None
|
|
70
|
+
for r in rows:
|
|
71
|
+
if r["unit"] != previous:
|
|
72
|
+
previous = r["unit"]
|
|
73
|
+
lines += ["", f"UNIT {previous}: {titles[previous]}"]
|
|
74
|
+
lines += [r["term"], " " + r["definition"]]
|
|
75
|
+
if not rows:
|
|
76
|
+
lines.append("No matching definitions.")
|
|
77
|
+
return _output("\n".join(lines), pager, return_text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
unit = units
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def topics(number=None, *, pager=False, return_text=False):
|
|
84
|
+
"""List every numbered source subsection and its original PDF page."""
|
|
85
|
+
number = _unit_number(number) if number is not None else None
|
|
86
|
+
rows = [r for r in _json("topics") if number is None or r["unit"] == number]
|
|
87
|
+
text = "SOURCE TOPICS\n" + "\n".join(f'{r["id"]:5} {r["title"]} (PDF p. {r["page"]})' for r in rows)
|
|
88
|
+
return _output(text, pager, return_text)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def notes(number, *, pager=True, return_text=False):
|
|
92
|
+
"""Display the complete extracted chapter for a unit (including examples).
|
|
93
|
+
|
|
94
|
+
Source wording is preserved, not independently fact-checked. PDF extraction
|
|
95
|
+
can flatten tables/code indentation. Original PDF page numbers are retained.
|
|
96
|
+
"""
|
|
97
|
+
number = _unit_number(number)
|
|
98
|
+
text = files(__package__).joinpath("data", f"unit{number}.txt").read_text(encoding="utf-8")
|
|
99
|
+
header = "FULL SOURCE NOTES - UNIT " + str(number) + "\nPDF text extraction; table/code layout may differ.\n"
|
|
100
|
+
return _output(header + text, pager, return_text)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def search(query, *, full=False, pager=False, return_text=False):
|
|
104
|
+
"""Search commands, definitions and topic headings; full=True adds source lines."""
|
|
105
|
+
if not isinstance(query, str) or not query.strip():
|
|
106
|
+
raise ValueError("Provide a non-empty search phrase.")
|
|
107
|
+
parts = []
|
|
108
|
+
for r in _json("commands"):
|
|
109
|
+
if _match(query, r["group"], r["command"], r["use"]):
|
|
110
|
+
parts.append(f'[command/{r["group"]}] {r["command"]}\n {r["use"]}')
|
|
111
|
+
for r in _json("definitions"):
|
|
112
|
+
if _match(query, r["term"], r["definition"]):
|
|
113
|
+
parts.append(f'[unit {r["unit"]}] {r["term"]}\n {r["definition"]}')
|
|
114
|
+
for r in _json("topics"):
|
|
115
|
+
if _match(query, r["title"], r["id"]):
|
|
116
|
+
parts.append(f'[topic {r["id"]}, PDF p. {r["page"]}] {r["title"]}')
|
|
117
|
+
if full:
|
|
118
|
+
for number in range(1, 6):
|
|
119
|
+
page = "?"
|
|
120
|
+
text = files(__package__).joinpath("data", f"unit{number}.txt").read_text(encoding="utf-8")
|
|
121
|
+
for line in text.splitlines():
|
|
122
|
+
if line.startswith("--- PDF page "):
|
|
123
|
+
page = line.split()[3]
|
|
124
|
+
elif _match(query, line):
|
|
125
|
+
parts.append(f'[source unit {number}, PDF p. {page}] {line.strip()}')
|
|
126
|
+
return _output("\n\n".join(parts) if parts else "No matches found.", pager, return_text)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def show(section=None, *, unit=None, group=None, query=None, pager=False, return_text=False):
|
|
130
|
+
"""show(): two-section menu; show(1): commands; show(2): definitions.
|
|
131
|
+
|
|
132
|
+
Examples: show(1, group='docker'); show(2, unit=3); show('all').
|
|
133
|
+
"""
|
|
134
|
+
key = str(section).casefold() if section is not None else None
|
|
135
|
+
if key in {"1", "commands", "command"}:
|
|
136
|
+
if unit is not None:
|
|
137
|
+
raise ValueError("Use unit= with section 2; use group= with section 1.")
|
|
138
|
+
return commands(group, query, pager=pager, return_text=return_text)
|
|
139
|
+
if key in {"2", "units", "unit", "definitions"} or (key is None and unit is not None):
|
|
140
|
+
if group is not None:
|
|
141
|
+
raise ValueError("Use group= with section 1.")
|
|
142
|
+
return units(unit, query, pager=pager, return_text=return_text)
|
|
143
|
+
if key == "all":
|
|
144
|
+
text = commands(group, query, return_text=True) + "\n\n" + units(unit, query, return_text=True)
|
|
145
|
+
return _output(text, pager, return_text)
|
|
146
|
+
if key is None and group is not None:
|
|
147
|
+
return commands(group, query, pager=pager, return_text=return_text)
|
|
148
|
+
if key is None and query:
|
|
149
|
+
return search(query, pager=pager, return_text=return_text)
|
|
150
|
+
if key is not None:
|
|
151
|
+
raise ValueError("Section must be 1/'commands', 2/'units', or 'all'.")
|
|
152
|
+
counts = (len(_json("commands")), len(_json("definitions")), len(_json("topics")))
|
|
153
|
+
menu = f'''NP2 - OFFLINE IMAP REVISION ({__version__})
|
|
154
|
+
{counts[0]} command examples | {counts[1]} definitions | {counts[2]} source topics | 5 units
|
|
155
|
+
|
|
156
|
+
1. COMMANDS + USE CASES
|
|
157
|
+
np2.show(1) All commands, grouped by tool
|
|
158
|
+
np2.show(1, group="git") Git commands
|
|
159
|
+
np2.show(1, group="docker") Docker commands
|
|
160
|
+
np2.show(1, group="compose") Compose commands
|
|
161
|
+
np2.show(1, query="migrate") Filter command examples
|
|
162
|
+
|
|
163
|
+
2. UNIT-WISE DEFINITIONS
|
|
164
|
+
np2.show(2) Definitions for all five units
|
|
165
|
+
np2.show(2, unit=1) Definitions for Unit 1
|
|
166
|
+
np2.unit(4) Shortcut for Unit 4
|
|
167
|
+
np2.topics(4) Source subsection/page index
|
|
168
|
+
np2.notes(4) Full Unit 4 text and code
|
|
169
|
+
|
|
170
|
+
np2.search("promise") Search both sections
|
|
171
|
+
np2.search("promise", full=True) Also search complete unit text
|
|
172
|
+
np2.show("all", pager=True) Read both sections with paging
|
|
173
|
+
|
|
174
|
+
Units: 1 Web/Git | 2 Python/Django | 3 Databases/Cloud | 4 React | 5 Docker
|
|
175
|
+
Use import np2 as NP2 if you prefer NP2.show().
|
|
176
|
+
This is an offline study reference. It does not run the displayed commands.'''
|
|
177
|
+
return _output(menu, pager, return_text)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main(argv=None):
|
|
181
|
+
"""Command-line interface: np2 commands docker; np2 unit 1; np2 search hooks."""
|
|
182
|
+
parser = argparse.ArgumentParser(prog="np2", description="Offline IMAP study reference")
|
|
183
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
184
|
+
parser.add_argument("--pager", action="store_true", help="Page output in an interactive terminal")
|
|
185
|
+
sub = parser.add_subparsers(dest="action")
|
|
186
|
+
cmd = sub.add_parser("commands", help="Section 1: commands with use cases")
|
|
187
|
+
cmd.add_argument("group", nargs="?")
|
|
188
|
+
cmd.add_argument("--query")
|
|
189
|
+
unt = sub.add_parser("unit", aliases=["units"], help="Section 2: unit definitions")
|
|
190
|
+
unt.add_argument("number", nargs="?", type=int)
|
|
191
|
+
unt.add_argument("--query")
|
|
192
|
+
top = sub.add_parser("topics", help="Source subsection index")
|
|
193
|
+
top.add_argument("number", nargs="?", type=int)
|
|
194
|
+
note = sub.add_parser("notes", help="Complete source chapter")
|
|
195
|
+
note.add_argument("number", type=int)
|
|
196
|
+
find = sub.add_parser("search", help="Search all reference entries")
|
|
197
|
+
find.add_argument("query")
|
|
198
|
+
find.add_argument("--full", action="store_true")
|
|
199
|
+
sub.add_parser("all", help="Show both complete sections")
|
|
200
|
+
args = parser.parse_args(argv)
|
|
201
|
+
try:
|
|
202
|
+
if args.action == "commands":
|
|
203
|
+
commands(args.group, args.query, pager=args.pager)
|
|
204
|
+
elif args.action in {"unit", "units"}:
|
|
205
|
+
units(args.number, args.query, pager=args.pager)
|
|
206
|
+
elif args.action == "topics":
|
|
207
|
+
topics(args.number, pager=args.pager)
|
|
208
|
+
elif args.action == "notes":
|
|
209
|
+
notes(args.number, pager=args.pager)
|
|
210
|
+
elif args.action == "search":
|
|
211
|
+
search(args.query, full=args.full, pager=args.pager)
|
|
212
|
+
else:
|
|
213
|
+
show("all" if args.action == "all" else None, pager=args.pager)
|
|
214
|
+
except ValueError as error:
|
|
215
|
+
parser.error(str(error))
|
np2/__main__.py
ADDED