Better integration; introduce subcommand

This commit is contained in:
clsty
2025-10-18 00:38:49 +08:00
parent eede0e3c34
commit 731beb0f7c
6 changed files with 237 additions and 212 deletions
+14 -29
View File
@@ -1,10 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cd "$(dirname "$0")" cd "$(dirname "$0")"
export base="$(pwd)" export base="$(pwd)"
# Store original arguments for experimental scripts
ORIGINAL_ARGS=("$@")
source ./sdata/lib/environment-variables.sh source ./sdata/lib/environment-variables.sh
source ./sdata/lib/functions.sh source ./sdata/lib/functions.sh
source ./sdata/lib/package-installers.sh source ./sdata/lib/package-installers.sh
@@ -14,28 +10,17 @@ prevent_sudo_or_root
set -e set -e
##################################################################################### #####################################################################################
# For uninstall script # For subcommands
if [[ "${EXPERIMENTAL_UNINSTALL_SCRIPT}" = true ]]; then case ${SCRIPT_SUBCOMMAND} in
source ./sdata/exp/uninstall.sh exp-uninstall)
exit source ./sdata/exp/uninstall.sh
fi exit
# For update script ;;
if [[ "${EXPERIMENTAL_UPDATE_SCRIPT}" = true ]]; then exp-update)
export SOURCED_FROM_INSTALL=true source ./sdata/exp/update.sh
# Pass only update-specific arguments exit
UPDATE_ARGS=() ;;
for arg in "${ORIGINAL_ARGS[@]}"; do esac
case "$arg" in
-u|--update-force|-p|--packages|-n|--dry-run|-v|--verbose|--skip-notice)
UPDATE_ARGS+=("$arg")
;;
*)
;;
esac
done
bash ./sdata/exp/update.sh "${UPDATE_ARGS[@]}"
exit
fi
##################################################################################### #####################################################################################
# 0. Before we start # 0. Before we start
if [[ "${SKIP_ALLGREETING}" != true ]]; then if [[ "${SKIP_ALLGREETING}" != true ]]; then
@@ -54,9 +39,9 @@ fi
##################################################################################### #####################################################################################
if [[ "${SKIP_ALLFILES}" != true ]]; then if [[ "${SKIP_ALLFILES}" != true ]]; then
printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}"
if [[ "${EXPERIMENTAL_FILES_SCRIPT}" != true ]]; then if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then
source ./sdata/step/3.install-files.sh
else
source ./sdata/step/3.install-files.experimental.sh source ./sdata/step/3.install-files.experimental.sh
else
source ./sdata/step/3.install-files.sh
fi fi
fi fi
Executable → Regular
+25 -113
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env bash # This script is meant to be sourced.
# It's not for directly running.
#####################################################################################
# #
# update.sh - Enhanced dotfiles update script # update.sh - Enhanced dotfiles update script
# #
@@ -11,12 +14,11 @@
# #
set -euo pipefail set -euo pipefail
# === Configuration === REPO_DIR="$(pwd)"
FORCE_CHECK=false
CHECK_PACKAGES=false # TODO: For Arch(-Linux) specific part please check if pacman exists first, if not it should be skipped.
DRY_RUN=false
VERBOSE=false # TODO: Is this really needed? `git pull` should do a full upgrade, not partially, which means this script will be updated along with the folder structure together.
REPO_DIR="$(cd "$(dirname "$(dirname "$(dirname "$0")")")" &>/dev/null && pwd)"
# Try to find the packages directory (different names in different versions) # Try to find the packages directory (different names in different versions)
if [[ -d "${REPO_DIR}/dist-arch" ]]; then if [[ -d "${REPO_DIR}/dist-arch" ]]; then
ARCH_PACKAGES_DIR="${REPO_DIR}/dist-arch" ARCH_PACKAGES_DIR="${REPO_DIR}/dist-arch"
@@ -30,6 +32,7 @@ fi
UPDATE_IGNORE_FILE="${REPO_DIR}/.updateignore" UPDATE_IGNORE_FILE="${REPO_DIR}/.updateignore"
HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore" HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore"
# TODO: Is this really needed? `git pull` should do a full upgrade, not partially, which means this script will be updated along with the folder structure together.
# Auto-detect repository structure # Auto-detect repository structure
detect_repo_structure() { detect_repo_structure() {
local found_dirs=() local found_dirs=()
@@ -67,41 +70,6 @@ detect_repo_structure() {
# Directories to monitor for changes (will be auto-detected) # Directories to monitor for changes (will be auto-detected)
MONITOR_DIRS=() MONITOR_DIRS=()
# === Color Codes ===
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# === Helper Functions ===
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
log_header() {
echo -e "\n${PURPLE}=== $1 ===${NC}"
}
die() {
log_error "$1"
exit 1
}
# Function to safely read input with terminal compatibility # Function to safely read input with terminal compatibility
safe_read() { safe_read() {
local prompt="$1" local prompt="$1"
@@ -216,9 +184,9 @@ show_diff() {
local file1="$1" local file1="$1"
local file2="$2" local file2="$2"
echo -e "\n${CYAN}Showing differences:${NC}" echo -e "\n${STY_CYAN}Showing differences:${STY_RST}"
echo -e "${CYAN}Old file: $file1${NC}" echo -e "${STY_CYAN}Old file: $file1${STY_RST}"
echo -e "${CYAN}New file: $file2${NC}" echo -e "${STY_CYAN}New file: $file2${STY_RST}"
echo "----------------------------------------" echo "----------------------------------------"
if command -v diff &>/dev/null; then if command -v diff &>/dev/null; then
@@ -236,7 +204,7 @@ handle_file_conflict() {
local filename=$(basename "$home_file") local filename=$(basename "$home_file")
local dirname=$(dirname "$home_file") local dirname=$(dirname "$home_file")
echo -e "\n${YELLOW}Conflict detected:${NC} $home_file" echo -e "\n${STY_YELLOW}Conflict detected:${STY_RST} $home_file"
echo "Repository version differs from your local version." echo "Repository version differs from your local version."
echo echo
echo "Choose an action:" echo "Choose an action:"
@@ -431,17 +399,17 @@ list_packages() {
return 1 return 1
fi fi
echo -e "\n${CYAN}Available packages:${NC}" echo -e "\n${STY_CYAN}Available packages:${STY_RST}"
for pkg in "${available_packages[@]}"; do for pkg in "${available_packages[@]}"; do
if [[ " ${changed_packages[*]} " =~ " ${pkg} " ]]; then if [[ " ${changed_packages[*]} " =~ " ${pkg} " ]]; then
echo -e " ${GREEN}${pkg}${NC} (PKGBUILD changed)" echo -e " ${STY_GREEN}${pkg}${STY_RST} (PKGBUILD changed)"
else else
echo -e "${pkg}" echo -e "${pkg}"
fi fi
done done
if [[ ${#changed_packages[@]} -gt 0 ]]; then if [[ ${#changed_packages[@]} -gt 0 ]]; then
echo -e "\n${YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${NC}" echo -e "\n${STY_YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${STY_RST}"
fi fi
return 0 return 0
@@ -497,7 +465,7 @@ build_packages() {
return return
fi fi
echo -e "\n${CYAN}Packages to build: ${packages_to_build[*]}${NC}" echo -e "\n${STY_CYAN}Packages to build: ${packages_to_build[*]}${STY_RST}"
if ! safe_read "Proceed with building these packages? (Y/n): " confirm "Y"; then if ! safe_read "Proceed with building these packages? (Y/n): " confirm "Y"; then
log_warning "Failed to read input. Skipping package builds." log_warning "Failed to read input. Skipping package builds."
@@ -533,7 +501,7 @@ build_packages() {
log_error "Failed to build package $pkg_name" log_error "Failed to build package $pkg_name"
fi fi
cd "$REPO_DIR" || die "Failed to return to repository directory" cd "$REPO_DIR" || log_die "Failed to return to repository directory"
done done
if [[ $rebuilt_packages -eq 0 ]]; then if [[ $rebuilt_packages -eq 0 ]]; then
@@ -586,63 +554,7 @@ has_new_commits() {
# Main script starts here # Main script starts here
log_header "Dotfiles Update Script" log_header "Dotfiles Update Script"
check=true if [[ "$SKIP_NOTICE" != true ]]; then
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-f | --force)
FORCE_CHECK=true
log_info "Force check mode enabled - will check all files regardless of git changes"
shift
;;
-p | --packages)
CHECK_PACKAGES=true
log_info "Package checking enabled"
shift
;;
-n | --dry-run)
DRY_RUN=true
log_info "Dry-run mode enabled - no changes will be made"
shift
;;
-v | --verbose)
VERBOSE=true
log_info "Verbose mode enabled"
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -f, --force Force check all files even if no new commits"
echo " -p, --packages Enable package checking and building"
echo " -n, --dry-run Show what would be done without making changes"
echo " -v, --verbose Enable verbose output"
echo " -h, --help Show this help message"
echo ""
echo "This script updates your dotfiles by:"
echo " 1. Auto-detecting repository structure (dots/ prefix or direct)"
echo " 2. Pulling latest changes from git remote"
echo " 3. Optionally rebuilding packages (if -p flag is used)"
echo " 4. Syncing configuration files to home directory"
echo " 5. Updating script permissions"
exit 0
;;
--skip-notice)
log_warning "Skipping notice about script being untested"
check=false
shift
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
if [[ "$check" == true ]]; then
log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!" log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!"
log_warning "It might be safer if you want to preserve your modifications and not delete added files," log_warning "It might be safer if you want to preserve your modifications and not delete added files,"
log_warning " but this can cause partial updates and therefore unexpected behavior like in #1856." log_warning " but this can cause partial updates and therefore unexpected behavior like in #1856."
@@ -656,7 +568,7 @@ if [[ "$check" == true ]]; then
fi fi
# Check if we're in a git repository # Check if we're in a git repository
cd "$REPO_DIR" || die "Failed to change to repository directory" cd "$REPO_DIR" || log_die "Failed to change to repository directory"
if git rev-parse --is-inside-work-tree &>/dev/null; then if git rev-parse --is-inside-work-tree &>/dev/null; then
log_info "Running in git repository: $(git rev-parse --show-toplevel)" log_info "Running in git repository: $(git rev-parse --show-toplevel)"
@@ -678,7 +590,7 @@ if detected_dirs=$(detect_repo_structure); then
fi fi
done done
else else
die "Failed to detect repository structure. Make sure you're in the correct directory." log_die "Failed to detect repository structure. Make sure you're in the correct directory."
fi fi
# Step 1: Pull latest commits # Step 1: Pull latest commits
@@ -694,7 +606,7 @@ if [[ -z "$current_branch" ]]; then
git checkout master git checkout master
current_branch="master" current_branch="master"
else else
die "Could not find main or master branch" log_die "Could not find main or master branch"
fi fi
fi fi
@@ -712,7 +624,7 @@ if ! git diff --quiet || ! git diff --cached --quiet; then
fi fi
if [[ ! "$response" =~ ^[Yy]$ ]]; then if [[ ! "$response" =~ ^[Yy]$ ]]; then
die "Aborted by user" log_die "Aborted by user"
fi fi
if [[ "$DRY_RUN" == true ]]; then if [[ "$DRY_RUN" == true ]]; then
log_info "[DRY-RUN] Would stash changes" log_info "[DRY-RUN] Would stash changes"
@@ -946,7 +858,7 @@ else
fi fi
echo echo
echo -e "${CYAN}Summary:${NC}" echo -e "${STY_CYAN}Summary:${STY_RST}"
if command -v git >/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then if command -v git >/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then
echo "- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)' 2>/dev/null || echo 'Unknown')" echo "- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)' 2>/dev/null || echo 'Unknown')"
else else
+20
View File
@@ -95,3 +95,23 @@ function latest_commit_timestamp(){
fi fi
echo $result echo $result
} }
function log_info() {
echo -e "${STY_BLUE}[INFO]${STY_RST} $1"
}
function log_success() {
echo -e "${STY_GREEN}[SUCCESS]${STY_RST} $1"
}
function log_warning() {
echo -e "${STY_YELLOW}[WARNING]${STY_RST} $1"
}
function log_error() {
echo -e "${STY_RED}[ERROR]${STY_RST} $1" >&2
}
function log_header() {
echo -e "\n${STY_PURPLE}=== $1 ===${STY_RST}"
}
function log_die() {
log_error "$1"
exit 1
}
+75
View File
@@ -0,0 +1,75 @@
# Handle args for subcmd: exp-update
showhelp(){
echo -e "Syntax: $0 exp-update [OPTIONS]...
Options:
-f, --force Force check all files even if no new commits
-p, --packages Enable package checking and building
-n, --dry-run Show what would be done without making changes
-v, --verbose Enable verbose output
-h, --help Show this help message
This script updates your dotfiles by:
1. Auto-detecting repository structure (dots/ prefix or direct)
2. Pulling latest changes from git remote
3. Optionally rebuilding packages (if -p flag is used)
4. Syncing configuration files to home directory
5. Updating script permissions
"
}
0
;;
shift
;;
*)
log_error "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# `man getopt` to see more
para=$(getopt \
-o hfpnv \
-l help,force,packages,dry-run,verbose,skip-notice \
-n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
#####################################################################################
## getopt Phase 1
# ignore parameter's order, execute options below first
eval set -- "$para"
while true ; do
case "$1" in
-h|--help) showhelp;exit;;
--) break ;;
*) shift ;;
esac
done
#####################################################################################
## getopt Phase 2
FORCE_CHECK=false
CHECK_PACKAGES=false
DRY_RUN=false
VERBOSE=false
eval set -- "$para"
while true ; do
case "$1" in
## Ones without parameter
-f|--force) FORCE_CHECK=true;shift;;
# log_info "Force check mode enabled - will check all files regardless of git changes"
-p|--packages) CHECK_PACKAGES=true;shift;;
# log_info "Package checking enabled"
-n|--dry-run) DRY_RUN=true;shift;;
# log_info "Dry-run mode enabled - no changes will be made"
-v|--verbose) VERBOSE=true;shift;;
# log_info "Verbose mode enabled"
--skip-notice) SKIP_NOTICE=true;shift;;
# log_warning "Skipping notice about script being untested"
## Ending
--) break ;;
*) echo -e "$0: Wrong parameters.";exit 1;;
esac
done
+64
View File
@@ -0,0 +1,64 @@
# Handle args for subcmd: install
cleancache(){
rm -rf "$base/cache"
}
# `man getopt` to see more
para=$(getopt \
-o hfk:cs \
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \
-n "$0" -- "$@")
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1
#####################################################################################
## getopt Phase 1
# ignore parameter's order, execute options below first
eval set -- "$para"
while true ; do
case "$1" in
-h|--help) showhelp_global;exit;;
-c|--clean) cleancache;shift;;
--) break ;;
*) shift ;;
esac
done
#####################################################################################
## getopt Phase 2
eval set -- "$para"
while true ; do
case "$1" in
## Already processed in phase 1, but not exited
-c|--clean) shift;;
## Ones without parameter
-f|--force) ask=false;shift;;
--skip-allgreeting) SKIP_ALLGREETING=true;shift;;
--skip-alldeps) SKIP_ALLDEPS=true;shift;;
--skip-allsetups) SKIP_ALLSETUPS=true;shift;;
--skip-allfiles) SKIP_ALLFILES=true;shift;;
-s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;
--skip-hyprland) SKIP_HYPRLAND=true;shift;;
--skip-fish) SKIP_FISH=true;shift;;
--skip-miscconf) SKIP_MISCCONF=true;shift;;
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
--exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
--via-nix) INSTALL_VIA_NIX=true;shift;;
## Update script specific options
-u|--update-force) UPDATE_FORCE=true;shift;;
-p|--packages) UPDATE_PACKAGES=true;shift;;
-n|--dry-run) UPDATE_DRY_RUN=true;shift;;
-v|--verbose) UPDATE_VERBOSE=true;shift;;
--skip-notice) SKIP_NOTICE=true;shift;;
## Ones with parameter
--fontset)
case $2 in
"default"|"zh-CN"|"vi") fontset="$2";;
*) echo -e "Wrong argument for $1.";exit 1;;
esac;echo "The fontset is ${fontset}.";shift 2;;
## Ending
--) break ;;
*) echo -e "$0: Wrong parameters.";exit 1;;
esac
done
+38 -69
View File
@@ -3,12 +3,15 @@
# The script that use this file should have two lines on its top as follows: # The script that use this file should have two lines on its top as follows:
# cd "$(dirname "$0")" export base="$(pwd)" # cd "$(dirname "$0")" export base="$(pwd)"
showhelp(){ showhelp_global(){
echo -e "Syntax: $0 [Options]... echo -e "Syntax: $0 [subcommand] [options]...
Idempotent installation script for dotfiles. Idempotent installation script for dotfiles.
If no option is specified, run default install process. If no option nor subcommand is specified, run default install process.
Subcommand:
install The default subcommand which can be omitted.
Options for install:
-h, --help Print this help message and exit -h, --help Print this help message and exit
-f, --force (Dangerous) Force mode without any confirm -f, --force (Dangerous) Force mode without any confirm
-c, --clean Clean the build cache first -c, --clean Clean the build cache first
@@ -26,9 +29,13 @@ If no option is specified, run default install process.
--fontset <set> (Unavailable yet) Use a set of pre-defined font and config --fontset <set> (Unavailable yet) Use a set of pre-defined font and config
--via-nix (Unavailable yet) Use Nix to install dependencies --via-nix (Unavailable yet) Use Nix to install dependencies
--exp-uninstall Use experimental uninstall script --exp-uninstall Use experimental uninstall script
--exp-update Use experimental update script
Update Script Options (only with --exp-update): Subcommand:
exp-uninstall Using experimental uninstall script.
Subcommand:
exp-update Using experimental update script.
Options for exp-update:
-u, --update-force Force check all files even if no new commits (update script) -u, --update-force Force check all files even if no new commits (update script)
-p, --packages Enable package checking and building (update script) -p, --packages Enable package checking and building (update script)
-n, --dry-run Show what would be done without making changes (update script) -n, --dry-run Show what would be done without making changes (update script)
@@ -37,68 +44,30 @@ Update Script Options (only with --exp-update):
" "
} }
cleancache(){ # Handle subcommand
rm -rf "$base/cache" case $1 in
} # subcommand specified
install|exp-uninstall|exp-update)
SCRIPT_SUBCOMMAND=$1
shift
;;
# no subcommand (has options: -* ; no options: "")
-*|"")
SCRIPT_SUBCOMMAND=install
;;
# wrong subcommand
*)echo "Unknown subcommand \"$1\", aborting...";exit 1;;
esac
# `man getopt` to see more # Handle options for subcommand
para=$(getopt \ case ${SCRIPT_SUBCOMMAND} in
-o hfk:csu:p:n:v \ install)
-l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix,exp-uninstall,exp-update,update-force,packages,dry-run,verbose,skip-notice \ source ./sdata/lib/options-install.sh
-n "$0" -- "$@") ;;
[ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 exp-uninstall)
##################################################################################### #source ./sdata/lib/options-exp-uninstall.sh
## getopt Phase 1 ;;
# ignore parameter's order, execute options below first exp-update)
eval set -- "$para" source ./sdata/lib/options-exp-update.sh
while true ; do ;;
case "$1" in esac
-h|--help) showhelp;exit;;
-c|--clean) cleancache;shift;;
--) break ;;
*) shift ;;
esac
done
#####################################################################################
## getopt Phase 2
eval set -- "$para"
while true ; do
case "$1" in
## Already processed in phase 1, but not exited
-c|--clean) shift;;
## Ones without parameter
-f|--force) ask=false;shift;;
--skip-allgreeting) SKIP_ALLGREETING=true;shift;;
--skip-alldeps) SKIP_ALLDEPS=true;shift;;
--skip-allsetups) SKIP_ALLSETUPS=true;shift;;
--skip-allfiles) SKIP_ALLFILES=true;shift;;
-s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;
--skip-hyprland) SKIP_HYPRLAND=true;shift;;
--skip-fish) SKIP_FISH=true;shift;;
--skip-miscconf) SKIP_MISCCONF=true;shift;;
--skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;
--exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;
--via-nix) INSTALL_VIA_NIX=true;shift;;
--exp-uninstall) EXPERIMENTAL_UNINSTALL_SCRIPT=true;shift;;
--exp-update) EXPERIMENTAL_UPDATE_SCRIPT=true;shift;;
## Update script specific options
-u|--update-force) UPDATE_FORCE=true;shift;;
-p|--packages) UPDATE_PACKAGES=true;shift;;
-n|--dry-run) UPDATE_DRY_RUN=true;shift;;
-v|--verbose) UPDATE_VERBOSE=true;shift;;
--skip-notice) SKIP_NOTICE=true;shift;;
## Ones with parameter
--fontset)
case $2 in
"default"|"zh-CN"|"vi") fontset="$2";;
*) echo -e "Wrong argument for $1.";exit 1;;
esac;echo "The fontset is ${fontset}.";shift 2;;
## Ending
--) break ;;
*) echo -e "$0: Wrong parameters.";exit 1;;
esac
done