Repository navigation
Rewrite angle-bracket and query links whole, and link only captured notes - #326
Conversation
Review at 0f15a8dVerdict: Request changes. Render now rewrites angle-bracket destinations and queries whole and, with repository coordinates, links a Fixes before merge
Optional
Landing
Decision for the maintainer
This PR came from the same PR swarm that reviewed it, so this is the author's review, not an independent one. It was a static review: no tests were run locally. CI at this head: all 7 checks pass, including the real-Lean jobs. Posted by PR swarm: PR Swarm Lead |
0f15a8d to
f91863d
Compare
Review changes addressed at f91863d
Still open for you: whether a hidden note under CI is running on this head. Posted by PR swarm: PR Swarm Lead |
…otes Render's link rewriting published three kinds of dead link that `mkdocs build --strict` let through: - `_MARKDOWN_LINK` cut `[Smith](<../sources/Smith 2019.pdf>)` at the space and `[S](../sources/Smith(2019).pdf)` at its first `)`. It now reads angle-bracket destinations, and bare destinations and titles holding one level of balanced parentheses, which Python-Markdown accepts. A quoted title may leave a `(` open and an escaped parenthesis is text, so no match runs past its own link's `)` and drops what follows. - `moved_target` resolved a query as part of the file name, so `paper.md?plain=1` reached the repository as `paper.md%3Fplain%3D1`. The query is now split off and written back before the fragment. The book's page order splits it off too, so a query no longer hides a chapter from the book. - Every path under `sources/` got a repository URL, so a missing note got one that 404s. Only a path the captured blueprint holds does now; any other stays site-relative, and the strict build fails on it. Capture skips hidden files, so a committed hidden note now fails it too. An inline link whose destination does not move is left as written. A rewritten destination holding whitespace or a parenthesis is written in angle brackets. In a reference definition, which Python-Markdown reads only up to whitespace, whitespace is percent-encoded instead. Closes #303.
f91863d to
a2f35a5
Compare
Follow-up review at a2f35a5Verdict: Ready to merge. This pass checked each fix from the last round against the code, merged the branch with Changed in this pass
Decision for the maintainer
This PR came from the same PR swarm that reviewed it, so this is the author's review, not an independent one. No tests were run under pytest locally. The 12 new tests, both cases of the reference-definition test and 15 existing link, page-order and slot tests were called directly at this head and pass, and each of 24 mutants of the new rules fails the test meant to catch it. CI at this head: all 7 checks pass, including the real-Lean jobs. Posted by PR swarm: PR Swarm Lead |
Closes #303.
Problem
Render's link rewriting published three kinds of dead link, and
mkdocs build --strictlet each one through:_MARKDOWN_LINKhad no<...>alternative, so[Smith](<../sources/Smith 2019.pdf>)was cut at the space and published as[Smith](<../sources/Smith). Its bare alternative ended at the first), so with repository coordinates[S](../sources/Smith(2019).pdf)got a URL ending inSmith%282019that 404s.moved_targetsplit off only the fragment, so a query was resolved as part of the file name. With repository coordinates,../sources/paper.md?plain=1became a URL ending inpaper.md%3Fplain%3D1.sources/went to the repository once the directory was in the commit, so a link to a note that does not exist got a URL that 404s.checkreads only## Sources, so such a link in an article body or on another page reached the deployed site.Change
All in
autoform_cli/render.py:_MARKDOWN_LINKtakes the<[^>]+>alternative that graph's_LINKand render's_LINK_DEFINITIONalready have, and its bare alternative takes one level of balanced parentheses, which Python-Markdown and CommonMark accept, so[S](../sources/Smith(2019).pdf)is read whole. A title may hold balanced parentheses too, and a quoted title an unclosed(, so[S](../sources/paper.md "Smith (2019)")no longer leaves")on the page, and neither title carries the match on to a later)and drops the text before it. A backslash-escaped parenthesis counts in neither, since Python-Markdown reads it as text, so(see [x](../sources/a\(b.md) and [y](../sources/c.md))keeps everything after[x]; the escape is not decoded, so with coordinates[x]is left as written, where the strict build reports it. This also stops an external<https://...>destination with a space from being rewritten as a relative path. The book's page order reads links with the same pattern, so it now follows an angle-bracket link with a space, or a bare one with balanced parentheses, to a book page too.f(p),$[X,Y](f(p))$would gain angle brackets, and$[X,Y](f \circ g)$keeps the\circ gthatmaindrops as a title.[x](<a(b.md>)and[x](<a b.md>)as one link each. Bare, it reads no link toa(b.mdand endsa)b.mdat the), and CommonMark allows no space in a bare destination.[paper]: <../sources/paper note.md>, kept on the site, was published as text, and every[Paper][paper]with it, whether render dropped the brackets, as it did before, or kept them.moved_targetsplits the query off the path before decoding and resolving it, and writes it back after the moved path and before the fragment; on a node link, before the anchor. This is the split draft Treat every published page as untrusted #215 makes, with the same comment. The book's page order splits the query off too, so a landing-page link such asroadmap/README.md?plain=1no longer drops its chapter from the book, as it does onmain.sources/target gets the repository URL only when render's private copy of the captured blueprint holds it. Every captured file is in the verified commit, so that URL resolves. A captured directory Git does not hold, one that is empty or holds only ignored hidden files, still gets a repository URL that 404s. Any other target keeps its site-relative link. The site does not publishsources/in that mode, so the strict build fails on a link to a missing file and names it. One form still gets past it: a target MkDocs reads as having no file extension, which it only logs, as when the name has none or a decoded?or#ends the path early. The directory link from Publish blueprint sites transactionally #165 is unchanged.This changes three outcomes. Capture skips hidden files, so a committed hidden note such as
sources/.private/paper.mdgot a repository link that resolved, and now fails the strict build; #304 proposes thatcheckreject such targets in## Sources, and #321 does. An unbalanced bare target that Python-Markdown reads as text, as[x]inSee [x](../sources/a(b.md) and [y](../sources/c.md)., is now left as written, so with coordinates it stays text too, where it used to get a repository URL; the link after it is still rewritten. And a bare target with an unclosed(and a quoted title, as[x](../sources/a(b.md "t"), which Python-Markdown reads as a link toa(b.md, is left as written too, so with coordinates the strict build fails on it where it used to get a repository URL that resolved.The existence check reads the private copy, so on a case-insensitive filesystem a link whose spelling differs from the file only in case still gets a repository URL, as before. The generated Pages workflow renders on Linux, where it does not.
Tests
New render tests, rendered from a committed project with repository coordinates unless noted, cover: an angle-bracket destination with a space; a query with a fragment on a source note link; a missing note; a bare link with balanced parentheses to a note that exists;
See [x](../sources/a(b.md) and [y](../sources/c.md)., which must keep[x]as written and lose no text;(see [x](../sources/a\(b.md) and [y](../sources/c.md)), which must keep the text after[x]; titles with parentheses, balanced and unclosed, in either quote and with a space before the); a link to thesources/directory; math shaped like a link, which must come out as written; and, without coordinates, a site-relative link whose decoded path holds a space or a(, a query on a node link and on a page link from the coverage page, and a query on the landing page's chapter link, which must keep the chapter in the book. The existing angle-bracket reference-definition test now also runs without coordinates.Called directly, each test fails when the rule it covers is reverted: the angle-bracket alternative; the balanced bare alternative; the title rules, whether the title part goes back to
main's or loses its quoted alternatives, its single-quoted one or the space after a quoted title; the escape handling; the query inmoved_target's split, its site-relative return or its node branch, or in the page order; the existence check, oros.path.isfilein its place; either half ofbracketed; the encoding in definitions; and leaving unmoved links as written.The test suite was not run locally; CI runs it. Called directly from Python at this head, the twelve new tests pass, as do both cases of the reference-definition test and fifteen existing render tests that rewrite links, order the book or place statements at their authored slots, among them #318's test for links that leave the blueprint.
Over 29 inputs, among them each form above, links inside an aside, an external URL with parentheses, titles in parentheses, a node link with a title, and math and code spans shaped like links, Python-Markdown 3.10.3 shows the same text before and after the rewrite, with coordinates and without, but in two places: a quoted title holding a
)on a link that moves, as[S](../sources/paper.md "a) b")does with coordinates, still leavesb")on the page, as onmain; and an angle-bracket definition with a space, which Python-Markdown reads as text, becomes a working definition.mkdocs build --strictwith MkDocs 1.6.1 failed on each form render writes for a missing file with a space or a parenthesis (an angle-bracket link, a definition with the space encoded, a definition holding the parenthesis) and passed when the files existed.ruff check autoform_cli servers testspasses.Landing
Rebased onto
mainat 7e34a3a; it merges cleanly into d1ad2fb, which adds #337. None of the PRs merged since the previous base, feec945 (#323, #331, #332, #335, #336, #337, #338, #341, #343), touches_MARKDOWN_LINKor_rewrite_links. An earlier rebase resolved the one conflict with #318, which makes a link to a target outside the blueprint keep only its text:replaceends with #318'sif href else match.group("label"), so a target outside the blueprint written in angle brackets with a space keeps only its text too. #300 and #301 change the sources base, notmoved_target: under #300 there is one only when the blueprint's own bytes are in the commit, which the existence check relies on, and #301's committed spellings change only the URL the check lets through.git merge-tree --write-treeof each open PR head against this branch, compared with the same againstmain:Treat every published page as untrusted #215 and the drafts that carry its render changes (Add read-back cards and the testimony validator #216, Show statement review on the site #217, Label statement approvals by the repository's code-owner review policy #219, Add statement-review commands #284) get four new conflicts in
render.py: on_MARKDOWN_LINK, beside the_FENCEline Treat every published page as untrusted #215 deletes; on the#split, where Treat every published page as untrusted #215 names the separatorhashed; on the node branch ofmoved_target; and on itssources/and site-relative returns, whichbracketedfollows. Their other conflicts withmainare unchanged. Keep Treat every published page as untrusted #215's lines, except that_MARKDOWN_LINKkeeps this branch's pattern and Treat every published page as untrusted #215'ssources/condition gainsand os.path.exists(candidate); keepbracketed, whichreplacestill calls. Treat every published page as untrusted #215 percent-encodes every destination it writes, so the decoded-path test should then expect its links as written. The balanced-parentheses test takes thesources/branch, where Treat every published page as untrusted #215's_URL_SAFEkeeps the%28and%29that_source_hrefwrites, so it should pass as written. That resolution was not run at this head.Scale roadmap inventories and the full DAG explorer #90 gets two new conflicts in
render.py. On_MARKDOWN_LINK, keep its label pattern and take this branch's target and title patterns. On the last line ofreplace, keep Scale roadmap inventories and the full DAG explorer #90's fallback for a link that loses its target, and have itsif href:branch returnf"[{label}]({bracketed(href)})".Leave malformed link targets as written in render and the runtime #327 wraps the lines just after the split in a
try, inmoved_targetand in the book's page order, so the two conflict in both places. Whichever lands second keeps this branch's split above thetry; in the page order that ispath = bare.partition("#")[0].partition("?")[0], and inmoved_target:No other open PR gets a new conflict: not Feedback form #195, Extract Lean skeletons one probe per root module #213, Run skeleton probes in parallel and isolate probe failures #214, Keep source ledgers out of the public book #299, Read code-only labels, reference links and nested brackets as links #320, Make check reject Sources links to hidden paths #321, Stop worker groups that Lean's watchdog leaves running at LSP close #322, Close the gaps optional claims left in runs without claims #324, Make check reject a container article that sets a declaration #328, Say how unused-hypotheses differs from Batteries' unusedArguments linter #329, Link folders under the blueprint by the spelling Git committed, and fix render --ref's help #333, Restore SIGCHLD's default so the runner sees real exit statuses #334, Guard #289's linear render and drop caches lazy links made redundant #339 or Pin the comment stripper's events, and end raw strings at their first closer #340. Keep source ledgers out of the public book #299 drops
sources/from the MkDocs build; a site rendered with coordinates has nosources/to drop, so a link to a missing note still fails the strict build. Guard #289's linear render and drop caches lazy links made redundant #339 swaps the node branch's_anchored_links(...)call for its page-widelinksmapping, which the query splice after it reads unchanged.#320 replaces graph's
_LINK, cited above, withmarkdown.link_targets. #327'stryblocks also cover a form this branch starts reading:[x](<https://[::1/a b>)now raises inurlsplit, inmoved_targetand in the book's page order, as[x](https://[::1/a)already does onmain.