From a217d4c5daa624e60e2af1f3c1eb38cb61d5c053 Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Thu, 22 Jan 2026 15:14:39 +0200 Subject: [PATCH 1/3] feat(exp-update): add --default-choice option for non-interactive updates --- sdata/subcmd-exp-update/0.run.sh | 239 +++++++++++++++-------------- sdata/subcmd-exp-update/options.sh | 14 +- 2 files changed, 136 insertions(+), 117 deletions(-) diff --git a/sdata/subcmd-exp-update/0.run.sh b/sdata/subcmd-exp-update/0.run.sh index bbbf5830f..87c78a16c 100644 --- a/sdata/subcmd-exp-update/0.run.sh +++ b/sdata/subcmd-exp-update/0.run.sh @@ -113,7 +113,16 @@ safe_read() { echo -n "$prompt" - # Try to read from terminal with better detection + # First, try reading from stdin (supports piped input like "yes 1 |") + if read -r -t 0.1 input_value 2>/dev/null; then + # Successfully read from stdin (piped input) + if [[ -n "$input_value" ]]; then + printf -v "$varname" '%s' "$input_value" + return 0 + fi + fi + + # If stdin had no data, try interactive terminal if [[ -t 0 ]]; then # stdin is a terminal read -r input_value @@ -339,145 +348,124 @@ handle_file_conflict() { local home_file="$2" local filename=$(basename "$home_file") local dirname=$(dirname "$home_file") + local choice="" + local default_val="${DEFAULT_CHOICE:-6}" # Use DEFAULT_CHOICE or 6 (skip) as fallback - echo -e "\n${STY_YELLOW}Conflict detected:${STY_RST} $home_file" - echo "Repository version differs from your local version." - echo - echo "Choose an action:" - echo "1) Replace local file with repository version" - echo "2) Keep local file unchanged" - echo "3) Backup local file as ${filename}.old, use repository version" - echo "4) Save repository version as ${filename}.new, keep local file" - echo "5) Show diff and decide" - echo "6) Skip this file" - echo "7) Add to ignore and skip" - echo "8) Backup to .update-backups/ and replace with repository version" - echo + # In non-interactive mode, use default directly (acts like pressing Enter) + if [[ "$NON_INTERACTIVE" == true ]]; then + choice="$default_val" + log_info "Using choice $choice for: $home_file" + else + echo -e "\n${STY_YELLOW}Conflict detected:${STY_RST} $home_file" + echo "Repository version differs from your local version." + echo + echo "Choose an action:" + echo "1) Replace local file with repository version" + echo "2) Keep local file unchanged" + echo "3) Backup local file as ${filename}.old, use repository version" + echo "4) Save repository version as ${filename}.new, keep local file" + echo "5) Show diff and decide" + echo "6) Skip this file" + echo "7) Add to ignore and skip" + echo "8) Backup to .update-backups/ and replace with repository version" + echo - while true; do - if ! safe_read "Enter your choice (1-8): " choice "6"; then + while true; do + if ! safe_read "Enter your choice (1-8) [${default_val}]: " choice "$default_val"; then + echo + log_warning "Failed to read input. Skipping file." + return + fi + + # Validate choice + if [[ "$choice" =~ ^[1-8]$ ]]; then + break + else + echo "Invalid choice. Please enter 1-8." + fi + done + fi + + case $choice in + 1) + if [[ "$DRY_RUN" == true ]]; then + log_info "[DRY-RUN] Would replace $home_file with repository version" + else + cp -p "$repo_file" "$home_file" + log_success "Replaced $home_file with repository version" + fi + ;; + 2) + log_info "Keeping local version of $home_file" + ;; + 3) + if [[ "$DRY_RUN" == true ]]; then + log_info "[DRY-RUN] Would backup local file to ${filename}.old and update with repository version" + else + mv "$home_file" "${dirname}/${filename}.old" + cp -p "$repo_file" "$home_file" + log_success "Backed up local file to ${filename}.old and updated with repository version" + fi + ;; + 4) + if [[ "$DRY_RUN" == true ]]; then + log_info "[DRY-RUN] Would save repository version as ${filename}.new, keep local file" + else + cp -p "$repo_file" "${dirname}/${filename}.new" + log_success "Saved repository version as ${filename}.new, kept local file" + fi + ;; + 5) + show_diff "$home_file" "$repo_file" + echo + echo "After reviewing the diff, choose:" + echo "r) Replace with repository version" + echo "k) Keep local version" + echo "b) Backup local and use repository version" + echo "n) Save repository version as .new" + echo "s) Skip this file" + echo "i) Add to ignore and skip" + echo "B) Backup to .update-backups/ and replace" + + if ! safe_read "Enter your choice (r/k/b/n/s/i/B): " subchoice "s"; then echo log_warning "Failed to read input. Skipping file." return fi - case $choice in - 1) + case $subchoice in + r) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would replace $home_file with repository version" else cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi - break ;; - 2) + k) log_info "Keeping local version of $home_file" - break ;; - 3) + b) if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would backup local file to ${filename}.old and update with repository version" + log_info "[DRY-RUN] Would backup local file to ${filename}.old and update" else mv "$home_file" "${dirname}/${filename}.old" cp -p "$repo_file" "$home_file" - log_success "Backed up local file to ${filename}.old and updated with repository version" + log_success "Backed up local file to ${filename}.old and updated" fi - break ;; - 4) + n) if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would save repository version as ${filename}.new, keep local file" + log_info "[DRY-RUN] Would save repository version as ${filename}.new" else cp -p "$repo_file" "${dirname}/${filename}.new" - log_success "Saved repository version as ${filename}.new, kept local file" + log_success "Saved repository version as ${filename}.new" fi - break ;; - 5) - show_diff "$home_file" "$repo_file" - echo - echo "After reviewing the diff, choose:" - echo "r) Replace with repository version" - echo "k) Keep local version" - echo "b) Backup local and use repository version" - echo "n) Save repository version as .new" - echo "s) Skip this file" - echo "i) Add to ignore and skip" - echo "B) Backup to .update-backups/ and replace" - - if ! safe_read "Enter your choice (r/k/b/n/s/i/B): " subchoice "s"; then - echo - log_warning "Failed to read input. Skipping file." - return - fi - - case $subchoice in - r) - if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would replace $home_file with repository version" - else - cp -p "$repo_file" "$home_file" - log_success "Replaced $home_file with repository version" - fi - break - ;; - k) - log_info "Keeping local version of $home_file" - break - ;; - b) - if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would backup local file to ${filename}.old and update" - else - mv "$home_file" "${dirname}/${filename}.old" - cp -p "$repo_file" "$home_file" - log_success "Backed up local file to ${filename}.old and updated" - fi - break - ;; - n) - if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would save repository version as ${filename}.new" - else - cp -p "$repo_file" "${dirname}/${filename}.new" - log_success "Saved repository version as ${filename}.new" - fi - break - ;; - s) - log_info "Skipping $home_file" - break - ;; - i) - local relative_path_to_home="${home_file#$HOME/}" - if [[ "$DRY_RUN" == true ]]; then - log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" - else - echo "$relative_path_to_home" >>"$XDG_UPDATE_IGNORE_FILE" - log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." - fi - break - ;; - B) - if backup_file "$home_file"; then - if [[ "$DRY_RUN" != true ]]; then - cp -p "$repo_file" "$home_file" - log_success "Replaced $home_file with repository version" - fi - fi - break - ;; - *) - echo "Invalid choice. Please try again." - ;; - esac - ;; - 6) + s) log_info "Skipping $home_file" - break ;; - 7) + i) local relative_path_to_home="${home_file#$HOME/}" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" @@ -485,22 +473,41 @@ handle_file_conflict() { echo "$relative_path_to_home" >>"$XDG_UPDATE_IGNORE_FILE" log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." fi - break ;; - 8) + B) if backup_file "$home_file"; then if [[ "$DRY_RUN" != true ]]; then cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi fi - break ;; *) - echo "Invalid choice. Please enter 1-8." + log_info "Skipping $home_file" ;; esac - done + ;; + 6) + log_info "Skipping $home_file" + ;; + 7) + local relative_path_to_home="${home_file#$HOME/}" + if [[ "$DRY_RUN" == true ]]; then + log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" + else + echo "$relative_path_to_home" >>"$XDG_UPDATE_IGNORE_FILE" + log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." + fi + ;; + 8) + if backup_file "$home_file"; then + if [[ "$DRY_RUN" != true ]]; then + cp -p "$repo_file" "$home_file" + log_success "Replaced $home_file with repository version" + fi + fi + ;; + esac } # Function to check if PKGBUILD has changed diff --git a/sdata/subcmd-exp-update/options.sh b/sdata/subcmd-exp-update/options.sh index 30198299c..0ca87ec6e 100644 --- a/sdata/subcmd-exp-update/options.sh +++ b/sdata/subcmd-exp-update/options.sh @@ -16,6 +16,10 @@ Options: -s, --skip-notice Skip notice about script being untested --non-interactive Run without prompting for user input + --default-choice=N + Set default choice for file conflicts (1-8, used with --non-interactive) + 1=Replace local 2=Keep local 3=Backup as .old 4=Save as .new + 5=Show diff 6=Skip 7=Add to ignore 8=Backup and replace This script updates your dotfiles by: 1. Auto-detecting repository structure (dots/ prefix or direct) @@ -35,7 +39,7 @@ Ignore file patterns support: # `man getopt` to see more para=$(getopt \ -o hfpnvs \ - -l help,force,packages,dry-run,verbose,skip-notice,non-interactive \ + -l help,force,packages,dry-run,verbose,skip-notice,non-interactive,default-choice: \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### @@ -58,6 +62,7 @@ DRY_RUN=false VERBOSE=false SKIP_NOTICE=false NON_INTERACTIVE=false +DEFAULT_CHOICE="" eval set -- "$para" while true ; do @@ -81,6 +86,13 @@ while true ; do --non-interactive) NON_INTERACTIVE=true;shift log_info "Non-interactive mode enabled" ;; + --default-choice) DEFAULT_CHOICE="$2";shift 2 + if [[ ! "$DEFAULT_CHOICE" =~ ^[1-8]$ ]]; then + log_error "Invalid --default-choice value: $DEFAULT_CHOICE (must be 1-8)" + exit 1 + fi + log_info "Default conflict choice set to: $DEFAULT_CHOICE" + ;; ## Ending --) break ;; From c30776e811a984629399a793aa1b76f784cc8d4d Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Thu, 22 Jan 2026 19:42:29 +0200 Subject: [PATCH 2/3] feat: update exp-update default-choice to use string flags --- sdata/subcmd-exp-update/options.sh | 31 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/sdata/subcmd-exp-update/options.sh b/sdata/subcmd-exp-update/options.sh index 0ca87ec6e..3883d320f 100644 --- a/sdata/subcmd-exp-update/options.sh +++ b/sdata/subcmd-exp-update/options.sh @@ -15,11 +15,10 @@ Options: -h, --help Show this help message -s, --skip-notice Skip notice about script being untested --non-interactive - Run without prompting for user input - --default-choice=N - Set default choice for file conflicts (1-8, used with --non-interactive) - 1=Replace local 2=Keep local 3=Backup as .old 4=Save as .new - 5=Show diff 6=Skip 7=Add to ignore 8=Backup and replace + Set default choice for file conflicts (usually used with --non-interactive) + replace: Replace local keep: Keep local old: Backup as .old + new: Save as .new diff: Show diff skip: Skip + ignore: Add to ignore backup: Backup and replace This script updates your dotfiles by: 1. Auto-detecting repository structure (dots/ prefix or direct) @@ -86,11 +85,23 @@ while true ; do --non-interactive) NON_INTERACTIVE=true;shift log_info "Non-interactive mode enabled" ;; - --default-choice) DEFAULT_CHOICE="$2";shift 2 - if [[ ! "$DEFAULT_CHOICE" =~ ^[1-8]$ ]]; then - log_error "Invalid --default-choice value: $DEFAULT_CHOICE (must be 1-8)" - exit 1 - fi + --default-choice) + case "$2" in + replace) DEFAULT_CHOICE="1" ;; + keep) DEFAULT_CHOICE="2" ;; + old) DEFAULT_CHOICE="3" ;; + new) DEFAULT_CHOICE="4" ;; + diff) DEFAULT_CHOICE="5" ;; + skip) DEFAULT_CHOICE="6" ;; + ignore) DEFAULT_CHOICE="7" ;; + backup) DEFAULT_CHOICE="8" ;; + *) + log_error "Invalid --default-choice value: $2" + log_error "Valid values: replace, keep, old, new, diff, skip, ignore, backup" + exit 1 + ;; + esac + shift 2 log_info "Default conflict choice set to: $DEFAULT_CHOICE" ;; From 76ee7b6bb1d7c06cf6e0a11d89e16e745ecdb5ab Mon Sep 17 00:00:00 2001 From: Bishoy Ehab Date: Thu, 22 Jan 2026 19:52:17 +0200 Subject: [PATCH 3/3] feat(exp-update): support string inputs in interactive mode and help text --- sdata/subcmd-exp-update/0.run.sh | 22 +++++++++++----------- sdata/subcmd-exp-update/options.sh | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdata/subcmd-exp-update/0.run.sh b/sdata/subcmd-exp-update/0.run.sh index 87c78a16c..f31a19283 100644 --- a/sdata/subcmd-exp-update/0.run.sh +++ b/sdata/subcmd-exp-update/0.run.sh @@ -371,23 +371,23 @@ handle_file_conflict() { echo while true; do - if ! safe_read "Enter your choice (1-8) [${default_val}]: " choice "$default_val"; then + if ! safe_read "Enter your choice (1-8 or name) [${default_val}]: " choice "$default_val"; then echo log_warning "Failed to read input. Skipping file." return fi # Validate choice - if [[ "$choice" =~ ^[1-8]$ ]]; then + if [[ "$choice" =~ ^[1-8]$ ]] || [[ "$choice" =~ ^(replace|keep|old|new|diff|skip|ignore|backup)$ ]]; then break else - echo "Invalid choice. Please enter 1-8." + echo "Invalid choice. Please enter 1-8 or a valid name (replace, keep, old ...)." fi done fi case $choice in - 1) + 1|replace) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would replace $home_file with repository version" else @@ -395,10 +395,10 @@ handle_file_conflict() { log_success "Replaced $home_file with repository version" fi ;; - 2) + 2|keep) log_info "Keeping local version of $home_file" ;; - 3) + 3|old) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup local file to ${filename}.old and update with repository version" else @@ -407,7 +407,7 @@ handle_file_conflict() { log_success "Backed up local file to ${filename}.old and updated with repository version" fi ;; - 4) + 4|new) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would save repository version as ${filename}.new, keep local file" else @@ -415,7 +415,7 @@ handle_file_conflict() { log_success "Saved repository version as ${filename}.new, kept local file" fi ;; - 5) + 5|diff) show_diff "$home_file" "$repo_file" echo echo "After reviewing the diff, choose:" @@ -487,10 +487,10 @@ handle_file_conflict() { ;; esac ;; - 6) + 6|skip) log_info "Skipping $home_file" ;; - 7) + 7|ignore) local relative_path_to_home="${home_file#$HOME/}" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" @@ -499,7 +499,7 @@ handle_file_conflict() { log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." fi ;; - 8) + 8|backup) if backup_file "$home_file"; then if [[ "$DRY_RUN" != true ]]; then cp -p "$repo_file" "$home_file" diff --git a/sdata/subcmd-exp-update/options.sh b/sdata/subcmd-exp-update/options.sh index 3883d320f..79acbbcce 100644 --- a/sdata/subcmd-exp-update/options.sh +++ b/sdata/subcmd-exp-update/options.sh @@ -15,7 +15,7 @@ Options: -h, --help Show this help message -s, --skip-notice Skip notice about script being untested --non-interactive - Set default choice for file conflicts (usually used with --non-interactive) + Set default choice for file conflicts replace: Replace local keep: Keep local old: Backup as .old new: Save as .new diff: Show diff skip: Skip ignore: Add to ignore backup: Backup and replace