diff --git a/sdata/subcmd-install/3.files-exp.sh b/sdata/subcmd-install/3.files-exp.sh index c483cdd98..dd227fd4a 100644 --- a/sdata/subcmd-install/3.files-exp.sh +++ b/sdata/subcmd-install/3.files-exp.sh @@ -2,139 +2,149 @@ # It's not for directly running. # See https://github.com/end-4/dots-hyprland/issues/2137 +# +# Stage 1 todos: +# TODO: add --exp-files-path Use 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 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 --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. +# +# 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. + # Configuration file -CONFIG_FILE="sdata/subcmd-install/3.files.yaml" +CONFIG_FILE="sdata/subcmd-install/3.files-exp.yaml" # ============================================================================= wizard_update_preferences() { - echo -e "${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}" - + echo -e "${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}" + # Get current preferences current_shell=$(yq '.user_preferences.shell // "fish"' "$CONFIG_FILE") current_terminal=$(yq '.user_preferences.terminal // "kitty"' "$CONFIG_FILE") current_keybindings=$(yq '.user_preferences.keybindings // "default"' "$CONFIG_FILE") - + echo "Current preferences:" echo " Shell: $current_shell" echo " Terminal: $current_terminal" echo " Keybindings: $current_keybindings" echo - + # Shell selection echo "Which shell do you prefer?" echo "1) fish (default)" echo "2) zsh" read -p "Enter choice [1-2]: " shell_choice - + case "$shell_choice" in - 1|"") shell="fish" ;; - 2) shell="zsh" ;; - *) echo "Invalid choice, using fish"; shell="fish" ;; + 1|"") shell="fish" ;; + 2) shell="zsh" ;; + *) echo "Invalid choice, using fish"; shell="fish" ;; esac - + # Terminal selection echo echo "Which terminal do you prefer?" echo "1) kitty (default)" echo "2) foot" read -p "Enter choice [1-2]: " terminal_choice - + case "$terminal_choice" in - 1|"") terminal="kitty" ;; - 2) terminal="foot" ;; - *) echo "Invalid choice, using kitty"; terminal="kitty" ;; + 1|"") terminal="kitty" ;; + 2) terminal="foot" ;; + *) echo "Invalid choice, using kitty"; terminal="kitty" ;; esac - + # Keybindings selection echo echo "Which keybinding style do you prefer?" echo "1) default (arrow keys)" echo "2) vim (H/J/K/L)" read -p "Enter choice [1-2]: " keybind_choice - + case "$keybind_choice" in - 1|"") keybindings="default" ;; - 2) keybindings="vim" ;; - *) echo "Invalid choice, using default"; keybindings="default" ;; + 1|"") keybindings="default" ;; + 2) keybindings="vim" ;; + *) echo "Invalid choice, using default"; keybindings="default" ;; esac - + # Update YAML in-place yq -i ".user_preferences.shell = \"$shell\"" "$CONFIG_FILE" yq -i ".user_preferences.terminal = \"$terminal\"" "$CONFIG_FILE" yq -i ".user_preferences.keybindings = \"$keybindings\"" "$CONFIG_FILE" - + echo echo "Preferences updated!" -} + } # Get user preference 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 should_process_pattern() { - local pattern="$1" - local condition=$(echo "$pattern" | yq '.condition // "true"') + local pattern="$1" + local condition=$(echo "$pattern" | yq '.condition // "true"') # If no condition or condition is "true", always process if [[ "$condition" == "true" ]]; then return 0 fi - + # Extract the preference type and value from condition local type=$(echo "$condition" | yq '.type') local value=$(echo "$condition" | yq '.value') - + [[ "$(get_pref "$type")" == "$value" ]] - -} + + } # Compare hashes of files/directories, return true if they are the same, false otherwise files_are_same() { - local path1="$1" - local path2="$2" - + local path1="$1" + local path2="$2" + # Check if paths exist if [[ ! -e "$path1" || ! -e "$path2" ]]; then - return 1 + return 1 fi - + # For directories, use find + md5sum to compare recursively # For files, use md5sum directly if [[ -d "$path1" && -d "$path2" ]]; then - # Compare directory contents using find and md5sum - 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}') - [[ "$hash1" == "$hash2" ]] + # Compare directory contents using find and md5sum + 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}') + [[ "$hash1" == "$hash2" ]] elif [[ -f "$path1" && -f "$path2" ]]; then - # Compare file hashes - local hash1=$(md5sum "$path1" | awk '{print $1}') - local hash2=$(md5sum "$path2" | awk '{print $1}') - [[ "$hash1" == "$hash2" ]] + # Compare file hashes + local hash1=$(md5sum "$path1" | awk '{print $1}') + local hash2=$(md5sum "$path2" | awk '{print $1}') + [[ "$hash1" == "$hash2" ]] else - # One is a file, one is a directory - different types - return 1 + # One is a file, one is a directory - different types + return 1 fi -} + } # Find next backup number get_next_backup_number() { - local base_path="$1" - local counter=1 + local base_path="$1" + local counter=1 - while [[ -e "${base_path}.old.${counter}" ]]; do - ((counter++)) - done + while [[ -e "${base_path}.old.${counter}" ]]; do + ((counter++)) + done - echo $counter + echo $counter } # ============================================================================= @@ -152,118 +162,118 @@ readarray patterns < <(yq -o=j -I=0 '.patterns[]' "$CONFIG_FILE") # Process each pattern for pattern in "${patterns[@]}"; do - from=$(echo "$pattern" | yq '.from' - | envsubst) - to=$(echo "$pattern" | yq '.to' - | envsubst) - mode=$(echo "$pattern" | yq '.mode' - | envsubst) - condition=$(echo "$pattern" | yq '.condition // "true"') - + from=$(echo "$pattern" | yq '.from' - | envsubst) + to=$(echo "$pattern" | yq '.to' - | envsubst) + mode=$(echo "$pattern" | yq '.mode' - | envsubst) + condition=$(echo "$pattern" | yq '.condition // "true"') + # Handle fontconfig fontset override # 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 - from="dots-extra/fontsets/${FONTSET_DIR_NAME}" - echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig" + from="dots-extra/fontsets/${FONTSET_DIR_NAME}" + echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig" fi - + # Check if pattern should be processed if ! should_process_pattern "$pattern"; then - # Format condition message nicely - if [[ "$condition" != "true" ]]; then - cond_type=$(echo "$condition" | yq -r '.type // ""') - cond_value=$(echo "$condition" | yq -r '.value // ""') - if [[ -n "$cond_type" && -n "$cond_value" ]]; then - echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')" - else - echo "Skipping $from -> $to (condition not met)" - fi + # Format condition message nicely + if [[ "$condition" != "true" ]]; then + cond_type=$(echo "$condition" | yq -r '.type // ""') + cond_value=$(echo "$condition" | yq -r '.value // ""') + if [[ -n "$cond_type" && -n "$cond_value" ]]; then + echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')" else - echo "Skipping $from -> $to (condition not met)" + echo "Skipping $from -> $to (condition not met)" fi - continue + else + echo "Skipping $from -> $to (condition not met)" + fi + continue fi - + echo "Processing: $from -> $to (mode: $mode)" - + # Build exclude arguments for rsync excludes=() if echo "$pattern" | yq -e '.excludes' >/dev/null 2>&1; then - while IFS= read -r exclude; do - excludes+=(--exclude "$exclude") - done < <(echo "$pattern" | yq -r '.excludes[]') + while IFS= read -r exclude; do + excludes+=(--exclude "$exclude") + done < <(echo "$pattern" | yq -r '.excludes[]') fi - + # Check if source exists if [[ ! -e "$from" ]]; then - echo "Warning: Source does not exist: $from (skipping)" - continue + echo "Warning: Source does not exist: $from (skipping)" + continue fi - + # Ensure destination directory exists for files if [[ -f "$from" ]]; then - v mkdir -p "$(dirname "$to")" + v mkdir -p "$(dirname "$to")" fi - + # Execute based on mode case $mode in - "sync") - if [[ -d "$from" ]]; then - warning_rsync_delete - v rsync -av --delete "${excludes[@]}" "$from/" "$to/" - else - warning_rsync_normal - # For files, don't use trailing slash and don't use --delete - v rsync -av "${excludes[@]}" "$from" "$to" - fi - ;; - "soft") - warning_rsync_normal - if [[ -d "$from" ]]; then - v rsync -av "${excludes[@]}" "$from/" "$to/" - else - # For files, don't use trailing slash - v rsync -av "${excludes[@]}" "$from" "$to" - fi - ;; - "hard") + "sync") + if [[ -d "$from" ]]; then + warning_rsync_delete + v rsync -av --delete "${excludes[@]}" "$from/" "$to/" + else + warning_rsync_normal + # For files, don't use trailing slash and don't use --delete + v rsync -av "${excludes[@]}" "$from" "$to" + fi + ;; + "soft") + warning_rsync_normal + if [[ -d "$from" ]]; then + v rsync -av "${excludes[@]}" "$from/" "$to/" + else + # For files, don't use trailing slash + v rsync -av "${excludes[@]}" "$from" "$to" + fi + ;; + "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" - ;; - "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" - fi - else - v cp -r "$from" "$to" - fi - ;; - "soft-backup") - if [[ -e "$to" ]]; then - if files_are_same "$from" "$to"; then - echo "Files are identical, skipping backup" - else - v cp -r "$from" "$to.new" - fi - else - v cp -r "$from" "$to" - fi - ;; - "skip") - echo "Skipping $from" - ;; - "skip-if-exists") - if [[ -e "$to" ]]; then - echo "Skipping $from (destination exists)" - else - v cp -r "$from" "$to" - fi - ;; - *) - echo "Unknown mode: $mode" - ;; + fi + else + v cp -r "$from" "$to" + fi + ;; + "soft-backup") + if [[ -e "$to" ]]; then + if files_are_same "$from" "$to"; then + echo "Files are identical, skipping backup" + else + v cp -r "$from" "$to.new" + fi + else + v cp -r "$from" "$to" + fi + ;; + "skip") + echo "Skipping $from" + ;; + "skip-if-exists") + if [[ -e "$to" ]]; then + echo "Skipping $from (destination exists)" + else + v cp -r "$from" "$to" + fi + ;; + *) + echo "Unknown mode: $mode" + ;; esac -done + done ##################################################################################### diff --git a/sdata/subcmd-install/3.files.yaml b/sdata/subcmd-install/3.files-exp.yaml similarity index 100% rename from sdata/subcmd-install/3.files.yaml rename to sdata/subcmd-install/3.files-exp.yaml diff --git a/sdata/subcmd-install/options.sh b/sdata/subcmd-install/options.sh index 5116ab9d5..c5e860215 100644 --- a/sdata/subcmd-install/options.sh +++ b/sdata/subcmd-install/options.sh @@ -36,11 +36,6 @@ New features (experimental): see https://github.com/end-4/dots-hyprland/issues/1061 for details. ${STY_RST}" } -# TODO: implement options below for --exp-files -# --exp-files-path Use 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(){ rm -rf "${REPO_ROOT}/cache"