#!/bin/bash # ============================================================================= # 🔧 StianNOR-ZSHkit — .zshrc Merge Script # Merges managed sections from ZSHkit/.zshrc into the live ~/.zshrc # without overwriting personal aliases or custom configuration. # # How it works: # - Managed sections in ~/.zshrc are wrapped with markers: # # --- ZSHKIT:section_name --- # ... content ... # # --- /ZSHKIT:section_name --- # - On first run, markers are injected into your existing ~/.zshrc # - On subsequent runs, content between markers is updated from ZSHkit # - Everything outside the markers is never touched # # Usage: # ./update_zshrc.sh — merge and apply # ./update_zshrc.sh --dry-run — show what would change, no writes # ./update_zshrc.sh --status — show which sections are present # ============================================================================= set -euo pipefail GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m' RED='\033[0;31m'; BOLD='\033[1m'; RESET='\033[0m' LINE="────────────────────────────────────────────────────────────────────────────" info() { echo -e "${CYAN}ℹ $*${RESET}"; } success() { echo -e "${GREEN}✅ $*${RESET}"; } warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } error() { echo -e "${RED}❌ $*${RESET}"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE="$SCRIPT_DIR/.zshrc" TARGET="$HOME/.zshrc" BACKUP="$HOME/.zshrc.bak.$(date +%Y%m%d-%H%M%S)" DRY_RUN=false STATUS_ONLY=false case "${1:-}" in --dry-run) DRY_RUN=true ;; --status) STATUS_ONLY=true ;; esac if [[ ! -f "$SOURCE" ]]; then error "Source not found: $SOURCE" exit 1 fi if [[ ! -f "$TARGET" ]]; then warn "No ~/.zshrc found — copying source directly" cp "$SOURCE" "$TARGET" success "Created $TARGET from ZSHkit source" exit 0 fi declare -A SECTION_START=( [autosuggestions]="# ─── Autosuggestions" [aliases]="# ─── User Aliases and Functions" ) declare -A SECTION_END=( [autosuggestions]="source \$ZSH/oh-my-zsh.sh" [aliases]="alias rsy=" ) # ============================================================================= # STATUS MODE # ============================================================================= if [[ "$STATUS_ONLY" = true ]]; then echo -e "\n${BOLD}${CYAN}🔧 .zshrc Managed Sections Status${RESET}" echo -e "${LINE}" for section in "${!SECTION_START[@]}"; do marker="# --- ZSHKIT:${section} ---" if grep -qF "$marker" "$TARGET" 2>/dev/null; then success "Section present: $section" else warn "Section missing (will be injected on next run): $section" fi done echo -e "${LINE}\n" exit 0 fi # ============================================================================= # BACKUP # ============================================================================= cp "$TARGET" "$BACKUP" info "Backup saved: $BACKUP" echo -e "\n${BOLD}${CYAN}🔧 Merging .zshrc sections${RESET}" echo -e "${LINE}" CHANGED=0 merge_section() { local name="$1" local start_pat="$2" local end_pat="$3" local begin_marker="# --- ZSHKIT:${name} ---" local end_marker="# --- /ZSHKIT:${name} ---" local new_content new_content=$(awk "/$start_pat/{found=1} found{print} /$end_pat/{if(found) {found=0}}" "$SOURCE") if [[ -z "$new_content" ]]; then warn "Could not extract section '$name' from source — skipping" return fi local block block="$begin_marker $new_content $end_marker" if grep -qF "$begin_marker" "$TARGET"; then local existing existing=$(awk "/$begin_marker/{found=1; next} /$end_marker/{found=0; next} found{print}" "$TARGET") if [[ "$existing" == "$new_content" ]]; then success "Up to date: $name" return fi if [[ "$DRY_RUN" = true ]]; then warn "[DRY RUN] Would update section: $name" diff <(echo "$existing") <(echo "$new_content") || true else python3 - "$TARGET" "$begin_marker" "$end_marker" "$new_content" <<'PYEOF' import sys, re target = sys.argv[1] begin = sys.argv[2] end = sys.argv[3] new_body = sys.argv[4] with open(target, 'r') as f: content = f.read() pattern = re.escape(begin) + r'.*?' + re.escape(end) replacement = begin + '\n' + new_body + '\n' + end new_content = re.sub(pattern, replacement, content, flags=re.DOTALL) with open(target, 'w') as f: f.write(new_content) PYEOF success "Updated section: $name" (( CHANGED++ )) || true fi else if [[ "$DRY_RUN" = true ]]; then warn "[DRY RUN] Would inject new section: $name" else if grep -q "# End of .zshrc" "$TARGET"; then python3 - "$TARGET" "# End of .zshrc" "$block" <<'PYEOF' import sys target = sys.argv[1] marker = sys.argv[2] new_block = sys.argv[3] with open(target, 'r') as f: content = f.read() content = content.replace(marker, new_block + '\n\n' + marker) with open(target, 'w') as f: f.write(content) PYEOF else echo "" >> "$TARGET" echo "$block" >> "$TARGET" fi success "Injected new section: $name" (( CHANGED++ )) || true fi fi } merge_section "autosuggestions" \ "# ─── Autosuggestions" \ "source \\\$ZSH/oh-my-zsh.sh" merge_section "aliases" \ "# ─── User Aliases and Functions" \ "alias rsy=" echo -e "${LINE}" if [[ "$DRY_RUN" = true ]]; then warn "Dry run complete — no files were modified" elif [[ $CHANGED -eq 0 ]]; then success "Everything already up to date — no changes needed" else success "$CHANGED section(s) updated in $TARGET" echo -e "\n ${YELLOW}Run ${CYAN}fresh${RESET}${YELLOW} or open a new terminal to apply changes.${RESET}" fi echo -e "${LINE}\n"