#!/bin/sh # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2026 Martino Ferrari # # The manual's check, the way tools/check_ui.py is the .ui files' check: it runs the four # yelp-check subcommands that need no network, checks the structural rules below, checks the # figures, then renders the whole set to HTML with yelp-build so a page that only fails at the # transform step is caught too. # # `yelp-check validate` is deliberately absent, and it is worth being exact about why: it # builds the RelaxNG schema locally with `xsltproc` and `mal-rng.xsl`, but the schema it # generates includes the base grammar over the network, so on a machine where fetching is # blocked it fails for that reason rather than for a fault in the pages. # # What that leaves unverified is *structure*, and that is not a small gap: three pages # carrying a `
` whose `` was a sentence and which had no `<desc>` opened in # nothing, while links, media, orphans, ids and the HTML render all passed. So the rules this # project has actually needed are checked here. Add a rule when a page *teaches* one -- and do # not add one you have not seen broken: a rule that fails a legal page is worse than no rule. # # Every XPath expression uses local-name() on purpose. A Mallard page declares # `xmlns="http://projectmallard.org/1.0/"`, and XPath 1.0 has no default namespace, so # `//figure` matches *nothing* and every rule below silently passes. Written naively, this # check reported "ok" for the very defect it was written for. # # Exit status is nonzero on the first finding. set -eu dir=${1:?usage: check_help.sh HELP_DIR} if [ ! -f "$dir/index.page" ]; then echo "check_help: no index.page under $dir" >&2 exit 1 fi tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT for sub in links media orphans ids; do printf 'yelp-check %s %s\n' "$sub" "$dir" yelp-check "$sub" "$dir" >/dev/null done failed=0 for page in "$dir"/*.page; do while IFS='|' read -r what expr; do [ -n "$what" ] || continue n=$(xmllint --xpath "$expr" "$page" 2>/dev/null || echo "?") if [ "$n" != "0" ]; then printf ' %s: %s (%s)\n' "$(basename "$page")" "$what" "$n" >&2 failed=1 fi done <<'RULES' a figure describes itself|count(//*[local-name()='figure'][not(*[local-name()='desc'])]) a section has a title|count(//*[local-name()='section'][not(*[local-name()='title'])]) info is the page's first child|count(/*[local-name()='page']/*[1][local-name()!='info']) no text sits directly under page|count(/*[local-name()='page']/text()[normalize-space()]) a media names its src|count(//*[local-name()='media'][not(@src)]) RULES done # The figures, and this one is a *decoder's* rule rather than ours: a `bKGD` chunk is forbidden # by the PNG specification on an image with an alpha channel, and `import` writes one because it # records the root window's background. ImageMagick both writes and reads it, so a round trip # through it proves nothing -- which is how three figures passed every check while WebKit # refused them, showing a figure that never loaded and, over the `help:` URI, a page that spun # forever. Only the chunks PNG allows for the colour type are accepted; a whitelist, because a # decoder is one. python3 - "$dir" <<'PY' || failed=1 import glob, os, struct, sys seen = 0 for path in sorted(glob.glob(os.path.join(sys.argv[1], 'figures', '*.png'))): seen += 1 with open(path, 'rb') as f: if f.read(8) != b'\x89PNG\r\n\x1a\n': print(f" {os.path.basename(path)}: not a PNG", file=sys.stderr) continue chunks = [] while True: head = f.read(8) if len(head) < 8: break length, kind = struct.unpack('>I4s', head) f.read(length) f.read(4) chunks.append(kind) # Colour type 4 (grey+alpha) and 6 (RGB+alpha) have an alpha channel; bKGD is not permitted # on either. Anything else is left alone: this checks the rule a page was broken by. colour_type = None with open(path, 'rb') as f: f.read(16) colour_type = f.read(1)[0] if colour_type in (4, 6) and b'bKGD' in chunks: print(f" {os.path.basename(path)}: bKGD on an image with alpha (colour type {colour_type})" " -- PNG forbids it and a strict decoder refuses the file", file=sys.stderr) sys.exit(1) if seen == 0: print(" no figures to check", file=sys.stderr) PY if [ "$failed" != 0 ]; then echo "check_help: findings above" >&2 exit 1 fi printf 'structure %s: ok; figures: ok\n' "$dir" mkdir -p "$tmp/html" yelp-build html -o "$tmp/html" "$dir" pages=$(find "$tmp/html" -maxdepth 1 -name '*.html' | wc -l) printf 'yelp-build html: %s page(s) rendered\n' "$pages"