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 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
@@ -0,0 +1,2 @@
1
+ from . import main
2
+ main()