feat(M4): Reusable code abstractions and dead-code removal
This commit is contained in:
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
# safe-replace-dir.sh — safely replace a directory within a safety-root boundary
|
||||
#
|
||||
# Provides safe_replace_dir() for sourcing, or run standalone:
|
||||
# ./scripts/lib/safe-replace-dir.sh <source> <target> <safety_root>
|
||||
#
|
||||
# Safety contract (mirrors safe-replace-dir.mjs):
|
||||
# - <target> must be a non-empty path.
|
||||
# - <target> must be a strict descendant of <safety_root> (not equal to it).
|
||||
# - Prints an error and returns/exits 1 if either constraint is violated.
|
||||
#
|
||||
# Usage (sourced):
|
||||
# source "$(dirname "${BASH_SOURCE[0]}")/safe-replace-dir.sh"
|
||||
# safe_replace_dir "$source" "$target" "$safety_root"
|
||||
#
|
||||
# Usage (standalone):
|
||||
# ./scripts/lib/safe-replace-dir.sh /path/to/source /safe/root/target /safe/root
|
||||
|
||||
safe_replace_dir() {
|
||||
local source=$1
|
||||
local target=$2
|
||||
local safety_root=$3
|
||||
|
||||
if [[ -z "$target" ]]; then
|
||||
echo "safe_replace_dir: refusing to replace unsafe target: (empty string)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Resolve the real (symlink-resolved) safety root.
|
||||
local abs_safety
|
||||
abs_safety=$(cd "$safety_root" 2>/dev/null && pwd -P) || {
|
||||
echo "safe_replace_dir: safety root does not exist: $safety_root" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Build an absolute lexical path for target's parent directory.
|
||||
local target_parent target_base
|
||||
target_base=$(basename "$target")
|
||||
target_parent=$(dirname "$target")
|
||||
# Make target_parent absolute without relying on cd (target may not exist yet).
|
||||
if [[ "$target_parent" != /* ]]; then
|
||||
target_parent="${PWD}/${target_parent}"
|
||||
fi
|
||||
|
||||
# Walk up from target_parent to find the deepest existing directory,
|
||||
# accumulating the non-existing path suffix as we go.
|
||||
local suffix=""
|
||||
local walk="$target_parent"
|
||||
while [[ ! -d "$walk" ]]; do
|
||||
local component
|
||||
component=$(basename "$walk")
|
||||
if [[ -z "$suffix" ]]; then
|
||||
suffix="$component"
|
||||
else
|
||||
suffix="${component}/${suffix}"
|
||||
fi
|
||||
local next
|
||||
next=$(dirname "$walk")
|
||||
if [[ "$next" == "$walk" ]]; then
|
||||
echo "safe_replace_dir: could not find existing ancestor for: $target" >&2
|
||||
return 1
|
||||
fi
|
||||
walk="$next"
|
||||
done
|
||||
|
||||
# Resolve the real path of the existing ancestor (follows symlinks).
|
||||
local abs_parent
|
||||
abs_parent=$(cd "$walk" && pwd -P) || {
|
||||
echo "safe_replace_dir: could not resolve parent directory: $walk" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Reconstruct the full absolute target path.
|
||||
local abs_target
|
||||
if [[ -n "$suffix" ]]; then
|
||||
abs_target="${abs_parent}/${suffix}/${target_base}"
|
||||
else
|
||||
abs_target="${abs_parent}/${target_base}"
|
||||
fi
|
||||
|
||||
# Check that abs_target is strictly inside abs_safety
|
||||
case "$abs_target" in
|
||||
"${abs_safety}/"*) ;;
|
||||
*)
|
||||
echo "safe_replace_dir: refusing to replace target outside safety root: $target" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -rf "$abs_target"
|
||||
mkdir -p "$abs_target"
|
||||
cp -R "${source}/." "$abs_target/"
|
||||
}
|
||||
|
||||
# Allow standalone use
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "Usage: $0 <source> <target> <safety_root>" >&2
|
||||
exit 1
|
||||
fi
|
||||
safe_replace_dir "$1" "$2" "$3" || exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user