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
4 changes: 4 additions & 0 deletions capture/build_golden.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
choose to redirect to the parent post rather than reproduce.
"""

import argparse
import json
import math
import os
Expand All @@ -29,6 +30,9 @@
from pathlib import Path
from queue import Queue

# Parsed before the capture values are read, so --help answers where none is set.
argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0]).parse_args()

def env(name: str) -> str:
"""A required capture value, refused rather than guessed when unset.

Expand Down
26 changes: 10 additions & 16 deletions capture/build_redirects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
that read the capture rather than in checks/ beside the gates.
"""

import argparse
import os
import pathlib
import re
Expand Down Expand Up @@ -69,27 +70,20 @@ def write_map(path, pairs):
return len(pairs)


def main(argv):
def main(argv=None):
# CAPTURE_ROOT is the default and an argument wins over it, the same shape DEPLOY_ROOT uses.
# Unset and unsupplied is refused rather than guessed: a wrong-but-plausible capture yields maps that are empty and indistinguishable from working ones.
# --print-export names the selected export and writes nothing.
# The run-wp2hugo.sh script calls it rather than reimplementing the choice, so the conversion and the maps are provably built from the same file.
# A shell reimplementation cannot match this anyway: the test is that ONE item carries both post_type=post and status=publish, and two greps over a whole file would accept a media-only export that happens to contain both words.
args = [a for a in argv[1:] if a != "--print-export"]
print_export = "--print-export" in argv[1:]
# An unknown option is refused rather than taken as a path.
# Without this, `--help` is read as a capture directory and the run fails with "no export XML found under --help", which sends the reader looking for a missing file rather than a mistyped flag.
unknown = [a for a in args if a.startswith("-")]
if unknown:
print(f"unknown option: {unknown[0]}", file=sys.stderr)
print(f"usage: {argv[0]} [--print-export] [capture-dir]", file=sys.stderr)
return 2
if len(args) > 1:
print(f"usage: {argv[0]} [--print-export] [capture-dir]", file=sys.stderr)
return 2
root = args[0] if args else os.environ.get("CAPTURE_ROOT", "")
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.add_argument("--print-export", action="store_true", help="name the selected export and write nothing")
parser.add_argument("capture", nargs="?", help="the capture directory, else CAPTURE_ROOT")
args = parser.parse_args(argv)
print_export = args.print_export
root = args.capture or os.environ.get("CAPTURE_ROOT", "")
if not root:
print(f"usage: {argv[0]} [--print-export] [capture-dir]", file=sys.stderr)
parser.print_usage(sys.stderr)
print("CAPTURE_ROOT is not set and no capture directory was given", file=sys.stderr)
print("see .secrets/example.env and ENVIRONMENT.md", file=sys.stderr)
return 2
Expand Down Expand Up @@ -273,4 +267,4 @@ def main(argv):


if __name__ == "__main__":
sys.exit(main(sys.argv))
sys.exit(main())
4 changes: 4 additions & 0 deletions capture/classify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
from it is an attachment page, not content.
"""

import argparse
import os
import re
import sys
import urllib.request
from collections import Counter
from pathlib import Path

# Parsed before the capture values are read, so --help answers where none is set.
argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0]).parse_args()



def env(name: str) -> str:
Expand Down
18 changes: 13 additions & 5 deletions capture/clean_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@
consumes it), and every `_`-prefixed WordPress internal.
"""

import argparse
import os
import re
import shutil
import sys
import pathlib
from pathlib import Path

# A missing PyYAML is reported once the arguments are parsed, so --help answers without it.
try:
import yaml
except ModuleNotFoundError: # the only third-party import in capture/ or checks/
sys.exit("PyYAML is required by this script: pip install PyYAML (Debian: apt install python3-yaml)")
yaml = None

KEEP = ["title", "date", "url", "categories", "tags", "post_id", "cover"]
FM = re.compile(r"^---\n(.*?)\n---\n(.*)$", re.S)
Expand All @@ -59,15 +61,15 @@ def capture_root() -> pathlib.Path:
return pathlib.Path(root)


def converted_site(argv) -> pathlib.Path:
def converted_site(site: str | None) -> pathlib.Path:
"""The converted site: a first argument, else the one generated-* under the capture.

