Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Miscellaneous/Documentation Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ To check that none of the internal links in the most recent documentation build

check.bat

This runs [Lychee](https://github.com/lycheeverse/lychee) in offline mode against the built `_site/`.
This runs three checks: [Lychee](https://github.com/lycheeverse/lychee) in offline mode against `_site/` (the live tree), the same against `_site-offline/` (the file://-browsable mirror), and a small Python pass over `_site-offline/` that flags any surviving `https://docs.twinbasic.com/<path>` link --- the offline mirror should not navigate back to the live docs site.

### Building and Local Serving

Expand Down
2 changes: 1 addition & 1 deletion docs/Miscellaneous/FAQs.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ For a full list of all the new features available right now, see the Wiki articl
{: #to-learn-more }
[twinBASIC Home Page](https://twinbasic.com)

twinBASIC GitHub: [Main section](https://github.com/twinbasic/twinbasic) \| [Issues](https://github.com/twinbasic/twinbasic/issues) \| [Discussions](https://github.com/twinbasic/twinbasic/discussions) \| [Language Design](https://github.com/twinbasic/lang-design) \| [ Language Specification](https://github.com/twinbasic/lang-spec) \| [Documentation](https://docs.twinbasic.comi)
twinBASIC GitHub: [Main section](https://github.com/twinbasic/twinbasic) \| [Issues](https://github.com/twinbasic/twinbasic/issues) \| [Discussions](https://github.com/twinbasic/twinbasic/discussions) \| [Language Design](https://github.com/twinbasic/lang-design) \| [ Language Specification](https://github.com/twinbasic/lang-spec) \| [Documentation](https://docs.twinbasic.com)

[twinBASIC Discord](https://discord.gg/UaW9GgKKuE)

Expand Down
5 changes: 5 additions & 0 deletions docs/_plugins/book-href-rewrite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ def self.process(page)
return if parent_map.empty?
landing_anchors = build_landing_anchors(site)

start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

rewritten = 0
landings_stripped = 0
page.output = page.output.gsub(/(<article[^>]*id="(ch-[^"]+)"[^>]*>)(.*?)(<\/article>)/m) do
Expand All @@ -254,6 +256,9 @@ def self.process(page)
"#{article_open}#{body}#{article_end}"
end
Jekyll.logger.info "BookHrefRewrite:", "rewrote #{rewritten} chapter bodies, stripped #{landings_stripped} landing H3s"

elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(0)
Jekyll.logger.info "BookHrefRewrite:", "BookHrefRewriter ran in #{elapsed_ms}ms."
end
end

Expand Down
191 changes: 129 additions & 62 deletions docs/_plugins/offlinify.md

Large diffs are not rendered by default.

612 changes: 420 additions & 192 deletions docs/_plugins/offlinify.rb

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/_plugins/pdfify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def self.run(site, source_root, dest_root)
return
end

start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

# Wipe the destination tree so previous runs do not leave stale
# images behind when source pages are deleted or renamed.
FileUtils.rm_rf(dest)
Expand Down Expand Up @@ -132,6 +134,9 @@ def self.run(site, source_root, dest_root)
book_src.delete

Jekyll.logger.info "Pdfify:", "wrote #{dest_root} -- copied #{copied} file(s) (#{image_paths.size} image(s)#{skipped.zero? ? "" : ", #{skipped} missing"})"

elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(0)
Jekyll.logger.info "Pdfify:", "Pdfifier ran in #{elapsed_ms}ms."
end

# Walks book.html for relative `<img src=>` URLs and returns the
Expand Down
23 changes: 18 additions & 5 deletions docs/check.bat
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@rem Use lychee to check the links in both build outputs.
@rem Use lychee to check the links in both build outputs, then scan
@rem _site-offline/ for live-site links that survived offlinify.
@rem
@rem _site/ Online tree. `--fallback-extensions html` mirrors what
@rem GitHub Pages does at request time: an extensionless
Expand All @@ -10,10 +11,17 @@
@rem markdown sources whose permalink shape doesn't match
@rem the rendered filename (e.g. `[Foo](Foo/)` when Jekyll
@rem wrote `Foo.html`, not `Foo/index.html`).
@rem live-links Greps _site-offline/ HTML for any surviving
@rem https://docs.twinbasic.com reference outside <code> /
@rem <pre> blocks. After _plugins/offlinify.rb strips the
@rem jekyll-seo-tag block from each page, none should
@rem remain -- a hit means a source link goes to the live
@rem site instead of the canonical /tB/... permalink.
@rem See ../scripts/check_offline_live_links.py.
@rem
@rem Both checks always run so you see all errors in one pass; the script
@rem exits non-zero if either fails (online failure takes precedence in
@rem the reported code).
@rem All three checks always run so you see all errors in one pass; the
@rem script exits non-zero if any fails (earlier failures take precedence
@rem in the reported code).
@setlocal
@set LYCHEE="%~dp0..\.claude\lychee.exe"
@echo Checking _site/ (online) ...
Expand All @@ -28,5 +36,10 @@
@rem such fallback, and the link is just broken.
@%LYCHEE% --offline --include-fragments --index-files "index.html" --root-dir ".\_site-offline" ".\_site-offline" %*
@set EXIT2=%ERRORLEVEL%
@echo.
@echo Checking _site-offline/ for live-site links ...
@python "%~dp0..\scripts\check_offline_live_links.py"
@set EXIT3=%ERRORLEVEL%
@if %EXIT1% NEQ 0 exit /b %EXIT1%
@exit /b %EXIT2%
@if %EXIT2% NEQ 0 exit /b %EXIT2%
@exit /b %EXIT3%
97 changes: 97 additions & 0 deletions scripts/check_offline_live_links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
Scan docs/_site-offline/ for any https://docs.twinbasic.com/<path>
reference outside of <code> / <pre> blocks. Exit 1 if any found,
0 otherwise.

Run by docs/check.bat after the offline lychee pass. After
_plugins/offlinify.rb's SEO-block strip, no live-site references
should remain except:

* Sample URLs inside <code> / <pre> blocks (tutorial code that
legitimately shows live URLs as data, e.g. the VBRUN.Hyperlink
`NavigateTo "https://docs.twinbasic.com/"` example). Skipped
via the same code-block shape offlinify uses for its URL
rewrite.
* The bare root URL `https://docs.twinbasic.com` or
`https://docs.twinbasic.com/` -- intentional "go to the live
docs site" links (e.g. the Documentation entry in the FAQ
resource list). Skipped via the tail check below.

Anything deeper (`https://docs.twinbasic.com/tB/Core/Const`,
`https://docs.twinbasic.comi`, ...) is flagged: in the offline
copy those navigate back to the live site, undermining the local
read; in source they should be a relative link or a /tB/...
permalink that resolves locally.

Run from anywhere:
python scripts/check_offline_live_links.py
"""

import re
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
OFFLINE_TREE = REPO_ROOT / "docs" / "_site-offline"

# Matches a <code>...</code> or <pre>...</pre> block. Same shape as
# _plugins/offlinify.rb CODE_BLOCK_RE so sample URLs in tutorial
# code are skipped here too.
CODE_BLOCK_RE = re.compile(r"<(code|pre)\b[^>]*>.*?</\1>", re.DOTALL)

# Captures the trailing path/typo characters after the domain. An
# empty tail or `/` means the bare root URL (intentional). Anything
# else is a deep link or a typo (`.comi`, `.com/tB/...`).
LIVE_LINK_RE = re.compile(r"https://docs\.twinbasic\.com(?P<tail>[^\s\"'<>]*)")


def main() -> int:
if not OFFLINE_TREE.is_dir():
print(
f"_site-offline/ not found at {OFFLINE_TREE} -- run docs/build.bat first."
)
return 2

hits = []
for html in sorted(OFFLINE_TREE.rglob("*.html")):
content = html.read_text(encoding="utf-8")
link_matches = list(LIVE_LINK_RE.finditer(content))
if not link_matches:
continue
code_ranges = [(m.start(), m.end()) for m in CODE_BLOCK_RE.finditer(content)]
for m in link_matches:
tail = m.group("tail")
if tail == "" or tail == "/":
continue
if any(s <= m.start() < e for s, e in code_ranges):
continue
line_num = content.count("\n", 0, m.start()) + 1
start = max(0, m.start() - 60)
end = min(len(content), m.start() + 80)
snippet = re.sub(r"[\r\n]+", " ", content[start:end])
hits.append((html, line_num, snippet))

if hits:
print(
f"FAIL: {len(hits)} reference(s) to docs.twinbasic.com in "
f"_site-offline/ outside code blocks:"
)
for path, line_num, snippet in hits:
try:
rel = path.relative_to(REPO_ROOT)
except ValueError:
rel = path
print(f" {rel}:{line_num}: ...{snippet}...")
print()
print(
"Update the source markdown to use a relative link or /tB/... "
"permalink instead."
)
return 1

return 0


if __name__ == "__main__":
sys.exit(main())
Loading