157 lines
4.5 KiB
Bash
Executable File
157 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Recolour all SVGs under the current directory in place using /tmp (tmpfs),
|
|
# skipping symlinked directories and files; parallel by physical cores by default.
|
|
set -euo pipefail
|
|
|
|
# Defaults
|
|
PALETTE="${PALETTE:-$HOME/dotfiles/theme/color-theme.json}"
|
|
RECOLOR="${RECOLOR:-$HOME/dotfiles/scripts/svg-recolor.py}"
|
|
SIGMA="${SIGMA:-20}"
|
|
ALPHA="${ALPHA:-0.6}"
|
|
THREADS="${THREADS:-auto}" # auto => number of physical cores
|
|
QUIET=0
|
|
DRY_RUN=0
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [--sigma S] [--alpha A] [--palette PATH] [--recolor PATH] [--threads N|auto] [--dry-run] [--quiet]
|
|
|
|
--sigma S Std-dev in Lab units (default: ${SIGMA})
|
|
--alpha A Pull strength 0..1 (default: ${ALPHA})
|
|
--palette P Palette JSON path (default: ${PALETTE})
|
|
--recolor R Recolor script path (default: ${RECOLOR})
|
|
--threads N Parallel workers (default: auto = physical cores)
|
|
--dry-run Only print files that would be recoloured
|
|
--quiet Suppress per-file messages
|
|
|
|
Environment overrides: SIGMA, ALPHA, PALETTE, RECOLOR, THREADS
|
|
EOF
|
|
}
|
|
|
|
# Parse options
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--sigma) SIGMA="$2"; shift 2 ;;
|
|
--alpha) ALPHA="$2"; shift 2 ;;
|
|
--palette) PALETTE="$2"; shift 2 ;;
|
|
--recolor) RECOLOR="$2"; shift 2 ;;
|
|
--threads) THREADS="$2"; shift 2 ;;
|
|
--dry-run) DRY_RUN=1; shift ;;
|
|
--quiet) QUIET=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "Unknown option: $1" >&2; usage; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
# Preflight
|
|
[[ -f "$PALETTE" ]] || { echo "Palette not found: $PALETTE" >&2; exit 1; }
|
|
[[ -f "$RECOLOR" ]] || { echo "Recolor script not found: $RECOLOR" >&2; exit 1; }
|
|
|
|
# Get physical core count (prefer lscpu), fall back to nproc
|
|
get_physical_cores() {
|
|
if command -v lscpu >/dev/null 2>&1; then
|
|
# Unique Core,Socket pairs => physical cores
|
|
local n
|
|
n=$(lscpu -p=Core,Socket 2>/dev/null | grep -v '^#' | sort -u | wc -l)
|
|
if [[ "$n" -gt 0 ]]; then
|
|
echo "$n"; return
|
|
fi
|
|
# Fallback via totals / threads-per-core
|
|
local cpu tpc
|
|
cpu=$(lscpu | awk -F: '/^CPU\(s\)/{gsub(/ /,"",$2);print $2;exit}')
|
|
tpc=$(lscpu | awk -F: '/^Thread\(s\) per core/{gsub(/ /,"",$2);print $2;exit}')
|
|
if [[ -n "${cpu:-}" && -n "${tpc:-}" && "$tpc" -gt 0 ]]; then
|
|
echo $(( (cpu + tpc - 1) / tpc )); return
|
|
fi
|
|
fi
|
|
# Last resort
|
|
if command -v nproc >/dev/null 2>&1; then
|
|
echo "$(nproc)"
|
|
else
|
|
echo 1
|
|
fi
|
|
}
|
|
|
|
calc_jobs() {
|
|
local files="$1"
|
|
local j
|
|
if [[ "$THREADS" == "auto" || -z "$THREADS" ]]; then
|
|
j=$(get_physical_cores)
|
|
else
|
|
j="$THREADS"
|
|
fi
|
|
# Bound by number of files (avoid spawning more workers than tasks)
|
|
if [[ "$files" -lt "$j" ]]; then
|
|
j="$files"
|
|
fi
|
|
# Ensure >=1
|
|
[[ "$j" -ge 1 ]] || j=1
|
|
echo "$j"
|
|
}
|
|
|
|
TMP_LIST=()
|
|
cleanup() {
|
|
for t in "${TMP_LIST[@]:-}"; do [[ -e "$t" ]] && rm -f -- "$t"; done
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
process_one() {
|
|
local f="$1"
|
|
# Double guard: skip if symlink file (in addition to find -type f)
|
|
[[ -L "$f" ]] && { (( QUIET == 0 )) && echo "Skip symlink file: $f"; return 0; }
|
|
|
|
local tmp
|
|
tmp="$(mktemp /tmp/svg-recolor.XXXXXX.svg)"
|
|
TMP_LIST+=("$tmp")
|
|
|
|
if (( DRY_RUN )); then
|
|
echo "Would recolor: $f"
|
|
rm -f -- "$tmp"; TMP_LIST=("${TMP_LIST[@]:0:${#TMP_LIST[@]}-1}")
|
|
return 0
|
|
fi
|
|
|
|
if ! python3 "$RECOLOR" "$f" "$PALETTE" "$tmp" --sigma "$SIGMA" --alpha "$ALPHA"; then
|
|
echo "ERROR: recolor failed on: $f" >&2
|
|
rm -f -- "$tmp"; TMP_LIST=("${TMP_LIST[@]:0:${#TMP_LIST[@]}-1}")
|
|
return 1
|
|
fi
|
|
|
|
if cmp -s -- "$f" "$tmp"; then
|
|
(( QUIET == 0 )) && echo "Unchanged: $f"
|
|
rm -f -- "$tmp"; TMP_LIST=("${TMP_LIST[@]:0:${#TMP_LIST[@]}-1}")
|
|
return 0
|
|
fi
|
|
|
|
chmod --reference="$f" "$tmp" 2>/dev/null || true
|
|
touch --reference="$f" "$tmp" 2>/dev/null || true
|
|
mv -f -- "$tmp" "$f"
|
|
TMP_LIST=("${TMP_LIST[@]:0:${#TMP_LIST[@]}-1}")
|
|
(( QUIET == 0 )) && echo "Recoloured: $f"
|
|
}
|
|
|
|
export -f process_one
|
|
export RECOLOR PALETTE SIGMA ALPHA QUIET DRY_RUN TMP_LIST
|
|
|
|
# Collect file list first to decide threads; -P => never follow symlinks
|
|
mapfile -d '' FILES < <(find -P . -type f -iname '*.svg' -print0)
|
|
TOTAL="${#FILES[@]}"
|
|
if (( TOTAL == 0 )); then
|
|
(( QUIET == 0 )) && echo "No SVG files found."
|
|
exit 0
|
|
fi
|
|
|
|
JOBS="$(calc_jobs "$TOTAL")"
|
|
(( QUIET == 0 )) && echo "Processing $TOTAL SVG(s) with $JOBS worker(s)..."
|
|
|
|
# Parallel execution (preserving NUL safety)
|
|
if (( JOBS > 1 )); then
|
|
printf '%s\0' "${FILES[@]}" | \
|
|
xargs -0 -n1 -P"$JOBS" bash -c 'process_one "$@"' _
|
|
else
|
|
# serial fallback
|
|
for path in "${FILES[@]}"; do
|
|
process_one "$path"
|
|
done
|
|
fi
|
|
|