mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
The changelog said nothing had been released since 1.0.0. Twenty-two builds had shipped. Twenty-one entries sat under "## Unreleased" in a file with exactly two headings, so a reader at any tag found no section for the version they were holding. Which release carried which entry is DERIVED, not guessed: for every line in the region, the commit that introduced it, then the earliest tag containing that commit. That yields seven releases - .22, .21, .20, .19, .14, .8 and .0. No entry text changed. A verification pass compares the multiset of non-heading lines before and after and refuses to write if anything was lost, gained or duplicated; entries move under their release, so it compares as a multiset rather than in order. CHANGELOG.txt was kept BY HAND and drifted the moment the .md was reorganised, which is the failure the plain-text-twin rule exists to prevent. readme_txt.py becomes plaintext_twins.py and renders every pair. It also drops <!-- --> markers, invisible in markdown and stray punctuation in a text file - that is the whole of README.txt's diff. The --check that keeps twins honest was never run. readme_txt.py shipped one and its docstring claimed "the test suite runs --check"; nothing invoked it, so a twin could disagree with its markdown indefinitely. tests/PlainTextTwins.Tests.ps1 runs it. That test skipped on its first run while claiming to pass: -Skip is evaluated during DISCOVERY, before BeforeAll, so the python lookup left the flag $null. Resolved in BeforeDiscovery, and when python really is absent the result is INCONCLUSIVE rather than a green tick for a check that never happened. Mutation-checked: appending one line to CHANGELOG.txt fails "every twin is in sync with its markdown", and only that test. tests: 243 passed, 0 failed, 1 skipped (the no-python reporter, correctly).
105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
# Evomedia.net Token Savers — https://github.com/evomedia-net/evo.zscripts
|
|
# Created by Kelly Michels · dev@evomedia.net
|
|
# Licensed under the MIT License. See LICENSE.
|
|
|
|
"""Render this repo's markdown to plain-text twins with the markup removed.
|
|
|
|
The .txt files exist for terminals, pagers and anywhere markdown doesn't
|
|
render. They are generated - never edit one by hand:
|
|
|
|
python scripts/plaintext_twins.py # rewrite every .txt twin
|
|
python scripts/plaintext_twins.py --check # exit 1 if any is out of sync
|
|
|
|
tests/PlainTextTwins.Tests.ps1 runs --check, so a markdown edit that forgets
|
|
to regenerate fails the suite instead of shipping a twin that disagrees with
|
|
the file it mirrors.
|
|
|
|
Was readme_txt.py, which did README only. CHANGELOG.txt was kept by hand and
|
|
drifted the moment CHANGELOG.md was reorganised - and its docstring claimed a
|
|
--check the suite never actually ran. Both are fixed here: one renderer, every
|
|
pair, and a test that invokes it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
# Every markdown file that owes the repo a plain-text twin.
|
|
PAIRS = (
|
|
("README.md", "README.txt"),
|
|
("CHANGELOG.md", "CHANGELOG.txt"),
|
|
)
|
|
|
|
|
|
def _inline(text: str) -> str:
|
|
text = re.sub(r"!\[([^\]]*)\]\([^)]*\)", r"\1", text) # images -> alt text
|
|
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r"\1 (\2)", text) # links -> text (url)
|
|
text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text) # bold
|
|
text = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"\1", text) # italic
|
|
text = re.sub(r"`([^`]+)`", r"\1", text) # inline code
|
|
return text
|
|
|
|
|
|
def render(md: str) -> str:
|
|
out: list[str] = []
|
|
in_fence = False
|
|
for line in md.splitlines():
|
|
if line.lstrip().startswith("```"):
|
|
# Drop the fence markers; the code itself stays, indented so it
|
|
# still reads as a block without the backticks.
|
|
in_fence = not in_fence
|
|
continue
|
|
if in_fence:
|
|
out.append((" " + line) if line else "")
|
|
continue
|
|
# An HTML comment is invisible in rendered markdown, but its markers
|
|
# are not invisible in a text file - they read as stray punctuation.
|
|
# Keep what the comment says, drop the <!-- --> around it.
|
|
if line.strip() in ("<!--", "-->"):
|
|
continue
|
|
heading = re.match(r"^(#{1,6})\s+(.*)$", line)
|
|
if heading:
|
|
text = _inline(heading.group(2))
|
|
out.append(text)
|
|
out.append(("=" if len(heading.group(1)) == 1 else "-") * len(text))
|
|
continue
|
|
out.append(_inline(line))
|
|
text = "\n".join(out)
|
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
return text.strip() + "\n"
|
|
|
|
|
|
def main() -> int:
|
|
check = "--check" in sys.argv
|
|
stale: list[str] = []
|
|
for md_name, txt_name in PAIRS:
|
|
source = ROOT / md_name
|
|
if not source.exists():
|
|
print(f"{md_name} is missing - nothing to render")
|
|
return 1
|
|
rendered = render(source.read_text(encoding="utf-8"))
|
|
target = ROOT / txt_name
|
|
if check:
|
|
current = target.read_text(encoding="utf-8") if target.exists() else ""
|
|
if current != rendered:
|
|
stale.append(txt_name)
|
|
continue
|
|
target.write_text(rendered, encoding="utf-8", newline="\n")
|
|
print(f"Wrote {target} ({len(rendered.splitlines())} lines)")
|
|
|
|
if check:
|
|
if stale:
|
|
print(f"out of sync: {', '.join(stale)}"
|
|
f" - run: python scripts/plaintext_twins.py")
|
|
return 1
|
|
print(f"in sync: {', '.join(t for _, t in PAIRS)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|