greatspectations 0.1.0__tar.gz

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.
Files changed (30) hide show
  1. greatspectations-0.1.0/LICENSE +22 -0
  2. greatspectations-0.1.0/PKG-INFO +210 -0
  3. greatspectations-0.1.0/README.md +191 -0
  4. greatspectations-0.1.0/pyproject.toml +43 -0
  5. greatspectations-0.1.0/setup.cfg +4 -0
  6. greatspectations-0.1.0/src/greatspectations/__init__.py +1 -0
  7. greatspectations-0.1.0/src/greatspectations/__main__.py +6 -0
  8. greatspectations-0.1.0/src/greatspectations/cli.py +231 -0
  9. greatspectations-0.1.0/src/greatspectations/config.py +190 -0
  10. greatspectations-0.1.0/src/greatspectations/coverage.py +269 -0
  11. greatspectations-0.1.0/src/greatspectations/formats/__init__.py +55 -0
  12. greatspectations-0.1.0/src/greatspectations/formats/_common.py +103 -0
  13. greatspectations-0.1.0/src/greatspectations/formats/markdown.py +14 -0
  14. greatspectations-0.1.0/src/greatspectations/formats/mediawiki.py +21 -0
  15. greatspectations-0.1.0/src/greatspectations/formats/rfc_text.py +85 -0
  16. greatspectations-0.1.0/src/greatspectations/matching.py +234 -0
  17. greatspectations-0.1.0/src/greatspectations/quotes.py +217 -0
  18. greatspectations-0.1.0/src/greatspectations.egg-info/PKG-INFO +210 -0
  19. greatspectations-0.1.0/src/greatspectations.egg-info/SOURCES.txt +28 -0
  20. greatspectations-0.1.0/src/greatspectations.egg-info/dependency_links.txt +1 -0
  21. greatspectations-0.1.0/src/greatspectations.egg-info/entry_points.txt +2 -0
  22. greatspectations-0.1.0/src/greatspectations.egg-info/top_level.txt +1 -0
  23. greatspectations-0.1.0/tests/test_cli.py +239 -0
  24. greatspectations-0.1.0/tests/test_cli_smoke.py +29 -0
  25. greatspectations-0.1.0/tests/test_config.py +278 -0
  26. greatspectations-0.1.0/tests/test_coverage.py +219 -0
  27. greatspectations-0.1.0/tests/test_formats.py +185 -0
  28. greatspectations-0.1.0/tests/test_matching.py +281 -0
  29. greatspectations-0.1.0/tests/test_quotes.py +302 -0
  30. greatspectations-0.1.0/tests/test_rfc_text.py +195 -0
