Split disk_cleanup_check.sh: extract the helpers and the output blocks

329 -> 230 + 124. The colour constants, human_readable, dir_size_kb,
report, confirm and try_clean move to lib/disk_cleanup_common.sh, along
with the report-only loop and the summary block as print_report_only and
print_summary.

The seam passes state -- report accumulates into TOTAL_RECLAIMABLE and
confirm reads CLEAN -- so both are set before the source line, and the
verification is a trace diff rather than a static check: identical exit
status, identical ordered du calls, identical stdout and stderr against a
detached worktree at the pre-split commit.

Also ran it for real from /tmp (not its own directory) to confirm
SCRIPT_DIR resolves the lib from any cwd: 303.7 GB reported reclaimable,
nothing deleted. The one shellcheck finding (SC2034 on RED) was fixed by
moving the function that uses it, not suppressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
Krzysztof Rudnicki 2026-08-16 22:57:06 +02:00
parent eef14d7bce
commit 5b6973e11d
3 changed files with 174 additions and 106 deletions

View File

@ -0,0 +1,43 @@
{
"intent": "Bring meta/scripts/disk_cleanup_check.sh (329 lines) under the 250-line cap by moving its formatting, sizing, confirmation and output helpers into a sourced lib.",
"scope": [
"meta/scripts/disk_cleanup_check.sh",
"meta/scripts/lib/disk_cleanup_common.sh (new)",
"Non-goal: no change to which directories are checked, what is reported, or what --clean deletes"
],
"changes": [
"Move the six colour constants and human_readable/dir_size_kb/report/confirm/try_clean into lib/disk_cleanup_common.sh.",
"Move the report-only loop and the summary block into print_report_only and print_summary in the same lib; the entry script now ends with two calls.",
"Add SCRIPT_DIR resolved from BASH_SOURCE so the source line works from any cwd, and set CLEAN/TOTAL_RECLAIMABLE before sourcing, since confirm reads CLEAN and report accumulates into TOTAL_RECLAIMABLE."
],
"verification": [
{
"command": "meta/scripts/trace_shell_split.sh <script> --stub du --out before.txt (detached worktree at the pre-split commit), then again after the split, then diff",
"result": "pass",
"evidence": "TRACE IDENTICAL -- exit status, the ordered list of stubbed `du -sk` calls, stdout and stderr all byte-identical. This is the check that matters here: the seam passes state (report mutates TOTAL_RECLAIMABLE, confirm reads CLEAN), so a static check could not have caught a break."
},
{
"command": "bash ~/testsAndMisc/meta/scripts/disk_cleanup_check.sh (run for real from /tmp, not from the script's own directory)",
"result": "pass",
"evidence": "exit 0, full report against real data: 303.7 GB auto-reclaimable, current usage 1445.6 GB / 3665.4 GB (42%). Running from a different cwd confirms SCRIPT_DIR resolves the lib correctly -- a known failure mode for split scripts. Dry-run default, nothing deleted."
},
{
"command": "bash -n + shellcheck -x on the entry, shellcheck on the lib, shfmt -d on both",
"result": "pass",
"evidence": "All exit 0 with no output. An initial SC2034 (RED appears unused) was resolved by moving print_report_only -- which uses RED -- into the lib, rather than by adding a suppression."
},
{
"command": "bash ~/utils/scripts/check_file_length.sh on both files",
"result": "pass",
"evidence": "329 -> 230 (entry) + 124 (lib), both under the 250 cap."
}
],
"risks": [
"The lib depends on CLEAN and TOTAL_RECLAIMABLE being set by the caller before it is sourced. That is documented in its header comment; sourcing it standalone would leave confirm reading an unset CLEAN.",
"`bc` was briefly stubbed during the first baseline attempt, which produced 'printf: invalid number' noise -- bc is a calculator, not a mutator. The recorded baseline uses real bc and has empty stderr."
],
"rollback": [
"git revert the commit; the helpers return inline and lib/disk_cleanup_common.sh disappears.",
"Re-run the script from any directory and confirm it prints the analysis table and a non-zero total."
]
}

View File

