gosalyn: row 231 round 2, only a body embed is an attachment and only after it fails to resolve, README:87 restored, U+061C escaped, survey count by name, Dr.Quinn reader test, singles 61/0 (webby-0070, sage-1900, sage-1902, sage-1907)
This commit is contained in:
1 parent
88daaac505
commit
f37a65857a
8 files changed
+110
-34
No files matched your search
@@ -41,3 +41,56 @@ file, every rc 0.
|
||||
| packages/cli/tests/stack-copy.test.mjs | 16 | 0 |
|
||||
|
||||
Total: 60 pass, 0 fail, 0 cancelled, summed by awk.
|
||||
|
||||
## Round 2, after webby-0070 (CHANGES) and sage-1900
|
||||
|
||||
Webby's round 1 review is agents/webby/work/queue-231/REVIEW-r1.md at 387f04dfd, rev 2774.
|
||||
|
||||
Ruling sage-1900, recorded here as asked. B2: the brief stays as approved (sage-1517, case2-rows.md
|
||||
C2-01b N3). Only a `![[x.ext]]` embed can be an attachment. Plain wikilinks and front matter values keep
|
||||
resolving, and an unresolved one is R4. README's line "A link to a `.canvas` file or an attachment is
|
||||
R4" is back as it was at fefd54e13. B1: resolve first. An embed is an attachment only when it resolves
|
||||
to nothing and its target has an extension other than `.md`. So `![[Dr.Quinn]]` with Dr.Quinn.md
|
||||
indexed resolves, `![[cover.png]]` gives no problem, `![[missing-note]]` is R4 and `![[note.md]]`
|
||||
resolves. My own design ruling after r11 (28213ac27) also made plain links attachments. The brief
|
||||
narrowed that, and the brief governs this row.
|
||||
|
||||
Changes:
|
||||
|
||||
- `links.mjs`: `isAttachment` is true only for a body embed, `link.embed === true && !link.field`,
|
||||
with the extension test as before. Its comment says it goes by name, so the reader asks it only
|
||||
after resolving.
|
||||
- `reader.mjs`: every link resolves first. An unresolved one is R4 unless `isAttachment` holds.
|
||||
- `survey.mjs`: `quote()` also escapes U+061C (Webby N1). The attachment count now covers body embeds
|
||||
only, since it uses `isAttachment`.
|
||||
- Webby N2, my choice of Sage's two options: the survey keeps a count by name and the README says so,
|
||||
"the count of body embeds whose target has an extension other than `.md`", and that `![[Dr.Quinn]]`
|
||||
counts there even when Dr.Quinn.md exists. Checking against the survey's path list would mean a second
|
||||
resolver in the survey, for a count that only guides the mapping.
|
||||
- Tests. links: embeds only, with `[[cover.png]]`, `[[Board.canvas]]`, `[[Dr.Quinn]]`, a front matter
|
||||
value and a markdown link all false, and `![[Dr.Quinn]]` true by name. reader: the Shore case now
|
||||
gives R4 for the front matter `"![[portrait.JPG]]"` (line 3), for `[[Board.canvas]]` (line 5) and for
|
||||
`![[missing-note]]` (line 6). The new Dr.Quinn case has Dr.Quinn.md indexed and resolves `[[Dr.Quinn]]` in
|
||||
a block list and in the body, plus `![[Dr.Quinn]]`, with no problem. survey: a U+061C folder name
|
||||
prints escaped, and the attachment count is 1, since `[[map.PDF]]` is a plain link.
|
||||
|
||||
Round 2 tests, one `node --test` per file, each run after a busy check.
|
||||
|
||||
Run 1, slot grant sage-1903, 09:39:32Z to 09:39:46Z: 60 pass, 1 fail. The new Dr.Quinn reader test failed
|
||||
on order alone. All three links resolved to Dr.Quinn.md with the right field, line and embed values, and
|
||||
there was no problem. The reader lists body links before front matter links, and my expected array put
|
||||
the `relationships` link first. I stopped and reported it (gosalyn-0098).
|
||||
|
||||
Run 2, after the test-only fix sage-1907 approved, which moves that entry to the end of the expected array,
|
||||
09:43:42Z to 09:43:56Z. Every rc 0.
|
||||
|
||||
| File | pass | fail |
|
||||
|---|---|---|
|
||||
| packages/library/tests/links.test.mjs | 7 | 0 |
|
||||
| packages/library/tests/mapping.test.mjs | 7 | 0 |
|
||||
| packages/library/tests/reader.test.mjs | 14 | 0 |
|
||||
| packages/library/tests/render.test.mjs | 11 | 0 |
|
||||
| packages/library/tests/survey.test.mjs | 6 | 0 |
|
||||
| packages/cli/tests/stack-copy.test.mjs | 16 | 0 |
|
||||
|
||||
Total: 61 pass, 0 fail, 0 cancelled, summed by awk.
|
||||
@@ -85,12 +85,13 @@ then with `.md` added:
|
||||
3. `relative`: relative to the linking file, never above the repo root.
|
||||
4. `shortest`: a target with no `/` that matches the base name of exactly one indexed file.
|
||||
|
||||
It resolves only against indexed files. A link or `![[...]]` embed whose target ends in an extension
|
||||
other than `.md`, in any case, is an attachment: an extension is a dot and up to 8 letters and digits,
|
||||
at least one a letter. The reader neither resolves nor indexes an attachment, and it is never R4. A
|
||||
note embed, with no extension or with `.md`, resolves like any link, so an unresolved one is R4. A
|
||||
link to a `.canvas` file is an attachment too. A link into another repo resolves to nothing.
|
||||
`extractLinks` skips fenced and inline code.
|
||||
It resolves only against indexed files. A link to a `.canvas` file or an attachment is R4. A link into
|
||||
another repo resolves to nothing. `extractLinks` skips fenced and inline code.
|
||||
|
||||
A `![[...]]` embed resolves like any link. A body embed that resolves to nothing and whose target ends
|
||||
in an extension other than `.md`, in any case, embeds an attachment, and it is never R4. An extension is
|
||||
a dot and up to 8 letters and digits, at least one a letter. Any other unresolved embed is R4, and so is
|
||||
an unresolved front matter value written as an embed (C2-01b N3, sage-1900).
|
||||
|
||||
## renderItems
|
||||
|
||||
@@ -115,9 +116,11 @@ mosaic library survey <repo> [--mapping <file>]
|
||||
```
|
||||
|
||||
It reads the repo's HEAD commit and prints folder names, file counts by extension, front matter key
|
||||
sets, link-form counts and the count of body links to attachments, which still count under their
|
||||
form. Folder names print as JSON strings, with DEL, C1 and bidi controls also as `\uXXXX`. It never
|
||||
prints file text, front matter values, file names or link targets.
|
||||
sets, link-form counts and the count of body embeds whose target has an extension other than `.md`,
|
||||
which still count as embeds. That count goes by the name alone, so an embed of `Dr.Quinn.md` written
|
||||
`![[Dr.Quinn]]` counts there too. Folder names print as JSON strings, with DEL, C1 and bidi controls,
|
||||
U+061C among them, also as `\uXXXX`. It never prints file text, front matter values, file names or
|
||||
link targets.
|
||||
A key that is not a short identifier prints as `(other key)`, since it could be her words. With
|
||||
`--mapping` it adds scope counts, resolver step counts and problem counts by rule.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Wikilinks and the link resolver of design 1.3. Links are [[target]], [[target|label]] and
|
||||
// [[target#heading]], with ![[...]] embeds read the same way. Links inside fenced code blocks and
|
||||
// inline code spans are not links, as in Obsidian. A link or embed whose target ends in an extension
|
||||
// other than `.md`, in any case, refers to an attachment (design 1.3): the reader neither resolves
|
||||
// nor indexes it, and it raises no problem.
|
||||
// inline code spans are not links, as in Obsidian. A body embed that resolves to no indexed note and whose
|
||||
// target ends in an extension other than `.md`, in any case, embeds an attachment (C2-01b N3): the
|
||||
// reader doesn't index it, and it raises no problem.
|
||||
import { posix } from 'node:path';
|
||||
|
||||
const WIKILINK = /(!?)\[\[([^\[\]\n]+?)\]\]/g;
|
||||
@@ -25,9 +25,12 @@ function parts(inner, embed) {
|
||||
embed,
|
||||
};
|
||||
}
|
||||
// An extension is a dot and up to 8 letters and digits, at least one a letter, at the end of the
|
||||
// target's last segment, so `Chapter 1.5` and `Mr. Smith` are notes and `cover.png` is not.
|
||||
export const isAttachment = link => !link.markdown
|
||||
// Whether a body embed's target ends in an extension other than `.md`. An extension is a dot and up
|
||||
// to 8 letters and digits, at least one a letter, at the end of the target's last segment, so
|
||||
// `Chapter 1.5` and `Mr. Smith` have none and `cover.png` has one. `Dr.Quinn` has one too, so the
|
||||
// reader asks this only of an embed that resolves to nothing. A plain link or a front matter value is
|
||||
// never an attachment (sage-1900).
|
||||
export const isAttachment = link => link.embed === true && !link.field
|
||||
&& /\.(?=[a-z0-9]*[a-z])[a-z0-9]{1,8}$/i.test(link.file.slice(link.file.lastIndexOf('/') + 1))
|
||||
&& !/\.md$/i.test(link.file);
|
||||
// The form a link is counted under in the survey.
|
||||
|
||||
@@ -199,9 +199,9 @@ export async function readLibrary({ repo, commit, mapping }) {
|
||||
for (const file of files) {
|
||||
const kept = [];
|
||||
for (const link of file.links) {
|
||||
if (!link.file || isAttachment(link)) continue;
|
||||
if (!link.file) continue;
|
||||
const target = resolve(file.path, link.file);
|
||||
if (!target.path) { problems.push(problem('R4', file.path, link.line)); continue; }
|
||||
if (!target.path) { if (!isAttachment(link)) problems.push(problem('R4', file.path, link.line)); continue; }
|
||||
const to = byPath.get(target.path);
|
||||
if (file.scope === 'canon') {
|
||||
if (to.scope === 'playground') problems.push(problem('R5', file.path, link.line));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// `mosaic library survey <repo> [--mapping <file>]` (design 1.1, 10 step 7). It prints what a mapping
|
||||
// needs: folder names, file counts by extension, front matter key sets, link-form counts and the
|
||||
// count of body links to attachments. It never prints file text, front matter values, file names or
|
||||
// link targets. With a mapping it adds
|
||||
// scope counts, resolver step counts and R-problem counts by code. It reads the repo's HEAD commit
|
||||
// count of body embeds whose target has an extension other than `.md`. It never prints file text,
|
||||
// front matter values, file names or link targets. With a mapping it adds scope counts, resolver
|
||||
// step counts and R-problem counts by code. It reads the repo's HEAD commit
|
||||
// through the commit reader, never the working tree.
|
||||
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
@@ -78,7 +78,7 @@ export async function survey({ repo, commit, mapping }) {
|
||||
// Folder names are quoted as JSON strings, which escape C0 controls but leave DEL, C1 and bidi
|
||||
// controls raw. quote() escapes those too, so no name can carry a control sequence to the terminal
|
||||
// or reorder the line around it.
|
||||
const RAW_CONTROLS = /[\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
|
||||
const RAW_CONTROLS = /[\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
|
||||
export const quote = name => JSON.stringify(name).replace(RAW_CONTROLS, c => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
||||
export function formatSurvey(s) {
|
||||
const out = [`commit ${s.commit}`, `entries ${s.entries}`, 'folders (direct children: md canvas base other)'];
|
||||
|
||||
@@ -49,11 +49,14 @@ test('wikilink forms, embeds, and links skipped inside code', () => {
|
||||
assert.deepEqual(extractLinks(lines, { markdown: true }).filter(l => l.markdown).map(l => l.file), ['Five.md']);
|
||||
});
|
||||
|
||||
test('a target with an extension other than .md, in any case, is an attachment', () => {
|
||||
test('only a body embed whose target has an extension other than .md, in any case, is an attachment', () => {
|
||||
const att = text => isAttachment(parseWikilink(text));
|
||||
for (const s of ['![[cover.png]]', '[[cover.PNG]]', '![[Art/map.pdf|Map]]', '[[Board.canvas]]', '![[clip.mp4#t=5]]'])
|
||||
// By name alone: the reader asks only of an embed that resolved to nothing (sage-1900).
|
||||
for (const s of ['![[cover.png]]', '![[cover.PNG]]', '![[Art/map.pdf|Map]]', '![[Board.canvas]]', '![[clip.mp4#t=5]]', '![[Dr.Quinn]]'])
|
||||
assert.equal(att(s), true, s);
|
||||
for (const s of ['![[missing-note]]', '![[note.md]]', '[[Note.MD]]', '[[Chapter 1.5]]', '[[Mr. Smith]]', '[[A.b/Note]]', '[[#Self]]'])
|
||||
for (const s of ['![[missing-note]]', '![[note.md]]', '![[Note.MD]]', '![[Chapter 1.5]]', '![[Mr. Smith]]', '![[A.b/Note]]',
|
||||
'[[cover.png]]', '[[Board.canvas]]', '[[Dr.Quinn]]', '[[#Self]]'])
|
||||
assert.equal(att(s), false, s);
|
||||
assert.equal(isAttachment({ file: 'pic.png', markdown: true }), false, 'markdown links are never followed anyway');
|
||||
assert.equal(isAttachment({ ...parseWikilink('![[portrait.jpg]]'), field: 'relationships' }), false, 'a front matter value');
|
||||
assert.equal(isAttachment({ file: 'pic.png', markdown: true, embed: false }), false, 'a markdown link');
|
||||
});
|
||||
@@ -107,16 +107,30 @@ test('a block list link carries its item line, a scalar or flow link its key lin
|
||||
[['R4', 2], ['R4', 6], ['R5', 7]]);
|
||||
});
|
||||
|
||||
test('an attachment link or embed is not followed and gives no problem; a note embed resolves', async t => {
|
||||
// sage-1900: only an unresolved body embed with an extension other than .md is an attachment. A plain
|
||||
// link to one and a front matter value stay R4 when they resolve to nothing.
|
||||
test('an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves', async t => {
|
||||
const r = repo(t, { ...BASE, 'Book One/Places/note.md': 'n\n', 'Book One/Places/Shore.md': ['---',
|
||||
'relationships:', ' - "![[portrait.JPG]]"', '---', '![[cover.png]] [[Board.canvas]] ![[map.pdf|Map]]',
|
||||
'![[missing-note]] ![[note.md]] ![[Harbor Town]]', ''].join('\n') });
|
||||
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
||||
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Places/Shore.md').map(p => [p.code, p.line]), [['R4', 6]]);
|
||||
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Places/Shore.md').map(p => [p.code, p.line]),
|
||||
[['R4', 3], ['R4', 5], ['R4', 6]]);
|
||||
assert.deepEqual(file(result, 'Book One/Places/Shore.md').links.map(l => [l.path, l.embed]),
|
||||
[['Book One/Places/note.md', true], ['Book One/Places/Harbor Town.md', true]]);
|
||||
});
|
||||
|
||||
// webby-0070 B1: a note whose name looks like it has an extension resolves before the attachment test.
|
||||
test('an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed', async t => {
|
||||
const r = repo(t, { ...BASE, 'Book One/Characters/Dr.Quinn.md': 'q\n', 'Book One/Characters/Ana.md': ['---',
|
||||
'relationships:', ' - "[[Dr.Quinn]]"', '---', '[[Dr.Quinn]] ![[Dr.Quinn]]', ''].join('\n') });
|
||||
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
||||
assert.deepEqual(file(result, 'Book One/Characters/Ana.md').links.map(l => [l.field ?? null, l.path, l.line, l.embed]), [
|
||||
[null, 'Book One/Characters/Dr.Quinn.md', 5, false], [null, 'Book One/Characters/Dr.Quinn.md', 5, true],
|
||||
['relationships', 'Book One/Characters/Dr.Quinn.md', 3, false]]);
|
||||
assert.deepEqual(result.problems.filter(p => p.path === 'Book One/Characters/Ana.md'), []);
|
||||
});
|
||||
|
||||
test('R5: a canon file that refers into the playground is reported, and the link still goes by path', async t => {
|
||||
const r = repo(t, { ...BASE, 'Book One/Places/Dock.md': 'See [[What If]].\n' });
|
||||
const result = await readLibrary({ repo: r.repo, commit: r.commit, mapping: mapping() });
|
||||
|
||||
@@ -46,22 +46,22 @@ test('the survey prints folder names, counts, key sets and link-form counts, and
|
||||
for (const s of NEVER) assert.ok(!out.stdout.includes(s), s);
|
||||
});
|
||||
|
||||
test('folder names print DEL, C1 and bidi controls as escapes', async t => {
|
||||
test('folder names print DEL, C1 and bidi controls, U+061C among them, as escapes', async t => {
|
||||
// One name per escaped range, with each range's first and last code point.
|
||||
const names = ['A\u007fB', 'C\u0080D\u009fE', 'F\u200eG\u200fH', 'I\u202aJ\u202eK', 'L\u2066M\u2069N'];
|
||||
const names = ['A\u007fB', 'C\u0080D\u009fE', 'F\u200eG\u200fH', 'I\u202aJ\u202eK', 'L\u2066M\u2069N', 'O\u061cP'];
|
||||
const r = repo(t, Object.fromEntries(names.map(n => [`${n}/a.md`, `${MARKER}\n`])));
|
||||
const out = await run(['survey', r.repo]);
|
||||
assert.equal(out.code, 0, out.stderr);
|
||||
assert.deepEqual(out.stdout.split('\n').slice(3, 8), [
|
||||
assert.deepEqual(out.stdout.split('\n').slice(3, 9), [
|
||||
' "A\\u007fB" 1 0 0 0', ' "C\\u0080D\\u009fE" 1 0 0 0', ' "F\\u200eG\\u200fH" 1 0 0 0',
|
||||
' "I\\u202aJ\\u202eK" 1 0 0 0', ' "L\\u2066M\\u2069N" 1 0 0 0']);
|
||||
assert.doesNotMatch(out.stdout, /[\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/);
|
||||
' "I\\u202aJ\\u202eK" 1 0 0 0', ' "L\\u2066M\\u2069N" 1 0 0 0', ' "O\\u061cP" 1 0 0 0']);
|
||||
assert.doesNotMatch(out.stdout, /[\u007f-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/);
|
||||
});
|
||||
|
||||
test('an embed of an attachment counts as an embed and as an attachment', async t => {
|
||||
test('an embed of an attachment counts as an embed and as an attachment; a plain link to one does not', async t => {
|
||||
const r = repo(t, { 'Book One/Chapters/A.md': ['![[cover.png]] ![[missing-note]] [[map.PDF]] [[Chapter 1.5]]', ''].join('\n') });
|
||||
const out = await run(['survey', r.repo]);
|
||||
assert.match(out.stdout, /^body links plain 2, label 0, heading 0, embed 2, self 0, markdown 0; attachment 2$/m);
|
||||
assert.match(out.stdout, /^body links plain 2, label 0, heading 0, embed 2, self 0, markdown 0; attachment 1$/m);
|
||||
});
|
||||
|
||||
test('with --mapping the survey adds scope, resolver step and problem counts', async t => {
|
||||
|
||||
Reference in new issue
Block a user