@@ -0,0 +1,22 @@
1
+ Note: the modules in the ccan/ directory have their own licenses, but
2
+ the rest of the code is covered by the following (BSD-MIT) license:
3
+
4
+ Copyright Rusty Russell (Blockstream) 2015-2024.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: greatspectations
3
+ Version: 0.1.0
4
+ Summary: Check that spec quotes in source comments match the spec
5
+ Author: Rusty Russell
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://greatspectations.com
8
+ Project-URL: Repository, https://github.com/rustyrussell/greatspectations
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Software Development :: Documentation
14
+ Classifier: Topic :: Software Development :: Quality Assurance
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # Great Spectations
21
+
22
+ Checks that quotes of a spec embedded in source-code comments actually
23
+ say what the spec says -- so when the spec changes, the comments (and
24
+ the requirement they claim to implement) don't silently drift out of
25
+ sync.
26
+
27
+ Originally CLN's `check_quotes.py`/`bolt-coverage.py`, hardwired to
28
+ BOLTs. `greatspectations` generalizes the same idea to any spec source
29
+ you point it at: BOLTs, BIPs, RFCs, or a single-file spec like
30
+ `SPECIFICATION.md`.
31
+
32
+ ## Install
33
+
34
+ ```
35
+ pip install -e .
36
+ ```
37
+
38
+ This installs the `spectate` command (also runnable as
39
+ `python -m greatspectations`).
40
+
41
+ ## Quickstart
42
+
43
+ A comment like:
44
+
45
+ ```c
46
+ /* BOLT #11: A writer:
47
+ * - MUST set `payment_hash` to the SHA256 of `payment_preimage`.
48
+ */
49
+ ```
50
+
51
+ is checked against your BOLT spec checkout by:
52
+
53
+ ```
54
+ spectate check --config specquotes.toml src/invoice.c
55
+ ```
56
+
57
+ `spectate` exits 0 if every quote is found verbatim (modulo whitespace)
58
+ in the spec, or 1 and prints `file:line:message` for anything that
59
+ doesn't match.
60
+
61
+ ## `specquotes.toml`
62
+
63
+ Each repository that wants quote-checking declares its own
64
+ `specquotes.toml`, naming the spec sources it checks against:
65
+
66
+ ```toml
67
+ [sources.bolt]
68
+ format = "markdown"
69
+ dir = "../lightning-rfc"
70
+ pattern = "{id:02d}-*.md"
71
+
72
+ [sources.bip]
73
+ format = "mediawiki"
74
+ dir = "../bips"
75
+ pattern = "bip-{id:04d}.mediawiki"
76
+
77
+ [sources.cmdata-spec]
78
+ format = "markdown"
79
+ file = "SPECIFICATION.md"
80
+
81
+ [sources.rfc]
82
+ format = "rfc-text"
83
+ dir = "/usr/share/doc/RFC/standard"
84
+ pattern = "rfc{id}.txt.gz"
85
+ ```
86
+
87
+ `spectate` doesn't fetch anything -- point `dir`/`file` at a checkout or
88
+ package you already have (a `git clone` of `bitcoin/bips`, `apt install
89
+ doc-rfc-std`, or a spec file that lives in your own repo).
90
+
91
+ Each `[sources.NAME]` table is:
92
+
93
+ - `format` -- which parser splits the document into sections:
94
+ - `markdown` -- splits on `#`-prefixed headers. BOLT files and
95
+ single-file specs like `SPECIFICATION.md` both use this.
96
+ - `mediawiki` -- splits on `==Header==` lines, as used by
97
+ `bitcoin/bips`.
98
+ - `rfc-text` -- the classic RFC-editor plaintext layout, as shipped by
99
+ Debian's `doc-rfc-std` package (`/usr/share/doc/RFC/<category>/
100
+ rfcNNNN.txt.gz`). Transparently gunzips `.gz` files, strips page
101
+ headers/footers and form-feed page breaks so a requirement split
102
+ across a page boundary still reads as one section, and splits on
103
+ column-0 `N.`/`N.N.` numbered headers -- while ignoring the table of
104
+ contents, whose entries have the same shape but end in a
105
+ right-aligned page number (space- or dot-leader-padded), which a
106
+ real header never does. `doc-rfc-std` splits RFCs across category
107
+ subdirectories (`standard/`, `draft-standard/`, ...); point `dir`
108
+ at the single category you need, as in the example above. A
109
+ recursive `pattern` like `"**/rfc{id}.txt.gz"` also works if your
110
+ RFCs span categories, but `doc-rfc-std` additionally ships a
111
+ `links/` directory that symlinks every RFC into one flat directory
112
+ regardless of category, so a recursive pattern matches both the
113
+ symlink and the real file for the same id and `spectate` refuses
114
+ the ambiguity (`glob.glob` has no way to exclude a subdirectory by
115
+ name). If you need multiple categories, glob each one explicitly
116
+ with a separate `[sources.NAME]` table instead.
117
+ - `dir` + `pattern` -- for a source with one file per id (BOLT, BIP,
118
+ RFC): `pattern` is a glob template with an `{id}` placeholder, e.g.
119
+ `"{id:02d}-*.md"`, `"bip-{id:04d}.mediawiki"`, or
120
+ `"rfc{id}.txt.gz"`.
121
+ - `file` -- for a source that's a single fixed document (no id needed),
122
+ e.g. a spec that lives directly in your repo.
123
+ - `comment_marker` (optional) -- the literal word that opens a quote
124
+ comment for this source. Defaults to the source name, uppercased
125
+ (`bolt` -> `BOLT`, `cmdata-spec` -> `CMDATA-SPEC`), which is exactly
126
+ CLN's existing `# BOLT #11:` convention -- no source comments need to
127
+ change to adopt this tool for BOLT.
128
+
129
+ Relative `dir`/`file` paths are resolved against the directory
130
+ containing `specquotes.toml`, not your current working directory.
131
+
132
+ ## Marker syntax
133
+
134
+ ```
135
+ <MARKER>[-<commit>][ #<id>][/<section-hint>]: <quoted text>
136
+ ```
137
+
138
+ - `<MARKER>` is a source's `comment_marker`.
139
+ - `-<commit>` is CLN's "draft BOLT" convention: the line is only parsed
140
+ when `<commit>` is a prefix of one of `--include-commit`'s values
141
+ (repeatable), letting a branch reference an unmerged spec PR by
142
+ commit. Ignored otherwise.
143
+ - `#<id>` selects the document for sources with `dir`+`pattern`
144
+ (required there, and disallowed for `file` sources).
145
+ - `/<section-hint>` restricts the match to sections whose header
146
+ contains that text (case-insensitive). Useful when near-identical
147
+ wording repeats across sections -- e.g. `SPECIFICATION.md`'s "Reader
148
+ Requirements" and "Writer Requirements" both have a "MUST fail
149
+ parsing if the length is wrong" bullet, and without a hint a quote
150
+ could silently match the wrong one:
151
+
152
+ ```c
153
+ /* CMDATA-SPEC/Reader Requirements: A reader:
154
+ * - MUST fail parsing if the length is wrong.
155
+ */
156
+ ```
157
+
158
+ - The quoted text may use `...` as a wildcard, including once at the
159
+ very start of a quote to mean "must immediately follow the previous
160
+ quote from this source in this file" (handy for splitting one long
161
+ requirement across several separately-commented lines of code).
162
+ - Continuation lines repeat the marker's `--comment-continue` prefix
163
+ (default `#`); an inline/single-line comment style also needs
164
+ `--comment-end` (e.g. for C: `--comment-start='/* ' --comment-continue='*'
165
+ --comment-end='*/'`).
166
+ - `--comment-aside` marks a prefix for commentary that lives inside a
167
+ quote block but isn't part of the quote -- a continuation line
168
+ starting with it is dropped instead of appended, so it's never
169
+ checked against the spec:
170
+
171
+ ```c
172
+ /* BOLT #2: A sending node:
173
+ * - MUST set `funding_satoshis`.
174
+ * Note: We did not implement this yet.
175
+ */
176
+ ```
177
+
178
+ with `--comment-aside='* Note:'` (adjust the prefix to match
179
+ whatever `--comment-continue` you're using, e.g. `'# Note:'` for the
180
+ default `#`-style comments).
181
+
182
+ ## Matching modes
183
+
184
+ `--mode normalized` (default) collapses runs of whitespace in both the
185
+ quote and the spec before comparing, so line-wrapping differences don't
186
+ matter. `--mode exact` compares the literal, uncollapsed text instead,
187
+ for specs where exact byte matching matters.
188
+
189
+ ## Coverage
190
+
191
+ `spectate check --coverage=FILE ...` appends a record for every quote
192
+ that matched. `spectate coverage --coverage=FILE` then reports spec text
193
+ in Requirements sections (or, with `--all-sections`, every section) that
194
+ no quote covers:
195
+
196
+ ```
197
+ spectate check --config specquotes.toml --coverage=.coverage src/*.c
198
+ spectate coverage --config specquotes.toml --coverage=.coverage
199
+ ```
200
+
201
+ Restrict to specific documents with `--source NAME[:ID]` (repeatable);
202
+ by default every `(source, id)` pair found in the coverage file is
203
+ checked. `--mode` must match whatever mode `check` used to write the
204
+ coverage file, since match offsets are mode-specific.
205
+
206
+ ## CI output
207
+
208
+ Both subcommands accept `--format json` for machine-readable output
209
+ instead of the default `file:line:message` text, if you want to post
210
+ results elsewhere rather than just scrape build logs.
@@ -0,0 +1,191 @@
1
+ # Great Spectations
2
+
3
+ Checks that quotes of a spec embedded in source-code comments actually
4
+ say what the spec says -- so when the spec changes, the comments (and
5
+ the requirement they claim to implement) don't silently drift out of
6
+ sync.
7
+
8
+ Originally CLN's `check_quotes.py`/`bolt-coverage.py`, hardwired to
9
+ BOLTs. `greatspectations` generalizes the same idea to any spec source
10
+ you point it at: BOLTs, BIPs, RFCs, or a single-file spec like
11
+ `SPECIFICATION.md`.
12
+
13
+ ## Install
14
+
15
+ ```
16
+ pip install -e .
17
+ ```
18
+
19
+ This installs the `spectate` command (also runnable as
20
+ `python -m greatspectations`).
21
+
22
+ ## Quickstart
23
+
24
+ A comment like:
25
+
26
+ ```c
27
+ /* BOLT #11: A writer:
28
+ * - MUST set `payment_hash` to the SHA256 of `payment_preimage`.
29
+ */
30
+ ```
31
+
32
+ is checked against your BOLT spec checkout by:
33
+
34
+ ```
35
+ spectate check --config specquotes.toml src/invoice.c
36
+ ```
37
+
38
+ `spectate` exits 0 if every quote is found verbatim (modulo whitespace)
39
+ in the spec, or 1 and prints `file:line:message` for anything that
40
+ doesn't match.
41
+
42
+ ## `specquotes.toml`
43
+
44
+ Each repository that wants quote-checking declares its own
45
+ `specquotes.toml`, naming the spec sources it checks against:
46
+
47
+ ```toml
48
+ [sources.bolt]
49
+ format = "markdown"
50
+ dir = "../lightning-rfc"
51
+ pattern = "{id:02d}-*.md"
52
+
53
+ [sources.bip]
54
+ format = "mediawiki"
55
+ dir = "../bips"
56
+ pattern = "bip-{id:04d}.mediawiki"
57
+
58
+ [sources.cmdata-spec]
59
+ format = "markdown"
60
+ file = "SPECIFICATION.md"
61
+
62
+ [sources.rfc]
63
+ format = "rfc-text"
64
+ dir = "/usr/share/doc/RFC/standard"
65
+ pattern = "rfc{id}.txt.gz"
66
+ ```
67
+
68
+ `spectate` doesn't fetch anything -- point `dir`/`file` at a checkout or
69
+ package you already have (a `git clone` of `bitcoin/bips`, `apt install
70
+ doc-rfc-std`, or a spec file that lives in your own repo).
71
+
72
+ Each `[sources.NAME]` table is:
73
+
74
+ - `format` -- which parser splits the document into sections:
75
+ - `markdown` -- splits on `#`-prefixed headers. BOLT files and
76
+ single-file specs like `SPECIFICATION.md` both use this.
77
+ - `mediawiki` -- splits on `==Header==` lines, as used by
78
+ `bitcoin/bips`.
79
+ - `rfc-text` -- the classic RFC-editor plaintext layout, as shipped by
80
+ Debian's `doc-rfc-std` package (`/usr/share/doc/RFC/<category>/
81
+ rfcNNNN.txt.gz`). Transparently gunzips `.gz` files, strips page
82
+ headers/footers and form-feed page breaks so a requirement split
83
+ across a page boundary still reads as one section, and splits on
84
+ column-0 `N.`/`N.N.` numbered headers -- while ignoring the table of
85
+ contents, whose entries have the same shape but end in a
86
+ right-aligned page number (space- or dot-leader-padded), which a
87
+ real header never does. `doc-rfc-std` splits RFCs across category
88
+ subdirectories (`standard/`, `draft-standard/`, ...); point `dir`
89
+ at the single category you need, as in the example above. A
90
+ recursive `pattern` like `"**/rfc{id}.txt.gz"` also works if your
91
+ RFCs span categories, but `doc-rfc-std` additionally ships a
92
+ `links/` directory that symlinks every RFC into one flat directory
93
+ regardless of category, so a recursive pattern matches both the
94
+ symlink and the real file for the same id and `spectate` refuses
95
+ the ambiguity (`glob.glob` has no way to exclude a subdirectory by
96
+ name). If you need multiple categories, glob each one explicitly
97
+ with a separate `[sources.NAME]` table instead.
98
+ - `dir` + `pattern` -- for a source with one file per id (BOLT, BIP,
99
+ RFC): `pattern` is a glob template with an `{id}` placeholder, e.g.
100
+ `"{id:02d}-*.md"`, `"bip-{id:04d}.mediawiki"`, or
101
+ `"rfc{id}.txt.gz"`.
102
+ - `file` -- for a source that's a single fixed document (no id needed),
103
+ e.g. a spec that lives directly in your repo.
104
+ - `comment_marker` (optional) -- the literal word that opens a quote
105
+ comment for this source. Defaults to the source name, uppercased
106
+ (`bolt` -> `BOLT`, `cmdata-spec` -> `CMDATA-SPEC`), which is exactly
107
+ CLN's existing `# BOLT #11:` convention -- no source comments need to
108
+ change to adopt this tool for BOLT.
109
+
110
+ Relative `dir`/`file` paths are resolved against the directory
111
+ containing `specquotes.toml`, not your current working directory.
112
+
113
+ ## Marker syntax
114
+
115
+ ```
116
+ <MARKER>[-<commit>][ #<id>][/<section-hint>]: <quoted text>
117
+ ```
118
+
119
+ - `<MARKER>` is a source's `comment_marker`.
120
+ - `-<commit>` is CLN's "draft BOLT" convention: the line is only parsed
121
+ when `<commit>` is a prefix of one of `--include-commit`'s values
122
+ (repeatable), letting a branch reference an unmerged spec PR by
123
+ commit. Ignored otherwise.
124
+ - `#<id>` selects the document for sources with `dir`+`pattern`
125
+ (required there, and disallowed for `file` sources).
126
+ - `/<section-hint>` restricts the match to sections whose header
127
+ contains that text (case-insensitive). Useful when near-identical
128
+ wording repeats across sections -- e.g. `SPECIFICATION.md`'s "Reader
129
+ Requirements" and "Writer Requirements" both have a "MUST fail
130
+ parsing if the length is wrong" bullet, and without a hint a quote
131
+ could silently match the wrong one:
132
+
133
+ ```c
134
+ /* CMDATA-SPEC/Reader Requirements: A reader:
135
+ * - MUST fail parsing if the length is wrong.
136
+ */
137
+ ```
138
+
139
+ - The quoted text may use `...` as a wildcard, including once at the
140
+ very start of a quote to mean "must immediately follow the previous
141
+ quote from this source in this file" (handy for splitting one long
142
+ requirement across several separately-commented lines of code).
143
+ - Continuation lines repeat the marker's `--comment-continue` prefix
144
+ (default `#`); an inline/single-line comment style also needs
145
+ `--comment-end` (e.g. for C: `--comment-start='/* ' --comment-continue='*'
146
+ --comment-end='*/'`).
147
+ - `--comment-aside` marks a prefix for commentary that lives inside a
148
+ quote block but isn't part of the quote -- a continuation line
149
+ starting with it is dropped instead of appended, so it's never
150
+ checked against the spec:
151
+
152
+ ```c
153
+ /* BOLT #2: A sending node:
154
+ * - MUST set `funding_satoshis`.
155
+ * Note: We did not implement this yet.
156
+ */
157
+ ```
158
+
159
+ with `--comment-aside='* Note:'` (adjust the prefix to match
160
+ whatever `--comment-continue` you're using, e.g. `'# Note:'` for the
161
+ default `#`-style comments).
162
+
163
+ ## Matching modes
164
+
165
+ `--mode normalized` (default) collapses runs of whitespace in both the
166
+ quote and the spec before comparing, so line-wrapping differences don't
167
+ matter. `--mode exact` compares the literal, uncollapsed text instead,
168
+ for specs where exact byte matching matters.
169
+
170
+ ## Coverage
171
+
172
+ `spectate check --coverage=FILE ...` appends a record for every quote
173
+ that matched. `spectate coverage --coverage=FILE` then reports spec text
174
+ in Requirements sections (or, with `--all-sections`, every section) that
175
+ no quote covers:
176
+
177
+ ```
178
+ spectate check --config specquotes.toml --coverage=.coverage src/*.c
179
+ spectate coverage --config specquotes.toml --coverage=.coverage
180
+ ```
181
+
182
+ Restrict to specific documents with `--source NAME[:ID]` (repeatable);
183
+ by default every `(source, id)` pair found in the coverage file is
184
+ checked. `--mode` must match whatever mode `check` used to write the
185
+ coverage file, since match offsets are mode-specific.
186
+
187
+ ## CI output
188
+
189
+ Both subcommands accept `--format json` for machine-readable output
190
+ instead of the default `file:line:message` text, if you want to post
191
+ results elsewhere rather than just scrape build logs.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "greatspectations"
7
+ version = "0.1.0"
8
+ description = "Check that spec quotes in source comments match the spec"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ authors = [{ name = "Rusty Russell" }]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Software Development :: Documentation",
20
+ "Topic :: Software Development :: Quality Assurance",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://greatspectations.com"
25
+ Repository = "https://github.com/rustyrussell/greatspectations"
26
+
27
+ [project.scripts]
28
+ spectate = "greatspectations.cli:main"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
35
+
36
+ [tool.mypy]
37
+ files = ["src/greatspectations"]
38
+ python_version = "3.11"
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pytest>=9.1.1",
43
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from greatspectations.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())