@erclx/canon 4.30.1 → 4.30.2
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.
package/docs/agents/capture.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Rendering HTML sources to PNG, what the command asserts about fonts
|
|
|
5
5
|
|
|
6
6
|
# Capture
|
|
7
7
|
|
|
8
|
-
`canon capture [source] --selector <sel>` renders HTML sources to PNG, which is how a generated documentation image is rebuilt from the markup it was generated out of. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same command.
|
|
8
|
+
`canon capture [source] --selector <sel>` renders HTML sources to PNG, which is how a generated documentation image is rebuilt from the markup it was generated out of. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same command. That default names this repository's own folder rather than a convention every target shares, and it stays because a missing one still refuses loud, naming the argument, rather than failing silently.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
canon capture --selector .window
|
|
@@ -38,7 +38,7 @@ Each source renders at `deviceScaleFactor` 2 with a transparent background, and
|
|
|
38
38
|
|
|
39
39
|
What is asserted is the font. The command reads the first family the captured element declares and fails when the browser did not resolve it, because a fallback face rewraps the block and silently changes the output. Sources therefore name a real font rather than relying on `monospace`. A source that cannot render reports its own line and exits 1 without dropping the rest of the batch.
|
|
40
40
|
|
|
41
|
-
A run refuses before it reads anything else when `--selector` is absent, naming the flag and pointing at `--help`. Ordering it first is what keeps the message about the invocation: the source defaults to `assets`, so checking that first would answer `assets not found` from whatever directory the caller happened to be in and say nothing about the flag that was actually missing.
|
|
41
|
+
A run refuses before it reads anything else when `--selector` is absent, naming the flag and pointing at `--help`. Ordering it first is what keeps the message about the invocation: the source defaults to `assets`, so checking that first would answer `assets not found` from whatever directory the caller happened to be in and say nothing about the flag that was actually missing. That source default is a deliberate choice rather than one inherited from some general convention: a missing folder still refuses loud, naming the argument, once the flag check passes, where the selector's dropped default cropped the wrong region in silence.
|
|
42
42
|
|
|
43
43
|
The browser binary installs separately from the package. A first run does `bunx playwright install chromium` once, and a run that cannot launch one reports the engine's own remediation inside the frame and exits 1 rather than escaping as a stack trace.
|
|
44
44
|
|
package/package.json
CHANGED
package/src/commands/capture.ts
CHANGED
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
pipeOutput,
|
|
17
17
|
} from '@/ui'
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Kept rather than removed, unlike the selector's default that came out for
|
|
21
|
+
* silently cropping the wrong region: a missing folder still refuses loud,
|
|
22
|
+
* naming the argument that failed to resolve.
|
|
23
|
+
*/
|
|
19
24
|
const DEFAULT_SOURCE = 'assets'
|
|
20
25
|
|
|
21
26
|
/**
|
package/src/gov/citations.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { isMarked } from '@/exempt-marker'
|
|
|
6
6
|
import { gitEnv } from '@/git-env'
|
|
7
7
|
import { listRuleFiles } from '@/gov/payload'
|
|
8
8
|
import { parseFrontmatter } from '@/indexes/frontmatter'
|
|
9
|
+
import { findBacktickSpans } from '@/markdown/backticks'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* The inline token exempting one line from this sweep, shaped on the
|
|
@@ -114,13 +115,6 @@ const FENCE = /^\s*(?:```|~~~)/
|
|
|
114
115
|
|
|
115
116
|
const FRONTMATTER_DELIMITER = /^---\s*$/
|
|
116
117
|
|
|
117
|
-
/**
|
|
118
|
-
* A backticked span, which is the only carrier a rule writes a citation in. No
|
|
119
|
-
* rule in either corpus uses a markdown link, and matching running prose would
|
|
120
|
-
* report every sentence that happens to name a file.
|
|
121
|
-
*/
|
|
122
|
-
const BACKTICKED = /`([^`\n]+)`/g
|
|
123
|
-
|
|
124
118
|
/**
|
|
125
119
|
* The verb form, with the name captured. A leading letter or digit is required,
|
|
126
120
|
* which is what leaves `canon standards <name>` unmatched: that line teaches the
|
|
@@ -254,8 +248,10 @@ export function collectCitations(text: string): RawCitation[] {
|
|
|
254
248
|
})
|
|
255
249
|
}
|
|
256
250
|
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
// A backticked span is the only carrier a rule writes a citation in. No
|
|
252
|
+
// rule in either corpus uses a markdown link, and matching running prose
|
|
253
|
+
// would report every sentence that happens to name a file.
|
|
254
|
+
for (const { content: span } of findBacktickSpans(line)) {
|
|
259
255
|
const form = classifySpan(span)
|
|
260
256
|
if (form === undefined) continue
|
|
261
257
|
found.push({ line: index + 1, form, cited: span, preview, marked })
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** One code span, paired by matching backtick-run length rather than count. */
|
|
2
|
+
export interface BacktickSpan {
|
|
3
|
+
/** Index of the opening run's first backtick. */
|
|
4
|
+
readonly start: number
|
|
5
|
+
/** Index one past the closing run's last backtick. */
|
|
6
|
+
readonly end: number
|
|
7
|
+
/** The text between the two runs, delimiters excluded. */
|
|
8
|
+
readonly content: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Every code span in the text, pairing a run of backticks only with the next
|
|
13
|
+
* run of the same length.
|
|
14
|
+
*
|
|
15
|
+
* A single-backtick regex reads a doubled delimiter, such as
|
|
16
|
+
* ``` ``git status`` ```, as two unrelated single backticks: it opens on the
|
|
17
|
+
* second backtick of the pair, closes on the first backtick of the closing
|
|
18
|
+
* pair, and leaves one backtick before and after the span unconsumed. Every
|
|
19
|
+
* scan after that treats a stray leftover backtick as an opener, which
|
|
20
|
+
* absorbs the next real span's opening delimiter as its closer and drops the
|
|
21
|
+
* path inside past it. Pairing by run length rather than by single backtick
|
|
22
|
+
* keeps a doubled delimiter closed by a doubled delimiter, so nothing after
|
|
23
|
+
* it loses its pairing.
|
|
24
|
+
*
|
|
25
|
+
* An opening run with no same-length run after it is not a delimiter, per
|
|
26
|
+
* CommonMark, so it is left as literal text and the scan resumes at the next
|
|
27
|
+
* run rather than backtracking into the unmatched one.
|
|
28
|
+
*/
|
|
29
|
+
export function findBacktickSpans(text: string): BacktickSpan[] {
|
|
30
|
+
const runs = [...text.matchAll(/`+/g)].map((match) => ({
|
|
31
|
+
start: match.index ?? 0,
|
|
32
|
+
length: match[0].length,
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const spans: BacktickSpan[] = []
|
|
36
|
+
let i = 0
|
|
37
|
+
while (i < runs.length) {
|
|
38
|
+
const open = runs[i]
|
|
39
|
+
const closeIndex = runs.findIndex(
|
|
40
|
+
(run, index) => index > i && run.length === open.length,
|
|
41
|
+
)
|
|
42
|
+
if (closeIndex === -1) {
|
|
43
|
+
i += 1
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
const close = runs[closeIndex]
|
|
47
|
+
spans.push({
|
|
48
|
+
start: open.start,
|
|
49
|
+
end: close.start + close.length,
|
|
50
|
+
content: text.slice(open.start + open.length, close.start),
|
|
51
|
+
})
|
|
52
|
+
i = closeIndex + 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return spans
|
|
56
|
+
}
|
package/src/pr/paths.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { findBacktickSpans } from '@/markdown/backticks'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The heading whose bullets state what a branch changed.
|
|
3
5
|
*
|
|
@@ -114,63 +116,6 @@ function isDottedNumber(span: string): boolean {
|
|
|
114
116
|
return /^\d+(?:\.\d+)+$/.test(segment)
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
/** One code span, paired by matching backtick-run length rather than count. */
|
|
118
|
-
interface BacktickSpan {
|
|
119
|
-
/** Index of the opening run's first backtick. */
|
|
120
|
-
readonly start: number
|
|
121
|
-
/** Index one past the closing run's last backtick. */
|
|
122
|
-
readonly end: number
|
|
123
|
-
/** The text between the two runs, delimiters excluded. */
|
|
124
|
-
readonly content: string
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Every code span in a bullet, pairing a run of backticks only with the next
|
|
129
|
-
* run of the same length.
|
|
130
|
-
*
|
|
131
|
-
* A single-backtick regex reads a doubled delimiter, such as
|
|
132
|
-
* ``` ``git status`` ```, as two unrelated single backticks: it opens on the
|
|
133
|
-
* second backtick of the pair, closes on the first backtick of the closing
|
|
134
|
-
* pair, and leaves one backtick before and after the span unconsumed. Every
|
|
135
|
-
* scan after that treats a stray leftover backtick as an opener, which
|
|
136
|
-
* absorbs the next real span's opening delimiter as its closer and drops the
|
|
137
|
-
* path inside past it. Pairing by run length rather than by single backtick
|
|
138
|
-
* keeps a doubled delimiter closed by a doubled delimiter, so nothing after
|
|
139
|
-
* it loses its pairing.
|
|
140
|
-
*
|
|
141
|
-
* An opening run with no same-length run after it is not a delimiter, per
|
|
142
|
-
* CommonMark, so it is left as literal text and the scan resumes at the next
|
|
143
|
-
* run rather than backtracking into the unmatched one.
|
|
144
|
-
*/
|
|
145
|
-
function findBacktickSpans(text: string): BacktickSpan[] {
|
|
146
|
-
const runs = [...text.matchAll(/`+/g)].map((match) => ({
|
|
147
|
-
start: match.index ?? 0,
|
|
148
|
-
length: match[0].length,
|
|
149
|
-
}))
|
|
150
|
-
|
|
151
|
-
const spans: BacktickSpan[] = []
|
|
152
|
-
let i = 0
|
|
153
|
-
while (i < runs.length) {
|
|
154
|
-
const open = runs[i]
|
|
155
|
-
const closeIndex = runs.findIndex(
|
|
156
|
-
(run, index) => index > i && run.length === open.length,
|
|
157
|
-
)
|
|
158
|
-
if (closeIndex === -1) {
|
|
159
|
-
i += 1
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
|
-
const close = runs[closeIndex]
|
|
163
|
-
spans.push({
|
|
164
|
-
start: open.start,
|
|
165
|
-
end: close.start + close.length,
|
|
166
|
-
content: text.slice(open.start + open.length, close.start),
|
|
167
|
-
})
|
|
168
|
-
i = closeIndex + 1
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return spans
|
|
172
|
-
}
|
|
173
|
-
|
|
174
119
|
/** Blanks every backticked span so a cue search never fires inside one. */
|
|
175
120
|
function maskSpans(text: string): string {
|
|
176
121
|
let out = text
|