Ambiguity aborts rather than picking, the same rule build_redirects.py applies to the
export. wp2hugo stamps the directory with a run timestamp, so there is no fixed name
to default to and a glob that matched two would otherwise choose by filesystem order.
"""
if len(argv) > 1 and not argv[1].startswith("--"):
return pathlib.Path(argv[1])
if site:
return pathlib.Path(site)
found = sorted((capture_root() / "converted").glob("generated-*"))
if len(found) != 1:
sys.exit(
Expand Down Expand Up @@ -123,4 +125,10 @@ def main(root: Path, apply: bool):


if __name__ == "__main__":
main(converted_site(sys.argv), "--apply" in sys.argv)
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.add_argument("site", nargs="?", help="the converted site, else the one generated-* under the capture")
parser.add_argument("--apply", action="store_true", help="write the changes rather than report them")
args = parser.parse_args()
if yaml is None:
sys.exit("PyYAML is required by this script: pip install PyYAML (Debian: apt install python3-yaml)")
main(converted_site(args.site), args.apply)
4 changes: 4 additions & 0 deletions capture/enumerate_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Writes: media-urls.tsv (host, url, post_id, post_url), posts.json (raw archive).
"""

import argparse
import json
import os
import re
Expand All @@ -24,6 +25,9 @@
from collections import Counter, defaultdict
from pathlib import Path

# Parsed before the capture values are read, so --help answers where none is set.
argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0]).parse_args()

def env(name: str) -> str:
"""A required capture value, refused rather than guessed when unset.

Expand Down
13 changes: 9 additions & 4 deletions capture/localize_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
the mapping in external-media-map.tsv for auditability, and rewrites every reference.
"""

import argparse
import hashlib
import pathlib
import os
Expand Down Expand Up @@ -58,15 +59,15 @@ def capture_root() -> pathlib.Path:
return pathlib.Path(root)


