Update files-exp; Rename 3.files.yaml to 3.files-exp.yaml

This commit is contained in:
clsty
2025-11-01 09:52:34 +08:00
parent e3549e639e
commit 17984c812f
3 changed files with 152 additions and 147 deletions
+152 -142
View File
@@ -2,139 +2,149 @@
# It's not for directly running. # It's not for directly running.
# See https://github.com/end-4/dots-hyprland/issues/2137 # See https://github.com/end-4/dots-hyprland/issues/2137
#
# Stage 1 todos:
# TODO: add --exp-files-path <path> Use <path> instead of the default yaml config
# TODO: add --exp-files-regen Force copy the default config to ${EXP_FILE_PATH} (auto do this when not existed)
# TODO: Implement versioning, i.e. when user-defined yaml config file has version number mismatch with the default one, produce error. If only minor version number is not the same, the error can be ommitted via --exp-file-no-strict . # TODO: Implement versioning, i.e. when user-defined yaml config file has version number mismatch with the default one, produce error. If only minor version number is not the same, the error can be ommitted via --exp-file-no-strict .
# TODO: Implement user-define yaml with merging (override) ability for user who only wants little customization and is satisfied with most of the defaults. # TODO: add --exp-files-no-strict Ignore error when minor version number is not the same
#
# Stage 2 todos:
# TODO: Implement symlink (readable or non-readable) as sync mode # TODO: Implement symlink (readable or non-readable) as sync mode
# TODO: Implement --exp-file-reset-symlink to try to remove all symlink in .config and .local, which point to the local repo # TODO: add --exp-file-reset-symlink Try to remove all symlink in .config and .local, which point to the local repo
# TODO: Update help and doc about `--exp-files` and the yaml config, including the possible values of mode. # TODO: Update help and doc about `--exp-files` and the yaml config, including the possible values of mode.
#
# Stage 3 todos:
# TODO: Implement user-define yaml with merging (override) ability for user who only wants little customization and is satisfied with most of the defaults.
# TODO: Support exclude pattern for each target to skip some files inside it. # TODO: Support exclude pattern for each target to skip some files inside it.
# Configuration file # Configuration file
CONFIG_FILE="sdata/subcmd-install/3.files.yaml" CONFIG_FILE="sdata/subcmd-install/3.files-exp.yaml"
# ============================================================================= # =============================================================================
wizard_update_preferences() { wizard_update_preferences() {
echo -e "${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}" echo -e "${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}"
# Get current preferences # Get current preferences
current_shell=$(yq '.user_preferences.shell // "fish"' "$CONFIG_FILE") current_shell=$(yq '.user_preferences.shell // "fish"' "$CONFIG_FILE")
current_terminal=$(yq '.user_preferences.terminal // "kitty"' "$CONFIG_FILE") current_terminal=$(yq '.user_preferences.terminal // "kitty"' "$CONFIG_FILE")
current_keybindings=$(yq '.user_preferences.keybindings // "default"' "$CONFIG_FILE") current_keybindings=$(yq '.user_preferences.keybindings // "default"' "$CONFIG_FILE")
echo "Current preferences:" echo "Current preferences:"
echo " Shell: $current_shell" echo " Shell: $current_shell"
echo " Terminal: $current_terminal" echo " Terminal: $current_terminal"
echo " Keybindings: $current_keybindings" echo " Keybindings: $current_keybindings"
echo echo
# Shell selection # Shell selection
echo "Which shell do you prefer?" echo "Which shell do you prefer?"
echo "1) fish (default)" echo "1) fish (default)"
echo "2) zsh" echo "2) zsh"
read -p "Enter choice [1-2]: " shell_choice read -p "Enter choice [1-2]: " shell_choice
case "$shell_choice" in case "$shell_choice" in
1|"") shell="fish" ;; 1|"") shell="fish" ;;
2) shell="zsh" ;; 2) shell="zsh" ;;
*) echo "Invalid choice, using fish"; shell="fish" ;; *) echo "Invalid choice, using fish"; shell="fish" ;;
esac esac
# Terminal selection # Terminal selection
echo echo
echo "Which terminal do you prefer?" echo "Which terminal do you prefer?"
echo "1) kitty (default)" echo "1) kitty (default)"
echo "2) foot" echo "2) foot"
read -p "Enter choice [1-2]: " terminal_choice read -p "Enter choice [1-2]: " terminal_choice
case "$terminal_choice" in case "$terminal_choice" in
1|"") terminal="kitty" ;; 1|"") terminal="kitty" ;;
2) terminal="foot" ;; 2) terminal="foot" ;;
*) echo "Invalid choice, using kitty"; terminal="kitty" ;; *) echo "Invalid choice, using kitty"; terminal="kitty" ;;
esac esac
# Keybindings selection # Keybindings selection
echo echo
echo "Which keybinding style do you prefer?" echo "Which keybinding style do you prefer?"
echo "1) default (arrow keys)" echo "1) default (arrow keys)"
echo "2) vim (H/J/K/L)" echo "2) vim (H/J/K/L)"
read -p "Enter choice [1-2]: " keybind_choice read -p "Enter choice [1-2]: " keybind_choice
case "$keybind_choice" in case "$keybind_choice" in
1|"") keybindings="default" ;; 1|"") keybindings="default" ;;
2) keybindings="vim" ;; 2) keybindings="vim" ;;
*) echo "Invalid choice, using default"; keybindings="default" ;; *) echo "Invalid choice, using default"; keybindings="default" ;;
esac esac
# Update YAML in-place # Update YAML in-place
yq -i ".user_preferences.shell = \"$shell\"" "$CONFIG_FILE" yq -i ".user_preferences.shell = \"$shell\"" "$CONFIG_FILE"
yq -i ".user_preferences.terminal = \"$terminal\"" "$CONFIG_FILE" yq -i ".user_preferences.terminal = \"$terminal\"" "$CONFIG_FILE"
yq -i ".user_preferences.keybindings = \"$keybindings\"" "$CONFIG_FILE" yq -i ".user_preferences.keybindings = \"$keybindings\"" "$CONFIG_FILE"
echo echo
echo "Preferences updated!" echo "Preferences updated!"
} }
# Get user preference # Get user preference
get_pref() { get_pref() {
yq -r ".user_preferences.$1" "$CONFIG_FILE" yq -r ".user_preferences.$1" "$CONFIG_FILE"
} }
# Check if pattern should be processed based on user preferences # Check if pattern should be processed based on user preferences
should_process_pattern() { should_process_pattern() {
local pattern="$1" local pattern="$1"
local condition=$(echo "$pattern" | yq '.condition // "true"') local condition=$(echo "$pattern" | yq '.condition // "true"')
# If no condition or condition is "true", always process # If no condition or condition is "true", always process
if [[ "$condition" == "true" ]]; then if [[ "$condition" == "true" ]]; then
return 0 return 0
fi fi
# Extract the preference type and value from condition # Extract the preference type and value from condition
local type=$(echo "$condition" | yq '.type') local type=$(echo "$condition" | yq '.type')
local value=$(echo "$condition" | yq '.value') local value=$(echo "$condition" | yq '.value')
[[ "$(get_pref "$type")" == "$value" ]] [[ "$(get_pref "$type")" == "$value" ]]
} }
# Compare hashes of files/directories, return true if they are the same, false otherwise # Compare hashes of files/directories, return true if they are the same, false otherwise
files_are_same() { files_are_same() {
local path1="$1" local path1="$1"
local path2="$2" local path2="$2"
# Check if paths exist # Check if paths exist
if [[ ! -e "$path1" || ! -e "$path2" ]]; then if [[ ! -e "$path1" || ! -e "$path2" ]]; then
return 1 return 1
fi fi
# For directories, use find + md5sum to compare recursively # For directories, use find + md5sum to compare recursively
# For files, use md5sum directly # For files, use md5sum directly
if [[ -d "$path1" && -d "$path2" ]]; then if [[ -d "$path1" && -d "$path2" ]]; then
# Compare directory contents using find and md5sum # Compare directory contents using find and md5sum
local hash1=$(find "$path1" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}') local hash1=$(find "$path1" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}')
local hash2=$(find "$path2" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}') local hash2=$(find "$path2" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}')
[[ "$hash1" == "$hash2" ]] [[ "$hash1" == "$hash2" ]]
elif [[ -f "$path1" && -f "$path2" ]]; then elif [[ -f "$path1" && -f "$path2" ]]; then
# Compare file hashes # Compare file hashes
local hash1=$(md5sum "$path1" | awk '{print $1}') local hash1=$(md5sum "$path1" | awk '{print $1}')
local hash2=$(md5sum "$path2" | awk '{print $1}') local hash2=$(md5sum "$path2" | awk '{print $1}')
[[ "$hash1" == "$hash2" ]] [[ "$hash1" == "$hash2" ]]
else else
# One is a file, one is a directory - different types # One is a file, one is a directory - different types
return 1 return 1
fi fi
} }
# Find next backup number # Find next backup number
get_next_backup_number() { get_next_backup_number() {
local base_path="$1" local base_path="$1"
local counter=1 local counter=1
while [[ -e "${base_path}.old.${counter}" ]]; do while [[ -e "${base_path}.old.${counter}" ]]; do
((counter++)) ((counter++))
done done
echo $counter echo $counter
} }
# ============================================================================= # =============================================================================
@@ -152,118 +162,118 @@ readarray patterns < <(yq -o=j -I=0 '.patterns[]' "$CONFIG_FILE")
# Process each pattern # Process each pattern
for pattern in "${patterns[@]}"; do for pattern in "${patterns[@]}"; do
from=$(echo "$pattern" | yq '.from' - | envsubst) from=$(echo "$pattern" | yq '.from' - | envsubst)
to=$(echo "$pattern" | yq '.to' - | envsubst) to=$(echo "$pattern" | yq '.to' - | envsubst)
mode=$(echo "$pattern" | yq '.mode' - | envsubst) mode=$(echo "$pattern" | yq '.mode' - | envsubst)
condition=$(echo "$pattern" | yq '.condition // "true"') condition=$(echo "$pattern" | yq '.condition // "true"')
# Handle fontconfig fontset override # Handle fontconfig fontset override
# If FONTSET_DIR_NAME is set and this is the fontconfig pattern, use the fontset instead # If FONTSET_DIR_NAME is set and this is the fontconfig pattern, use the fontset instead
if [[ "$from" == "dots/.config/fontconfig" ]] && [[ -n "${FONTSET_DIR_NAME:-}" ]]; then if [[ "$from" == "dots/.config/fontconfig" ]] && [[ -n "${FONTSET_DIR_NAME:-}" ]]; then
from="dots-extra/fontsets/${FONTSET_DIR_NAME}" from="dots-extra/fontsets/${FONTSET_DIR_NAME}"
echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig" echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig"
fi fi
# Check if pattern should be processed # Check if pattern should be processed
if ! should_process_pattern "$pattern"; then if ! should_process_pattern "$pattern"; then
# Format condition message nicely # Format condition message nicely
if [[ "$condition" != "true" ]]; then if [[ "$condition" != "true" ]]; then
cond_type=$(echo "$condition" | yq -r '.type // ""') cond_type=$(echo "$condition" | yq -r '.type // ""')
cond_value=$(echo "$condition" | yq -r '.value // ""') cond_value=$(echo "$condition" | yq -r '.value // ""')
if [[ -n "$cond_type" && -n "$cond_value" ]]; then if [[ -n "$cond_type" && -n "$cond_value" ]]; then
echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')" echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')"
else
echo "Skipping $from -> $to (condition not met)"
fi
else else
echo "Skipping $from -> $to (condition not met)" echo "Skipping $from -> $to (condition not met)"
fi fi
continue else
echo "Skipping $from -> $to (condition not met)"
fi
continue
fi fi
echo "Processing: $from -> $to (mode: $mode)" echo "Processing: $from -> $to (mode: $mode)"
# Build exclude arguments for rsync # Build exclude arguments for rsync
excludes=() excludes=()
if echo "$pattern" | yq -e '.excludes' >/dev/null 2>&1; then if echo "$pattern" | yq -e '.excludes' >/dev/null 2>&1; then
while IFS= read -r exclude; do while IFS= read -r exclude; do
excludes+=(--exclude "$exclude") excludes+=(--exclude "$exclude")
done < <(echo "$pattern" | yq -r '.excludes[]') done < <(echo "$pattern" | yq -r '.excludes[]')
fi fi
# Check if source exists # Check if source exists
if [[ ! -e "$from" ]]; then if [[ ! -e "$from" ]]; then
echo "Warning: Source does not exist: $from (skipping)" echo "Warning: Source does not exist: $from (skipping)"
continue continue
fi fi
# Ensure destination directory exists for files # Ensure destination directory exists for files
if [[ -f "$from" ]]; then if [[ -f "$from" ]]; then
v mkdir -p "$(dirname "$to")" v mkdir -p "$(dirname "$to")"
fi fi
# Execute based on mode # Execute based on mode
case $mode in case $mode in
"sync") "sync")
if [[ -d "$from" ]]; then if [[ -d "$from" ]]; then
warning_rsync_delete warning_rsync_delete
v rsync -av --delete "${excludes[@]}" "$from/" "$to/" v rsync -av --delete "${excludes[@]}" "$from/" "$to/"
else else
warning_rsync_normal warning_rsync_normal
# For files, don't use trailing slash and don't use --delete # For files, don't use trailing slash and don't use --delete
v rsync -av "${excludes[@]}" "$from" "$to" v rsync -av "${excludes[@]}" "$from" "$to"
fi fi
;; ;;
"soft") "soft")
warning_rsync_normal warning_rsync_normal
if [[ -d "$from" ]]; then if [[ -d "$from" ]]; then
v rsync -av "${excludes[@]}" "$from/" "$to/" v rsync -av "${excludes[@]}" "$from/" "$to/"
else else
# For files, don't use trailing slash # For files, don't use trailing slash
v rsync -av "${excludes[@]}" "$from" "$to" v rsync -av "${excludes[@]}" "$from" "$to"
fi fi
;; ;;
"hard") "hard")
v cp -r "$from" "$to"
;;
"hard-backup")
if [[ -e "$to" ]]; then
if files_are_same "$from" "$to"; then
echo "Files are identical, skipping backup"
else
backup_number=$(get_next_backup_number "$to")
v mv "$to" "$to.old.$backup_number"
v cp -r "$from" "$to" v cp -r "$from" "$to"
;; fi
"hard-backup") else
if [[ -e "$to" ]]; then v cp -r "$from" "$to"
if files_are_same "$from" "$to"; then fi
echo "Files are identical, skipping backup" ;;
else "soft-backup")
backup_number=$(get_next_backup_number "$to") if [[ -e "$to" ]]; then
v mv "$to" "$to.old.$backup_number" if files_are_same "$from" "$to"; then
v cp -r "$from" "$to" echo "Files are identical, skipping backup"
fi else
else v cp -r "$from" "$to.new"
v cp -r "$from" "$to" fi
fi else
;; v cp -r "$from" "$to"
"soft-backup") fi
if [[ -e "$to" ]]; then ;;
if files_are_same "$from" "$to"; then "skip")
echo "Files are identical, skipping backup" echo "Skipping $from"
else ;;
v cp -r "$from" "$to.new" "skip-if-exists")
fi if [[ -e "$to" ]]; then
else echo "Skipping $from (destination exists)"
v cp -r "$from" "$to" else
fi v cp -r "$from" "$to"
;; fi
"skip") ;;
echo "Skipping $from" *)
;; echo "Unknown mode: $mode"
"skip-if-exists") ;;
if [[ -e "$to" ]]; then
echo "Skipping $from (destination exists)"
else
v cp -r "$from" "$to"
fi
;;
*)
echo "Unknown mode: $mode"
;;
esac esac
done done
##################################################################################### #####################################################################################
-5
View File
@@ -36,11 +36,6 @@ New features (experimental):
see https://github.com/end-4/dots-hyprland/issues/1061 for details. see https://github.com/end-4/dots-hyprland/issues/1061 for details.
${STY_RST}" ${STY_RST}"
} }
# TODO: implement options below for --exp-files
# --exp-files-path <path> Use <path> instead of the default yaml config
# --exp-files-no-strict Ignore error when minor version number is not the same
# --exp-files-regen Force copy the default config to ${EXP_FILE_PATH}
# (auto do this when not existed)
cleancache(){ cleancache(){
rm -rf "${REPO_ROOT}/cache" rm -rf "${REPO_ROOT}/cache"