#!/usr/bin/env bash # publish-rename: normalize filenames for ipfs.kane-il.us publishing # - Category content: //YYYYMMDDThhmmssZ_. # - Root design docs: /_vN.md set -euo pipefail VERSION="1.0" # ---------- config ---------- ALLOWED_EXT_REGEX='^(md|txt|jsonl)$' UTC_FMT='+%Y%m%dT%H%M%SZ' # ---------- helpers ---------- err(){ printf 'error: %s\n' "$*" >&2; exit 1; } note(){ printf '%s\n' "$*" >&2; } ts(){ date -u "${UTC_FMT}"; } slugify(){ # lower, spaces->_, remove disallowed chars, collapse repeats, trim underscores printf '%s' "$1" \ | tr '[:upper:] ' '[:lower:]_' \ | sed -E 's/[^a-z0-9_]+/_/g; s/_+/_/g; s/^_+//; s/_+$//' } print_man(){ cat <<'MAN' NAME publish-rename — normalize filenames for publishing to ipfs.kane-il.us SYNOPSIS publish-rename category [--dry-run] publish-rename root [--dry-run] --version DESCRIPTION Renames content into the repository’s required shapes: 1) CATEGORY MODE Target: //YYYYMMDDThhmmssZ_. - must be one of: legal, civics, naics, barter, w3pbs, onet - must be md|txt|jsonl (unchanged from ) - is normalized (lowercase, underscores, ASCII) 2) ROOT MODE (design docs) Target: /_vN.md (no timestamp) - is normalized (e.g., CONVENTIONS -> conventions) - --version N is required (integer >= 1) - Extension forced to .md OPTIONS --dry-run Print the rename that would occur; do not move files. --version N Root mode only: version number N (e.g., 1, 2, 3...) -h, --help Show this help. EXAMPLES # Category: rename to legal/20250829T171500Z_conventions_v1.md publish-rename category legal CONVENTIONS_v1.md conventions_v1 # Category: rename a text file into naics with a slug publish-rename category naics notes.txt procurement_policy_notes # Root: versioned design doc at repo root (CONVENTIONS_v1.md) publish-rename root --version 1 CONVENTIONS.md CONVENTIONS NOTES - This tool changes only the filename/location; it does not git add/commit. - For legal citations, always use the CID after publishing; the repo path is for structure and operator convenience only. AUTHOR vMAN 1.0 — tailored for ipfs.kane-il.us MAN } # ---------- argparse ---------- [[ $# -lt 1 ]] && { print_man; exit 1; } mode="$1"; shift || true dry_run=0 root_version='' while [[ $# -gt 0 ]]; do case "$1" in --dry-run) dry_run=1; shift;; -h|--help) print_man; exit 0;; --version) shift; root_version="${1:-}"; [[ -z "$root_version" ]] && err "--version requires a number"; shift;; *) break;; esac done case "$mode" in category) [[ $# -ne 3 ]] && { print_man; err "category mode requires "; } category_raw="$1"; file="$2"; slug_raw="$3" category="$(slugify "$category_raw")" case "$category" in legal|civics|naics|barter|w3pbs|onet) : ;; *) err "invalid category '$category_raw' (expected: legal|civics|naics|barter|w3pbs|onet)";; esac [[ -f "$file" ]] || err "file not found: $file" ext="${file##*.}"; [[ "$ext" =~ $ALLOWED_EXT_REGEX ]] || err "extension '.$ext' not allowed (md|txt|jsonl)" slug="$(slugify "$slug_raw")"; [[ -n "$slug" ]] || err "slug normalization produced empty value" stamp="$(ts)" dest_dir="$category" dest_file="${stamp}_${slug}.${ext}" dest_path="${dest_dir}/${dest_file}" [[ $dry_run -eq 1 ]] && { printf 'DRY-RUN: %s -> %s\n' "$file" "$dest_path"; exit 0; } mkdir -p "$dest_dir" mv -- "$file" "$dest_path" printf 'Renamed %s -> %s\n' "$file" "$dest_path" ;; root) # root design docs: _vN.md at repo root [[ -z "$root_version" ]] && { print_man; err "root mode requires --version "; } [[ $# -ne 2 ]] && { print_man; err "root mode requires "; } file="$1"; name_raw="$2" [[ -f "$file" ]] || err "file not found: $file" # version must be integer >=1 [[ "$root_version" =~ ^[0-9]+$ ]] || err "version must be an integer" [[ "$root_version" -ge 1 ]] || err "version must be >= 1" name="$(slugify "$name_raw")"; [[ -n "$name" ]] || err "name normalization produced empty value" dest_path="${name}_v${root_version}.md" [[ $dry_run -eq 1 ]] && { printf 'DRY-RUN: %s -> %s\n' "$file" "$dest_path"; exit 0; } mv -- "$file" "$dest_path" printf 'Renamed %s -> %s\n' "$file" "$dest_path" ;; *) print_man; err "unknown mode '$mode' (use 'category' or 'root')" ;; esac