@ -7,13 +7,10 @@
#
# Safe by default: nothing is deleted without --clean AND user confirmation.
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[1;33m'
CYAN=$'\033[0;36m'
BOLD=$'\033[1m'
RESET=$'\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# CLEAN and TOTAL_RECLAIMABLE must exist before the helpers are sourced:
# `confirm` reads CLEAN and `report` accumulates into TOTAL_RECLAIMABLE.
CLEAN=false
if [[ "${1:-}" == "--clean" ]]; then
CLEAN=true
@ -21,65 +18,8 @@ fi
TOTAL_RECLAIMABLE=0
# ───────────────────── helpers ─────────────────────
human_readable() {
local kb=$1
if ((kb >= 1048576)); then
printf "%.1f GB" "$(echo "scale=1; $kb / 1048576" | bc)"
elif ((kb >= 1024)); then
printf "%.1f MB" "$(echo "scale=1; $kb / 1024" | bc)"
else
printf "%d KB" "$kb"
fi
}
dir_size_kb() {
local dir="$1"
if [[ -d "$dir" ]]; then
du -sk "$dir" 2>/dev/null | awk '{print $1}'
else
echo 0
fi
}
report() {
local label="$1" size_kb="$2" detail="${3:-}"
if ((size_kb > 0)); then
local hr
hr=$(human_readable "$size_kb")
printf "${YELLOW}%-40s${RESET} %10s" "$label" "$hr"
if [[ -n "$detail" ]]; then
printf " ${CYAN}(%s)${RESET}" "$detail"
fi
printf "\n"
TOTAL_RECLAIMABLE=$((TOTAL_RECLAIMABLE + size_kb))
fi
}
# Returns 0 if user confirms, 1 otherwise. Always 1 in dry-run.
confirm() {
local prompt="$1"
if $CLEAN; then
printf "${BOLD} → %s [y/N]: ${RESET}" "$prompt"
read -r ans
if [[ "$ans" =~ ^[Yy]$ ]]; then
return 0
fi
fi
return 1
}
# Safe wrapper: confirm + action, never fails under errexit
try_clean() {
local prompt="$1"
shift
if confirm "$prompt"; then
"$@"
printf "%s ✓ Done%s\n" "${GREEN}" "${RESET}"
fi
return 0
}
# shellcheck source=./lib/disk_cleanup_common.sh
source "$SCRIPT_DIR/lib/disk_cleanup_common.sh"
# ───────────────────── checks ─────────────────────
@ -286,44 +226,5 @@ if [[ -d "$aur_dir" ]]; then
fi
fi
# ───────────────────── report-only items ─────────────────────
printf "\n%s--- Report-only (manual review needed) ---%s\n" "${BOLD}" "${RESET}"
for manual_entry in \
"$HOME/Downloads/too_big:~/Downloads/too_big" \
"$HOME/Downloads:~/Downloads total" \
"$HOME/inne:~/inne" \
"/Games:/Games — still playing?"; do
dir="${manual_entry%%:*}"
label="${manual_entry#*:}"
size=$(dir_size_kb "$dir")
if ((size > 0)); then
hr=$(human_readable "$size")
printf "${RED}%-40s %10s${RESET} ${CYAN}(review manually)${RESET}\n" \
"$label" "$hr"
fi
done
# ───────────────────── summary ─────────────────────
printf "\n%s\n" "$(printf '%.0s─' {1..60})"
total_hr=$(human_readable "$TOTAL_RECLAIMABLE")
printf "${BOLD}Total auto-reclaimable: ${GREEN}%s${RESET}\n" "$total_hr"
read -r used_kb total_kb used_pct <<<"$(df -k / | awk 'NR==2{print $3, $2, $5}')"
used_pct="${used_pct%\%}"
printf "${BOLD}Current usage: %s / %s (%s%%)${RESET}\n" \
"$(human_readable "$used_kb")" "$(human_readable "$total_kb")" "$used_pct"
if ((TOTAL_RECLAIMABLE > 0)); then
new_used=$((used_kb - TOTAL_RECLAIMABLE))
if ((new_used < 0)); then new_used=0; fi
new_pct=$((new_used * 100 / total_kb))
printf "${BOLD}After cleanup: %s / %s (%d%%)${RESET}\n" \
"$(human_readable "$new_used")" "$(human_readable "$total_kb")" "$new_pct"
fi
if ! $CLEAN; then
printf "\n%sRun with --clean to interactively clean each category.%s\n" "${CYAN}" "${RESET}"
fi
printf "\n"
print_report_only
print_summary

View File

@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Formatting, sizing and confirmation helpers for disk_cleanup_check.sh.
#
# Split out to keep the entry script under the 250-line cap. The seam carries
# state deliberately: `report` accumulates into TOTAL_RECLAIMABLE and `confirm`
# reads CLEAN, both owned by the caller. Sourcing this file does not define
# either -- the caller must set them first, which is what keeps the dry-run
# default safe.
#
# shellcheck shell=bash
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[1;33m'
CYAN=$'\033[0;36m'
BOLD=$'\033[1m'
RESET=$'\033[0m'
human_readable() {
local kb=$1
if ((kb >= 1048576)); then
printf "%.1f GB" "$(echo "scale=1; $kb / 1048576" | bc)"
elif ((kb >= 1024)); then
printf "%.1f MB" "$(echo "scale=1; $kb / 1024" | bc)"
else
printf "%d KB" "$kb"
fi
}
dir_size_kb() {
local dir="$1"
if [[ -d "$dir" ]]; then
du -sk "$dir" 2>/dev/null | awk '{print $1}'
else
echo 0
fi
}
# Prints a row and adds to the caller's TOTAL_RECLAIMABLE. Silent for size 0.
report() {
local label="$1" size_kb="$2" detail="${3:-}"
if ((size_kb > 0)); then
local hr
hr=$(human_readable "$size_kb")
printf "${YELLOW}%-40s${RESET} %10s" "$label" "$hr"
if [[ -n "$detail" ]]; then
printf " ${CYAN}(%s)${RESET}" "$detail"
fi
printf "\n"
TOTAL_RECLAIMABLE=$((TOTAL_RECLAIMABLE + size_kb))
fi
}
# Returns 0 if user confirms, 1 otherwise. Always 1 in dry-run.
confirm() {
local prompt="$1"
if $CLEAN; then
printf "${BOLD} → %s [y/N]: ${RESET}" "$prompt"
read -r ans
if [[ "$ans" =~ ^[Yy]$ ]]; then
return 0
fi
fi
return 1
}
# Safe wrapper: confirm + action, never fails under errexit
try_clean() {
local prompt="$1"
shift
if confirm "$prompt"; then
"$@"
printf "%s ✓ Done%s\n" "${GREEN}" "${RESET}"
fi
return 0
}
# Directories that are never auto-cleaned: too personal to guess at, so they
# are listed with sizes and left alone.
print_report_only() {
printf "\n%s--- Report-only (manual review needed) ---%s\n" "${BOLD}" "${RESET}"
local manual_entry dir label size hr
for manual_entry in \
"$HOME/Downloads/too_big:~/Downloads/too_big" \
"$HOME/Downloads:~/Downloads total" \
"$HOME/inne:~/inne" \
"/Games:/Games — still playing?"; do
dir="${manual_entry%%:*}"
label="${manual_entry#*:}"
size=$(dir_size_kb "$dir")
if ((size > 0)); then
hr=$(human_readable "$size")
printf "${RED}%-40s %10s${RESET} ${CYAN}(review manually)${RESET}\n" \
"$label" "$hr"
fi
done
}
# Totals plus a before/after projection against the real filesystem.
print_summary() {
printf "\n%s\n" "$(printf '%.0s─' {1..60})"
local total_hr used_kb total_kb used_pct new_used new_pct
total_hr=$(human_readable "$TOTAL_RECLAIMABLE")
printf "${BOLD}Total auto-reclaimable: ${GREEN}%s${RESET}\n" "$total_hr"
read -r used_kb total_kb used_pct <<<"$(df -k / | awk 'NR==2{print $3, $2, $5}')"
used_pct="${used_pct%\%}"
printf "${BOLD}Current usage: %s / %s (%s%%)${RESET}\n" \
"$(human_readable "$used_kb")" "$(human_readable "$total_kb")" "$used_pct"
if ((TOTAL_RECLAIMABLE > 0)); then
new_used=$((used_kb - TOTAL_RECLAIMABLE))
if ((new_used < 0)); then new_used=0; fi
new_pct=$((new_used * 100 / total_kb))
printf "${BOLD}After cleanup: %s / %s (%d%%)${RESET}\n" \
"$(human_readable "$new_used")" "$(human_readable "$total_kb")" "$new_pct"
fi
if ! $CLEAN; then
printf "\n%sRun with --clean to interactively clean each category.%s\n" \
"${CYAN}" "${RESET}"
fi
printf "\n"
}