def converted_site(argv) -> pathlib.Path:
def converted_site(site: str | None) -> pathlib.Path:
"""The converted site: a first argument, else the one generated-* under the capture.

Ambiguity aborts rather than picking, the same rule build_redirects.py applies to the
export. wp2hugo stamps the directory with a run timestamp, so there is no fixed name
to default to and a glob that matched two would otherwise choose by filesystem order.
"""
if len(argv) > 1 and not argv[1].startswith("--"):
return pathlib.Path(argv[1])
if site:
return pathlib.Path(site)
found = sorted((capture_root() / "converted").glob("generated-*"))
if len(found) != 1:
sys.exit(
Expand Down Expand Up @@ -176,4 +177,8 @@ def main(site: pathlib.Path, apply: bool):


if __name__ == "__main__":
sys.exit(main(converted_site(sys.argv), "--apply" in sys.argv))
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.add_argument("site", nargs="?", help="the converted site, else the one generated-* under the capture")
parser.add_argument("--apply", action="store_true", help="write the changes rather than report them")
args = parser.parse_args()
sys.exit(main(converted_site(args.site), args.apply))
13 changes: 9 additions & 4 deletions capture/restructure_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
make Hugo publish a `/pages/` listing URL, the same unwanted extra as `/posts/`.
"""

import argparse
import collections
import os
import pathlib
Expand All @@ -43,15 +44,15 @@ def capture_root() -> pathlib.Path:
return pathlib.Path(root)


def converted_site(argv) -> pathlib.Path:
def converted_site(site: str | None) -> pathlib.Path:
"""The converted site: a first argument, else the one generated-* under the capture.

Ambiguity aborts rather than picking, the same rule build_redirects.py applies to the
export. wp2hugo stamps the directory with a run timestamp, so there is no fixed name
to default to and a glob that matched two would otherwise choose by filesystem order.
"""
if len(argv) > 1 and not argv[1].startswith("--"):
return pathlib.Path(argv[1])
if site:
return pathlib.Path(site)
found = sorted((capture_root() / "converted").glob("generated-*"))
if len(found) != 1:
sys.exit(
Expand Down Expand Up @@ -149,4 +150,8 @@ def main(site: pathlib.Path, apply: bool):


if __name__ == "__main__":
sys.exit(main(converted_site(sys.argv), "--apply" in sys.argv))
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.add_argument("site", nargs="?", help="the converted site, else the one generated-* under the capture")
parser.add_argument("--apply", action="store_true", help="write the changes rather than report them")
args = parser.parse_args()
sys.exit(main(converted_site(args.site), args.apply))
16 changes: 16 additions & 0 deletions capture/run-wp2hugo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@
# The selection cannot be reimplemented in shell: the test is that ONE item carries both post_type=post and status=publish, where two greps over a whole file would accept the media-only export.
set -Eeuo pipefail

# Arguments are checked before CAPTURE_ROOT is read, so --help answers where none is set.
for arg in "$@"; do
case "$arg" in
-h | --help)
echo "usage: $0"
echo " converts the export build_redirects.py selects under CAPTURE_ROOT"
exit 0
;;
esac
done
if [ "$#" -gt 0 ]; then
echo "unknown argument: $1" >&2
echo "usage: $0" >&2
exit 2
fi

# A `go install`ed wp2hugo lands here. $HOME rather than a literal path.
export PATH="$HOME/.local/bin:$PATH"

Expand Down
38 changes: 31 additions & 7 deletions checks/check-live-urls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,41 @@

set -uo pipefail

usage() {
echo "usage: $0 <base-url> | --print-sample"
}

# Help is answered wherever it appears, before the count is checked.
for arg in "$@"; do
case "$arg" in
-h | --help)
usage
exit 0
;;
esac
done
PRINT_SAMPLE=''
BASE="${1:-}"
if [ "$BASE" = "--print-sample" ]; then
if [ "$#" -gt 1 ]; then
echo "unexpected argument: $2" >&2
usage >&2
exit 2
fi
case "$BASE" in
--print-sample)
PRINT_SAMPLE=1
BASE=''
else
[ -n "$BASE" ] || {
echo "usage: $0 <base-url> | --print-sample" >&2
exit 2
}
fi
;;
'')
usage >&2
exit 2
;;
-*)
echo "unknown option: $BASE" >&2
usage >&2
exit 2
;;
esac
BASE="${BASE%/}"

case "${SAMPLE_CONTRACT:-}" in
Expand Down
9 changes: 6 additions & 3 deletions checks/env_docs_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path
Expand All @@ -36,7 +37,7 @@

# Workflow references.
# `vars.X` and `secrets.X` are the GitHub Environment surface, and a value added there is exactly as undocumented as one added to a template.
WORKFLOWS = sorted((REPO / ".github" / "workflows").glob("*.yml"))
WORKFLOWS = REPO / ".github" / "workflows"

# Set per invocation rather than stored, so they appear in no template and would otherwise be invisible to this check.
# Listed here because the doc has a table for them, and a knob nobody documented is the same failure as an undocumented file value.
Expand Down Expand Up @@ -76,7 +77,9 @@
DOC_INLINE = re.compile(r"`([A-Z][A-Z0-9_]{2,})(?:=[^`]*)?`")


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.parse_args(argv)
if not DOC.exists():
print(f"ERROR: {DOC.name} does not exist", file=sys.stderr)
return 1
Expand All @@ -103,7 +106,7 @@ def note(name: str, where: str) -> None:
for name in COMMENTED_DECL.findall(text):
note(name, rel)

for path in WORKFLOWS:
for path in sorted(WORKFLOWS.glob("*.yml")):
rel = path.relative_to(REPO).as_posix()
for name in GH_REF.findall(path.read_text(encoding="utf-8")):
note(name, rel)
Expand Down
5 changes: 4 additions & 1 deletion checks/github_env_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from __future__ import annotations

import argparse
import re
import subprocess
import sys
Expand Down Expand Up @@ -191,7 +192,9 @@ def other(kind: str) -> str:
return findings


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.parse_args(argv)
try:
listed = read_table(DOC.read_text(encoding="utf-8"))
except (OSError, ValueError) as error:
Expand Down
5 changes: 4 additions & 1 deletion checks/media_metadata_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
pixels and writing a fresh file that is clean by construction.
"""

import argparse
import hashlib
import lzma
import os
Expand Down Expand Up @@ -1439,7 +1440,9 @@ def walk_archive(path: pathlib.Path, name: str) -> list[tuple[str, set[str]]]:
return out


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.parse_args(argv)
found = findings()
scanned = sum(
1
Expand Down
5 changes: 4 additions & 1 deletion checks/media_redactions_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
`scripts/redact_media.py` is what brings either back.
"""

import argparse
import hashlib
import json
import pathlib
Expand All @@ -22,7 +23,9 @@ def declared(entry: dict) -> str:
return hashlib.sha256(json.dumps(spec, sort_keys=True).encode()).hexdigest()


def main() -> int:
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=(__doc__ or "").partition("\n")[0])
parser.parse_args(argv)
files = json.loads(MANIFEST.read_text())["files"]
failures = []
for name, entry in files.items():
Expand Down
Loading
Loading