a46e7eb961
check-branding.sh could say what disagreed but not fix it, and its canonical block lived in its own header — so renaming meant hand-editing the checker, which is exactly the fragile step it exists to catch. branding.conf now holds the identity once. Both scripts read it and neither contains a branding literal anywhere, so a rename never has to rewrite its own tooling. brand-plugin.sh derives the package name and Rust types from the display name, validates them against what Cargo actually accepts, rewrites every tracked file longest-value-first (a short name can be a substring of a longer one), appends the retired spellings to STALE, and finishes by running the checker. A run that ends green is a rename that landed. Dry run unless --apply. It refuses three things worth refusing: - Package names Cargo rejects — digit-leading (`206 Prototype` derives to `206_prototype`), Rust keywords, built-in crate collisions. Verified against cargo init rather than assumed. - Moving CLAP_ID or VST3_CLASS_ID without --allow-identity-change. Hosts key saved projects off those; changing either orphans every existing session silently. The 16-byte class ID is *proposed*, never derived — it is a fixed-width frozen identity, so the choice stays human. - Development history in CLAP_DESCRIPTION. Hosts show it to users who never knew the codename, so any retired name or the repo codename appearing there is rejected. The checker gains the other half of its job: asserting canonical values are present where they belong, not only that retired ones are gone. Absence alone would pass a rename to the *wrong* new spelling. New positive assertions cover Cargo authors, Cargo.lock, the CI bundle command and NOTICE — every one of which the recent Bari 206 rename had to touch. Structural checks read their file locations from branding.conf so a plugin whose params outgrow src/lib.rs can point them elsewhere. Also fixes NOTICE naming the repo rather than the product, found by the new assertion, and excludes branding.conf from the stale sweep — it lists every retired spelling by definition, the same reason check-branding.sh was already excluded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
224 lines
11 KiB
Bash
Executable File
224 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# brand-plugin.sh - rename this plugin, everywhere, in one go.
|
|
#
|
|
# check-branding.sh tells you what disagrees. This changes it. They share
|
|
# branding.conf: this script rewrites that file and every copy of the old values
|
|
# in tracked files, then runs the checker and reports what it says. A run that
|
|
# ends with the checker green is a rename that landed.
|
|
#
|
|
# Dry run by default:
|
|
# sh brand-plugin.sh --display "Bari 206"
|
|
# sh brand-plugin.sh --display "Bari 206" --vendor Novoyuuparosk --apply
|
|
#
|
|
# Options:
|
|
# --display NAME the product name, with spaces and capitals. Everything
|
|
# else that can be derived, is derived from this.
|
|
# --pkg NAME override the derived Cargo package name.
|
|
# --vendor NAME --email ADDR --homepage URL
|
|
# --description D --clap-description D
|
|
# --struct NAME override the derived Rust type name.
|
|
# --allow-identity-change permit CLAP_ID / VST3_CLASS_ID to move. See below.
|
|
# --apply actually write. Without it, nothing is modified.
|
|
|
|
set -u
|
|
cd "$(dirname "$0")" || exit 1
|
|
[ -f branding.conf ] || { printf 'branding.conf not found next to this script\n' >&2; exit 1; }
|
|
# shellcheck disable=SC1091
|
|
. ./branding.conf
|
|
|
|
OLD_PKG="$PKG"; OLD_DISPLAY="$DISPLAY"; OLD_PLUGIN_NAME="$PLUGIN_NAME"
|
|
OLD_VENDOR="$VENDOR"; OLD_EMAIL="$EMAIL"; OLD_HOMEPAGE="$HOMEPAGE"
|
|
OLD_DESCRIPTION="$DESCRIPTION"; OLD_CLAP_DESCRIPTION="$CLAP_DESCRIPTION"
|
|
OLD_CLAP_ID="$CLAP_ID"; OLD_VST3="$VST3_CLASS_ID"
|
|
OLD_STRUCT="$STRUCT"; OLD_PARAMS_STRUCT="$PARAMS_STRUCT"
|
|
|
|
new_display=""; new_pkg=""; new_vendor=""; new_email=""; new_homepage=""
|
|
new_description=""; new_clap_description=""; new_struct=""
|
|
new_clap_id=""; new_vst3=""; apply=0; allow_identity=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--display) new_display="${2:?}"; shift 2 ;;
|
|
--pkg) new_pkg="${2:?}"; shift 2 ;;
|
|
--vendor) new_vendor="${2:?}"; shift 2 ;;
|
|
--email) new_email="${2:?}"; shift 2 ;;
|
|
--homepage) new_homepage="${2:?}"; shift 2 ;;
|
|
--description) new_description="${2:?}"; shift 2 ;;
|
|
--clap-description) new_clap_description="${2:?}"; shift 2 ;;
|
|
--struct) new_struct="${2:?}"; shift 2 ;;
|
|
--clap-id) new_clap_id="${2:?}"; shift 2 ;;
|
|
--vst3-class-id) new_vst3="${2:?}"; shift 2 ;;
|
|
--allow-identity-change) allow_identity=1; shift ;;
|
|
--apply) apply=1; shift ;;
|
|
-h|--help) sed -n '3,25p' "$0"; exit 0 ;;
|
|
*) printf 'unknown option: %s\n' "$1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
[ -n "$new_display" ] || { printf 'nothing to do: pass --display "New Name"\n' >&2; exit 2; }
|
|
|
|
# --- derivation -------------------------------------------------------------
|
|
# Package name: lowercase, every run of non-alphanumerics becomes one separator,
|
|
# ends trimmed. Cargo accepts both hyphens and underscores in a package name and
|
|
# converts to underscores for the built artifact, which nih_plug_xtask already
|
|
# accounts for; underscores are used here to match the Rust convention.
|
|
derive_pkg() {
|
|
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
|
|
| sed -e 's/[^a-z0-9]\{1,\}/_/g' -e 's/^_//' -e 's/_$//'
|
|
}
|
|
# Rust type name: CamelCase, non-alphanumerics dropped entirely.
|
|
derive_struct() {
|
|
printf '%s' "$1" | sed -e 's/[^A-Za-z0-9]\{1,\}/ /g' \
|
|
| awk '{for(i=1;i<=NF;i++) printf "%s%s", toupper(substr($i,1,1)), substr($i,2); print ""}'
|
|
}
|
|
[ -n "$new_pkg" ] || new_pkg=$(derive_pkg "$new_display")
|
|
[ -n "$new_struct" ] || new_struct=$(derive_struct "$new_display")
|
|
new_params_struct="${new_struct}Params"
|
|
[ -n "$new_vendor" ] || new_vendor="$OLD_VENDOR"
|
|
[ -n "$new_email" ] || new_email="$OLD_EMAIL"
|
|
[ -n "$new_homepage" ] || new_homepage="$OLD_HOMEPAGE"
|
|
[ -n "$new_description" ] || new_description="$OLD_DESCRIPTION"
|
|
[ -n "$new_clap_description" ] || new_clap_description="$OLD_CLAP_DESCRIPTION"
|
|
|
|
# --- validation -------------------------------------------------------------
|
|
fail=0
|
|
die() { printf ' INVALID %s\n' "$1"; fail=$((fail+1)); }
|
|
|
|
case "$new_pkg" in
|
|
"") die "derived package name is empty" ;;
|
|
[0-9]*) die "package name \"$new_pkg\" starts with a digit - cargo rejects this outright" ;;
|
|
esac
|
|
case "$new_pkg" in
|
|
test|core|std|alloc|proc_macro)
|
|
die "package name \"$new_pkg\" collides with a built-in crate" ;;
|
|
as|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|static|struct|super|trait|true|type|unsafe|use|where|while|async|await|box|do|final|macro|override|priv|typeof|unsized|virtual|yield|try)
|
|
die "package name \"$new_pkg\" is a Rust keyword - cargo rejects this" ;;
|
|
esac
|
|
# bundler.toml uses the package name as a TOML bare key.
|
|
case "$new_pkg" in *[!a-zA-Z0-9_-]*) die "package name \"$new_pkg\" is not a valid TOML bare key" ;; esac
|
|
case "$new_struct" in ""|[0-9]*) die "derived type name \"$new_struct\" is not a valid Rust identifier" ;; esac
|
|
|
|
# --- frozen identity --------------------------------------------------------
|
|
identity_note=""
|
|
if [ -n "$new_clap_id" ] || [ -n "$new_vst3" ]; then
|
|
if [ "$allow_identity" -eq 0 ]; then
|
|
die "CLAP_ID / VST3_CLASS_ID are frozen identity; pass --allow-identity-change to move them"
|
|
fi
|
|
fi
|
|
[ -n "$new_clap_id" ] || new_clap_id="$OLD_CLAP_ID"
|
|
[ -n "$new_vst3" ] || new_vst3="$OLD_VST3"
|
|
|
|
# VST3_CLASS_ID is a fixed 16-byte array, so it cannot simply be derived. Propose
|
|
# one from the new name and let a human decide - it is frozen the moment it ships.
|
|
proposed_vst3=$(printf '%s' "${new_struct}Plugin000000000000" | cut -c1-16)
|
|
n=$(printf '%s' "$new_vst3" | wc -c | tr -d ' ')
|
|
[ "$n" -eq 16 ] || die "VST3_CLASS_ID \"$new_vst3\" is $n bytes, must be exactly 16"
|
|
|
|
case "$new_clap_id" in
|
|
*\ *|*[A-Z]*) die "CLAP_ID \"$new_clap_id\" must be lowercase with no spaces" ;;
|
|
*.*) ;;
|
|
*) die "CLAP_ID \"$new_clap_id\" should be dotted reverse-DNS" ;;
|
|
esac
|
|
|
|
# CLAP_DESCRIPTION is user-facing: keep development history out of it.
|
|
for hist in "$CODENAME" "$OLD_DISPLAY" "$OLD_PKG"; do
|
|
case "$new_clap_description" in
|
|
*"$hist"*) die "CLAP_DESCRIPTION still mentions \"$hist\" - it is shown to users, who never knew the development name" ;;
|
|
esac
|
|
done
|
|
|
|
# --- plan -------------------------------------------------------------------
|
|
printf 'rename plan\n\n'
|
|
row() { [ "$2" = "$3" ] && printf ' %-18s %s (unchanged)\n' "$1" "$2" || printf ' %-18s %s -> %s\n' "$1" "$2" "$3"; }
|
|
row "package" "$OLD_PKG" "$new_pkg"
|
|
row "display" "$OLD_DISPLAY" "$new_display"
|
|
row "Plugin::NAME" "$OLD_PLUGIN_NAME" "$new_display"
|
|
row "vendor" "$OLD_VENDOR" "$new_vendor"
|
|
row "email" "$OLD_EMAIL" "$new_email"
|
|
row "homepage" "$OLD_HOMEPAGE" "$new_homepage"
|
|
row "description" "$OLD_DESCRIPTION" "$new_description"
|
|
row "clap desc" "$OLD_CLAP_DESCRIPTION" "$new_clap_description"
|
|
row "struct" "$OLD_STRUCT" "$new_struct"
|
|
row "params struct" "$OLD_PARAMS_STRUCT" "$new_params_struct"
|
|
row "CLAP_ID" "$OLD_CLAP_ID" "$new_clap_id"
|
|
row "VST3_CLASS_ID" "$OLD_VST3" "$new_vst3"
|
|
printf '\n repo codename stays %s - fixed at creation, never renamed\n' "$CODENAME"
|
|
if [ "$new_vst3" = "$OLD_VST3" ]; then
|
|
printf ' suggested VST3_CLASS_ID for this name (16 bytes): "%s"\n' "$proposed_vst3"
|
|
printf ' apply with --vst3-class-id "%s" --allow-identity-change, and only before first release\n' "$proposed_vst3"
|
|
fi
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
printf '\n%d problem(s); nothing written.\n' "$fail"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$apply" -eq 0 ]; then
|
|
printf '\nDry run. Re-run with --apply to write these changes.\n'
|
|
exit 0
|
|
fi
|
|
|
|
# --- apply ------------------------------------------------------------------
|
|
# Longest values first: a shorter spelling can be a substring of a longer one
|
|
# (the package name inside a URL, say), and replacing it first would corrupt the
|
|
# longer match. branding.conf is excluded and rewritten wholesale below, so its
|
|
# STALE list is never rewritten into meaninglessness.
|
|
subst_file="$(mktemp)"
|
|
trap 'rm -f "$subst_file"' EXIT
|
|
{
|
|
printf '%s\t%s\n' "$OLD_CLAP_ID" "$new_clap_id"
|
|
printf '%s\t%s\n' "$OLD_HOMEPAGE" "$new_homepage"
|
|
printf '%s\t%s\n' "$OLD_CLAP_DESCRIPTION" "$new_clap_description"
|
|
printf '%s\t%s\n' "$OLD_DESCRIPTION" "$new_description"
|
|
printf '%s\t%s\n' "$OLD_PARAMS_STRUCT" "$new_params_struct"
|
|
printf '%s\t%s\n' "$OLD_VST3" "$new_vst3"
|
|
printf '%s\t%s\n' "$OLD_PLUGIN_NAME" "$new_display"
|
|
printf '%s\t%s\n' "$OLD_DISPLAY" "$new_display"
|
|
printf '%s\t%s\n' "$OLD_STRUCT" "$new_struct"
|
|
printf '%s\t%s\n' "$OLD_EMAIL" "$new_email"
|
|
printf '%s\t%s\n' "$OLD_VENDOR" "$new_vendor"
|
|
printf '%s\t%s\n' "$OLD_PKG" "$new_pkg"
|
|
} | awk -F'\t' '$1 != $2 { print length($1)"\t"$0 }' | sort -rn | cut -f2- > "$subst_file"
|
|
|
|
changed=0
|
|
git ls-files 2>/dev/null | while IFS= read -r f; do
|
|
[ -f "$f" ] || continue
|
|
case "$f" in branding.conf) continue ;; esac
|
|
before=$(cat "$f")
|
|
after="$before"
|
|
while IFS="$(printf '\t')" read -r old new; do
|
|
[ -n "$old" ] || continue
|
|
after=$(printf '%s' "$after" | sed "s|$(printf '%s' "$old" | sed 's/[][\.*^$/|&]/\\&/g')|$(printf '%s' "$new" | sed 's/[\\/&|]/\\&/g')|g")
|
|
done < "$subst_file"
|
|
if [ "$before" != "$after" ]; then printf '%s\n' "$after" > "$f"; printf ' rewrote %s\n' "$f"; fi
|
|
done
|
|
|
|
# branding.conf last, with the retired spellings appended to STALE so the next
|
|
# run of the checker sweeps for anything this pass missed.
|
|
# Deduplicated: two fields can retire to the same spelling - DISPLAY and
|
|
# PLUGIN_NAME are commonly identical - and listing it twice makes the checker
|
|
# report the same drift twice. Note that neither this script nor the checker
|
|
# contains a branding literal anywhere: both read every name from
|
|
# branding.conf, so a rename never has to rewrite its own tooling.
|
|
retired=$(cut -f1 "$subst_file" | awk '!seen[$0]++')
|
|
sed -e "s|^PKG=.*|PKG=\"$new_pkg\"|" \
|
|
-e "s|^DISPLAY=.*|DISPLAY=\"$new_display\"|" \
|
|
-e "s|^PLUGIN_NAME=.*|PLUGIN_NAME=\"$new_display\"|" \
|
|
-e "s|^VENDOR=.*|VENDOR=\"$new_vendor\"|" \
|
|
-e "s|^EMAIL=.*|EMAIL=\"$new_email\"|" \
|
|
-e "s|^HOMEPAGE=.*|HOMEPAGE=\"$new_homepage\"|" \
|
|
-e "s|^DESCRIPTION=.*|DESCRIPTION=\"$new_description\"|" \
|
|
-e "s|^CLAP_DESCRIPTION=.*|CLAP_DESCRIPTION=\"$new_clap_description\"|" \
|
|
-e "s|^CLAP_ID=.*|CLAP_ID=\"$new_clap_id\"|" \
|
|
-e "s|^VST3_CLASS_ID=.*|VST3_CLASS_ID=\"$new_vst3\"|" \
|
|
-e "s|^STRUCT=.*|STRUCT=\"$new_struct\"|" \
|
|
-e "s|^PARAMS_STRUCT=.*|PARAMS_STRUCT=\"$new_params_struct\"|" \
|
|
branding.conf > branding.conf.new
|
|
{ sed '$ s/"$//' branding.conf.new; printf '%s"\n' "$retired"; } > branding.conf
|
|
rm -f branding.conf.new
|
|
printf ' rewrote branding.conf (retired spellings appended to STALE)\n'
|
|
|
|
printf '\nverifying with check-branding.sh\n\n'
|
|
sh check-branding.sh
|