primarysource 0.1.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.
- primarysource/__init__.py +5 -0
- primarysource/bbf/__init__.py +56 -0
- primarysource/bbf/_xml.py +63 -0
- primarysource/bbf/catalogue.json +777 -0
- primarysource/bbf/catalogue.py +137 -0
- primarysource/bbf/cli.py +79 -0
- primarysource/bbf/datamodel.py +281 -0
- primarysource/bbf/importer.py +151 -0
- primarysource/bbf/library.py +380 -0
- primarysource/bbf/server.py +72 -0
- primarysource/budget.py +63 -0
- primarysource/c2sp/__init__.py +53 -0
- primarysource/c2sp/_fetch.py +5 -0
- primarysource/c2sp/catalogue.json +231 -0
- primarysource/c2sp/catalogue.py +95 -0
- primarysource/c2sp/cli.py +62 -0
- primarysource/c2sp/importer.py +90 -0
- primarysource/c2sp/library.py +309 -0
- primarysource/c2sp/markdown.py +31 -0
- primarysource/c2sp/server.py +68 -0
- primarysource/cavp/__init__.py +42 -0
- primarysource/cavp/cli.py +84 -0
- primarysource/cavp/data/vectors.toml +47 -0
- primarysource/cavp/importer.py +156 -0
- primarysource/cavp/model.py +279 -0
- primarysource/cavp/parse.py +269 -0
- primarysource/cavp/paths.py +17 -0
- primarysource/cavp/server.py +67 -0
- primarysource/cavp/tools.py +169 -0
- primarysource/cli.py +107 -0
- primarysource/cursor.py +82 -0
- primarysource/cve/__init__.py +5 -0
- primarysource/cve/__main__.py +6 -0
- primarysource/cve/cli.py +144 -0
- primarysource/cve/collection.py +20 -0
- primarysource/cve/config.py +58 -0
- primarysource/cve/errors.py +16 -0
- primarysource/cve/kernel.py +232 -0
- primarysource/cve/mcp.py +139 -0
- primarysource/cve/parse.py +318 -0
- primarysource/cve/py.typed +0 -0
- primarysource/cve/service.py +760 -0
- primarysource/cve/store.py +214 -0
- primarysource/cve/sync.py +438 -0
- primarysource/cve/views.py +304 -0
- primarysource/debpolicy/__init__.py +16 -0
- primarysource/debpolicy/catalogue.py +263 -0
- primarysource/debpolicy/cli.py +12 -0
- primarysource/debpolicy/server.py +11 -0
- primarysource/draft/__init__.py +11 -0
- primarysource/draft/catalogue.py +117 -0
- primarysource/draft/cli.py +149 -0
- primarysource/draft/collection.py +21 -0
- primarysource/draft/datatracker.py +158 -0
- primarysource/draft/importer.py +163 -0
- primarysource/draft/licence.py +89 -0
- primarysource/draft/names.py +64 -0
- primarysource/draft/net.py +133 -0
- primarysource/draft/segment.py +192 -0
- primarysource/draft/server.py +91 -0
- primarysource/draft/status.py +146 -0
- primarysource/draft/store.py +199 -0
- primarysource/draft/tracked.json +104 -0
- primarysource/draft/views.py +430 -0
- primarysource/draft/xmlmeta.py +74 -0
- primarysource/ecma/__init__.py +52 -0
- primarysource/ecma/_pdf.py +143 -0
- primarysource/ecma/catalogue.json +22 -0
- primarysource/ecma/catalogue.py +100 -0
- primarysource/ecma/cli.py +67 -0
- primarysource/ecma/identify.py +25 -0
- primarysource/ecma/importer.py +108 -0
- primarysource/ecma/library.py +240 -0
- primarysource/ecma/server.py +67 -0
- primarysource/errors.py +29 -0
- primarysource/fetch.py +360 -0
- primarysource/fsutil.py +150 -0
- primarysource/gitdocs/__init__.py +15 -0
- primarysource/gitdocs/catalogue.py +116 -0
- primarysource/gitdocs/cli.py +12 -0
- primarysource/gitdocs/server.py +8 -0
- primarysource/home.py +65 -0
- primarysource/iana/__init__.py +32 -0
- primarysource/iana/catalogue.json +244 -0
- primarysource/iana/catalogue.py +69 -0
- primarysource/iana/cli.py +69 -0
- primarysource/iana/importer.py +157 -0
- primarysource/iana/model.py +325 -0
- primarysource/iana/reader.py +315 -0
- primarysource/iana/server.py +67 -0
- primarysource/iana/store.py +83 -0
- primarysource/iana/xmlsafe.py +47 -0
- primarysource/identify.py +186 -0
- primarysource/ieee/__init__.py +58 -0
- primarysource/ieee/_html.py +49 -0
- primarysource/ieee/catalogue.json +74 -0
- primarysource/ieee/catalogue.py +123 -0
- primarysource/ieee/cli.py +79 -0
- primarysource/ieee/identify.py +24 -0
- primarysource/ieee/ieee802.py +47 -0
- primarysource/ieee/importer.py +305 -0
- primarysource/ieee/posix.py +233 -0
- primarysource/ieee/server.py +79 -0
- primarysource/index.py +446 -0
- primarysource/iso/__init__.py +51 -0
- primarysource/iso/cli.py +98 -0
- primarysource/iso/designation.py +95 -0
- primarysource/iso/identify.py +100 -0
- primarysource/iso/importer.py +224 -0
- primarysource/iso/library.py +249 -0
- primarysource/iso/server.py +69 -0
- primarysource/itu/__init__.py +41 -0
- primarysource/itu/__main__.py +8 -0
- primarysource/itu/asn1mod.py +478 -0
- primarysource/itu/catalog.py +472 -0
- primarysource/itu/cli.py +445 -0
- primarysource/itu/collection.py +20 -0
- primarysource/itu/corpus.py +118 -0
- primarysource/itu/errata.py +16 -0
- primarysource/itu/formal.py +565 -0
- primarysource/itu/identify.py +26 -0
- primarysource/itu/index.py +490 -0
- primarysource/itu/mcp.py +474 -0
- primarysource/itu/py.typed +0 -0
- primarysource/itu/sources.py +600 -0
- primarysource/itu/text.py +609 -0
- primarysource/kconfig/__init__.py +16 -0
- primarysource/kconfig/catalogue.py +98 -0
- primarysource/kconfig/cli.py +12 -0
- primarysource/kconfig/server.py +8 -0
- primarysource/model.py +63 -0
- primarysource/nist/__init__.py +5 -0
- primarysource/nist/collection.py +46 -0
- primarysource/nist/data/acquired-nist.json +1657 -0
- primarysource/nist/data/nist-catalog.json +12090 -0
- primarysource/nist/data/sources.toml +1996 -0
- primarysource/nist/identify.py +41 -0
- primarysource/nist/ingest/__init__.py +1 -0
- primarysource/nist/ingest/acquire_nist.py +128 -0
- primarysource/nist/ingest/controls.py +82 -0
- primarysource/nist/ingest/discover_nist.py +218 -0
- primarysource/nist/ingest/extract.py +48 -0
- primarysource/nist/ingest/fetch.py +211 -0
- primarysource/nist/ingest/index.py +14 -0
- primarysource/nist/ingest/oscal.py +480 -0
- primarysource/nist/ingest/prose_layouts.py +236 -0
- primarysource/nist/ingest/sources.py +94 -0
- primarysource/nist/ingest/status.py +255 -0
- primarysource/nist/models.py +7 -0
- primarysource/nist/paths.py +33 -0
- primarysource/nist/server/__init__.py +3 -0
- primarysource/nist/server/corpus.py +38 -0
- primarysource/nist/server/mcp.py +19 -0
- primarysource/nist/server/tools.py +35 -0
- primarysource/nist/spec.py +38 -0
- primarysource/pdfcorpus/__init__.py +16 -0
- primarysource/pdfcorpus/corpus.py +159 -0
- primarysource/pdfcorpus/fts.py +175 -0
- primarysource/pdfcorpus/index.py +70 -0
- primarysource/pdfcorpus/models.py +30 -0
- primarysource/pdfcorpus/server.py +96 -0
- primarysource/pdfcorpus/spec.py +37 -0
- primarysource/pdfcorpus/split.py +391 -0
- primarysource/pdfcorpus/tools.py +244 -0
- primarysource/pep/__init__.py +15 -0
- primarysource/pep/catalogue.py +191 -0
- primarysource/pep/cli.py +12 -0
- primarysource/pep/server.py +8 -0
- primarysource/pkgdocs/__init__.py +19 -0
- primarysource/pkgdocs/fetch.py +356 -0
- primarysource/pkgdocs/index.py +89 -0
- primarysource/pkgdocs/model.py +76 -0
- primarysource/pkgdocs/readers/__init__.py +1 -0
- primarysource/pkgdocs/readers/_common.py +134 -0
- primarysource/pkgdocs/readers/asciidoc.py +160 -0
- primarysource/pkgdocs/readers/latex.py +315 -0
- primarysource/pkgdocs/readers/markdown.py +173 -0
- primarysource/pkgdocs/readers/pod.py +132 -0
- primarysource/pkgdocs/readers/rst.py +239 -0
- primarysource/pkgdocs/readers/scdoc.py +108 -0
- primarysource/pkgdocs/runtime.py +552 -0
- primarysource/pkgdocs/serving.py +158 -0
- primarysource/pkgdocs/store.py +167 -0
- primarysource/pms/__init__.py +16 -0
- primarysource/pms/catalogue.py +88 -0
- primarysource/pms/cli.py +12 -0
- primarysource/pms/server.py +8 -0
- primarysource/py.typed +0 -0
- primarysource/readers/__init__.py +10 -0
- primarysource/readers/bookmarks.py +192 -0
- primarysource/readers/html.py +1312 -0
- primarysource/readers/markdown.py +189 -0
- primarysource/readers/pdf.py +187 -0
- primarysource/readers/xml.py +230 -0
- primarysource/refs.py +87 -0
- primarysource/registry.py +161 -0
- primarysource/render.py +49 -0
- primarysource/rfc/__init__.py +3 -0
- primarysource/rfc/__main__.py +6 -0
- primarysource/rfc/cite.py +204 -0
- primarysource/rfc/cli.py +903 -0
- primarysource/rfc/collection.py +19 -0
- primarysource/rfc/config.py +138 -0
- primarysource/rfc/errata.py +203 -0
- primarysource/rfc/fsutil.py +82 -0
- primarysource/rfc/graph.py +213 -0
- primarysource/rfc/index/__init__.py +1 -0
- primarysource/rfc/index/chunker.py +281 -0
- primarysource/rfc/index/embed.py +166 -0
- primarysource/rfc/index/hybrid.py +305 -0
- primarysource/rfc/index/lexical.py +223 -0
- primarysource/rfc/index/rfcxml.py +673 -0
- primarysource/rfc/index/search.py +508 -0
- primarysource/rfc/index/txtseg.py +585 -0
- primarysource/rfc/library.py +294 -0
- primarysource/rfc/mcp/__init__.py +1 -0
- primarysource/rfc/mcp/server.py +381 -0
- primarysource/rfc/packs/__init__.py +1 -0
- primarysource/rfc/packs/builder.py +228 -0
- primarysource/rfc/packs/collections/all-current-txt.json +10 -0
- primarysource/rfc/packs/collections/essentials.json +19 -0
- primarysource/rfc/packs/collections/fyi-current.json +10 -0
- primarysource/rfc/packs/collections/standards-current.json +10 -0
- primarysource/rfc/packs/collections/year-2025.json +10 -0
- primarysource/rfc/packs/collections/year-2026.json +10 -0
- primarysource/rfc/packs/collections.py +163 -0
- primarysource/rfc/packs/docsetexport.py +163 -0
- primarysource/rfc/packs/epub.py +159 -0
- primarysource/rfc/packs/export.py +306 -0
- primarysource/rfc/packs/htmlexport.py +82 -0
- primarysource/rfc/packs/serve.py +143 -0
- primarysource/rfc/packs/zimexport.py +171 -0
- primarysource/rfc/query.py +466 -0
- primarysource/rfc/subjects.py +457 -0
- primarysource/rfc/sync/__init__.py +1 -0
- primarysource/rfc/sync/downloader.py +132 -0
- primarysource/rfc/sync/manifest.py +162 -0
- primarysource/rfc/sync/scanner.py +49 -0
- primarysource/rfc/sync/state.py +464 -0
- primarysource/rfc/views.py +1067 -0
- primarysource/rpmdocs/__init__.py +15 -0
- primarysource/rpmdocs/catalogue.py +117 -0
- primarysource/rpmdocs/cli.py +12 -0
- primarysource/rpmdocs/server.py +8 -0
- primarysource/scaffold.py +223 -0
- primarysource/serve.py +75 -0
- primarysource/settings.py +152 -0
- primarysource/usb/__init__.py +5 -0
- primarysource/usb/collection.py +37 -0
- primarysource/usb/data/acquired.json +2025 -0
- primarysource/usb/data/sources-usb.toml +852 -0
- primarysource/usb/data/sources.toml +10 -0
- primarysource/usb/data/usb-catalog.json +1663 -0
- primarysource/usb/data/usb-classification.json +1986 -0
- primarysource/usb/data/usb-corpus-build.json +608 -0
- primarysource/usb/data/usb-pdf-choice.json +839 -0
- primarysource/usb/data/usb-split-measure.json +1803 -0
- primarysource/usb/ingest/__init__.py +1 -0
- primarysource/usb/ingest/acquire.py +130 -0
- primarysource/usb/ingest/discover_usb.py +88 -0
- primarysource/usb/ingest/extract.py +48 -0
- primarysource/usb/ingest/fetch.py +197 -0
- primarysource/usb/ingest/index.py +14 -0
- primarysource/usb/ingest/sources.py +85 -0
- primarysource/usb/models.py +7 -0
- primarysource/usb/paths.py +33 -0
- primarysource/usb/server/__init__.py +3 -0
- primarysource/usb/server/corpus.py +38 -0
- primarysource/usb/server/mcp.py +19 -0
- primarysource/usb/server/tools.py +35 -0
- primarysource/usb/spec.py +20 -0
- primarysource/usbids/__init__.py +56 -0
- primarysource/usbids/cli.py +65 -0
- primarysource/usbids/importer.py +272 -0
- primarysource/usbids/parse.py +283 -0
- primarysource/usbids/server.py +55 -0
- primarysource/usbids/store.py +80 -0
- primarysource/usbids/tools.py +174 -0
- primarysource/zim/__init__.py +5 -0
- primarysource/zim/__main__.py +8 -0
- primarysource/zim/_libzim.py +37 -0
- primarysource/zim/catalog.py +594 -0
- primarysource/zim/cli.py +555 -0
- primarysource/zim/collection.py +41 -0
- primarysource/zim/errors.py +100 -0
- primarysource/zim/groups.py +30 -0
- primarysource/zim/library.py +668 -0
- primarysource/zim/manage.py +636 -0
- primarysource/zim/markdown.py +1247 -0
- primarysource/zim/mcp.py +201 -0
- primarysource/zim/purl.py +130 -0
- primarysource/zim/py.typed +0 -0
- primarysource/zim/refs.py +90 -0
- primarysource/zim/service.py +1182 -0
- primarysource/zim/snapshot.py +197 -0
- primarysource/zim/sync.py +269 -0
- primarysource/zim/views.py +230 -0
- primarysource-0.1.0.dist-info/METADATA +118 -0
- primarysource-0.1.0.dist-info/RECORD +303 -0
- primarysource-0.1.0.dist-info/WHEEL +4 -0
- primarysource-0.1.0.dist-info/entry_points.txt +2 -0
- primarysource-0.1.0.dist-info/licenses/LICENSE +203 -0
- primarysource-0.1.0.dist-info/licenses/NOTICE +51 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""The text of a result: `service`'s payload as `render()` text with the core's fixed footer (PLAN.md §5.9).
|
|
2
|
+
|
|
3
|
+
`service` answers in dictionaries, which the CLI's `--json` prints as they are. The MCP tools and the CLI's human
|
|
4
|
+
output show the same text instead: the passage or the rows first, then footer lines in the core's fixed order
|
|
5
|
+
(`cite`, `unit`, `span`, `next`, `relaxed`, `confidence`, `warning`). What a JSON result carried as fields is a
|
|
6
|
+
footer line where it is a fact about the whole result, and part of a row where it belongs to one hit.
|
|
7
|
+
|
|
8
|
+
`next:` is exactly what to pass back (PLAN.md §5.11): a ref for a read, `start=<n>` for the same list tool.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from primarysource.render import next_start, render
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _age(fresh: dict[str, Any]) -> str:
|
|
19
|
+
if fresh.get("age_days") is None:
|
|
20
|
+
return "undated"
|
|
21
|
+
return f"{fresh['date']}, {fresh['age_days']} days old" + (", STALE" if fresh.get("stale") else "")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _stale(fresh: dict[str, Any], what: str = "the ZIM") -> str:
|
|
25
|
+
"""The warning for a ZIM past `library.STALE_DAYS`: a snapshot, so version details may have moved on."""
|
|
26
|
+
if fresh.get("stale"):
|
|
27
|
+
return f"{what} is a snapshot from {fresh['date']}, {fresh['age_days']} days old; check version details"
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _warn(*parts: str) -> str:
|
|
32
|
+
"""One footer line: the non-empty parts, joined (a footer value is one line)."""
|
|
33
|
+
return "; ".join(" ".join(part.split()) for part in parts if part)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _clamped(payload: dict[str, Any]) -> str:
|
|
37
|
+
clamped = payload.get("clamped")
|
|
38
|
+
if not clamped:
|
|
39
|
+
return ""
|
|
40
|
+
return "limits were lowered: " + ", ".join(f"{name} to {value}" for name, value in clamped.items())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _row(row: dict[str, Any]) -> str:
|
|
44
|
+
"""One library as a line: name, title, then what choosing one needs."""
|
|
45
|
+
bits = [row["search"], f"{row['articles']} articles", f"{row['size_bytes'] / 1e6:.1f} MB", _age(row["freshness"])]
|
|
46
|
+
if row.get("version") is not None and not isinstance(row["version"], dict):
|
|
47
|
+
bits.append(f"version {row['version']}")
|
|
48
|
+
label = f"{row['family']} [{', '.join(row['versions'])}]" if row.get("versions") else row["name"]
|
|
49
|
+
line = f"{label} {row['title']} [{'; '.join(bits)}]"
|
|
50
|
+
extra = []
|
|
51
|
+
if row.get("other_editions"):
|
|
52
|
+
extra.append(f"other editions: {', '.join(row['other_editions'])}")
|
|
53
|
+
if row.get("provenance") and isinstance(row["provenance"], str):
|
|
54
|
+
extra.append(f"provenance: {row['provenance']}")
|
|
55
|
+
if row.get("match"):
|
|
56
|
+
extra.append(f"match: {row['match']}")
|
|
57
|
+
if row.get("description"):
|
|
58
|
+
extra.append(f"description: {row['description']}")
|
|
59
|
+
if row.get("creator"):
|
|
60
|
+
extra.append(f"creator: {row['creator']}")
|
|
61
|
+
if row.get("tags"):
|
|
62
|
+
extra.append(f"tags: {', '.join(row['tags'])}")
|
|
63
|
+
if "license" in row:
|
|
64
|
+
extra.append(f"licence: {row['license'] or 'not stated in the ZIM'}")
|
|
65
|
+
if isinstance(row.get("provenance"), dict):
|
|
66
|
+
record = row["provenance"]
|
|
67
|
+
extra.append(
|
|
68
|
+
f"provenance: {record['state']}" + (f", from {record['source']}" if record.get("source") else "")
|
|
69
|
+
)
|
|
70
|
+
return "\n".join([line, *(f" {item}" for item in extra)])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def libraries(payload: dict[str, Any]) -> str:
|
|
74
|
+
rows = payload["libraries"]
|
|
75
|
+
if not rows:
|
|
76
|
+
body = f"no installed library matches {payload.get('query', '')!r}"
|
|
77
|
+
else:
|
|
78
|
+
body = "\n".join(_row(row) for row in rows)
|
|
79
|
+
problems = payload.get("problems") or []
|
|
80
|
+
warning = _warn(
|
|
81
|
+
f"{len(problems)} ZIM file(s) could not be read: " + "; ".join(f"{p['path']}: {p['error']}" for p in problems)
|
|
82
|
+
if problems
|
|
83
|
+
else "",
|
|
84
|
+
_clamped(payload),
|
|
85
|
+
)
|
|
86
|
+
head = f"{payload['total']} installed ({payload['directory']})"
|
|
87
|
+
return render(f"{head}\n{body}", {"next": next_start(payload.get("next")), "warning": warning})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def resolve(payload: dict[str, Any]) -> str:
|
|
91
|
+
version = payload["version"]
|
|
92
|
+
lines = [_row({**payload, "version": None})]
|
|
93
|
+
if "upstream" in version:
|
|
94
|
+
others = f"; also installed: {', '.join(payload['other_versions'])}" if payload.get("other_versions") else ""
|
|
95
|
+
lines.append(f" upstream version: {version['upstream']} ({version['selected']}){others}")
|
|
96
|
+
else:
|
|
97
|
+
lines.append(f" version: {version['note']}")
|
|
98
|
+
return render("\n".join(lines), {"warning": _stale(payload["freshness"])})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _hit(hit: dict[str, Any]) -> str:
|
|
102
|
+
tags = [hit["match"]] + ([f"{hit['blocks']} blocks"] if "blocks" in hit else [])
|
|
103
|
+
if hit.get("language"):
|
|
104
|
+
tags.append(f"language {hit['language']}")
|
|
105
|
+
lines = [f"{hit['ref']} {hit['title']} [{'; '.join(tags)}]"]
|
|
106
|
+
if hit.get("excerpt"):
|
|
107
|
+
lines.append(f" {' '.join(hit['excerpt'].split())}")
|
|
108
|
+
if hit.get("note"):
|
|
109
|
+
lines.append(f" {hit['note']}")
|
|
110
|
+
if hit.get("section"):
|
|
111
|
+
section = hit["section"]
|
|
112
|
+
lines.append(f" best section: {section['ref']} {section['heading']} [{section['blocks']} blocks]")
|
|
113
|
+
return "\n".join(lines)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _obtainable(payload: dict[str, Any]) -> list[str]:
|
|
117
|
+
"""What the offline catalogue snapshot says could be installed, as lines a model relays to the user."""
|
|
118
|
+
lines = []
|
|
119
|
+
if payload.get("obtainable"):
|
|
120
|
+
age = payload.get("snapshot_age_days")
|
|
121
|
+
lines.append(f"not installed, but in the catalogue snapshot ({age} days old); give the user the command:")
|
|
122
|
+
lines += [f" {row['name']} {row['title']} -> {row['install']}" for row in payload["obtainable"]]
|
|
123
|
+
elif payload.get("user_action"):
|
|
124
|
+
lines.append(f"{payload['user_action']}")
|
|
125
|
+
return lines
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def search(payload: dict[str, Any]) -> str:
|
|
129
|
+
hits = payload["hits"]
|
|
130
|
+
searched = ", ".join(
|
|
131
|
+
f"{name} (ZIM edition {payload['editions'].get(name) or 'undated'})" for name in payload["libraries_searched"]
|
|
132
|
+
)
|
|
133
|
+
lines = [f"searched: {searched}"]
|
|
134
|
+
lines += [_hit(hit) for hit in hits] if hits else [payload.get("note", "no entry matched")]
|
|
135
|
+
if hits and payload.get("note"):
|
|
136
|
+
lines.append(payload["note"])
|
|
137
|
+
for key in ("title_only", "ranking"):
|
|
138
|
+
if payload.get(key):
|
|
139
|
+
lines.append(f"({payload[key]})")
|
|
140
|
+
lines += _obtainable(payload)
|
|
141
|
+
partial = [hit for hit in hits if hit["match"] == "some terms"]
|
|
142
|
+
relaxed = f"{len(partial)} of {len(hits)} hit(s) matched only some of the words" if partial else ""
|
|
143
|
+
low = "low: no hit matched all the words, so the topic may not be covered; do not answer from these hits"
|
|
144
|
+
footer = {
|
|
145
|
+
"next": next_start(payload.get("next")),
|
|
146
|
+
"relaxed": relaxed,
|
|
147
|
+
"confidence": low if payload.get("confidence") == "low" and hits else "",
|
|
148
|
+
"warning": _clamped(payload),
|
|
149
|
+
}
|
|
150
|
+
return render("\n".join(lines), footer)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def read(payload: dict[str, Any]) -> str:
|
|
154
|
+
blocks = payload["blocks"]
|
|
155
|
+
left = f"; {payload['remaining_tokens']} tokens left" if payload.get("remaining_tokens") else ""
|
|
156
|
+
title = payload.get("page_title") or payload["title"]
|
|
157
|
+
head = f"{title} [¶{blocks['from']}-{blocks['to']} of {blocks['of']}{left}]"
|
|
158
|
+
if payload.get("redirected_from"):
|
|
159
|
+
head += f" (redirected from {payload['redirected_from']})"
|
|
160
|
+
warning = _warn(
|
|
161
|
+
_stale(payload["freshness"]),
|
|
162
|
+
payload.get("oversize", ""),
|
|
163
|
+
payload.get("converted_truncated", ""),
|
|
164
|
+
payload.get("images", ""),
|
|
165
|
+
_clamped(payload),
|
|
166
|
+
)
|
|
167
|
+
footer = {"cite": payload["cite"], "unit": "block", "next": payload.get("next") or "", "warning": warning}
|
|
168
|
+
return render(f"{head}\n\n{payload['content']}", footer)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def outline(payload: dict[str, Any]) -> str:
|
|
172
|
+
footer = {"next": next_start(payload.get("next")), "warning": _clamped(payload)}
|
|
173
|
+
if "outline" in payload:
|
|
174
|
+
rows = payload["outline"]
|
|
175
|
+
lines = [f"{payload['ref']} {payload['title']} [{payload['total']} rows]"]
|
|
176
|
+
for row in rows:
|
|
177
|
+
indent = " " * (int(row.get("level") or 4) - 1)
|
|
178
|
+
anchor = f" #{row['anchor']}" if row.get("anchor") else ""
|
|
179
|
+
kind = " (definition)" if row.get("kind") else ""
|
|
180
|
+
lines.append(f"{indent}{row['text']}{anchor}{kind}")
|
|
181
|
+
if not rows:
|
|
182
|
+
lines.append("no headings or definitions")
|
|
183
|
+
return render("\n".join(lines), footer)
|
|
184
|
+
footer["warning"] = _warn(_stale(payload["freshness"]), footer["warning"])
|
|
185
|
+
lines = [f"{row['ref']} {row['title']}" for row in payload["entries"]]
|
|
186
|
+
if not lines:
|
|
187
|
+
lines = [f"no entries in {payload['library']} match"]
|
|
188
|
+
return render("\n".join(lines), footer)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def links(payload: dict[str, Any]) -> str:
|
|
192
|
+
lines = [f"{payload['ref']} [{payload['total']} links]"]
|
|
193
|
+
for row in payload["links"]:
|
|
194
|
+
target = row.get("url") or row["ref"] + ("" if row.get("in_zim") else " (not in this ZIM)")
|
|
195
|
+
lines.append(f"{row['text']} -> {target}")
|
|
196
|
+
if not payload["links"]:
|
|
197
|
+
lines.append("no links")
|
|
198
|
+
return render("\n".join(lines), {"next": next_start(payload.get("next")), "warning": _clamped(payload)})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def cite(payload: dict[str, Any]) -> str:
|
|
202
|
+
lines = [payload["cite"], f"licence: {payload['license']}"]
|
|
203
|
+
if payload.get("creator"):
|
|
204
|
+
lines.append(f"creator: {payload['creator']}")
|
|
205
|
+
record = payload["provenance"]
|
|
206
|
+
lines.append(f"ZIM {payload['uuid']}; provenance: {record['state']}")
|
|
207
|
+
footer = {
|
|
208
|
+
"cite": payload["cite"],
|
|
209
|
+
"unit": "block" if "span" in payload else "",
|
|
210
|
+
"span": payload.get("span", ""),
|
|
211
|
+
"warning": _stale(payload["freshness"]),
|
|
212
|
+
}
|
|
213
|
+
return render("\n".join(lines), footer)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def available(payload: dict[str, Any]) -> str:
|
|
217
|
+
snap = payload["snapshot"]
|
|
218
|
+
lines = []
|
|
219
|
+
for row in payload["available"]:
|
|
220
|
+
state = f"INSTALLED {row['installed_edition']}" if row["installed"] else "not installed"
|
|
221
|
+
lines.append(
|
|
222
|
+
f"{row['name']} {row['title']} [{row['date']}; ~{row['approx_mb']} MB; {row['articles']} articles; "
|
|
223
|
+
f"{state}]\n install: {row['install']}"
|
|
224
|
+
)
|
|
225
|
+
if not payload["available"]:
|
|
226
|
+
lines.append(f"nothing in the snapshot matches {payload['query']!r}")
|
|
227
|
+
lines.append(
|
|
228
|
+
f"({payload['matches']} matches; snapshot of {snap['fetched_at']}, {snap['age_days']} days old, offline)"
|
|
229
|
+
)
|
|
230
|
+
return render("\n".join(lines), {"next": next_start(payload.get("next"))})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: primarysource
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Citable primary-source libraries for agents: RFCs, NIST, USB-IF, ITU-T, Kiwix and CVE collections behind one core (ref grammar, token-budgeted paging, full-text index, CLI, MCP server).
|
|
5
|
+
Author: Justin Capella
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
License-File: NOTICE
|
|
9
|
+
Keywords: citation,documents,full-text-search,mcp,model-context-protocol,standards
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Provides-Extra: bbf
|
|
23
|
+
Requires-Dist: pypdf>=5; extra == 'bbf'
|
|
24
|
+
Provides-Extra: c2sp
|
|
25
|
+
Provides-Extra: cavp
|
|
26
|
+
Provides-Extra: cve
|
|
27
|
+
Provides-Extra: debpolicy
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: mypy>=1.20; extra == 'dev'
|
|
30
|
+
Requires-Dist: packaging>=24; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
33
|
+
Provides-Extra: draft
|
|
34
|
+
Provides-Extra: ecma
|
|
35
|
+
Requires-Dist: pypdf>=5; extra == 'ecma'
|
|
36
|
+
Provides-Extra: gitdocs
|
|
37
|
+
Provides-Extra: iana
|
|
38
|
+
Provides-Extra: ieee
|
|
39
|
+
Requires-Dist: pypdf>=5; extra == 'ieee'
|
|
40
|
+
Provides-Extra: iso
|
|
41
|
+
Requires-Dist: pypdf>=5; extra == 'iso'
|
|
42
|
+
Provides-Extra: itu
|
|
43
|
+
Requires-Dist: pypdf>=5; extra == 'itu'
|
|
44
|
+
Provides-Extra: kconfig
|
|
45
|
+
Provides-Extra: mcp
|
|
46
|
+
Requires-Dist: mcp>=2; extra == 'mcp'
|
|
47
|
+
Provides-Extra: nist
|
|
48
|
+
Provides-Extra: pep
|
|
49
|
+
Provides-Extra: pms
|
|
50
|
+
Provides-Extra: rfc
|
|
51
|
+
Requires-Dist: httpx>=0.27; extra == 'rfc'
|
|
52
|
+
Requires-Dist: rich>=13; extra == 'rfc'
|
|
53
|
+
Requires-Dist: typer>=0.12; extra == 'rfc'
|
|
54
|
+
Provides-Extra: rfc-semantic
|
|
55
|
+
Requires-Dist: httpx>=0.27; extra == 'rfc-semantic'
|
|
56
|
+
Requires-Dist: model2vec>=0.3; extra == 'rfc-semantic'
|
|
57
|
+
Requires-Dist: rich>=13; extra == 'rfc-semantic'
|
|
58
|
+
Requires-Dist: sqlite-vec; extra == 'rfc-semantic'
|
|
59
|
+
Requires-Dist: typer>=0.12; extra == 'rfc-semantic'
|
|
60
|
+
Provides-Extra: rfc-zim
|
|
61
|
+
Requires-Dist: httpx>=0.27; extra == 'rfc-zim'
|
|
62
|
+
Requires-Dist: libzim>=3.7; extra == 'rfc-zim'
|
|
63
|
+
Requires-Dist: rich>=13; extra == 'rfc-zim'
|
|
64
|
+
Requires-Dist: typer>=0.12; extra == 'rfc-zim'
|
|
65
|
+
Provides-Extra: rpmdocs
|
|
66
|
+
Provides-Extra: usb
|
|
67
|
+
Provides-Extra: usbids
|
|
68
|
+
Provides-Extra: zim
|
|
69
|
+
Requires-Dist: libzim>=4.1; (sys_platform != 'win32' or platform_machine != 'ARM64') and extra == 'zim'
|
|
70
|
+
Description-Content-Type: text/markdown
|
|
71
|
+
|
|
72
|
+
# primarysource
|
|
73
|
+
|
|
74
|
+
A primary source is the text itself: answer from it, and say exactly where it is.
|
|
75
|
+
|
|
76
|
+
One package, many collections. The core is a sectioned-document model, a ref grammar, token-budgeted paging, a fixed result shape, readers, a full-text index, a CLI and an MCP server. Each collection is a subpackage behind an extra: `rfc` (the RFC series, moved in from rfclib), `nist`, `usb`, `itu`, `zim`, `cve`, and more. `PLAN.md` has the design and `ROADMAP.md` the open work.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
pip install "primarysource[rfc,mcp]"
|
|
80
|
+
primarysource collections
|
|
81
|
+
primarysource mcp --collection rfc
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Pre-alpha. It ships no document text: you fetch the sources yourself.
|
|
85
|
+
|
|
86
|
+
## What works today
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from primarysource.budget import page
|
|
90
|
+
from primarysource.model import Document, Section, Unit, span_hash
|
|
91
|
+
from primarysource.refs import RefGrammar
|
|
92
|
+
from primarysource.render import render
|
|
93
|
+
|
|
94
|
+
grammar = RefGrammar(r"(?:RFC|BCP|STD)\d+")
|
|
95
|
+
ref = grammar.parse("RFC9000§9.6¶2") # Ref(doc='RFC9000', section='9.6', para=2)
|
|
96
|
+
|
|
97
|
+
document = Document("demo", "Demo", Unit.SENTENCE, (Section("1", "Scope", ("First.", "Second.")),))
|
|
98
|
+
section = document.section("1")
|
|
99
|
+
result = page(section.units, start=1, max_tokens=2000)
|
|
100
|
+
print(render(" ".join(result.units), {"cite": "Demo §1", "unit": document.unit, "span": span_hash("First.")}))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Module | What it does |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `primarysource.model` | `Document`, `Section`, the declared `Unit` that `¶n` counts, `span_hash` |
|
|
106
|
+
| `primarysource.refs` | `RefGrammar`: `<doc>§<section>[#anchor][¶n]` and `<doc>:<first>-<last>`, for a library's own document-id pattern |
|
|
107
|
+
| `primarysource.budget` | `tokens`, `page`: whole units up to a token budget, always making progress, with `next_start` |
|
|
108
|
+
| `primarysource.render` | body plus `cite:`, `unit:`, `span:`, `next:`, `relaxed:`, `warning:` footer lines in a fixed order |
|
|
109
|
+
| `primarysource.errors` | `LibError(message, hint)`: an error that names its remedy |
|
|
110
|
+
| `primarysource.home` | `resolve_home(app)`: `$<APP>_HOME` or the per-user data directory; `atomic_write` |
|
|
111
|
+
|
|
112
|
+
## What it does not do yet
|
|
113
|
+
|
|
114
|
+
It reads no file format, builds no index, has no command and serves nothing. No library depends on it.
|
|
115
|
+
|
|
116
|
+
## Tests
|
|
117
|
+
|
|
118
|
+
`python -m pytest -q` from this directory. All tests use invented text.
|