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,239 @@
|
|
|
1
|
+
"""A minimal reStructuredText reader for PEPs, Debian Policy, GLEPs and the kernel's Kconfig documents.
|
|
2
|
+
|
|
3
|
+
Sections follow the docutils rule: a title is a text line with an underline (or an underline and an overline of
|
|
4
|
+
the same character) at least as long as the title, and the level of a style is the order in which the style first
|
|
5
|
+
appears in the document, not the character used. A document whose first heading is the only one of its style
|
|
6
|
+
(a chapter title above its sections) has that heading promoted to the document title; with `base` it opens the
|
|
7
|
+
numbered root section, so chapter 5's subsections are `5.1`, `5.1.1`.
|
|
8
|
+
|
|
9
|
+
Blocks (the unit, so `¶n` counts blocks):
|
|
10
|
+
|
|
11
|
+
- a paragraph; a block quote; a literal block (the indented text after a paragraph ending in `::`);
|
|
12
|
+
- a list item (`-`, `*`, `+`, `1.`, `#.`, `(a)`) or a field (`:name: value`) with its indented continuation;
|
|
13
|
+
- a definition-list item (a term and its indented definition);
|
|
14
|
+
- a directive (`.. name:: args` and its indented body) as one block, written `[name] args` then the body;
|
|
15
|
+
- a footnote or citation (`.. [#]`), written `[footnote]`; a grid or simple table.
|
|
16
|
+
|
|
17
|
+
Comments (`.. text`), substitution definitions (`.. |x| ...`), hyperlink targets and transitions are dropped. A
|
|
18
|
+
target `.. _label:` immediately before a heading becomes that section's anchor, the way Sphinx labels are used;
|
|
19
|
+
otherwise the anchor is the GitHub-style slug of the title.
|
|
20
|
+
|
|
21
|
+
Nothing here follows `.. include::`, `.. literalinclude::` or `.. raw::`: they are inert text, and no path a
|
|
22
|
+
document names is ever opened. There is no recursion, so deep indentation costs time linear in the input.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
|
|
30
|
+
from primarysource.model import Unit
|
|
31
|
+
from primarysource.pkgdocs.model import Doc
|
|
32
|
+
from primarysource.pkgdocs.readers._common import Builder, clean_title, decode, slugify
|
|
33
|
+
|
|
34
|
+
_PUNCT = frozenset("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
|
|
35
|
+
_MARKER = re.compile(r"(?:[-*+•‣⁃](?:\s+\S|$)|(?:\d+|[a-zA-Z]|#)[.)]\s+\S|\((?:\d+|[a-zA-Z]|#)\)\s+\S)")
|
|
36
|
+
_FIELD = re.compile(r":[^\s:][^:]*:(?:\s|$)")
|
|
37
|
+
_DIRECTIVE = re.compile(r"\.\. +([A-Za-z][\w:+.-]*)::(?: +(.*))?$")
|
|
38
|
+
_TARGET = re.compile(r"\.\. +_(`[^`]+`|[^:`]+):(?: +(.*))?$")
|
|
39
|
+
_FOOTNOTE = re.compile(r"\.\. +\[([^\]]*)\](?: +(.*))?$")
|
|
40
|
+
_SUBST = re.compile(r"\.\. +\|[^|]+\|")
|
|
41
|
+
_SIMPLE_BORDER = re.compile(r"=+(?: +=+)+$")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class _Heading:
|
|
46
|
+
style: str
|
|
47
|
+
title: str
|
|
48
|
+
anchor: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _adornment(line: str) -> str | None:
|
|
52
|
+
"""The style character if `line` is one punctuation character repeated at least twice, else None."""
|
|
53
|
+
if len(line) >= 2 and line[0] in _PUNCT and line.count(line[0]) == len(line):
|
|
54
|
+
return line[0]
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _indent(line: str) -> int:
|
|
59
|
+
return len(line) - len(line.lstrip(" "))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _dedent(lines: list[str]) -> str:
|
|
63
|
+
live = [line for line in lines if line.strip()]
|
|
64
|
+
if not live:
|
|
65
|
+
return ""
|
|
66
|
+
margin = min(_indent(line) for line in live)
|
|
67
|
+
return "\n".join(line[margin:] if line.strip() else "" for line in lines).strip("\n")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _take_indented(lines: list[str], i: int) -> tuple[list[str], int]:
|
|
71
|
+
"""Lines from `i` that are indented, plus blank lines that are followed by more indented lines."""
|
|
72
|
+
body: list[str] = []
|
|
73
|
+
count = len(lines)
|
|
74
|
+
while i < count:
|
|
75
|
+
line = lines[i]
|
|
76
|
+
if line.strip() and _indent(line) > 0:
|
|
77
|
+
body.append(line)
|
|
78
|
+
i += 1
|
|
79
|
+
continue
|
|
80
|
+
if not line.strip():
|
|
81
|
+
j = i
|
|
82
|
+
while j < count and not lines[j].strip():
|
|
83
|
+
j += 1
|
|
84
|
+
if j < count and _indent(lines[j]) > 0:
|
|
85
|
+
body.extend([""] * (j - i))
|
|
86
|
+
i = j
|
|
87
|
+
continue
|
|
88
|
+
break
|
|
89
|
+
return body, i
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _parse(lines: list[str]) -> list[_Heading | str]:
|
|
93
|
+
events: list[_Heading | str] = []
|
|
94
|
+
pending_label = ""
|
|
95
|
+
count = len(lines)
|
|
96
|
+
i = 0
|
|
97
|
+
while i < count:
|
|
98
|
+
line = lines[i]
|
|
99
|
+
if not line.strip():
|
|
100
|
+
i += 1
|
|
101
|
+
continue
|
|
102
|
+
if _indent(line) > 0: # a block quote
|
|
103
|
+
body, i = _take_indented(lines, i)
|
|
104
|
+
events.append(_dedent(body))
|
|
105
|
+
pending_label = ""
|
|
106
|
+
continue
|
|
107
|
+
# headings: text over an underline, or an overlined title
|
|
108
|
+
style = _adornment(line)
|
|
109
|
+
if style and i + 2 < count and lines[i + 1].strip() and lines[i + 2].rstrip() == line:
|
|
110
|
+
title = lines[i + 1].strip()
|
|
111
|
+
if _indent(lines[i + 1]) == 0 and _adornment(lines[i + 1]) is None and len(line) >= len(title):
|
|
112
|
+
events.append(_Heading(style + "o", title, pending_label or slugify(title)))
|
|
113
|
+
pending_label = ""
|
|
114
|
+
i += 3
|
|
115
|
+
continue
|
|
116
|
+
if style is None and i + 1 < count and not line.startswith(".."):
|
|
117
|
+
under = _adornment(lines[i + 1])
|
|
118
|
+
if under and len(lines[i + 1]) >= len(line.strip()) and _MARKER.match(line) is None:
|
|
119
|
+
title = line.strip()
|
|
120
|
+
events.append(_Heading(under, title, pending_label or slugify(title)))
|
|
121
|
+
pending_label = ""
|
|
122
|
+
i += 2
|
|
123
|
+
continue
|
|
124
|
+
if style and len(line) >= 4 and (i + 1 >= count or not lines[i + 1].strip()):
|
|
125
|
+
i += 1 # a transition
|
|
126
|
+
continue
|
|
127
|
+
if line.startswith(".."):
|
|
128
|
+
i, pending_label = _explicit(lines, i, events, pending_label)
|
|
129
|
+
continue
|
|
130
|
+
if line.startswith(("+-", "+=")): # a grid table
|
|
131
|
+
start = i
|
|
132
|
+
while i < count and lines[i].strip():
|
|
133
|
+
i += 1
|
|
134
|
+
events.append("\n".join(lines[start:i]))
|
|
135
|
+
pending_label = ""
|
|
136
|
+
continue
|
|
137
|
+
if _SIMPLE_BORDER.match(line):
|
|
138
|
+
start, borders = i, 0
|
|
139
|
+
while i < count:
|
|
140
|
+
if _SIMPLE_BORDER.match(lines[i]):
|
|
141
|
+
borders += 1
|
|
142
|
+
if not lines[i].strip() and borders >= 2:
|
|
143
|
+
break
|
|
144
|
+
i += 1
|
|
145
|
+
events.append("\n".join(lines[start:i]).rstrip())
|
|
146
|
+
pending_label = ""
|
|
147
|
+
continue
|
|
148
|
+
# a paragraph, list item, field or definition item
|
|
149
|
+
start = i
|
|
150
|
+
first = _MARKER.match(line) is not None or _FIELD.match(line) is not None
|
|
151
|
+
i += 1
|
|
152
|
+
if not first:
|
|
153
|
+
while i < count and lines[i].strip() and _indent(lines[i]) == 0:
|
|
154
|
+
if _MARKER.match(lines[i]) or _FIELD.match(lines[i]):
|
|
155
|
+
break
|
|
156
|
+
i += 1
|
|
157
|
+
text_lines = lines[start:i]
|
|
158
|
+
if i < count and lines[i].strip() and _indent(lines[i]) > 0: # a definition or a list continuation
|
|
159
|
+
body, i = _take_indented(lines, i)
|
|
160
|
+
text_lines = [*text_lines, *body]
|
|
161
|
+
events.append(_dedent(text_lines) if first else "\n".join(text_lines).strip("\n"))
|
|
162
|
+
elif text_lines[-1].rstrip().endswith("::"):
|
|
163
|
+
paragraph = "\n".join(text_lines)
|
|
164
|
+
if paragraph.strip() != "::":
|
|
165
|
+
events.append(paragraph)
|
|
166
|
+
body, i = _take_indented(lines, _skip_blank(lines, i))
|
|
167
|
+
if body:
|
|
168
|
+
events.append(_dedent(body))
|
|
169
|
+
else:
|
|
170
|
+
events.append("\n".join(text_lines))
|
|
171
|
+
pending_label = ""
|
|
172
|
+
return events
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _skip_blank(lines: list[str], i: int) -> int:
|
|
176
|
+
while i < len(lines) and not lines[i].strip():
|
|
177
|
+
i += 1
|
|
178
|
+
return i
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _explicit(lines: list[str], i: int, events: list[_Heading | str], label: str) -> tuple[int, str]:
|
|
182
|
+
"""An explicit markup block (`.. ...`): returns the next index and the label still pending."""
|
|
183
|
+
head = lines[i]
|
|
184
|
+
body, nxt = _take_indented(lines, i + 1)
|
|
185
|
+
target = _TARGET.match(head)
|
|
186
|
+
if target:
|
|
187
|
+
name = target.group(1).strip("`")
|
|
188
|
+
if not (target.group(2) or "").strip() and not body:
|
|
189
|
+
return nxt, name
|
|
190
|
+
return nxt, label
|
|
191
|
+
directive = _DIRECTIVE.match(head)
|
|
192
|
+
if directive:
|
|
193
|
+
text = f"[{directive.group(1)}]" + (f" {directive.group(2).strip()}" if directive.group(2) else "")
|
|
194
|
+
if body:
|
|
195
|
+
text += "\n" + _dedent(body)
|
|
196
|
+
events.append(text)
|
|
197
|
+
return nxt, ""
|
|
198
|
+
footnote = _FOOTNOTE.match(head)
|
|
199
|
+
if footnote:
|
|
200
|
+
first = (footnote.group(2) or "").strip()
|
|
201
|
+
text = f"[footnote {footnote.group(1)}] " + first
|
|
202
|
+
if body:
|
|
203
|
+
text += "\n" + _dedent(body)
|
|
204
|
+
events.append(text.rstrip())
|
|
205
|
+
return nxt, ""
|
|
206
|
+
if _SUBST.match(head):
|
|
207
|
+
return nxt, label
|
|
208
|
+
return nxt, label # a comment: dropped
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def read_rst(data: bytes, doc_id: str, title: str | None = None, *, base: str = "") -> Doc:
|
|
212
|
+
"""Read one reStructuredText file. Raises `LibError` for NUL bytes or invalid UTF-8; everything else reads."""
|
|
213
|
+
text = decode(data, f"reStructuredText file {doc_id!r}")
|
|
214
|
+
lines = [line.rstrip() for line in text.expandtabs(8).split("\n")]
|
|
215
|
+
events = _parse(lines)
|
|
216
|
+
headings = [e for e in events if isinstance(e, _Heading)]
|
|
217
|
+
promote = bool(headings) and events[0] is headings[0] and sum(h.style == headings[0].style for h in headings) == 1
|
|
218
|
+
order: list[str] = []
|
|
219
|
+
for heading in headings[1 if promote else 0 :]:
|
|
220
|
+
if heading.style not in order:
|
|
221
|
+
order.append(heading.style)
|
|
222
|
+
builder = Builder(base)
|
|
223
|
+
doc_title = title or ""
|
|
224
|
+
rest = events
|
|
225
|
+
if promote:
|
|
226
|
+
first = headings[0]
|
|
227
|
+
doc_title = title or clean_title(first.title)
|
|
228
|
+
builder.root(first.title, first.anchor)
|
|
229
|
+
rest = events[1:]
|
|
230
|
+
else:
|
|
231
|
+
builder.set_preamble_title(doc_title or doc_id)
|
|
232
|
+
if base:
|
|
233
|
+
builder.root(doc_title or doc_id)
|
|
234
|
+
for event in rest:
|
|
235
|
+
if isinstance(event, _Heading):
|
|
236
|
+
builder.heading(order.index(event.style) + 1, event.title, event.anchor)
|
|
237
|
+
else:
|
|
238
|
+
builder.unit(event)
|
|
239
|
+
return builder.doc(doc_id, doc_title or doc_id, unit=Unit.BLOCK)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""A minimal scdoc reader for rpm's manual pages (`docs/man/*.scd`).
|
|
2
|
+
|
|
3
|
+
scdoc is the small man-page markup rpm uses in place of roff. The first line is `NAME(section)` (optionally with
|
|
4
|
+
quoted footer strings); `#`, `##` and `###` start sections; `;` at the start of a line is a comment.
|
|
5
|
+
|
|
6
|
+
Blocks (the unit): a paragraph, a list item (`- ` or `. ` with its indented continuation and nested items), a
|
|
7
|
+
literal block (a fenced ``` block, or lines indented by a tab or four spaces) and a table (`[[` to the blank
|
|
8
|
+
line). Inline markup (`*bold*`, `_underline_`, `\\` escapes) is left as written.
|
|
9
|
+
|
|
10
|
+
The document title is the header line, lower-cased: `rpm-macros(7)`. The collection adds the NAME description.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
from primarysource.model import Unit
|
|
18
|
+
from primarysource.pkgdocs.model import Doc
|
|
19
|
+
from primarysource.pkgdocs.readers._common import Builder, decode
|
|
20
|
+
|
|
21
|
+
_HEADER = re.compile(r"([A-Za-z0-9_.+-]+)\((\d)\)(?:\s+\"[^\"]*\"){0,2}")
|
|
22
|
+
_ITEM = re.compile(r"(?:-|\.)\s+\S")
|
|
23
|
+
_HEADING = re.compile(r"(#{1,3})\s+(\S.*)")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def read_scdoc(data: bytes, doc_id: str, title: str | None = None) -> Doc:
|
|
27
|
+
"""Read one scdoc file. Raises `LibError` for NUL bytes or invalid UTF-8; everything else reads."""
|
|
28
|
+
text = decode(data, f"scdoc file {doc_id!r}")
|
|
29
|
+
lines = [ln.rstrip() for ln in text.expandtabs(4).split("\n")]
|
|
30
|
+
count = len(lines)
|
|
31
|
+
i = 0
|
|
32
|
+
doc_title = title or ""
|
|
33
|
+
while i < count and (not lines[i].strip() or lines[i].startswith(";")):
|
|
34
|
+
i += 1
|
|
35
|
+
header = _HEADER.fullmatch(lines[i].strip()) if i < count else None
|
|
36
|
+
if header:
|
|
37
|
+
doc_title = doc_title or f"{header.group(1).lower()}({header.group(2)})"
|
|
38
|
+
i += 1
|
|
39
|
+
builder = Builder()
|
|
40
|
+
builder.set_preamble_title(doc_title or doc_id)
|
|
41
|
+
para: list[str] = []
|
|
42
|
+
|
|
43
|
+
def flush() -> None:
|
|
44
|
+
if para:
|
|
45
|
+
builder.unit("\n".join(para))
|
|
46
|
+
para.clear()
|
|
47
|
+
|
|
48
|
+
while i < count:
|
|
49
|
+
line = lines[i]
|
|
50
|
+
i += 1
|
|
51
|
+
if line.startswith(";"):
|
|
52
|
+
continue
|
|
53
|
+
if not line.strip():
|
|
54
|
+
flush()
|
|
55
|
+
continue
|
|
56
|
+
if line.startswith("```"):
|
|
57
|
+
flush()
|
|
58
|
+
block = [line]
|
|
59
|
+
while i < count:
|
|
60
|
+
block.append(lines[i])
|
|
61
|
+
i += 1
|
|
62
|
+
if block[-1].startswith("```"):
|
|
63
|
+
break
|
|
64
|
+
builder.unit("\n".join(block))
|
|
65
|
+
continue
|
|
66
|
+
heading = _HEADING.fullmatch(line)
|
|
67
|
+
if heading:
|
|
68
|
+
flush()
|
|
69
|
+
builder.heading(len(heading.group(1)), heading.group(2))
|
|
70
|
+
continue
|
|
71
|
+
if line.startswith("[["):
|
|
72
|
+
flush()
|
|
73
|
+
block = [line]
|
|
74
|
+
while i < count and lines[i].strip():
|
|
75
|
+
block.append(lines[i])
|
|
76
|
+
i += 1
|
|
77
|
+
builder.unit("\n".join(block))
|
|
78
|
+
continue
|
|
79
|
+
if line.startswith(" ") and not para:
|
|
80
|
+
block = [line]
|
|
81
|
+
while i < count and (lines[i].startswith(" ") or not lines[i].strip()):
|
|
82
|
+
block.append(lines[i])
|
|
83
|
+
i += 1
|
|
84
|
+
builder.unit("\n".join(block).rstrip())
|
|
85
|
+
continue
|
|
86
|
+
if _ITEM.match(line):
|
|
87
|
+
flush()
|
|
88
|
+
item = [line]
|
|
89
|
+
while i < count:
|
|
90
|
+
nxt = lines[i]
|
|
91
|
+
if nxt.strip() and (nxt.startswith(" ") or not _ITEM.match(nxt) and not _HEADING.fullmatch(nxt)):
|
|
92
|
+
item.append(nxt)
|
|
93
|
+
i += 1
|
|
94
|
+
continue
|
|
95
|
+
if not nxt.strip():
|
|
96
|
+
j = i
|
|
97
|
+
while j < count and not lines[j].strip():
|
|
98
|
+
j += 1
|
|
99
|
+
if j < count and lines[j].startswith(" "):
|
|
100
|
+
item.extend([""] * (j - i))
|
|
101
|
+
i = j
|
|
102
|
+
continue
|
|
103
|
+
break
|
|
104
|
+
builder.unit("\n".join(item))
|
|
105
|
+
continue
|
|
106
|
+
para.append(line)
|
|
107
|
+
flush()
|
|
108
|
+
return builder.doc(doc_id, doc_title or doc_id, unit=Unit.BLOCK)
|