#!/usr/bin/env bash # # check-public-names.sh # # Holds WAI's public text to its naming rule: it names no company, vendor, # customer or commercial product. Standards, standards bodies, protocols, # papers and open-source projects may be named, factually. # # The patterns are NOT in this repository. They are read from the # environment variable WAI_NAME_DENYLIST: newline-separated POSIX extended # regular expressions, matched case-sensitively (write `[Ff]oo` where both # cases matter). In CI the variable comes from a forge Actions secret of the # same name; locally, export it from a private file before committing: # # WAI_NAME_DENYLIST="$(cat /path/to/private/patterns)" scripts/check-public-names.sh # # A public list would itself publish the names it exists to keep out. # # Scope: the tracked files under wai/ with the extensions .md .rs .toml .py # .sh .mjs .js .json .html .css .ttl .h .c .txt, and wai/'s .gitignore files; # or, when files are given as arguments, those files (the self-test, # scripts/test-public-text-checks.sh, uses this), each a path relative to the # directory the script is run from, or absolute. No file is excluded: the # OER/2 documents and corpus once were, as settled text, until a minor version # edited them; they pass, and a change to them is checked like any other. # # Opt-out, for a functional identifier only (a model-hub id a tool defaults # to, a repository URL, an OS API name, a security advisory id, a capability # string): put `allow-name: ` on the same line, in a comment of the # file's own syntax (``, `// allow-name: reason`, # `# allow-name: reason`). The reason is required: it must contain a letter or # a digit. Prose around such an identifier describes the standard or the # function, not the owner. # # Exit status: 0 clean; 1 a hit; 2 WAI_NAME_DENYLIST unset or empty, a pattern # grep cannot compile, a file given that is not a readable regular file (each # such file is named), or any other grep error. The check never reads an error # as "clean". set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # The files given as arguments are paths from the caller's directory: remember it # before moving to the repository's root, where the default scope is listed. CALLER_DIR="$(pwd)" cd "$REPO_ROOT" if [ -z "${WAI_NAME_DENYLIST:-}" ]; then echo "WAI_NAME_DENYLIST is not set: nothing to check against (see the header of $0)" >&2 exit 2 fi patterns="$(mktemp)" out="$(mktemp)" trap 'rm -f "$patterns" "$out"' EXIT printf '%s\n' "$WAI_NAME_DENYLIST" | grep -v '^[[:space:]]*$' > "$patterns" || true if [ ! -s "$patterns" ]; then echo "WAI_NAME_DENYLIST holds no pattern" >&2 exit 2 fi # Compile every pattern once before scanning. grep exits 1 when nothing matches # and 2 on an error; only 0 and 1 mean the patterns are usable. grep's message # is suppressed: it could quote a private pattern. set +e grep -E -f "$patterns" /dev/null >/dev/null 2>&1 rc=$? set -e if [ $rc -gt 1 ]; then echo "::error::WAI_NAME_DENYLIST holds a pattern grep cannot compile (grep exit $rc); nothing was checked" >&2 exit 2 fi files=() if [ $# -gt 0 ]; then for f in "$@"; do case "$f" in /*) files+=("$f") ;; *) files+=("$CALLER_DIR/$f") ;; esac done else while IFS= read -r f; do files+=("$f") done < <(git ls-files 'wai/*.md' 'wai/*.rs' 'wai/*.toml' 'wai/*.py' 'wai/*.sh' 'wai/*.mjs' 'wai/*.js' \ 'wai/*.json' 'wai/*.html' 'wai/*.css' 'wai/*.ttl' 'wai/*.h' 'wai/*.c' 'wai/*.txt' \ 'wai/*.gitignore' 'wai/*.wgsl') fi if [ ${#files[@]} -eq 0 ]; then echo "::error::no file to check" >&2 exit 2 fi # A file grep cannot read makes it exit 2 with the whole list unchecked; name each # such file instead of reporting a bare grep failure. File names are not private. unreadable=0 for f in "${files[@]}"; do if [ ! -f "$f" ] || [ ! -r "$f" ]; then echo "::error::not a readable file: $f" >&2 unreadable=$((unreadable + 1)) fi done if [ $unreadable -gt 0 ]; then echo "::error::$unreadable of ${#files[@]} files given cannot be read; nothing was checked" >&2 exit 2 fi set +e grep -HnE -f "$patterns" -- "${files[@]}" > "$out" 2>/dev/null rc=$? set -e if [ $rc -gt 1 ]; then echo "::error::grep failed (exit $rc) while scanning; nothing was checked" >&2 exit 2 fi hits=0 while IFS= read -r line; do content="${line#*:}" content="${content#*:}" if printf '%s' "$content" | grep -qE 'allow-name:[[:space:]]*[[:alnum:]]'; then continue fi # Print the location only: the matched text may be a name the list keeps private. if [ $hits -eq 0 ]; then echo "::error::public text names a company, vendor, customer or product (patterns: WAI_NAME_DENYLIST)" fi echo "${line%%:*}:$(printf '%s' "${line#*:}" | cut -d: -f1)" hits=$((hits + 1)) done < "$out" if [ $hits -gt 0 ]; then echo "Fix: name the standard, the standards body, the open-source project or the function instead;" echo "for a functional identifier, mark the line with allow-name: ." exit 1 fi echo "public-name check: clean (${#files[@]} files)"