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,211 @@
|
|
|
1
|
+
"""``nistlib-fetch``: download the publications in data/sources.toml, extract their text and write the index.
|
|
2
|
+
|
|
3
|
+
This is the only module that touches the network, and only for URLs listed in sources.toml.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from primarysource.nist.paths import data_dir as default_data_dir, sources_file
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import urllib.error
|
|
14
|
+
import urllib.request
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from primarysource.nist.ingest.extract import (
|
|
19
|
+
ExtractError,
|
|
20
|
+
extract_text,
|
|
21
|
+
find_pdftotext,
|
|
22
|
+
split_sections,
|
|
23
|
+
)
|
|
24
|
+
from primarysource.nist.ingest.controls import split_control_sections
|
|
25
|
+
from primarysource.nist.ingest.prose_layouts import detect_layout, split_for_layout
|
|
26
|
+
from primarysource.nist.ingest.index import read_index, sha256_bytes, sha256_file, write_index
|
|
27
|
+
from primarysource.nist.ingest.sources import Source, SourcesError, load_sources
|
|
28
|
+
from primarysource.nist.models import Document
|
|
29
|
+
from primarysource.nist.spec import SPEC
|
|
30
|
+
from primarysource.pdfcorpus.fts import build_after_fetch
|
|
31
|
+
|
|
32
|
+
USER_AGENT = "nistlib-fetch/0.1 (corpus fetcher; one request per listed URL)"
|
|
33
|
+
TIMEOUT_SECONDS = 120
|
|
34
|
+
MAX_BYTES = 200 * 1024 * 1024
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FetchError(RuntimeError):
|
|
38
|
+
"""Raised when one document cannot be fetched or verified."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def download(url: str, *, timeout: float = TIMEOUT_SECONDS) -> bytes:
|
|
42
|
+
"""GET ``url`` and return the body, which must look like a PDF."""
|
|
43
|
+
request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) # noqa: S310
|
|
44
|
+
try:
|
|
45
|
+
with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310
|
|
46
|
+
body: bytes = response.read(MAX_BYTES + 1)
|
|
47
|
+
except (urllib.error.URLError, TimeoutError, OSError) as error:
|
|
48
|
+
raise FetchError(f"download failed for {url}: {error}") from error
|
|
49
|
+
if len(body) > MAX_BYTES:
|
|
50
|
+
raise FetchError(f"{url}: larger than {MAX_BYTES} bytes, refusing")
|
|
51
|
+
if not body.startswith(b"%PDF-"):
|
|
52
|
+
raise FetchError(f"{url}: response is not a PDF")
|
|
53
|
+
return body
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def check_digest(doc_id: str, actual: str, expected: str) -> None:
|
|
57
|
+
"""Raise FetchError when a recorded digest exists and differs from ``actual``."""
|
|
58
|
+
if expected and actual != expected:
|
|
59
|
+
raise FetchError(
|
|
60
|
+
f"{doc_id}: sha256 {actual} does not match the recorded {expected}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _store(pdf: Path, data: bytes) -> None:
|
|
65
|
+
temp = pdf.with_name(pdf.name + ".part")
|
|
66
|
+
temp.write_bytes(data)
|
|
67
|
+
os.replace(temp, pdf)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def obtain_pdf(
|
|
71
|
+
source: Source, corpus: Path, recorded: str, from_file: Path | None
|
|
72
|
+
) -> str:
|
|
73
|
+
"""Make ``corpus/<doc_id>.pdf`` exist with a matching digest and return that digest.
|
|
74
|
+
|
|
75
|
+
``recorded`` is the digest from sources.toml, or failing that the one recorded by an earlier run.
|
|
76
|
+
A file already in place whose digest matches (or for which none is recorded) is kept. A mismatching
|
|
77
|
+
file is replaced by a fresh copy, which is itself verified before it is kept.
|
|
78
|
+
"""
|
|
79
|
+
pdf = corpus / f"{source.doc_id}.pdf"
|
|
80
|
+
if from_file is not None:
|
|
81
|
+
data = from_file.read_bytes()
|
|
82
|
+
if not data.startswith(b"%PDF-"):
|
|
83
|
+
raise FetchError(f"{source.doc_id}: {from_file} is not a PDF")
|
|
84
|
+
digest = sha256_bytes(data)
|
|
85
|
+
check_digest(source.doc_id, digest, source.sha256)
|
|
86
|
+
if not (pdf.exists() and sha256_file(pdf) == digest):
|
|
87
|
+
_store(pdf, data)
|
|
88
|
+
return digest
|
|
89
|
+
if pdf.exists():
|
|
90
|
+
digest = sha256_file(pdf)
|
|
91
|
+
if not recorded or digest == recorded:
|
|
92
|
+
return digest
|
|
93
|
+
data = download(source.url)
|
|
94
|
+
digest = sha256_bytes(data)
|
|
95
|
+
check_digest(source.doc_id, digest, recorded)
|
|
96
|
+
_store(pdf, data)
|
|
97
|
+
return digest
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def build_document(source: Source, corpus: Path, digest: str) -> Document:
|
|
101
|
+
"""Extract text for ``source`` into the corpus and return its index entry."""
|
|
102
|
+
text = extract_text(corpus / f"{source.doc_id}.pdf")
|
|
103
|
+
text_name = f"{source.doc_id}.txt"
|
|
104
|
+
(corpus / text_name).write_text(text, encoding="utf-8", newline="")
|
|
105
|
+
if source.splitter == "controls":
|
|
106
|
+
sections = split_control_sections(text)
|
|
107
|
+
else:
|
|
108
|
+
sections = split_sections(text)
|
|
109
|
+
# Prose layouts the generic splitter cannot read (heading glued to a page header, "SECTION 1:",
|
|
110
|
+
# "CHAPTER ONE"). Used only when the generic splitter finds fewer than three sections.
|
|
111
|
+
if len(sections) < 3 and (layout := detect_layout(text)):
|
|
112
|
+
sections = split_for_layout(text, layout)
|
|
113
|
+
return Document(
|
|
114
|
+
doc_id=source.doc_id,
|
|
115
|
+
number=source.number,
|
|
116
|
+
title=source.title,
|
|
117
|
+
date=source.date,
|
|
118
|
+
status=source.status,
|
|
119
|
+
url=source.url,
|
|
120
|
+
sha256=digest,
|
|
121
|
+
text_file=text_name,
|
|
122
|
+
sections=sections,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def parse_from_files(values: Sequence[str]) -> dict[str, Path]:
|
|
127
|
+
"""Parse ``DOC_ID=PATH`` arguments."""
|
|
128
|
+
result: dict[str, Path] = {}
|
|
129
|
+
for value in values:
|
|
130
|
+
doc_id, sep, path = value.partition("=")
|
|
131
|
+
if not sep or not doc_id or not path:
|
|
132
|
+
raise SourcesError(f"--from-file expects DOC_ID=PATH, got {value!r}")
|
|
133
|
+
result[doc_id] = Path(path)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def run(data_dir: Path, from_files: dict[str, Path], only: Sequence[str]) -> int:
|
|
138
|
+
"""Fetch, verify, extract and index. Returns the process exit code."""
|
|
139
|
+
sources = load_sources(sources_file(data_dir))
|
|
140
|
+
known = {s.doc_id for s in sources}
|
|
141
|
+
unknown = sorted((set(from_files) | set(only)) - known)
|
|
142
|
+
if unknown:
|
|
143
|
+
raise SourcesError(f"not in sources.toml: {', '.join(unknown)}")
|
|
144
|
+
find_pdftotext()
|
|
145
|
+
corpus = data_dir / "corpus"
|
|
146
|
+
corpus.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
index_path = corpus / "index.json"
|
|
148
|
+
previous = {d.doc_id: d for d in read_index(index_path)}
|
|
149
|
+
wanted = set(only) | set(from_files) if (only or from_files) else known
|
|
150
|
+
documents: list[Document] = []
|
|
151
|
+
failures = 0
|
|
152
|
+
for source in sources:
|
|
153
|
+
old = previous.get(source.doc_id)
|
|
154
|
+
if source.doc_id not in wanted:
|
|
155
|
+
if old is not None and (corpus / old.text_file).exists():
|
|
156
|
+
documents.append(old)
|
|
157
|
+
continue
|
|
158
|
+
recorded = source.sha256 or (old.sha256 if old else "")
|
|
159
|
+
try:
|
|
160
|
+
digest = obtain_pdf(source, corpus, recorded, from_files.get(source.doc_id))
|
|
161
|
+
document = build_document(source, corpus, digest)
|
|
162
|
+
except (FetchError, ExtractError, OSError) as error:
|
|
163
|
+
print(f"{source.doc_id}: {error}", file=sys.stderr)
|
|
164
|
+
failures += 1
|
|
165
|
+
continue
|
|
166
|
+
note = "" if source.sha256 else " (digest recorded in index.json)"
|
|
167
|
+
print(
|
|
168
|
+
f"{source.doc_id}: {len(document.sections)} sections, sha256 {digest[:12]}{note}"
|
|
169
|
+
)
|
|
170
|
+
documents.append(document)
|
|
171
|
+
# Entries this run does not own (the -oscal catalogues from `nistlib.ingest.oscal`) are kept as they were.
|
|
172
|
+
for doc_id, old in previous.items():
|
|
173
|
+
if doc_id not in known and (corpus / old.text_file).exists():
|
|
174
|
+
documents.append(old)
|
|
175
|
+
write_index(index_path, documents)
|
|
176
|
+
print(f"wrote {index_path} with {len(documents)} document(s)")
|
|
177
|
+
if not build_after_fetch(SPEC, corpus):
|
|
178
|
+
failures += 1
|
|
179
|
+
return 1 if failures else 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
183
|
+
parser = argparse.ArgumentParser(prog="nistlib-fetch", description=__doc__)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--data-dir",
|
|
186
|
+
type=Path,
|
|
187
|
+
default=default_data_dir(),
|
|
188
|
+
help="directory holding sources.toml and corpus/ (default: $NISTLIB_DATA, else the collection's home)",
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--from-file",
|
|
192
|
+
action="append",
|
|
193
|
+
default=[],
|
|
194
|
+
metavar="DOC_ID=PATH",
|
|
195
|
+
help="use a PDF you already have for DOC_ID instead of downloading it (repeatable)",
|
|
196
|
+
)
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
"doc_ids",
|
|
199
|
+
nargs="*",
|
|
200
|
+
help="only these documents (default: all, or those named by --from-file)",
|
|
201
|
+
)
|
|
202
|
+
args = parser.parse_args(argv)
|
|
203
|
+
try:
|
|
204
|
+
return run(args.data_dir, parse_from_files(args.from_file), args.doc_ids)
|
|
205
|
+
except (SourcesError, ExtractError) as error:
|
|
206
|
+
print(f"nistlib-fetch: {error}", file=sys.stderr)
|
|
207
|
+
return 2
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if __name__ == "__main__":
|
|
211
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""The nist corpus index: the shared one (primarysource.pdfcorpus.index)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from primarysource.pdfcorpus.index import (
|
|
6
|
+
SCHEMA,
|
|
7
|
+
document_from_dict,
|
|
8
|
+
read_index,
|
|
9
|
+
sha256_bytes,
|
|
10
|
+
sha256_file,
|
|
11
|
+
write_index,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = ["SCHEMA", "document_from_dict", "read_index", "sha256_bytes", "sha256_file", "write_index"]
|