diff --git a/REFACTORING-GUIDE.md b/REFACTORING-GUIDE.md new file mode 100644 index 0000000..8316e4b --- /dev/null +++ b/REFACTORING-GUIDE.md @@ -0,0 +1,258 @@ +# Dotfiles Refactoring Guide + +This document explains the refactoring changes made to eliminate code duplication and improve maintainability. + +## Summary of Changes + +| Change | Impact | Lines Saved | +|--------|--------|-------------| +| Centralized header with `bootstrap.zsh` | All scripts use one source pattern | ~150 lines | +| Template-driven Python projects | `python-templates.zsh` refactored | ~60 lines | +| Unified config/color loading | Single entry point for dependencies | ~80 lines | + +**Total estimated reduction: ~290 lines of duplicated code** + +--- + +## 1. New File: `zsh/lib/bootstrap.zsh` + +### Purpose +Single entry point for all scripts to source. Handles loading config, colors, and utils in the correct order with proper fallbacks. + +### Usage + +**In bash scripts:** +```bash +#!/usr/bin/env bash +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + # Minimal fallback only if bootstrap unavailable + df_print_header() { echo "=== $1 ==="; } +} +``` + +**In zsh functions:** +```zsh +source "${0:A:h}/../lib/bootstrap.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/bootstrap.zsh" 2>/dev/null +``` + +### What It Provides +After sourcing `bootstrap.zsh`, you have access to: +- All `DF_*` color variables +- All `df_print_*` functions +- All `df_*` utility functions +- All config variables from `dotfiles.conf` + +--- + +## 2. Enhanced: `zsh/lib/utils.zsh` + +### New Functions Added + +```zsh +# Build a horizontal line +_df_hline "═" 66 # Returns: ══════════... + +# Print MOTD-style header for scripts +df_print_header "script-name" +# Output: +# ╒══════════════════════════════════════════════════════════════════╕ +# │ ✦ user@hostname script-name Thu Dec 25 14:30 │ +# ╘══════════════════════════════════════════════════════════════════╛ + +# Print simpler header for functions +df_print_func_name "Function Name" +# Output: +# ╒══════════════════════════════════════════════════════════════════╕ +# │ Function Name Thu Dec 25 14:30 │ +# ╘══════════════════════════════════════════════════════════════════╛ + +# Print divider line +df_print_divider +``` + +--- + +## 3. Refactored: `zsh/functions/python-templates.zsh` + +### Before (Duplicated Pattern) +Each function repeated ~15 lines: +```zsh +py-flask() { + _py_check_name "$1" || return 1 + df_print_func_name "Flask Project: $1" + mkdir -p "$1"/{app,tests} + # ... flask-specific setup ... + _py_venv "$1" + _py_gitignore "$1" + _py_git "$1" + df_print_success "Created: $1" + _py_next "$1" +} +``` + +### After (Template-Driven) +```zsh +# Common creation function handles all boilerplate +_py_create_project() { + local name="$1" template="$2" display_name="$3" extra_info="$4" + + _py_check_name "$name" || return 1 + df_print_func_name "${display_name}: ${name}" + mkdir -p "$name" + + # Run template-specific setup + local packages=$("_py_template_${template}" "$name") + + # Common finalization + _py_venv "$name" + [[ -n "$packages" ]] && _py_install "$name" $packages + _py_gitignore "$name" + _py_git "$name" + df_print_success "Created: $name" + _py_next_steps "$name" "$extra_info" +} + +# Each public function is now one line +py-flask() { _py_create_project "$1" "flask" "Flask Project" "Run: python app.py"; } +py-fastapi() { _py_create_project "$1" "fastapi" "FastAPI Project" "Docs: localhost:8000/docs"; } +``` + +### New Features Added +- `py-templates` - List all available templates +- Better pyproject.toml support for CLI projects +- Improved file templates with more comments + +--- + +## 4. Refactored Bin Scripts + +All scripts in `bin/` now follow this pattern: + +```bash +#!/usr/bin/env bash +# ============================================================================ +# Script Name +# ============================================================================ + +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + # Minimal fallback if bootstrap unavailable + DF_GREEN=$'\033[0;32m' DF_NC=$'\033[0m' + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +} + +# Script logic... + +main() { + df_print_header "script-name" + # ... +} + +main "$@" +``` + +### Scripts Updated +- `dotfiles-doctor.sh` +- `dotfiles-sync.sh` +- `dotfiles-update.sh` +- `dotfiles-version.sh` +- `dotfiles-stats.sh` +- `dotfiles-vault.sh` +- `dotfiles-compile.sh` +- `setup/setup-espanso.sh` +- `setup/setup-wizard.sh` + +--- + +## How to Apply These Changes + +### Option 1: Replace Files +Copy the refactored files over your existing ones: + +```bash +# Backup first +cp -r ~/.dotfiles ~/.dotfiles.backup.$(date +%Y%m%d) + +# Copy new lib files +cp /path/to/refactor/zsh/lib/*.zsh ~/.dotfiles/zsh/lib/ + +# Copy new bin scripts +cp /path/to/refactor/bin/*.sh ~/.dotfiles/bin/ + +# Copy new function file +cp /path/to/refactor/zsh/functions/python-templates.zsh ~/.dotfiles/zsh/functions/ + +# Copy new setup scripts +cp /path/to/refactor/setup/*.sh ~/.dotfiles/setup/ +``` + +### Option 2: Gradual Migration +1. First add `bootstrap.zsh` - it won't break anything +2. Update one script at a time to use bootstrap +3. Update `python-templates.zsh` last + +--- + +## Verification + +After applying changes, verify everything works: + +```bash +# Reload shell +source ~/.zshrc + +# Test health check +dfd + +# Test version +dfv + +# Test Python templates +py-templates +py-new test-project +rm -rf test-project + +# Test that headers display correctly +dotfiles-doctor.sh +dotfiles-stats.sh +``` + +--- + +## File Structure After Refactoring + +``` +~/.dotfiles/ +├── zsh/ +│ ├── lib/ +│ │ ├── bootstrap.zsh # NEW: Single entry point +│ │ ├── config.zsh # Loads dotfiles.conf +│ │ ├── colors.zsh # Color definitions +│ │ └── utils.zsh # ENHANCED: Centralized headers +│ └── functions/ +│ └── python-templates.zsh # REFACTORED: Template-driven +├── bin/ +│ ├── dotfiles-doctor.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-sync.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-update.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-version.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-stats.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-vault.sh # REFACTORED: Uses bootstrap +│ └── dotfiles-compile.sh # REFACTORED: Uses bootstrap +└── setup/ + ├── setup-espanso.sh # REFACTORED: Uses bootstrap + └── setup-wizard.sh # REFACTORED: Uses bootstrap +``` + +--- + +## Benefits + +1. **Single Source of Truth**: Header formatting defined once in `utils.zsh` +2. **Easier Maintenance**: Change header style in one place, affects all scripts +3. **Consistent Appearance**: All scripts look the same +4. **Smaller Files**: Each bin script is ~30-50 lines shorter +5. **Better Fallbacks**: `bootstrap.zsh` handles missing files gracefully +6. **Template System**: Adding new Python project types is now trivial diff --git a/bin/dotfiles-compile.sh b/bin/dotfiles-compile.sh index 1912448..945255c 100755 --- a/bin/dotfiles-compile.sh +++ b/bin/dotfiles-compile.sh @@ -5,48 +5,29 @@ set -e -DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" - -# Source shared colors and utils (provides DF_WIDTH) -source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ -source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' - DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } } -# Use DF_WIDTH from utils.zsh or default to 66 -typeset -g WIDTH="${DF_WIDTH:-66}" - # ============================================================================ -# MOTD-style header +# Functions # ============================================================================ -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-compile " - else - local user="${USER:-root}" - local hostname="${HOST:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local hline="" - for ((i=0; i/dev/null && \ - echo -e "${DF_GREEN}✓${DF_NC} Compiled: ${file##*/}" || \ + if zcompile "$file" 2>/dev/null; then + echo -e "${DF_GREEN}✓${DF_NC} Compiled: ${file##*/}" + else echo -e "${DF_YELLOW}⚠${DF_NC} Skipped: ${file##*/}" + fi else echo -e "${DF_CYAN}○${DF_NC} Current: ${file##*/}" fi @@ -55,54 +36,60 @@ compile_file() { clean_compiled() { echo "Removing compiled files..." - + local count=0 - - for zwc in "$DOTFILES_DIR"/**/*.zwc(N); do + + # Remove .zwc files in dotfiles directory + for zwc in "$DOTFILES_HOME"/**/*.zwc(N); do rm -f "$zwc" ((count++)) done - + + # Remove home directory compiled files rm -f ~/.zshrc.zwc ~/.zshenv.zwc ~/.zprofile.zwc 2>/dev/null - + echo -e "${DF_GREEN}✓${DF_NC} Removed $count compiled files" } compile_all() { echo -e "${DF_CYAN}Compiling zsh files for faster startup...${DF_NC}" - echo - + echo "" + echo "Core files:" compile_file ~/.zshrc compile_file ~/.zshenv compile_file ~/.zprofile - echo - + echo "" + echo "Dotfiles:" - compile_file "$DOTFILES_DIR/zsh/.zshrc" - compile_file "$DOTFILES_DIR/zsh/aliases.zsh" - - for file in "$DOTFILES_DIR/zsh/lib"/*.zsh(N); do + compile_file "$DOTFILES_HOME/zsh/.zshrc" + compile_file "$DOTFILES_HOME/zsh/aliases.zsh" + + # Lib files + for file in "$DOTFILES_HOME/zsh/lib"/*.zsh(N); do compile_file "$file" done - - for file in "$DOTFILES_DIR/zsh/functions"/*.zsh(N); do + + # Function files + for file in "$DOTFILES_HOME/zsh/functions"/*.zsh(N); do compile_file "$file" done - - for file in "$DOTFILES_DIR/zsh/themes"/*.zsh-theme(N); do + + # Theme files + for file in "$DOTFILES_HOME/zsh/themes"/*.zsh-theme(N); do compile_file "$file" done - echo - + echo "" + + # Oh-My-Zsh (optional) if [[ -d ~/.oh-my-zsh ]]; then echo "Oh-My-Zsh (optional):" compile_file ~/.oh-my-zsh/oh-my-zsh.sh - echo + echo "" fi - + echo -e "${DF_GREEN}✓${DF_NC} Compilation complete" - echo + echo "" echo "To measure startup time:" echo " time zsh -i -c exit" echo " hyperfine 'zsh -i -c exit' # More accurate" @@ -110,9 +97,9 @@ compile_all() { show_help() { echo "Usage: dotfiles-compile.sh [OPTIONS]" - echo + echo "" echo "Compile zsh files to bytecode for faster shell startup." - echo + echo "" echo "Options:" echo " (none) Compile all zsh files" echo " --clean Remove all compiled (.zwc) files" @@ -123,7 +110,7 @@ show_help() { # Main # ============================================================================ -print_header +df_print_header "dotfiles-compile" case "${1:-}" in --clean|-c) clean_compiled ;; diff --git a/bin/dotfiles-doctor.sh b/bin/dotfiles-doctor.sh index 24e4ca3..60c2c16 100755 --- a/bin/dotfiles-doctor.sh +++ b/bin/dotfiles-doctor.sh @@ -8,39 +8,25 @@ # dotfiles-doctor.sh --quick # Quick essential checks only # ============================================================================ -# ============================================================================ -# Source Configuration -# ============================================================================ -# utils.zsh sources config.zsh which sources dotfiles.conf -# This gives us access to all settings including DF_WIDTH, DOTFILES_VERSION, etc. - -_df_source_config() { - local locations=( - "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" - "$HOME/.dotfiles/zsh/lib/utils.zsh" - ) - for loc in "${locations[@]}"; do - [[ -f "$loc" ]] && { source "$loc"; return 0; } - done - - # Fallback defaults if utils.zsh not found +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + echo "Warning: bootstrap.zsh not found, using fallbacks" DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' - DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" - DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" - DF_WIDTH="${DF_WIDTH:-66}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } } -_df_source_config - # ============================================================================ # Parse Arguments # ============================================================================ DO_FIX=false QUICK_MODE=false + for arg in "$@"; do case "$arg" in --fix) DO_FIX=true ;; @@ -57,7 +43,10 @@ for arg in "$@"; do esac done -# Track results +# ============================================================================ +# Tracking Variables +# ============================================================================ + TOTAL_CHECKS=0 PASSED_CHECKS=0 FAILED_CHECKS=0 @@ -65,39 +54,41 @@ WARNING_CHECKS=0 FIXED_CHECKS=0 # ============================================================================ -# Header (uses DF_WIDTH from config) +# Check Helper Functions # ============================================================================ -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-doctor" - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local width="${DF_WIDTH:-66}" - local hline="" && for ((i=0; i/dev/null; then check_pass "Running CachyOS" @@ -109,60 +100,135 @@ check_os() { else check_fail "Not running on Linux" fi + check_pass "Kernel: $(uname -r)" } check_shell() { print_section "Shell Configuration" + [[ -f "$HOME/.zshrc" ]] && check_pass "Zsh configuration exists" || check_fail "Zsh configuration missing" [[ "$SHELL" == *"zsh"* ]] && check_pass "Zsh is default shell" || check_warn "Zsh is not default shell" - command -v zsh &>/dev/null && check_pass "Zsh version: $(zsh --version | awk '{print $2}')" + + if command -v zsh &>/dev/null; then + check_pass "Zsh version: $(zsh --version | awk '{print $2}')" + fi } check_symlinks() { print_section "Symlinks" - for symlink in ~/.zshrc ~/.gitconfig ~/.vimrc ~/.tmux.conf; do + + local symlinks=( + "$HOME/.zshrc" + "$HOME/.gitconfig" + "$HOME/.vimrc" + "$HOME/.tmux.conf" + ) + + for symlink in "${symlinks[@]}"; do if [[ -L "$symlink" ]]; then - [[ -e "$symlink" ]] && check_pass "$(basename $symlink) → $(readlink $symlink)" || check_fail "$(basename $symlink) is broken" + if [[ -e "$symlink" ]]; then + check_pass "$(basename "$symlink") → $(readlink "$symlink")" + else + check_fail "$(basename "$symlink") is broken symlink" + if [[ "$DO_FIX" == true ]]; then + rm "$symlink" + check_fixed "Removed broken symlink: $(basename "$symlink")" + fi + fi elif [[ -f "$symlink" ]]; then - check_warn "$(basename $symlink) is regular file (not symlink)" + check_warn "$(basename "$symlink") is regular file (not symlink)" fi done } check_pacman() { print_section "Package Manager" - command -v pacman &>/dev/null && check_pass "Pacman available" || { check_fail "Pacman not found"; return; } - command -v paru &>/dev/null && check_pass "AUR helper: paru" || \ - command -v yay &>/dev/null && check_pass "AUR helper: yay" || check_warn "No AUR helper installed" + + if ! command -v pacman &>/dev/null; then + check_fail "Pacman not found" + return + fi + + check_pass "Pacman available" + + if command -v paru &>/dev/null; then + check_pass "AUR helper: paru" + elif command -v yay &>/dev/null; then + check_pass "AUR helper: yay" + else + check_warn "No AUR helper installed (paru or yay recommended)" + fi } check_optional_tools() { print_section "Optional Tools" - command -v fzf &>/dev/null && check_pass "fzf" || check_warn "fzf not installed" - command -v bat &>/dev/null && check_pass "bat" || check_warn "bat not installed" - command -v eza &>/dev/null && check_pass "eza" || check_warn "eza not installed" - command -v tmux &>/dev/null && check_pass "tmux" || check_warn "tmux not installed" + + local tools=("fzf" "bat" "eza" "tmux" "nvim") + for tool in "${tools[@]}"; do + command -v "$tool" &>/dev/null && check_pass "$tool" || check_warn "$tool not installed" + done } check_dotfiles_dir() { print_section "Dotfiles Directory" - [[ -d "$DOTFILES_HOME" ]] && check_pass "Dotfiles: $DOTFILES_HOME" || { check_fail "Dotfiles not found"; return; } + + if [[ ! -d "$DOTFILES_HOME" ]]; then + check_fail "Dotfiles not found" + return + fi + + check_pass "Dotfiles: $DOTFILES_HOME" + [[ -f "$DOTFILES_HOME/dotfiles.conf" ]] && check_pass "Config file exists" || check_warn "Config file missing" [[ -d "$DOTFILES_HOME/.git" ]] && check_pass "Git repo initialized" || check_warn "Not a git repository" - check_pass "Version: $DOTFILES_VERSION" - check_pass "Display width: $DF_WIDTH" + + check_pass "Version: ${DOTFILES_VERSION:-unknown}" + check_pass "Display width: ${DF_WIDTH:-66}" } +check_bin_scripts() { + print_section "Bin Scripts" + + local scripts=( + "dotfiles-doctor.sh" + "dotfiles-sync.sh" + "dotfiles-update.sh" + "dotfiles-version.sh" + ) + + for script in "${scripts[@]}"; do + if [[ -x "$HOME/.local/bin/$script" ]]; then + check_pass "$script" + elif [[ -f "$HOME/.local/bin/$script" ]]; then + check_warn "$script exists but not executable" + if [[ "$DO_FIX" == true ]]; then + chmod +x "$HOME/.local/bin/$script" + check_fixed "Made executable: $script" + fi + else + check_fail "$script not linked" + fi + done +} + +# ============================================================================ +# Summary +# ============================================================================ + print_summary() { local width="${DF_WIDTH:-66}" echo "" - printf "${DF_CYAN}─%.0s${DF_NC}" $(seq 1 $width); echo "" + printf "${DF_CYAN}─%.0s${DF_NC}" $(seq 1 "$width") + echo "" + if [[ $FAILED_CHECKS -eq 0 ]]; then echo -e "${DF_GREEN}✓${DF_NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)" else echo -e "${DF_RED}✗${DF_NC} Issues found: $FAILED_CHECKS failed, $WARNING_CHECKS warnings" fi + + [[ $FIXED_CHECKS -gt 0 ]] && echo -e "${DF_CYAN}⚙${DF_NC} Auto-fixed: $FIXED_CHECKS issues" echo "" } @@ -171,13 +237,19 @@ print_summary() { # ============================================================================ main() { - print_header + df_print_header "dotfiles-doctor" + check_os check_pacman check_shell check_dotfiles_dir check_symlinks - [[ "$QUICK_MODE" != true ]] && check_optional_tools + + if [[ "$QUICK_MODE" != true ]]; then + check_optional_tools + check_bin_scripts + fi + print_summary } diff --git a/bin/dotfiles-stats.sh b/bin/dotfiles-stats.sh index 3156113..ae6b51e 100755 --- a/bin/dotfiles-stats.sh +++ b/bin/dotfiles-stats.sh @@ -5,41 +5,18 @@ set -e -readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" - -# Source shared colors and utils (provides DF_WIDTH) -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ -source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_MAGENTA=$'\033[0;35m' - DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DF_NC=$'\033[0m' + df_print_header() { echo "=== $1 ==="; } } -# Use DF_WIDTH from utils.zsh or default to 66 -readonly WIDTH="${DF_WIDTH:-66}" - # ============================================================================ -# MOTD-style header +# Helper Functions # ============================================================================ -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-stats " - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local hline="" && for ((i=0; i/dev/null | cut -d';' -f2 || cat "$HOME/.zsh_history" elif [[ -f "$HOME/.bash_history" ]]; then cat "$HOME/.bash_history" fi } +# ============================================================================ +# Analytics Functions +# ============================================================================ + show_dashboard() { print_section "Command History Dashboard" + local total=$(get_history | wc -l) - local unique=$(get_history | sort | uniq | wc -l) + local unique=$(get_history | sort -u | wc -l) + echo -e " ${DF_CYAN}Total Commands:${DF_NC} $total" echo -e " ${DF_CYAN}Unique Commands:${DF_NC} $unique" echo "" + print_section "Top 15 Commands" get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -15 | while read count cmd; do - printf " %-20s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count" + printf " %-25s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count" done echo "" } +show_top() { + local count="${1:-20}" + print_section "Top $count Commands" + get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -"$count" | while read cnt cmd; do + printf " %-25s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$cnt" + done +} + +show_git_stats() { + print_section "Git Command Breakdown" + get_history | grep "^git " | awk '{print $2}' | sort | uniq -c | sort -rn | head -10 | while read count subcmd; do + printf " git %-20s ${DF_GREEN}%5d${DF_NC}\n" "$subcmd" "$count" + done +} + +show_dirs() { + print_section "Most Visited Directories" + get_history | grep "^cd " | awk '{print $2}' | sort | uniq -c | sort -rn | head -10 | while read count dir; do + printf " %-30s ${DF_GREEN}%5d${DF_NC}\n" "$dir" "$count" + done +} + +show_help() { + echo "Usage: dotfiles-stats.sh [COMMAND]" + echo "" + echo "Commands:" + echo " dashboard Full analytics dashboard (default)" + echo " top [n] Top N commands (default: 20)" + echo " git Git command breakdown" + echo " dirs Most visited directories" + echo " help Show this help" +} + +# ============================================================================ +# Main +# ============================================================================ + main() { - print_header + df_print_header "dotfiles-stats" + case "${1:-dashboard}" in dashboard) show_dashboard ;; - top) get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -"${2:-20}" ;; - *) echo "Usage: $0 {dashboard|top [n]}"; exit 1 ;; + top) show_top "${2:-20}" ;; + git) show_git_stats ;; + dirs) show_dirs ;; + help|--help|-h) show_help ;; + *) + echo "Unknown command: $1" + show_help + exit 1 + ;; esac } diff --git a/bin/dotfiles-sync.sh b/bin/dotfiles-sync.sh index a0ddefa..8dc0364 100755 --- a/bin/dotfiles-sync.sh +++ b/bin/dotfiles-sync.sh @@ -5,50 +5,15 @@ set -e -# ============================================================================ -# Source Configuration -# ============================================================================ - -_df_source_config() { - local locations=( - "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" - "$HOME/.dotfiles/zsh/lib/utils.zsh" - ) - for loc in "${locations[@]}"; do - [[ -f "$loc" ]] && { source "$loc"; return 0; } - done - - # Fallback defaults +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' - DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_LIGHT_GREEN=$'\033[38;5;82m' DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" - DF_WIDTH="${DF_WIDTH:-66}" -} - -_df_source_config - -# ============================================================================ -# Header -# ============================================================================ - -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-sync" - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local width="${DF_WIDTH:-66}" - local hline="" && for ((i=0; i&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } } # ============================================================================ @@ -56,9 +21,6 @@ print_header() { # ============================================================================ print_status() { echo -e "${DF_CYAN}⎯${DF_NC} $1"; } -print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } -print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } -print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } # ============================================================================ @@ -66,7 +28,10 @@ print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } # ============================================================================ check_git_repo() { - git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1 || { print_error "Not a git repository: $DOTFILES_HOME"; exit 1; } + if ! git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1; then + df_print_error "Not a git repository: $DOTFILES_HOME" + exit 1 + fi } get_sync_status() { @@ -79,6 +44,7 @@ get_sync_status() { show_status() { print_section "Sync Status" cd "$DOTFILES_HOME" + print_status "Local branch: $(git rev-parse --abbrev-ref HEAD)" print_status "Last commit: $(git log -1 --pretty=format:'%h - %s' 2>/dev/null || echo 'N/A')" @@ -87,9 +53,9 @@ show_status() { local remote_commits="${status#*:}" echo "" - [[ $local_commits -gt 0 ]] && print_warning "$local_commits commit(s) ahead of remote" - [[ $remote_commits -gt 0 ]] && print_warning "$remote_commits commit(s) behind remote" - [[ $local_commits -eq 0 && $remote_commits -eq 0 ]] && print_success "In sync with remote" + [[ $local_commits -gt 0 ]] && df_print_warning "$local_commits commit(s) ahead of remote" + [[ $remote_commits -gt 0 ]] && df_print_warning "$remote_commits commit(s) behind remote" + [[ $local_commits -eq 0 && $remote_commits -eq 0 ]] && df_print_success "In sync with remote" } show_status_short() { @@ -111,9 +77,15 @@ show_status_short() { pull_changes() { print_section "Pulling Changes" cd "$DOTFILES_HOME" + print_status "Fetching from remote..." git fetch origin - git pull origin && print_success "Changes pulled" || print_success "Already up to date" + + if git pull origin; then + df_print_success "Changes pulled" + else + df_print_success "Already up to date" + fi } push_changes() { @@ -122,7 +94,7 @@ push_changes() { cd "$DOTFILES_HOME" if ! git status --porcelain | grep -q .; then - print_warning "No local changes to push" + df_print_warning "No local changes to push" return fi @@ -131,12 +103,12 @@ push_changes() { if [[ -z "$commit_msg" ]]; then read -p "Commit message: " commit_msg - [[ -z "$commit_msg" ]] && { print_error "Commit cancelled"; return 1; } + [[ -z "$commit_msg" ]] && { df_print_error "Commit cancelled"; return 1; } fi git commit -m "$commit_msg" git push origin - print_success "Changes pushed" + df_print_success "Changes pushed" } # ============================================================================ @@ -148,19 +120,40 @@ main() { case "${1:-status}" in status) - [[ "$2" == "-s" || "$2" == "--short" ]] && show_status_short || { print_header; show_status; } + if [[ "$2" == "-s" || "$2" == "--short" ]]; then + show_status_short + else + df_print_header "dotfiles-sync" + show_status + fi ;; push) - print_header; shift; push_changes "$*" + df_print_header "dotfiles-sync" + shift + push_changes "$*" ;; pull) - print_header; pull_changes + df_print_header "dotfiles-sync" + pull_changes ;; -s|--short) show_status_short ;; + --help|-h) + echo "Usage: dotfiles-sync.sh [COMMAND]" + echo "" + echo "Commands:" + echo " status [-s] Show sync status (default)" + echo " push [message] Push changes to remote" + echo " pull Pull changes from remote" + echo "" + echo "Options:" + echo " -s, --short Short status output" + echo " --help Show this help" + ;; *) - echo "Usage: $0 {status [-s]|push [message]|pull}" + echo "Unknown command: $1" + echo "Use --help for usage information" exit 1 ;; esac diff --git a/bin/dotfiles-update.sh b/bin/dotfiles-update.sh index 4959d26..621e9da 100755 --- a/bin/dotfiles-update.sh +++ b/bin/dotfiles-update.sh @@ -5,6 +5,10 @@ set -e +# ============================================================================ +# Parse Arguments First (before sourcing, in case we need --help) +# ============================================================================ + SKIP_DEPS=true PULL_ONLY=false @@ -26,86 +30,50 @@ for arg in "$@"; do esac done -# Load Configuration -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" -[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf" +# ============================================================================ +# Source Bootstrap +# ============================================================================ -if [[ -f "$DOTFILES_CONF" ]]; then - source "$DOTFILES_CONF" -else - DOTFILES_DIR="$HOME/.dotfiles" - DOTFILES_BRANCH="main" - DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" -fi - -# Source shared colors and utils (provides DF_WIDTH) -source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ -source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' - DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' - DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } + df_print_step() { echo -e "${DF_GREEN}==>${DF_NC} $1"; } } -# Use DF_WIDTH from utils.zsh or default to 66 -readonly WIDTH="${DF_WIDTH:-66}" - -# ============================================================================ -# MOTD-style header -# ============================================================================ - -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-update" - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local hline="" && for ((i=0; i${DF_NC} $1"; } - # ============================================================================ # Main # ============================================================================ -print_header +df_print_header "dotfiles-update" -if [ ! -d "$DOTFILES_DIR" ]; then - print_error "Dotfiles directory not found: $DOTFILES_DIR" +if [[ ! -d "$DOTFILES_HOME" ]]; then + df_print_error "Dotfiles directory not found: $DOTFILES_HOME" exit 1 fi -cd "$DOTFILES_DIR" +cd "$DOTFILES_HOME" -print_step "Updating dotfiles from repository..." -git pull origin "$DOTFILES_BRANCH" - -if [ $? -eq 0 ]; then - print_success "Dotfiles updated successfully" +df_print_step "Updating dotfiles from repository..." +if git pull origin "${DOTFILES_BRANCH:-main}"; then + df_print_success "Dotfiles updated successfully" + if [[ "$PULL_ONLY" == true ]]; then - echo - print_success "Pull complete (--pull-only mode)" + echo "" + df_print_success "Pull complete (--pull-only mode)" exit 0 fi - - echo - print_success "Update complete!" + + echo "" + df_print_success "Update complete!" echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}" else - print_error "Failed to update dotfiles" + df_print_error "Failed to update dotfiles" exit 1 fi diff --git a/bin/dotfiles-vault.sh b/bin/dotfiles-vault.sh index 733bc24..3c6f1f8 100755 --- a/bin/dotfiles-vault.sh +++ b/bin/dotfiles-vault.sh @@ -5,85 +5,133 @@ set -e -readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" -readonly VAULT_DIR="${HOME}/.dotfiles/vault" -readonly VAULT_FILE="${VAULT_DIR}/secrets.enc" - -# Source shared colors and utils (provides DF_WIDTH) -source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ -source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' - DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } } -# Use DF_WIDTH from utils.zsh or default to 66 -readonly WIDTH="${DF_WIDTH:-66}" - # ============================================================================ -# MOTD-style header +# Configuration # ============================================================================ -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-vault " - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local hline="" && for ((i=0; i&2; } print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } get_cipher() { - command -v age &> /dev/null && echo "age" || \ - command -v gpg &> /dev/null && echo "gpg" || \ - { print_error "No encryption tool available"; exit 1; } + if command -v age &>/dev/null; then + echo "age" + elif command -v gpg &>/dev/null; then + echo "gpg" + else + df_print_error "No encryption tool available (install 'age' or 'gpg')" + exit 1 + fi } +# ============================================================================ +# Vault Functions +# ============================================================================ + init_vault() { print_section "Initializing Vault" + mkdir -p "$VAULT_DIR" chmod 700 "$VAULT_DIR" - [[ ! -f "$VAULT_FILE" ]] && { echo "{}" > "$VAULT_FILE"; print_success "Vault initialized"; } || print_success "Vault exists" + + if [[ ! -f "$VAULT_FILE" ]]; then + echo "{}" > "$VAULT_FILE" + df_print_success "Vault initialized at $VAULT_DIR" + else + df_print_success "Vault already exists" + fi } vault_list() { - print_section "Secrets" - [[ -f "$VAULT_FILE" ]] && cat "$VAULT_FILE" | grep -o '"[^"]*":' | sed 's/"//g;s/:$//' | while read key; do - echo -e " ${DF_CYAN}•${DF_NC} $key" - done || print_error "No vault file" + print_section "Stored Secrets" + + if [[ ! -f "$VAULT_FILE" ]]; then + df_print_error "No vault file found. Run: vault init" + return 1 + fi + + local keys=$(cat "$VAULT_FILE" | grep -o '"[^"]*":' | sed 's/"//g;s/:$//') + + if [[ -z "$keys" ]]; then + echo " (no secrets stored)" + else + echo "$keys" | while read key; do + echo -e " ${DF_CYAN}•${DF_NC} $key" + done + fi echo "" } vault_status() { print_section "Vault Status" - [[ -d "$VAULT_DIR" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized"; return; } - [[ -f "$VAULT_FILE" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found"; return; } + + if [[ ! -d "$VAULT_DIR" ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized" + echo " Run: vault init" + return + fi + + if [[ ! -f "$VAULT_FILE" ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found" + return + fi + + local cipher=$(get_cipher) + local key_count=$(cat "$VAULT_FILE" | grep -o '"[^"]*":' | wc -l) + echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE" - echo -e " ${DF_CYAN}Encryption:${DF_NC} $(get_cipher)" + echo -e " ${DF_CYAN}Encryption:${DF_NC} $cipher" + echo -e " ${DF_CYAN}Secrets:${DF_NC} $key_count" echo "" } +show_help() { + echo "Usage: dotfiles-vault.sh [COMMAND]" + echo "" + echo "Commands:" + echo " init Initialize the vault" + echo " list, ls List all secret keys" + echo " status Show vault status" + echo " help Show this help" + echo "" + echo "The vault uses 'age' or 'gpg' for encryption." +} + +# ============================================================================ +# Main +# ============================================================================ + main() { - print_header + df_print_header "dotfiles-vault" + + # Auto-init if vault doesn't exist [[ ! -d "$VAULT_DIR" ]] && init_vault + case "${1:-list}" in init) init_vault ;; list|ls) vault_list ;; status) vault_status ;; - *) echo "Usage: $0 {init|list|status}"; exit 1 ;; + help|--help|-h) show_help ;; + *) + echo "Unknown command: $1" + show_help + exit 1 + ;; esac } diff --git a/bin/dotfiles-version.sh b/bin/dotfiles-version.sh index c73d529..0e88403 100755 --- a/bin/dotfiles-version.sh +++ b/bin/dotfiles-version.sh @@ -4,74 +4,58 @@ # ============================================================================ # ============================================================================ -# Source Configuration -# ============================================================================ - -_df_source_config() { - local locations=( - "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" - "$HOME/.dotfiles/zsh/lib/utils.zsh" - ) - for loc in "${locations[@]}"; do - [[ -f "$loc" ]] && { source "$loc"; return 0; } - done - - # Fallback defaults - DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' - DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' - DF_BOLD=$'\033[1m' DF_LIGHT_GREEN=$'\033[38;5;82m' - DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" - DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" - DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" - DF_WIDTH="${DF_WIDTH:-66}" -} - -_df_source_config - -# ============================================================================ -# Parse Arguments +# Parse Arguments First # ============================================================================ CHECK_ONLY=false + for arg in "$@"; do case "$arg" in --check|-c) CHECK_ONLY=true ;; - --help|-h) echo "Usage: dotfiles-version.sh [--check]"; exit 0 ;; + --help|-h) + echo "Usage: dotfiles-version.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --check, -c Output version only (for scripts)" + echo " --help Show this help" + exit 0 + ;; esac done # ============================================================================ -# Header +# Source Bootstrap # ============================================================================ -print_header() { - if declare -f df_print_header &>/dev/null; then - df_print_header "dotfiles-version " - else - local user="${USER:-root}" - local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" - local datetime=$(date '+%a %b %d %H:%M') - local width="${DF_WIDTH:-66}" - local hline="" && for ((i=0; i/dev/null || { + DF_GREEN=$'\033[0;32m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + DF_WIDTH="${DF_WIDTH:-66}" + df_print_header() { echo "=== $1 ==="; } } # ============================================================================ -# Version Info +# Version Info Functions # ============================================================================ get_local_commit() { - [[ -d "${DOTFILES_DIR}/.git" ]] && { cd "$DOTFILES_DIR"; git rev-parse --short HEAD 2>/dev/null || echo "unknown"; } || echo "not a git repo" + if [[ -d "${DOTFILES_HOME}/.git" ]]; then + cd "$DOTFILES_HOME" + git rev-parse --short HEAD 2>/dev/null || echo "unknown" + else + echo "not a git repo" + fi } get_local_date() { - [[ -d "${DOTFILES_DIR}/.git" ]] && { cd "$DOTFILES_DIR"; git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown"; } || echo "unknown" + if [[ -d "${DOTFILES_HOME}/.git" ]]; then + cd "$DOTFILES_HOME" + git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown" + else + echo "unknown" + fi } # ============================================================================ @@ -82,21 +66,23 @@ main() { local local_commit=$(get_local_commit) local local_date=$(get_local_date) + # Short output for scripts if [[ "$CHECK_ONLY" == true ]]; then - echo "Version: $DOTFILES_VERSION ($local_commit)" + echo "Version: ${DOTFILES_VERSION} (${local_commit})" exit 0 fi - print_header + # Full output + df_print_header "dotfiles-version" echo -e "${DF_CYAN}Local:${DF_NC}" echo -e " Version: ${DF_GREEN}${DOTFILES_VERSION}${DF_NC}" echo -e " Commit: ${local_commit}" echo -e " Date: ${local_date}" - echo -e " Path: ${DOTFILES_DIR}" + echo -e " Path: ${DOTFILES_HOME}" echo -e " Branch: ${DOTFILES_BRANCH}" echo -e " Width: ${DF_WIDTH}" - echo + echo "" } main "$@" diff --git a/dotfiles-refactor.tar.gz b/dotfiles-refactor.tar.gz new file mode 100644 index 0000000..5e5d40a Binary files /dev/null and b/dotfiles-refactor.tar.gz differ diff --git a/dotfiles-refactor/REFACTORING-GUIDE.md b/dotfiles-refactor/REFACTORING-GUIDE.md new file mode 100644 index 0000000..8316e4b --- /dev/null +++ b/dotfiles-refactor/REFACTORING-GUIDE.md @@ -0,0 +1,258 @@ +# Dotfiles Refactoring Guide + +This document explains the refactoring changes made to eliminate code duplication and improve maintainability. + +## Summary of Changes + +| Change | Impact | Lines Saved | +|--------|--------|-------------| +| Centralized header with `bootstrap.zsh` | All scripts use one source pattern | ~150 lines | +| Template-driven Python projects | `python-templates.zsh` refactored | ~60 lines | +| Unified config/color loading | Single entry point for dependencies | ~80 lines | + +**Total estimated reduction: ~290 lines of duplicated code** + +--- + +## 1. New File: `zsh/lib/bootstrap.zsh` + +### Purpose +Single entry point for all scripts to source. Handles loading config, colors, and utils in the correct order with proper fallbacks. + +### Usage + +**In bash scripts:** +```bash +#!/usr/bin/env bash +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + # Minimal fallback only if bootstrap unavailable + df_print_header() { echo "=== $1 ==="; } +} +``` + +**In zsh functions:** +```zsh +source "${0:A:h}/../lib/bootstrap.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/bootstrap.zsh" 2>/dev/null +``` + +### What It Provides +After sourcing `bootstrap.zsh`, you have access to: +- All `DF_*` color variables +- All `df_print_*` functions +- All `df_*` utility functions +- All config variables from `dotfiles.conf` + +--- + +## 2. Enhanced: `zsh/lib/utils.zsh` + +### New Functions Added + +```zsh +# Build a horizontal line +_df_hline "═" 66 # Returns: ══════════... + +# Print MOTD-style header for scripts +df_print_header "script-name" +# Output: +# ╒══════════════════════════════════════════════════════════════════╕ +# │ ✦ user@hostname script-name Thu Dec 25 14:30 │ +# ╘══════════════════════════════════════════════════════════════════╛ + +# Print simpler header for functions +df_print_func_name "Function Name" +# Output: +# ╒══════════════════════════════════════════════════════════════════╕ +# │ Function Name Thu Dec 25 14:30 │ +# ╘══════════════════════════════════════════════════════════════════╛ + +# Print divider line +df_print_divider +``` + +--- + +## 3. Refactored: `zsh/functions/python-templates.zsh` + +### Before (Duplicated Pattern) +Each function repeated ~15 lines: +```zsh +py-flask() { + _py_check_name "$1" || return 1 + df_print_func_name "Flask Project: $1" + mkdir -p "$1"/{app,tests} + # ... flask-specific setup ... + _py_venv "$1" + _py_gitignore "$1" + _py_git "$1" + df_print_success "Created: $1" + _py_next "$1" +} +``` + +### After (Template-Driven) +```zsh +# Common creation function handles all boilerplate +_py_create_project() { + local name="$1" template="$2" display_name="$3" extra_info="$4" + + _py_check_name "$name" || return 1 + df_print_func_name "${display_name}: ${name}" + mkdir -p "$name" + + # Run template-specific setup + local packages=$("_py_template_${template}" "$name") + + # Common finalization + _py_venv "$name" + [[ -n "$packages" ]] && _py_install "$name" $packages + _py_gitignore "$name" + _py_git "$name" + df_print_success "Created: $name" + _py_next_steps "$name" "$extra_info" +} + +# Each public function is now one line +py-flask() { _py_create_project "$1" "flask" "Flask Project" "Run: python app.py"; } +py-fastapi() { _py_create_project "$1" "fastapi" "FastAPI Project" "Docs: localhost:8000/docs"; } +``` + +### New Features Added +- `py-templates` - List all available templates +- Better pyproject.toml support for CLI projects +- Improved file templates with more comments + +--- + +## 4. Refactored Bin Scripts + +All scripts in `bin/` now follow this pattern: + +```bash +#!/usr/bin/env bash +# ============================================================================ +# Script Name +# ============================================================================ + +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + # Minimal fallback if bootstrap unavailable + DF_GREEN=$'\033[0;32m' DF_NC=$'\033[0m' + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +} + +# Script logic... + +main() { + df_print_header "script-name" + # ... +} + +main "$@" +``` + +### Scripts Updated +- `dotfiles-doctor.sh` +- `dotfiles-sync.sh` +- `dotfiles-update.sh` +- `dotfiles-version.sh` +- `dotfiles-stats.sh` +- `dotfiles-vault.sh` +- `dotfiles-compile.sh` +- `setup/setup-espanso.sh` +- `setup/setup-wizard.sh` + +--- + +## How to Apply These Changes + +### Option 1: Replace Files +Copy the refactored files over your existing ones: + +```bash +# Backup first +cp -r ~/.dotfiles ~/.dotfiles.backup.$(date +%Y%m%d) + +# Copy new lib files +cp /path/to/refactor/zsh/lib/*.zsh ~/.dotfiles/zsh/lib/ + +# Copy new bin scripts +cp /path/to/refactor/bin/*.sh ~/.dotfiles/bin/ + +# Copy new function file +cp /path/to/refactor/zsh/functions/python-templates.zsh ~/.dotfiles/zsh/functions/ + +# Copy new setup scripts +cp /path/to/refactor/setup/*.sh ~/.dotfiles/setup/ +``` + +### Option 2: Gradual Migration +1. First add `bootstrap.zsh` - it won't break anything +2. Update one script at a time to use bootstrap +3. Update `python-templates.zsh` last + +--- + +## Verification + +After applying changes, verify everything works: + +```bash +# Reload shell +source ~/.zshrc + +# Test health check +dfd + +# Test version +dfv + +# Test Python templates +py-templates +py-new test-project +rm -rf test-project + +# Test that headers display correctly +dotfiles-doctor.sh +dotfiles-stats.sh +``` + +--- + +## File Structure After Refactoring + +``` +~/.dotfiles/ +├── zsh/ +│ ├── lib/ +│ │ ├── bootstrap.zsh # NEW: Single entry point +│ │ ├── config.zsh # Loads dotfiles.conf +│ │ ├── colors.zsh # Color definitions +│ │ └── utils.zsh # ENHANCED: Centralized headers +│ └── functions/ +│ └── python-templates.zsh # REFACTORED: Template-driven +├── bin/ +│ ├── dotfiles-doctor.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-sync.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-update.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-version.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-stats.sh # REFACTORED: Uses bootstrap +│ ├── dotfiles-vault.sh # REFACTORED: Uses bootstrap +│ └── dotfiles-compile.sh # REFACTORED: Uses bootstrap +└── setup/ + ├── setup-espanso.sh # REFACTORED: Uses bootstrap + └── setup-wizard.sh # REFACTORED: Uses bootstrap +``` + +--- + +## Benefits + +1. **Single Source of Truth**: Header formatting defined once in `utils.zsh` +2. **Easier Maintenance**: Change header style in one place, affects all scripts +3. **Consistent Appearance**: All scripts look the same +4. **Smaller Files**: Each bin script is ~30-50 lines shorter +5. **Better Fallbacks**: `bootstrap.zsh` handles missing files gracefully +6. **Template System**: Adding new Python project types is now trivial diff --git a/dotfiles-refactor/bin/dotfiles-compile.sh b/dotfiles-refactor/bin/dotfiles-compile.sh new file mode 100644 index 0000000..945255c --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-compile.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env zsh +# ============================================================================ +# Dotfiles Compile - Pre-compile zsh files for faster loading +# ============================================================================ + +set -e + +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +} + +# ============================================================================ +# Functions +# ============================================================================ + +compile_file() { + local file="$1" + + if [[ -f "$file" ]]; then + if [[ ! -f "${file}.zwc" ]] || [[ "$file" -nt "${file}.zwc" ]]; then + if zcompile "$file" 2>/dev/null; then + echo -e "${DF_GREEN}✓${DF_NC} Compiled: ${file##*/}" + else + echo -e "${DF_YELLOW}⚠${DF_NC} Skipped: ${file##*/}" + fi + else + echo -e "${DF_CYAN}○${DF_NC} Current: ${file##*/}" + fi + fi +} + +clean_compiled() { + echo "Removing compiled files..." + + local count=0 + + # Remove .zwc files in dotfiles directory + for zwc in "$DOTFILES_HOME"/**/*.zwc(N); do + rm -f "$zwc" + ((count++)) + done + + # Remove home directory compiled files + rm -f ~/.zshrc.zwc ~/.zshenv.zwc ~/.zprofile.zwc 2>/dev/null + + echo -e "${DF_GREEN}✓${DF_NC} Removed $count compiled files" +} + +compile_all() { + echo -e "${DF_CYAN}Compiling zsh files for faster startup...${DF_NC}" + echo "" + + echo "Core files:" + compile_file ~/.zshrc + compile_file ~/.zshenv + compile_file ~/.zprofile + echo "" + + echo "Dotfiles:" + compile_file "$DOTFILES_HOME/zsh/.zshrc" + compile_file "$DOTFILES_HOME/zsh/aliases.zsh" + + # Lib files + for file in "$DOTFILES_HOME/zsh/lib"/*.zsh(N); do + compile_file "$file" + done + + # Function files + for file in "$DOTFILES_HOME/zsh/functions"/*.zsh(N); do + compile_file "$file" + done + + # Theme files + for file in "$DOTFILES_HOME/zsh/themes"/*.zsh-theme(N); do + compile_file "$file" + done + echo "" + + # Oh-My-Zsh (optional) + if [[ -d ~/.oh-my-zsh ]]; then + echo "Oh-My-Zsh (optional):" + compile_file ~/.oh-my-zsh/oh-my-zsh.sh + echo "" + fi + + echo -e "${DF_GREEN}✓${DF_NC} Compilation complete" + echo "" + echo "To measure startup time:" + echo " time zsh -i -c exit" + echo " hyperfine 'zsh -i -c exit' # More accurate" +} + +show_help() { + echo "Usage: dotfiles-compile.sh [OPTIONS]" + echo "" + echo "Compile zsh files to bytecode for faster shell startup." + echo "" + echo "Options:" + echo " (none) Compile all zsh files" + echo " --clean Remove all compiled (.zwc) files" + echo " --help Show this help" +} + +# ============================================================================ +# Main +# ============================================================================ + +df_print_header "dotfiles-compile" + +case "${1:-}" in + --clean|-c) clean_compiled ;; + --help|-h) show_help ;; + *) compile_all ;; +esac diff --git a/dotfiles-refactor/bin/dotfiles-doctor.sh b/dotfiles-refactor/bin/dotfiles-doctor.sh new file mode 100644 index 0000000..60c2c16 --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-doctor.sh @@ -0,0 +1,256 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Health Check (Arch/CachyOS) +# ============================================================================ +# Usage: +# dotfiles-doctor.sh # Run all checks +# dotfiles-doctor.sh --fix # Attempt automatic fixes +# dotfiles-doctor.sh --quick # Quick essential checks only +# ============================================================================ + +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + echo "Warning: bootstrap.zsh not found, using fallbacks" + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +} + +# ============================================================================ +# Parse Arguments +# ============================================================================ + +DO_FIX=false +QUICK_MODE=false + +for arg in "$@"; do + case "$arg" in + --fix) DO_FIX=true ;; + --quick) QUICK_MODE=true ;; + --help|-h) + echo "Usage: dotfiles-doctor.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --fix Attempt automatic fixes for issues" + echo " --quick Run quick essential checks only" + echo " --help Show this help" + exit 0 + ;; + esac +done + +# ============================================================================ +# Tracking Variables +# ============================================================================ + +TOTAL_CHECKS=0 +PASSED_CHECKS=0 +FAILED_CHECKS=0 +WARNING_CHECKS=0 +FIXED_CHECKS=0 + +# ============================================================================ +# Check Helper Functions +# ============================================================================ + +print_section() { echo -e "\n${DF_BLUE}▶${DF_NC} $1"; } + +check_pass() { + ((PASSED_CHECKS++)) + ((TOTAL_CHECKS++)) + echo -e " ${DF_GREEN}✓${DF_NC} $1" +} + +check_fail() { + ((FAILED_CHECKS++)) + ((TOTAL_CHECKS++)) + echo -e " ${DF_RED}✗${DF_NC} $1" +} + +check_warn() { + ((WARNING_CHECKS++)) + ((TOTAL_CHECKS++)) + echo -e " ${DF_YELLOW}⚠${DF_NC} $1" +} + +check_fixed() { + ((FIXED_CHECKS++)) + echo -e " ${DF_CYAN}⚙${DF_NC} Fixed: $1" +} + +# ============================================================================ +# Check Functions +# ============================================================================ + +check_os() { + print_section "Operating System" + + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + if grep -qi "cachyos" /etc/os-release 2>/dev/null; then + check_pass "Running CachyOS" + elif grep -qi "arch" /etc/os-release 2>/dev/null; then + check_pass "Running Arch Linux" + else + check_fail "Not running on Arch/CachyOS" + fi + else + check_fail "Not running on Linux" + fi + + check_pass "Kernel: $(uname -r)" +} + +check_shell() { + print_section "Shell Configuration" + + [[ -f "$HOME/.zshrc" ]] && check_pass "Zsh configuration exists" || check_fail "Zsh configuration missing" + [[ "$SHELL" == *"zsh"* ]] && check_pass "Zsh is default shell" || check_warn "Zsh is not default shell" + + if command -v zsh &>/dev/null; then + check_pass "Zsh version: $(zsh --version | awk '{print $2}')" + fi +} + +check_symlinks() { + print_section "Symlinks" + + local symlinks=( + "$HOME/.zshrc" + "$HOME/.gitconfig" + "$HOME/.vimrc" + "$HOME/.tmux.conf" + ) + + for symlink in "${symlinks[@]}"; do + if [[ -L "$symlink" ]]; then + if [[ -e "$symlink" ]]; then + check_pass "$(basename "$symlink") → $(readlink "$symlink")" + else + check_fail "$(basename "$symlink") is broken symlink" + if [[ "$DO_FIX" == true ]]; then + rm "$symlink" + check_fixed "Removed broken symlink: $(basename "$symlink")" + fi + fi + elif [[ -f "$symlink" ]]; then + check_warn "$(basename "$symlink") is regular file (not symlink)" + fi + done +} + +check_pacman() { + print_section "Package Manager" + + if ! command -v pacman &>/dev/null; then + check_fail "Pacman not found" + return + fi + + check_pass "Pacman available" + + if command -v paru &>/dev/null; then + check_pass "AUR helper: paru" + elif command -v yay &>/dev/null; then + check_pass "AUR helper: yay" + else + check_warn "No AUR helper installed (paru or yay recommended)" + fi +} + +check_optional_tools() { + print_section "Optional Tools" + + local tools=("fzf" "bat" "eza" "tmux" "nvim") + for tool in "${tools[@]}"; do + command -v "$tool" &>/dev/null && check_pass "$tool" || check_warn "$tool not installed" + done +} + +check_dotfiles_dir() { + print_section "Dotfiles Directory" + + if [[ ! -d "$DOTFILES_HOME" ]]; then + check_fail "Dotfiles not found" + return + fi + + check_pass "Dotfiles: $DOTFILES_HOME" + + [[ -f "$DOTFILES_HOME/dotfiles.conf" ]] && check_pass "Config file exists" || check_warn "Config file missing" + [[ -d "$DOTFILES_HOME/.git" ]] && check_pass "Git repo initialized" || check_warn "Not a git repository" + + check_pass "Version: ${DOTFILES_VERSION:-unknown}" + check_pass "Display width: ${DF_WIDTH:-66}" +} + +check_bin_scripts() { + print_section "Bin Scripts" + + local scripts=( + "dotfiles-doctor.sh" + "dotfiles-sync.sh" + "dotfiles-update.sh" + "dotfiles-version.sh" + ) + + for script in "${scripts[@]}"; do + if [[ -x "$HOME/.local/bin/$script" ]]; then + check_pass "$script" + elif [[ -f "$HOME/.local/bin/$script" ]]; then + check_warn "$script exists but not executable" + if [[ "$DO_FIX" == true ]]; then + chmod +x "$HOME/.local/bin/$script" + check_fixed "Made executable: $script" + fi + else + check_fail "$script not linked" + fi + done +} + +# ============================================================================ +# Summary +# ============================================================================ + +print_summary() { + local width="${DF_WIDTH:-66}" + echo "" + printf "${DF_CYAN}─%.0s${DF_NC}" $(seq 1 "$width") + echo "" + + if [[ $FAILED_CHECKS -eq 0 ]]; then + echo -e "${DF_GREEN}✓${DF_NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)" + else + echo -e "${DF_RED}✗${DF_NC} Issues found: $FAILED_CHECKS failed, $WARNING_CHECKS warnings" + fi + + [[ $FIXED_CHECKS -gt 0 ]] && echo -e "${DF_CYAN}⚙${DF_NC} Auto-fixed: $FIXED_CHECKS issues" + echo "" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + df_print_header "dotfiles-doctor" + + check_os + check_pacman + check_shell + check_dotfiles_dir + check_symlinks + + if [[ "$QUICK_MODE" != true ]]; then + check_optional_tools + check_bin_scripts + fi + + print_summary +} + +main "$@" diff --git a/dotfiles-refactor/bin/dotfiles-stats.sh b/dotfiles-refactor/bin/dotfiles-stats.sh new file mode 100644 index 0000000..ae6b51e --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-stats.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Shell Analytics (Arch/CachyOS) +# ============================================================================ + +set -e + +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_MAGENTA=$'\033[0;35m' + DF_NC=$'\033[0m' + df_print_header() { echo "=== $1 ==="; } +} + +# ============================================================================ +# Helper Functions +# ============================================================================ + +print_section() { + echo "" + echo -e "${DF_BLUE}▶${DF_NC} $1" + echo -e "${DF_CYAN}─────────────────────────────────────────────────────────────${DF_NC}" +} + +get_history() { + if [[ -f "$HOME/.zsh_history" ]]; then + # Handle zsh extended history format + grep -I "^:" "$HOME/.zsh_history" 2>/dev/null | cut -d';' -f2 || cat "$HOME/.zsh_history" + elif [[ -f "$HOME/.bash_history" ]]; then + cat "$HOME/.bash_history" + fi +} + +# ============================================================================ +# Analytics Functions +# ============================================================================ + +show_dashboard() { + print_section "Command History Dashboard" + + local total=$(get_history | wc -l) + local unique=$(get_history | sort -u | wc -l) + + echo -e " ${DF_CYAN}Total Commands:${DF_NC} $total" + echo -e " ${DF_CYAN}Unique Commands:${DF_NC} $unique" + echo "" + + print_section "Top 15 Commands" + get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -15 | while read count cmd; do + printf " %-25s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$count" + done + echo "" +} + +show_top() { + local count="${1:-20}" + print_section "Top $count Commands" + get_history | awk '{print $1}' | sort | uniq -c | sort -rn | head -"$count" | while read cnt cmd; do + printf " %-25s ${DF_GREEN}%5d${DF_NC}\n" "$cmd" "$cnt" + done +} + +show_git_stats() { + print_section "Git Command Breakdown" + get_history | grep "^git " | awk '{print $2}' | sort | uniq -c | sort -rn | head -10 | while read count subcmd; do + printf " git %-20s ${DF_GREEN}%5d${DF_NC}\n" "$subcmd" "$count" + done +} + +show_dirs() { + print_section "Most Visited Directories" + get_history | grep "^cd " | awk '{print $2}' | sort | uniq -c | sort -rn | head -10 | while read count dir; do + printf " %-30s ${DF_GREEN}%5d${DF_NC}\n" "$dir" "$count" + done +} + +show_help() { + echo "Usage: dotfiles-stats.sh [COMMAND]" + echo "" + echo "Commands:" + echo " dashboard Full analytics dashboard (default)" + echo " top [n] Top N commands (default: 20)" + echo " git Git command breakdown" + echo " dirs Most visited directories" + echo " help Show this help" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + df_print_header "dotfiles-stats" + + case "${1:-dashboard}" in + dashboard) show_dashboard ;; + top) show_top "${2:-20}" ;; + git) show_git_stats ;; + dirs) show_dirs ;; + help|--help|-h) show_help ;; + *) + echo "Unknown command: $1" + show_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/dotfiles-refactor/bin/dotfiles-sync.sh b/dotfiles-refactor/bin/dotfiles-sync.sh new file mode 100644 index 0000000..8dc0364 --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-sync.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Synchronization (Arch/CachyOS) +# ============================================================================ + +set -e + +# Source bootstrap (provides colors, config, and utility functions) +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +} + +# ============================================================================ +# Helper Functions +# ============================================================================ + +print_status() { echo -e "${DF_CYAN}⎯${DF_NC} $1"; } +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } + +# ============================================================================ +# Sync Functions +# ============================================================================ + +check_git_repo() { + if ! git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1; then + df_print_error "Not a git repository: $DOTFILES_HOME" + exit 1 + fi +} + +get_sync_status() { + cd "$DOTFILES_HOME" + local local_commits=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0) + local remote_commits=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo 0) + echo "$local_commits:$remote_commits" +} + +show_status() { + print_section "Sync Status" + cd "$DOTFILES_HOME" + + print_status "Local branch: $(git rev-parse --abbrev-ref HEAD)" + print_status "Last commit: $(git log -1 --pretty=format:'%h - %s' 2>/dev/null || echo 'N/A')" + + local status=$(get_sync_status) + local local_commits="${status%:*}" + local remote_commits="${status#*:}" + + echo "" + [[ $local_commits -gt 0 ]] && df_print_warning "$local_commits commit(s) ahead of remote" + [[ $remote_commits -gt 0 ]] && df_print_warning "$remote_commits commit(s) behind remote" + [[ $local_commits -eq 0 && $remote_commits -eq 0 ]] && df_print_success "In sync with remote" +} + +show_status_short() { + cd "$DOTFILES_HOME" + local changes=$(git status --porcelain | wc -l) + local status=$(get_sync_status) + local local_commits="${status%:*}" + local remote_commits="${status#*:}" + + if [[ $changes -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${changes} local change(s) not pushed" + elif [[ $local_commits -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${local_commits} commit(s) not pushed" + elif [[ $remote_commits -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${remote_commits} commit(s) behind remote" + fi +} + +pull_changes() { + print_section "Pulling Changes" + cd "$DOTFILES_HOME" + + print_status "Fetching from remote..." + git fetch origin + + if git pull origin; then + df_print_success "Changes pulled" + else + df_print_success "Already up to date" + fi +} + +push_changes() { + local commit_msg="$1" + print_section "Pushing Changes" + cd "$DOTFILES_HOME" + + if ! git status --porcelain | grep -q .; then + df_print_warning "No local changes to push" + return + fi + + print_status "Staging changes..." + git add -A + + if [[ -z "$commit_msg" ]]; then + read -p "Commit message: " commit_msg + [[ -z "$commit_msg" ]] && { df_print_error "Commit cancelled"; return 1; } + fi + + git commit -m "$commit_msg" + git push origin + df_print_success "Changes pushed" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + check_git_repo + + case "${1:-status}" in + status) + if [[ "$2" == "-s" || "$2" == "--short" ]]; then + show_status_short + else + df_print_header "dotfiles-sync" + show_status + fi + ;; + push) + df_print_header "dotfiles-sync" + shift + push_changes "$*" + ;; + pull) + df_print_header "dotfiles-sync" + pull_changes + ;; + -s|--short) + show_status_short + ;; + --help|-h) + echo "Usage: dotfiles-sync.sh [COMMAND]" + echo "" + echo "Commands:" + echo " status [-s] Show sync status (default)" + echo " push [message] Push changes to remote" + echo " pull Pull changes from remote" + echo "" + echo "Options:" + echo " -s, --short Short status output" + echo " --help Show this help" + ;; + *) + echo "Unknown command: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +} + +main "$@" diff --git a/dotfiles-refactor/bin/dotfiles-update.sh b/dotfiles-refactor/bin/dotfiles-update.sh new file mode 100644 index 0000000..621e9da --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-update.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# ============================================================================ +# Update Dotfiles Script +# ============================================================================ + +set -e + +# ============================================================================ +# Parse Arguments First (before sourcing, in case we need --help) +# ============================================================================ + +SKIP_DEPS=true +PULL_ONLY=false + +for arg in "$@"; do + case "$arg" in + --skip-deps) SKIP_DEPS=true ;; + --with-deps) SKIP_DEPS=false ;; + --pull-only) PULL_ONLY=true ;; + --help|-h) + echo "Usage: dotfiles-update.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --skip-deps Skip dependency check (default for updates)" + echo " --with-deps Run full dependency check" + echo " --pull-only Only git pull, don't re-run install script" + echo " --help Show this help message" + exit 0 + ;; + esac +done + +# ============================================================================ +# Source Bootstrap +# ============================================================================ + +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } + df_print_step() { echo -e "${DF_GREEN}==>${DF_NC} $1"; } +} + +# ============================================================================ +# Main +# ============================================================================ + +df_print_header "dotfiles-update" + +if [[ ! -d "$DOTFILES_HOME" ]]; then + df_print_error "Dotfiles directory not found: $DOTFILES_HOME" + exit 1 +fi + +cd "$DOTFILES_HOME" + +df_print_step "Updating dotfiles from repository..." + +if git pull origin "${DOTFILES_BRANCH:-main}"; then + df_print_success "Dotfiles updated successfully" + + if [[ "$PULL_ONLY" == true ]]; then + echo "" + df_print_success "Pull complete (--pull-only mode)" + exit 0 + fi + + echo "" + df_print_success "Update complete!" + echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}" +else + df_print_error "Failed to update dotfiles" + exit 1 +fi diff --git a/dotfiles-refactor/bin/dotfiles-vault.sh b/dotfiles-refactor/bin/dotfiles-vault.sh new file mode 100644 index 0000000..3c6f1f8 --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-vault.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Secrets Vault (Arch/CachyOS) +# ============================================================================ + +set -e + +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } +} + +# ============================================================================ +# Configuration +# ============================================================================ + +readonly VAULT_DIR="${DOTFILES_HOME}/vault" +readonly VAULT_FILE="${VAULT_DIR}/secrets.enc" + +# ============================================================================ +# Helper Functions +# ============================================================================ + +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } + +get_cipher() { + if command -v age &>/dev/null; then + echo "age" + elif command -v gpg &>/dev/null; then + echo "gpg" + else + df_print_error "No encryption tool available (install 'age' or 'gpg')" + exit 1 + fi +} + +# ============================================================================ +# Vault Functions +# ============================================================================ + +init_vault() { + print_section "Initializing Vault" + + mkdir -p "$VAULT_DIR" + chmod 700 "$VAULT_DIR" + + if [[ ! -f "$VAULT_FILE" ]]; then + echo "{}" > "$VAULT_FILE" + df_print_success "Vault initialized at $VAULT_DIR" + else + df_print_success "Vault already exists" + fi +} + +vault_list() { + print_section "Stored Secrets" + + if [[ ! -f "$VAULT_FILE" ]]; then + df_print_error "No vault file found. Run: vault init" + return 1 + fi + + local keys=$(cat "$VAULT_FILE" | grep -o '"[^"]*":' | sed 's/"//g;s/:$//') + + if [[ -z "$keys" ]]; then + echo " (no secrets stored)" + else + echo "$keys" | while read key; do + echo -e " ${DF_CYAN}•${DF_NC} $key" + done + fi + echo "" +} + +vault_status() { + print_section "Vault Status" + + if [[ ! -d "$VAULT_DIR" ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized" + echo " Run: vault init" + return + fi + + if [[ ! -f "$VAULT_FILE" ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found" + return + fi + + local cipher=$(get_cipher) + local key_count=$(cat "$VAULT_FILE" | grep -o '"[^"]*":' | wc -l) + + echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE" + echo -e " ${DF_CYAN}Encryption:${DF_NC} $cipher" + echo -e " ${DF_CYAN}Secrets:${DF_NC} $key_count" + echo "" +} + +show_help() { + echo "Usage: dotfiles-vault.sh [COMMAND]" + echo "" + echo "Commands:" + echo " init Initialize the vault" + echo " list, ls List all secret keys" + echo " status Show vault status" + echo " help Show this help" + echo "" + echo "The vault uses 'age' or 'gpg' for encryption." +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + df_print_header "dotfiles-vault" + + # Auto-init if vault doesn't exist + [[ ! -d "$VAULT_DIR" ]] && init_vault + + case "${1:-list}" in + init) init_vault ;; + list|ls) vault_list ;; + status) vault_status ;; + help|--help|-h) show_help ;; + *) + echo "Unknown command: $1" + show_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/dotfiles-refactor/bin/dotfiles-version.sh b/dotfiles-refactor/bin/dotfiles-version.sh new file mode 100644 index 0000000..0e88403 --- /dev/null +++ b/dotfiles-refactor/bin/dotfiles-version.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Version Checker +# ============================================================================ + +# ============================================================================ +# Parse Arguments First +# ============================================================================ + +CHECK_ONLY=false + +for arg in "$@"; do + case "$arg" in + --check|-c) CHECK_ONLY=true ;; + --help|-h) + echo "Usage: dotfiles-version.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --check, -c Output version only (for scripts)" + echo " --help Show this help" + exit 0 + ;; + esac +done + +# ============================================================================ +# Source Bootstrap +# ============================================================================ + +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + DF_WIDTH="${DF_WIDTH:-66}" + df_print_header() { echo "=== $1 ==="; } +} + +# ============================================================================ +# Version Info Functions +# ============================================================================ + +get_local_commit() { + if [[ -d "${DOTFILES_HOME}/.git" ]]; then + cd "$DOTFILES_HOME" + git rev-parse --short HEAD 2>/dev/null || echo "unknown" + else + echo "not a git repo" + fi +} + +get_local_date() { + if [[ -d "${DOTFILES_HOME}/.git" ]]; then + cd "$DOTFILES_HOME" + git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown" + else + echo "unknown" + fi +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + local local_commit=$(get_local_commit) + local local_date=$(get_local_date) + + # Short output for scripts + if [[ "$CHECK_ONLY" == true ]]; then + echo "Version: ${DOTFILES_VERSION} (${local_commit})" + exit 0 + fi + + # Full output + df_print_header "dotfiles-version" + + echo -e "${DF_CYAN}Local:${DF_NC}" + echo -e " Version: ${DF_GREEN}${DOTFILES_VERSION}${DF_NC}" + echo -e " Commit: ${local_commit}" + echo -e " Date: ${local_date}" + echo -e " Path: ${DOTFILES_HOME}" + echo -e " Branch: ${DOTFILES_BRANCH}" + echo -e " Width: ${DF_WIDTH}" + echo "" +} + +main "$@" diff --git a/dotfiles-refactor/setup/setup-espanso.sh b/dotfiles-refactor/setup/setup-espanso.sh new file mode 100644 index 0000000..2f9a52a --- /dev/null +++ b/dotfiles-refactor/setup/setup-espanso.sh @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +# ============================================================================ +# Espanso Setup and Configuration Script +# ============================================================================ + +set -e + +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } + df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } + df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } + df_print_step() { echo -e "${DF_GREEN}==>${DF_NC} $1"; } +} + +# ============================================================================ +# Helper Functions +# ============================================================================ + +ask_yes_no() { + local prompt="$1" + local default="${2:-y}" + local yn_prompt="[Y/n]" + [[ "$default" == "n" ]] && yn_prompt="[y/N]" + + read -p "$prompt $yn_prompt: " response + response=${response:-$default} + [[ "$response" =~ ^[Yy]$ ]] +} + +# ============================================================================ +# Espanso Functions +# ============================================================================ + +check_espanso() { + if ! command -v espanso &>/dev/null; then + df_print_error "espanso is not installed" + echo "Install with: paru -S espanso-wayland # or espanso-x11" + exit 1 + fi + df_print_success "espanso installed: $(espanso --version)" +} + +show_espanso_status() { + df_print_step "Checking espanso status" + + if espanso status 2>/dev/null | grep -q "running"; then + df_print_success "espanso service is running" + else + df_print_warning "espanso service is not running" + if ask_yes_no "Start espanso service?"; then + espanso service start + df_print_success "espanso service started" + fi + fi +} + +personalize_config() { + df_print_step "Personalizing espanso configuration" + + local personal_file="$HOME/.config/espanso/match/personal.yml" + + if [[ ! -f "$personal_file" ]]; then + df_print_warning "Personal config not found, creating from template..." + mkdir -p "$(dirname "$personal_file")" + + cat > "$personal_file" << 'EOF' +# ============================================================================ +# Personal Espanso Snippets +# ============================================================================ + +matches: + - trigger: "..myemail" + replace: "your.email@example.com" + + - trigger: "..myname" + replace: "Your Full Name" + + - trigger: "..myphone" + replace: "+1 (555) 123-4567" + + - trigger: "..myweb" + replace: "https://yourwebsite.com" + + - trigger: "..mygithub" + replace: "https://github.com/yourusername" + + - trigger: "..sig" + replace: | + Best regards, + Your Full Name + your.email@example.com +EOF + df_print_success "Created personal.yml template" + fi + + echo "" + echo "Personalizing your espanso configuration" + echo "(Press Enter to keep existing values)" + echo "" + + # Get current values or use config defaults + local fullname="${USER_FULLNAME:-}" + local email="${USER_EMAIL:-}" + local phone="${USER_PHONE:-}" + local website="${USER_WEBSITE:-}" + local github="${USER_GITHUB:-}" + + [[ -z "$fullname" ]] && read -p "Your full name: " fullname + [[ -z "$email" ]] && read -p "Your email: " email + [[ -z "$phone" ]] && read -p "Your phone (optional): " phone + [[ -z "$website" ]] && read -p "Your website (optional): " website + [[ -z "$github" ]] && read -p "Your GitHub username (optional): " github + + # Create backup + cp "$personal_file" "$personal_file.backup" + + # Update values if provided + [[ -n "$email" ]] && sed -i "s/your.email@example.com/$email/g" "$personal_file" + [[ -n "$fullname" ]] && sed -i "s/Your Full Name/$fullname/g" "$personal_file" + [[ -n "$phone" ]] && sed -i "s/+1 (555) 123-4567/$phone/g" "$personal_file" + [[ -n "$website" ]] && sed -i "s|https://yourwebsite.com|$website|g" "$personal_file" + [[ -n "$github" ]] && sed -i "s/yourusername/$github/g" "$personal_file" + + df_print_success "Personal configuration updated!" + df_print_warning "Backup saved to: $personal_file.backup" +} + +install_packages() { + df_print_step "Installing espanso packages" + + echo "" + echo "Available packages:" + echo " 1. emoji - Emoji snippets (:smile: → 😊)" + echo " 2. greek-letters - Greek letters (:alpha: → α)" + echo " 3. math - Math symbols (:sum: → ∑)" + echo "" + + ask_yes_no "Install emoji package?" && { + espanso install emoji --force 2>/dev/null + df_print_success "Emoji package installed" + } + + ask_yes_no "Install greek-letters package?" && { + espanso install greek-letters --force 2>/dev/null + df_print_success "Greek letters package installed" + } + + ask_yes_no "Install math package?" && { + espanso install math --force 2>/dev/null + df_print_success "Math package installed" + } +} + +show_usage_tips() { + df_print_step "Usage tips" + + cat << EOF + +${DF_GREEN}Espanso Quick Start:${DF_NC} + +${DF_YELLOW}Toggle on/off:${DF_NC} ALT+SHIFT+E +${DF_YELLOW}Search menu:${DF_NC} ALT+SPACE + +${DF_YELLOW}Basic triggers:${DF_NC} + ..date → Current date (YYYY-MM-DD) + ..time → Current time (HH:MM:SS) + ..shrug → ¯\\_(ツ)_/¯ + ..myemail → Your email + +${DF_YELLOW}Commands:${DF_NC} + espanso status Check if running + espanso restart Restart service + espanso log View logs + +EOF +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + df_print_header "setup-espanso" + + check_espanso + show_espanso_status + + echo "" + ask_yes_no "Personalize your configuration?" && personalize_config + + echo "" + ask_yes_no "Install additional packages?" && install_packages + + echo "" + show_usage_tips + + echo "" + df_print_success "Espanso setup complete!" + echo "" + echo "Try typing ${DF_YELLOW}..date${DF_NC} in any application to test!" +} + +main "$@" diff --git a/dotfiles-refactor/setup/setup-wizard.sh b/dotfiles-refactor/setup/setup-wizard.sh new file mode 100644 index 0000000..0a7e8c2 --- /dev/null +++ b/dotfiles-refactor/setup/setup-wizard.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Interactive Setup Wizard +# ============================================================================ + +set -e + +# Source bootstrap +source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" + DF_WIDTH="${DF_WIDTH:-66}" + df_print_header() { echo "=== $1 ==="; } + df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +} + +# ============================================================================ +# Gum Detection (for prettier TUI) +# ============================================================================ + +HAS_GUM=false +command -v gum &>/dev/null && HAS_GUM=true + +wizard_confirm() { + local prompt="$1" + local default="${2:-yes}" + + if [[ "$HAS_GUM" == true ]]; then + if [[ "$default" == "yes" ]]; then + gum confirm --default=yes "$prompt" + else + gum confirm --default=no "$prompt" + fi + else + local yn_prompt="[Y/n]" + [[ "$default" == "no" ]] && yn_prompt="[y/N]" + read -p "$prompt $yn_prompt: " response + response=${response:-${default:0:1}} + [[ "$response" =~ ^[Yy] ]] + fi +} + +wizard_input() { + local prompt="$1" + local default="$2" + + if [[ "$HAS_GUM" == true ]]; then + gum input --placeholder "$default" --value "$default" --prompt "$prompt: " + else + read -p "$prompt [$default]: " response + echo "${response:-$default}" + fi +} + +wizard_choose() { + local prompt="$1" + shift + local options=("$@") + + if [[ "$HAS_GUM" == true ]]; then + printf '%s\n' "${options[@]}" | gum choose --header "$prompt" + else + echo "$prompt" + local i=1 + for opt in "${options[@]}"; do + echo " $i) $opt" + ((i++)) + done + read -p "Choice [1]: " choice + choice=${choice:-1} + echo "${options[$((choice-1))]}" + fi +} + +# ============================================================================ +# Wizard Steps +# ============================================================================ + +step_welcome() { + clear + df_print_header "setup-wizard" + + echo -e "${DF_BOLD}Welcome to Dotfiles Setup Wizard${DF_NC}" + echo -e "${DF_DIM}Version: $DOTFILES_VERSION | Display Width: $DF_WIDTH${DF_NC}" + echo "" + + wizard_confirm "Ready to begin?" || { + echo "Setup cancelled." + exit 0 + } +} + +step_user_info() { + echo "" + echo -e "${DF_BLUE}▶${DF_NC} Personal Information" + echo "" + + USER_FULLNAME=$(wizard_input "Full Name" "${USER_FULLNAME:-}") + USER_EMAIL=$(wizard_input "Email" "${USER_EMAIL:-}") + USER_GITHUB=$(wizard_input "GitHub Username" "${USER_GITHUB:-}") +} + +step_features() { + echo "" + echo -e "${DF_BLUE}▶${DF_NC} Feature Selection" + echo "" + + MOTD_STYLE=$(wizard_choose "MOTD Style:" "compact" "mini" "full" "none") + + wizard_confirm "Enable smart suggestions (typo correction)?" && ENABLE_SMART_SUGGESTIONS="true" || ENABLE_SMART_SUGGESTIONS="false" + wizard_confirm "Enable command palette (Ctrl+Space)?" && ENABLE_COMMAND_PALETTE="true" || ENABLE_COMMAND_PALETTE="false" +} + +step_summary() { + echo "" + echo -e "${DF_GREEN}✓${DF_NC} Configuration Summary" + echo "" + echo " Name: $USER_FULLNAME" + echo " Email: $USER_EMAIL" + echo " GitHub: $USER_GITHUB" + echo " MOTD Style: $MOTD_STYLE" + echo " Smart Suggestions: $ENABLE_SMART_SUGGESTIONS" + echo " Command Palette: $ENABLE_COMMAND_PALETTE" + echo "" + + if wizard_confirm "Save this configuration?"; then + save_config + df_print_success "Configuration saved!" + else + echo "Configuration not saved." + fi +} + +save_config() { + local config_file="$DOTFILES_HOME/dotfiles.conf" + + # Update values in config file + if [[ -f "$config_file" ]]; then + sed -i "s/^USER_FULLNAME=.*/USER_FULLNAME=\"$USER_FULLNAME\"/" "$config_file" + sed -i "s/^USER_EMAIL=.*/USER_EMAIL=\"$USER_EMAIL\"/" "$config_file" + sed -i "s/^USER_GITHUB=.*/USER_GITHUB=\"$USER_GITHUB\"/" "$config_file" + sed -i "s/^MOTD_STYLE=.*/MOTD_STYLE=\"$MOTD_STYLE\"/" "$config_file" + sed -i "s/^ENABLE_SMART_SUGGESTIONS=.*/ENABLE_SMART_SUGGESTIONS=\"$ENABLE_SMART_SUGGESTIONS\"/" "$config_file" + sed -i "s/^ENABLE_COMMAND_PALETTE=.*/ENABLE_COMMAND_PALETTE=\"$ENABLE_COMMAND_PALETTE\"/" "$config_file" + fi +} + +step_next() { + echo "" + df_print_success "Setup Complete!" + echo "" + echo -e "${DF_DIM}Next steps:${DF_NC}" + echo " 1. Reload your shell: source ~/.zshrc" + echo " 2. Run health check: dfd" + echo " 3. Explore commands: dotfiles-cli help" + echo "" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + step_welcome + step_user_info + step_features + step_summary + step_next +} + +# Only run if executed directly (not sourced) +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" diff --git a/dotfiles-refactor/zsh/functions/python-templates.zsh b/dotfiles-refactor/zsh/functions/python-templates.zsh new file mode 100644 index 0000000..95d396b --- /dev/null +++ b/dotfiles-refactor/zsh/functions/python-templates.zsh @@ -0,0 +1,616 @@ +# ============================================================================ +# Python Project Template Functions +# ============================================================================ +# Template-driven project scaffolding for Python applications. +# Eliminates code duplication by using a common creation function. +# ============================================================================ + +# Source bootstrap (handles all dependencies) +source "${0:A:h}/../lib/bootstrap.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/bootstrap.zsh" 2>/dev/null + +# ============================================================================ +# Configuration +# ============================================================================ + +typeset -g PY_PYTHON="${PY_PYTHON:-python3}" +typeset -g PY_VENV="${PY_VENV:-venv}" +typeset -g PY_GIT_INIT="${PY_GIT_INIT:-true}" + +# ============================================================================ +# Internal Helper Functions +# ============================================================================ + +# Validate project name and ensure directory doesn't exist +_py_check_name() { + [[ -z "$1" ]] && { df_print_warning "Project name required"; return 1; } + [[ -d "$1" ]] && { df_print_warning "Directory '$1' already exists"; return 1; } + return 0 +} + +# Create and activate virtual environment +_py_venv() { + local project_dir="$1" + df_print_step "Creating virtual environment" + "$PY_PYTHON" -m venv "$project_dir/$PY_VENV" + df_print_success "Created: $PY_VENV" +} + +# Install packages into project's venv +_py_install() { + local project_dir="$1" + shift + local packages=("$@") + + [[ ${#packages[@]} -eq 0 ]] && return 0 + + df_print_step "Installing: ${packages[*]}" + "$project_dir/$PY_VENV/bin/pip" install "${packages[@]}" -q +} + +# Create standard .gitignore for Python projects +_py_gitignore() { + local project_dir="$1" + cat > "$project_dir/.gitignore" << 'EOF' +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +.venv/ +ENV/ +env/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.nox/ + +# Type checking +.mypy_cache/ +.dmypy.json + +# Environment +.env +.env.* +*.log + +# Distribution +*.manifest +*.spec +EOF + df_print_success "Created .gitignore" +} + +# Initialize git repository +_py_git() { + local project_dir="$1" + [[ "$PY_GIT_INIT" != "true" ]] && return 0 + + ( + cd "$project_dir" + git init -q + git add . + git commit -q -m "Initial commit" + ) + df_print_success "Git initialized" +} + +# Print next steps for user +_py_next_steps() { + local project_dir="$1" + local extra_info="$2" + + echo "" + df_print_section "Next steps" + df_print_indent "cd $project_dir" + df_print_indent "source $PY_VENV/bin/activate" + [[ -n "$extra_info" ]] && df_print_indent "$extra_info" +} + +# ============================================================================ +# Template Definitions +# ============================================================================ +# Each template function creates type-specific files and returns packages to install + +_py_template_basic() { + local name="$1" + + # Create directory structure + mkdir -p "$name"/{src,tests} + touch "$name/src/__init__.py" "$name/tests/__init__.py" + + # Create main.py + cat > "$name/src/main.py" << 'EOF' +#!/usr/bin/env python3 +"""Main entry point.""" + + +def main(): + """Main function.""" + print("Hello, World!") + + +if __name__ == "__main__": + main() +EOF + + # Create requirements.txt + cat > "$name/requirements.txt" << 'EOF' +# Project dependencies +# Add your dependencies here +EOF + + # Return packages to install (none for basic) + echo "" +} + +_py_template_flask() { + local name="$1" + + # Create directory structure + mkdir -p "$name"/{app/{templates,static/css},tests} + + # Create app/__init__.py + cat > "$name/app/__init__.py" << 'EOF' +"""Flask application factory.""" +from flask import Flask + + +def create_app(config_name=None): + """Create and configure the Flask application.""" + app = Flask(__name__) + + # Load configuration + app.config.from_mapping( + SECRET_KEY='dev', + DEBUG=True, + ) + + # Register blueprints + from app.routes import main + app.register_blueprint(main) + + return app +EOF + + # Create app/routes.py + cat > "$name/app/routes.py" << 'EOF' +"""Main application routes.""" +from flask import Blueprint, render_template + +main = Blueprint('main', __name__) + + +@main.route('/') +def index(): + """Home page.""" + return render_template('index.html') + + +@main.route('/health') +def health(): + """Health check endpoint.""" + return {'status': 'ok'} +EOF + + # Create template + cat > "$name/app/templates/index.html" << 'EOF' + + + + + + Flask App + + + +

Welcome to Flask

+

Your application is running!

+ + +EOF + + # Create basic CSS + cat > "$name/app/static/css/style.css" << 'EOF' +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + max-width: 800px; + margin: 0 auto; + padding: 2rem; +} +EOF + + # Create run script + cat > "$name/app.py" << 'EOF' +#!/usr/bin/env python3 +"""Application entry point.""" +from app import create_app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', port=5000) +EOF + + # Create requirements.txt + cat > "$name/requirements.txt" << 'EOF' +Flask>=3.0.0 +python-dotenv>=1.0.0 +EOF + + # Return packages to install + echo "flask python-dotenv" +} + +_py_template_fastapi() { + local name="$1" + + # Create directory structure + mkdir -p "$name"/{app/{routers,models},tests} + touch "$name/app/__init__.py" "$name/app/routers/__init__.py" "$name/app/models/__init__.py" + + # Create app/main.py + cat > "$name/app/main.py" << 'EOF' +"""FastAPI application.""" +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI( + title="FastAPI App", + description="A FastAPI application", + version="0.1.0", +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def root(): + """Root endpoint.""" + return {"message": "Hello, World!"} + + +@app.get("/health") +async def health(): + """Health check endpoint.""" + return {"status": "ok"} +EOF + + # Create run script + cat > "$name/run.py" << 'EOF' +#!/usr/bin/env python3 +"""Development server entry point.""" +import uvicorn + +if __name__ == "__main__": + uvicorn.run( + "app.main:app", + host="0.0.0.0", + port=8000, + reload=True, + ) +EOF + + # Create requirements.txt + cat > "$name/requirements.txt" << 'EOF' +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 +python-dotenv>=1.0.0 +EOF + + # Return packages to install + echo "fastapi uvicorn python-dotenv" +} + +_py_template_cli() { + local name="$1" + local pkg_name="${name//-/_}" # Replace hyphens with underscores for Python + + # Create directory structure + mkdir -p "$name"/{src/"$pkg_name",tests} + + # Create package __init__.py + cat > "$name/src/$pkg_name/__init__.py" << EOF +"""${name} - A command-line tool.""" +__version__ = "0.1.0" +EOF + + # Create CLI entry point + cat > "$name/src/$pkg_name/cli.py" << 'EOF' +#!/usr/bin/env python3 +"""Command-line interface.""" +import click + + +@click.group() +@click.version_option() +def cli(): + """A command-line tool.""" + pass + + +@cli.command() +@click.argument('name', default='World') +def greet(name): + """Greet someone by name.""" + click.echo(f"Hello, {name}!") + + +@cli.command() +@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output') +def info(verbose): + """Show application information.""" + click.echo("CLI Application v0.1.0") + if verbose: + click.echo("Built with Click") + + +if __name__ == '__main__': + cli() +EOF + + # Create pyproject.toml for modern packaging + cat > "$name/pyproject.toml" << EOF +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "$name" +version = "0.1.0" +description = "A command-line tool" +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "click>=8.1.0", +] + +[project.scripts] +$name = "${pkg_name}.cli:cli" + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov", +] +EOF + + # Create requirements.txt (for compatibility) + cat > "$name/requirements.txt" << 'EOF' +click>=8.1.0 +EOF + + # Create README + cat > "$name/README.md" << EOF +# ${name} + +A command-line tool. + +## Installation + +\`\`\`bash +pip install -e . +\`\`\` + +## Usage + +\`\`\`bash +${name} --help +${name} greet World +\`\`\` +EOF + + # Return packages to install + echo "click" +} + +_py_template_data() { + local name="$1" + + # Create directory structure + mkdir -p "$name"/{notebooks,data/{raw,processed},src,tests} + touch "$name/src/__init__.py" + + # Create main analysis script + cat > "$name/src/analysis.py" << 'EOF' +#!/usr/bin/env python3 +"""Data analysis module.""" +import pandas as pd +import numpy as np + + +def load_data(filepath): + """Load data from CSV file.""" + return pd.read_csv(filepath) + + +def basic_stats(df): + """Calculate basic statistics.""" + return df.describe() + + +if __name__ == "__main__": + print("Data Science Project") + print(f"NumPy version: {np.__version__}") + print(f"Pandas version: {pd.__version__}") +EOF + + # Create sample notebook + cat > "$name/notebooks/01_exploration.ipynb" << 'EOF' +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": ["# Data Exploration\n", "\n", "Initial data exploration notebook."] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": ["import pandas as pd\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "%matplotlib inline"] + } + ], + "metadata": { + "kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, + "language_info": {"name": "python", "version": "3.10.0"} + }, + "nbformat": 4, + "nbformat_minor": 4 +} +EOF + + # Create requirements.txt + cat > "$name/requirements.txt" << 'EOF' +pandas>=2.0.0 +numpy>=1.24.0 +matplotlib>=3.7.0 +seaborn>=0.12.0 +jupyter>=1.0.0 +scikit-learn>=1.3.0 +EOF + + # Create .gitkeep files + touch "$name/data/raw/.gitkeep" "$name/data/processed/.gitkeep" + + # Return packages to install + echo "pandas numpy matplotlib seaborn jupyter scikit-learn" +} + +# ============================================================================ +# Main Project Creation Function +# ============================================================================ + +_py_create_project() { + local name="$1" + local template="$2" + local display_name="$3" + local extra_info="$4" + + # Validate + _py_check_name "$name" || return 1 + + # Print header + df_print_func_name "${display_name}: ${name}" + + # Create base directory + mkdir -p "$name" + + # Run template-specific setup and capture packages + local packages + packages=$("_py_template_${template}" "$name") + + # Common setup steps + _py_venv "$name" + + # Install template-specific packages + if [[ -n "$packages" ]]; then + _py_install "$name" $packages + fi + + # Finalization + _py_gitignore "$name" + _py_git "$name" + + df_print_success "Created: $name" + _py_next_steps "$name" "$extra_info" +} + +# ============================================================================ +# Public API Functions +# ============================================================================ + +py-new() { + _py_create_project "$1" "basic" "Python Project" +} + +py-flask() { + _py_create_project "$1" "flask" "Flask Project" "Run: python app.py" +} + +py-fastapi() { + _py_create_project "$1" "fastapi" "FastAPI Project" "Run: python run.py | Docs: http://localhost:8000/docs" +} + +py-cli() { + _py_create_project "$1" "cli" "CLI Project" "Install: pip install -e ." +} + +py-data() { + _py_create_project "$1" "data" "Data Science Project" "Start Jupyter: jupyter notebook" +} + +# Quick venv activation helper +venv() { + local venv_dirs=("venv" ".venv" "env" ".env") + for dir in "${venv_dirs[@]}"; do + if [[ -f "$dir/bin/activate" ]]; then + source "$dir/bin/activate" + df_print_success "Activated: $dir" + return 0 + fi + done + df_print_error "No virtual environment found" + df_print_info "Create one with: python -m venv venv" + return 1 +} + +# List available templates +py-templates() { + df_print_func_name "Python Project Templates" + echo "" + df_print_indent "py-new Basic Python project" + df_print_indent "py-flask Flask web application" + df_print_indent "py-fastapi FastAPI REST API" + df_print_indent "py-cli CLI tool with Click" + df_print_indent "py-data Data science project" + echo "" + df_print_info "Example: py-flask mywebapp" +} + +# ============================================================================ +# Aliases +# ============================================================================ + +alias pynew='py-new' +alias pyflask='py-flask' +alias pyfast='py-fastapi' +alias pycli='py-cli' +alias pydata='py-data' +alias pytemplates='py-templates' diff --git a/dotfiles-refactor/zsh/lib/bootstrap.zsh b/dotfiles-refactor/zsh/lib/bootstrap.zsh new file mode 100644 index 0000000..5868b21 --- /dev/null +++ b/dotfiles-refactor/zsh/lib/bootstrap.zsh @@ -0,0 +1,135 @@ +# ============================================================================ +# Dotfiles Bootstrap - Single Entry Point +# ============================================================================ +# This is the ONE file to source in all scripts and functions. +# It handles loading config, colors, and utils in the correct order with +# proper fallbacks. +# +# Usage in zsh functions: +# source "${0:A:h}/../lib/bootstrap.zsh" +# +# Usage in bash scripts: +# source "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" +# +# After sourcing, you have access to: +# - All DF_* color variables +# - All df_print_* functions +# - All df_* utility functions +# - All config variables from dotfiles.conf +# ============================================================================ + +# Prevent double-sourcing (works in both bash and zsh) +[[ -n "$_DF_BOOTSTRAP_LOADED" ]] && return 0 + +# ============================================================================ +# Determine Dotfiles Root +# ============================================================================ + +_df_find_root() { + # Check common locations in order of preference + local locations=( + "${DOTFILES_DIR}" + "${DOTFILES_HOME}" + "$HOME/.dotfiles" + ) + + for loc in "${locations[@]}"; do + [[ -n "$loc" && -d "$loc" && -f "$loc/dotfiles.conf" ]] && { + echo "$loc" + return 0 + } + done + + # Fallback: try to find from script location (zsh) + if [[ -n "$ZSH_VERSION" ]]; then + local script_dir="${0:A:h}" + # Walk up looking for dotfiles.conf + while [[ "$script_dir" != "/" ]]; do + [[ -f "$script_dir/dotfiles.conf" ]] && { echo "$script_dir"; return 0; } + script_dir="${script_dir:h}" + done + fi + + # Last resort + echo "$HOME/.dotfiles" +} + +# Set the root directory +typeset -g _DF_ROOT="$(_df_find_root)" +typeset -g DOTFILES_DIR="$_DF_ROOT" +typeset -g DOTFILES_HOME="$_DF_ROOT" + +# ============================================================================ +# Source Core Files (in correct order) +# ============================================================================ + +# 1. Config first (sets DF_WIDTH, MOTD_STYLE, etc.) +if [[ -f "$_DF_ROOT/zsh/lib/config.zsh" ]]; then + source "$_DF_ROOT/zsh/lib/config.zsh" +else + # Minimal fallback config + typeset -g DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" + typeset -g DF_WIDTH="${DF_WIDTH:-66}" + typeset -g MOTD_STYLE="${MOTD_STYLE:-compact}" + typeset -g DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" +fi + +# 2. Colors second +if [[ -f "$_DF_ROOT/zsh/lib/colors.zsh" ]]; then + source "$_DF_ROOT/zsh/lib/colors.zsh" +else + # Minimal fallback colors + typeset -g DF_RED=$'\033[0;31m' + typeset -g DF_GREEN=$'\033[0;32m' + typeset -g DF_YELLOW=$'\033[1;33m' + typeset -g DF_BLUE=$'\033[0;34m' + typeset -g DF_CYAN=$'\033[0;36m' + typeset -g DF_NC=$'\033[0m' + typeset -g DF_GREY=$'\033[38;5;242m' + typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' + typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m' + typeset -g DF_BOLD=$'\033[1m' + typeset -g DF_DIM=$'\033[2m' +fi + +# 3. Utils last (depends on config and colors) +if [[ -f "$_DF_ROOT/zsh/lib/utils.zsh" ]]; then + source "$_DF_ROOT/zsh/lib/utils.zsh" +fi + +# ============================================================================ +# Ensure Critical Functions Exist +# ============================================================================ +# If utils.zsh failed to load, provide minimal implementations + +if ! declare -f df_print_header &>/dev/null; then + df_print_header() { + local name="${1:-script}" + echo "" + echo "=== ${name} ===" + echo "" + } +fi + +if ! declare -f df_print_func_name &>/dev/null; then + df_print_func_name() { + echo "--- ${1:-function} ---" + } +fi + +if ! declare -f df_print_success &>/dev/null; then + df_print_success() { echo "✓ $1"; } + df_print_error() { echo "✗ $1" >&2; } + df_print_warning() { echo "⚠ $1"; } + df_print_info() { echo "ℹ $1"; } + df_print_step() { echo "==> $1"; } +fi + +# ============================================================================ +# Mark as Loaded +# ============================================================================ + +typeset -g _DF_BOOTSTRAP_LOADED=1 + +# Export for subshells (bash compatibility) +export DOTFILES_DIR DOTFILES_HOME DOTFILES_VERSION DF_WIDTH diff --git a/dotfiles-refactor/zsh/lib/colors.zsh b/dotfiles-refactor/zsh/lib/colors.zsh new file mode 100644 index 0000000..fcd510c --- /dev/null +++ b/dotfiles-refactor/zsh/lib/colors.zsh @@ -0,0 +1,86 @@ +# ============================================================================ +# Shared Color Definitions for Dotfiles +# ============================================================================ +# Source this file in scripts and functions to get consistent color support. +# +# Usage in zsh functions: +# source "${0:A:h}/../lib/colors.zsh" +# +# Usage in bash scripts: +# source "$HOME/.dotfiles/zsh/lib/colors.zsh" +# +# All variables are prefixed with DF_ (dotfiles) to avoid conflicts. +# ============================================================================ + +# Prevent double-sourcing +[[ -n "$_DF_COLORS_LOADED" ]] && return 0 +typeset -g _DF_COLORS_LOADED=1 + +# ============================================================================ +# Standard Colors (ANSI escape codes) +# ============================================================================ + +typeset -g DF_RED=$'\033[0;31m' +typeset -g DF_GREEN=$'\033[0;32m' +typeset -g DF_YELLOW=$'\033[1;33m' +typeset -g DF_BLUE=$'\033[0;34m' +typeset -g DF_MAGENTA=$'\033[0;35m' +typeset -g DF_CYAN=$'\033[0;36m' +typeset -g DF_WHITE=$'\033[0;37m' + +# Bold variants +typeset -g DF_BOLD_RED=$'\033[1;31m' +typeset -g DF_BOLD_GREEN=$'\033[1;32m' +typeset -g DF_BOLD_YELLOW=$'\033[1;33m' +typeset -g DF_BOLD_BLUE=$'\033[1;34m' +typeset -g DF_BOLD_MAGENTA=$'\033[1;35m' +typeset -g DF_BOLD_CYAN=$'\033[1;36m' +typeset -g DF_BOLD_WHITE=$'\033[1;37m' + +# Text styles +typeset -g DF_BOLD=$'\033[1m' +typeset -g DF_DIM=$'\033[2m' +typeset -g DF_ITALIC=$'\033[3m' +typeset -g DF_UNDERLINE=$'\033[4m' +typeset -g DF_RESET=$'\033[0m' +typeset -g DF_NC=$'\033[0m' # Alias for reset (No Color) + +# ============================================================================ +# 256-Color Palette (used in theme and MOTD) +# ============================================================================ + +typeset -g DF_GREY=$'\033[38;5;242m' +typeset -g DF_LIGHT_GREY=$'\033[38;5;248m' +typeset -g DF_DARK_GREY=$'\033[38;5;239m' +typeset -g DF_ORANGE=$'\033[38;5;208m' +typeset -g DF_LIGHT_ORANGE=$'\033[38;5;220m' +typeset -g DF_PINK=$'\033[38;5;213m' +typeset -g DF_PURPLE=$'\033[38;5;141m' +typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' +typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m' +typeset -g DF_BRIGHT_GREEN=$'\033[38;5;118m' +typeset -g DF_TEAL=$'\033[38;5;51m' + +# ============================================================================ +# Semantic Colors (for consistent UI) +# ============================================================================ + +typeset -g DF_SUCCESS="$DF_GREEN" +typeset -g DF_ERROR="$DF_RED" +typeset -g DF_WARNING="$DF_YELLOW" +typeset -g DF_INFO="$DF_CYAN" +typeset -g DF_HINT="$DF_DIM" +typeset -g DF_ACCENT="$DF_BLUE" +typeset -g DF_MUTED="$DF_GREY" + +# ============================================================================ +# Bash Compatibility +# ============================================================================ + +# For bash scripts, export as regular variables too +if [[ -n "$BASH_VERSION" ]]; then + export DF_RED DF_GREEN DF_YELLOW DF_BLUE DF_MAGENTA DF_CYAN DF_WHITE + export DF_BOLD DF_DIM DF_RESET DF_NC + export DF_GREY DF_LIGHT_BLUE DF_LIGHT_GREEN + export DF_SUCCESS DF_ERROR DF_WARNING DF_INFO +fi diff --git a/dotfiles-refactor/zsh/lib/config.zsh b/dotfiles-refactor/zsh/lib/config.zsh new file mode 100644 index 0000000..4f714e5 --- /dev/null +++ b/dotfiles-refactor/zsh/lib/config.zsh @@ -0,0 +1,154 @@ +# ============================================================================ +# Dotfiles Configuration Loader +# ============================================================================ +# This file loads dotfiles.conf and sets up all configuration variables. +# It serves as the bridge between dotfiles.conf and the rest of the system. +# +# Source this file to get access to all configuration: +# source "${0:A:h}/config.zsh" +# +# This file: +# 1. Finds and sources dotfiles.conf +# 2. Sets sensible defaults for any missing values +# 3. Exports variables for use in subshells/scripts +# ============================================================================ + +# Prevent double-sourcing +[[ -n "$_DF_CONFIG_LOADED" ]] && return 0 + +# ============================================================================ +# Find and Source dotfiles.conf +# ============================================================================ + +_df_find_config() { + local locations=( + "${DOTFILES_DIR}/dotfiles.conf" + "${DOTFILES_HOME}/dotfiles.conf" + "$HOME/.dotfiles/dotfiles.conf" + "${0:A:h}/../../dotfiles.conf" + ) + + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { echo "$loc"; return 0; } + done + return 1 +} + +_DF_CONFIG_FILE=$(_df_find_config) + +if [[ -n "$_DF_CONFIG_FILE" && -f "$_DF_CONFIG_FILE" ]]; then + source "$_DF_CONFIG_FILE" + typeset -g _DF_CONFIG_LOADED=1 +else + # Config file not found - set critical defaults + typeset -g _DF_CONFIG_LOADED=1 +fi + +# ============================================================================ +# Set Defaults for Any Missing Values +# ============================================================================ +# These defaults ensure scripts work even if dotfiles.conf is incomplete + +# Core Settings +typeset -g DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" +typeset -g DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" +typeset -g DOTFILES_HOME="${DOTFILES_HOME:-$DOTFILES_DIR}" # Alias for compatibility +typeset -g DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" +typeset -g DOTFILES_BACKUP_PREFIX="${DOTFILES_BACKUP_PREFIX:-$HOME/.dotfiles_backup}" + +# GitHub Settings +typeset -g DOTFILES_GITHUB_USER="${DOTFILES_GITHUB_USER:-adlee-was-taken}" +typeset -g DOTFILES_REPO_NAME="${DOTFILES_REPO_NAME:-dotfiles}" +typeset -g DOTFILES_REPO_URL="${DOTFILES_REPO_URL:-https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}.git}" +typeset -g DOTFILES_RAW_URL="${DOTFILES_RAW_URL:-https://raw.githubusercontent.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}/${DOTFILES_BRANCH}}" + +# Display Settings +typeset -g DF_WIDTH="${DF_WIDTH:-66}" +typeset -g ENABLE_MOTD="${ENABLE_MOTD:-true}" +typeset -g MOTD_STYLE="${MOTD_STYLE:-compact}" +typeset -g MOTD_SHOW_FAILED_SERVICES="${MOTD_SHOW_FAILED_SERVICES:-true}" +typeset -g MOTD_SHOW_UPDATES="${MOTD_SHOW_UPDATES:-true}" + +# Theme Settings +typeset -g ZSH_THEME_NAME="${ZSH_THEME_NAME:-adlee}" +typeset -g THEME_TIMER_THRESHOLD="${THEME_TIMER_THRESHOLD:-10}" +typeset -g THEME_PATH_TRUNCATE_LENGTH="${THEME_PATH_TRUNCATE_LENGTH:-32}" + +# Feature Toggles +typeset -g ENABLE_SMART_SUGGESTIONS="${ENABLE_SMART_SUGGESTIONS:-true}" +typeset -g ENABLE_COMMAND_PALETTE="${ENABLE_COMMAND_PALETTE:-true}" +typeset -g ENABLE_SHELL_ANALYTICS="${ENABLE_SHELL_ANALYTICS:-false}" +typeset -g ENABLE_VAULT="${ENABLE_VAULT:-true}" +typeset -g DOTFILES_AUTO_SYNC_CHECK="${DOTFILES_AUTO_SYNC_CHECK:-true}" + +# Btrfs Settings +typeset -g BTRFS_DEFAULT_MOUNT="${BTRFS_DEFAULT_MOUNT:-/}" + +# Snapper Settings +typeset -g SNAPPER_CONFIG="${SNAPPER_CONFIG:-root}" +typeset -g LIMINE_CONF="${LIMINE_CONF:-/boot/limine.conf}" + +# Tmux Settings +typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}" +typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}" + +# Python Template Settings +typeset -g PY_TEMPLATE_BASE_DIR="${PY_TEMPLATE_BASE_DIR:-$HOME/projects}" +typeset -g PY_TEMPLATE_PYTHON="${PY_TEMPLATE_PYTHON:-python3}" +typeset -g PY_TEMPLATE_VENV_NAME="${PY_TEMPLATE_VENV_NAME:-venv}" +typeset -g PY_TEMPLATE_USE_POETRY="${PY_TEMPLATE_USE_POETRY:-false}" +typeset -g PY_TEMPLATE_GIT_INIT="${PY_TEMPLATE_GIT_INIT:-true}" + +# SSH Settings +typeset -g SSH_AUTO_TMUX="${SSH_AUTO_TMUX:-true}" +typeset -g SSH_TMUX_SESSION_PREFIX="${SSH_TMUX_SESSION_PREFIX:-ssh}" +typeset -g SSH_SYNC_DOTFILES="${SSH_SYNC_DOTFILES:-ask}" + +# Password Manager Settings +typeset -g PW_CLIP_TIME="${PW_CLIP_TIME:-45}" + +# Package Manager +typeset -g AUR_HELPER="${AUR_HELPER:-auto}" + +# Git Settings (with fallbacks to user identity) +typeset -g GIT_USER_NAME="${GIT_USER_NAME:-$USER_FULLNAME}" +typeset -g GIT_USER_EMAIL="${GIT_USER_EMAIL:-$USER_EMAIL}" +typeset -g GIT_DEFAULT_BRANCH="${GIT_DEFAULT_BRANCH:-main}" + +# ============================================================================ +# Export for Bash Scripts +# ============================================================================ +# Bash scripts can't see typeset -g, so we export key variables + +export DOTFILES_VERSION DOTFILES_DIR DOTFILES_HOME DOTFILES_BRANCH +export DOTFILES_GITHUB_USER DOTFILES_REPO_NAME DOTFILES_REPO_URL DOTFILES_RAW_URL +export DF_WIDTH MOTD_STYLE +export ZSH_THEME_NAME + +# ============================================================================ +# Helper Function: Get Config Value +# ============================================================================ +# Usage: df_config "VARIABLE_NAME" "default_value" + +df_config() { + local var_name="$1" + local default="$2" + local value="${(P)var_name}" + echo "${value:-$default}" +} + +# ============================================================================ +# Helper Function: Show Config Summary +# ============================================================================ + +df_show_config() { + echo "Dotfiles Configuration" + echo "======================" + echo "Config File: ${_DF_CONFIG_FILE:-not found}" + echo "Version: $DOTFILES_VERSION" + echo "Directory: $DOTFILES_DIR" + echo "Branch: $DOTFILES_BRANCH" + echo "Display Width: $DF_WIDTH" + echo "MOTD Style: $MOTD_STYLE" + echo "Theme: $ZSH_THEME_NAME" +} diff --git a/dotfiles-refactor/zsh/lib/utils.zsh b/dotfiles-refactor/zsh/lib/utils.zsh new file mode 100644 index 0000000..0413667 --- /dev/null +++ b/dotfiles-refactor/zsh/lib/utils.zsh @@ -0,0 +1,215 @@ +# ============================================================================ +# Shared Utility Functions for Zsh Dotfiles +# ============================================================================ +# Common helper functions used across multiple function files. +# +# This file is typically sourced via bootstrap.zsh, which handles loading +# config.zsh and colors.zsh first. +# +# Direct usage (if needed): +# source "${0:A:h}/../lib/utils.zsh" +# ============================================================================ + +# Prevent double-sourcing +[[ -n "$_DF_UTILS_LOADED" ]] && return 0 +typeset -g _DF_UTILS_LOADED=1 + +# ============================================================================ +# Source Dependencies (if not already loaded via bootstrap) +# ============================================================================ + +_df_lib_dir="${0:A:h}" +[[ ! -d "$_df_lib_dir" ]] && _df_lib_dir="$HOME/.dotfiles/zsh/lib" + +# Source config if not already loaded +[[ -z "$_DF_CONFIG_LOADED" ]] && { + source "${_df_lib_dir}/config.zsh" 2>/dev/null || \ + source "$HOME/.dotfiles/zsh/lib/config.zsh" 2>/dev/null || { + typeset -g DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + typeset -g DOTFILES_HOME="${DOTFILES_HOME:-$DOTFILES_DIR}" + typeset -g DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + typeset -g DF_WIDTH="${DF_WIDTH:-66}" + } +} + +# Source colors if not already loaded +[[ -z "$_DF_COLORS_LOADED" ]] && { + source "${_df_lib_dir}/colors.zsh" 2>/dev/null || \ + source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + typeset -g DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + typeset -g DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + typeset -g DF_LIGHT_GREEN=$'\033[38;5;82m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + } +} + +unset _df_lib_dir + +# ============================================================================ +# Header Box Drawing (Centralized Implementation) +# ============================================================================ +# These functions eliminate header duplication across all scripts. + +# Build a horizontal line of specified character and width +# Usage: _df_hline "═" 66 +_df_hline() { + local char="${1:-═}" + local width="${2:-$DF_WIDTH}" + local line="" + for ((i=0; i${DF_NC} $1"; } +df_print_success() { echo -e "${DF_GREEN}✓${DF_NC} $1"; } +df_print_error() { echo -e "${DF_RED}✗${DF_NC} $1" >&2; } +df_print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +df_print_info() { echo -e "${DF_CYAN}ℹ${DF_NC} $1"; } +df_print_section() { echo -e "${DF_CYAN}$1:${DF_NC}"; } +df_print_indent() { echo " $1"; } + +# ============================================================================ +# Command Dependency Checking +# ============================================================================ + +df_cmd_exists() { command -v "$1" &>/dev/null; } + +df_require_cmd() { + local cmd="$1" + local package="${2:-$1}" + + if ! command -v "$cmd" &>/dev/null; then + df_print_error "$cmd not installed" + echo "Install: sudo pacman -S $package" + return 1 + fi + return 0 +} + +# ============================================================================ +# User Confirmation +# ============================================================================ + +df_confirm() { + local prompt="$1" + local response + + if [[ -n "$ZSH_VERSION" ]]; then + read -q "response?$prompt [y/N]: " + echo + [[ "$response" =~ ^[Yy]$ ]] + else + read -p "$prompt [y/N]: " response + [[ "$response" =~ ^[Yy]$ ]] + fi +} + +df_confirm_warning() { + df_print_warning "$1" + df_confirm "Continue?" +} + +# ============================================================================ +# File/Directory Helpers +# ============================================================================ + +df_in_git_repo() { git rev-parse --git-dir &>/dev/null 2>&1; } +df_git_root() { git rev-parse --show-toplevel 2>/dev/null; } +df_ensure_dir() { [[ ! -d "$1" ]] && mkdir -p "$1"; } + +df_ensure_file() { + local file="$1" content="${2:-}" + if [[ ! -f "$file" ]]; then + df_ensure_dir "$(dirname "$file")" + [[ -n "$content" ]] && echo "$content" > "$file" || touch "$file" + fi +} + +# ============================================================================ +# Environment Checks +# ============================================================================ + +df_in_tmux() { [[ -n "$TMUX" ]]; } +df_is_btrfs() { [[ "$(df -T / 2>/dev/null | awk 'NR==2 {print $2}')" == "btrfs" ]]; } + +# ============================================================================ +# FZF Helpers +# ============================================================================ + +df_fzf_opts() { echo "--height=50% --layout=reverse --border=rounded"; } diff --git a/refactor_backup/bin/dotfiles-compile.sh b/refactor_backup/bin/dotfiles-compile.sh new file mode 100755 index 0000000..1912448 --- /dev/null +++ b/refactor_backup/bin/dotfiles-compile.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env zsh +# ============================================================================ +# Dotfiles Compile - Pre-compile zsh files for faster loading +# ============================================================================ + +set -e + +DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' +} + +# Use DF_WIDTH from utils.zsh or default to 66 +typeset -g WIDTH="${DF_WIDTH:-66}" + +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-compile " + else + local user="${USER:-root}" + local hostname="${HOST:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local hline="" + for ((i=0; i/dev/null && \ + echo -e "${DF_GREEN}✓${DF_NC} Compiled: ${file##*/}" || \ + echo -e "${DF_YELLOW}⚠${DF_NC} Skipped: ${file##*/}" + else + echo -e "${DF_CYAN}○${DF_NC} Current: ${file##*/}" + fi + fi +} + +clean_compiled() { + echo "Removing compiled files..." + + local count=0 + + for zwc in "$DOTFILES_DIR"/**/*.zwc(N); do + rm -f "$zwc" + ((count++)) + done + + rm -f ~/.zshrc.zwc ~/.zshenv.zwc ~/.zprofile.zwc 2>/dev/null + + echo -e "${DF_GREEN}✓${DF_NC} Removed $count compiled files" +} + +compile_all() { + echo -e "${DF_CYAN}Compiling zsh files for faster startup...${DF_NC}" + echo + + echo "Core files:" + compile_file ~/.zshrc + compile_file ~/.zshenv + compile_file ~/.zprofile + echo + + echo "Dotfiles:" + compile_file "$DOTFILES_DIR/zsh/.zshrc" + compile_file "$DOTFILES_DIR/zsh/aliases.zsh" + + for file in "$DOTFILES_DIR/zsh/lib"/*.zsh(N); do + compile_file "$file" + done + + for file in "$DOTFILES_DIR/zsh/functions"/*.zsh(N); do + compile_file "$file" + done + + for file in "$DOTFILES_DIR/zsh/themes"/*.zsh-theme(N); do + compile_file "$file" + done + echo + + if [[ -d ~/.oh-my-zsh ]]; then + echo "Oh-My-Zsh (optional):" + compile_file ~/.oh-my-zsh/oh-my-zsh.sh + echo + fi + + echo -e "${DF_GREEN}✓${DF_NC} Compilation complete" + echo + echo "To measure startup time:" + echo " time zsh -i -c exit" + echo " hyperfine 'zsh -i -c exit' # More accurate" +} + +show_help() { + echo "Usage: dotfiles-compile.sh [OPTIONS]" + echo + echo "Compile zsh files to bytecode for faster shell startup." + echo + echo "Options:" + echo " (none) Compile all zsh files" + echo " --clean Remove all compiled (.zwc) files" + echo " --help Show this help" +} + +# ============================================================================ +# Main +# ============================================================================ + +print_header + +case "${1:-}" in + --clean|-c) clean_compiled ;; + --help|-h) show_help ;; + *) compile_all ;; +esac diff --git a/refactor_backup/bin/dotfiles-doctor.sh b/refactor_backup/bin/dotfiles-doctor.sh new file mode 100755 index 0000000..24e4ca3 --- /dev/null +++ b/refactor_backup/bin/dotfiles-doctor.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Health Check (Arch/CachyOS) +# ============================================================================ +# Usage: +# dotfiles-doctor.sh # Run all checks +# dotfiles-doctor.sh --fix # Attempt automatic fixes +# dotfiles-doctor.sh --quick # Quick essential checks only +# ============================================================================ + +# ============================================================================ +# Source Configuration +# ============================================================================ +# utils.zsh sources config.zsh which sources dotfiles.conf +# This gives us access to all settings including DF_WIDTH, DOTFILES_VERSION, etc. + +_df_source_config() { + local locations=( + "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults if utils.zsh not found + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + DF_WIDTH="${DF_WIDTH:-66}" +} + +_df_source_config + +# ============================================================================ +# Parse Arguments +# ============================================================================ + +DO_FIX=false +QUICK_MODE=false +for arg in "$@"; do + case "$arg" in + --fix) DO_FIX=true ;; + --quick) QUICK_MODE=true ;; + --help|-h) + echo "Usage: dotfiles-doctor.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --fix Attempt automatic fixes for issues" + echo " --quick Run quick essential checks only" + echo " --help Show this help" + exit 0 + ;; + esac +done + +# Track results +TOTAL_CHECKS=0 +PASSED_CHECKS=0 +FAILED_CHECKS=0 +WARNING_CHECKS=0 +FIXED_CHECKS=0 + +# ============================================================================ +# Header (uses DF_WIDTH from config) +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-doctor" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i/dev/null; then + check_pass "Running CachyOS" + elif grep -qi "arch" /etc/os-release 2>/dev/null; then + check_pass "Running Arch Linux" + else + check_fail "Not running on Arch/CachyOS" + fi + else + check_fail "Not running on Linux" + fi + check_pass "Kernel: $(uname -r)" +} + +check_shell() { + print_section "Shell Configuration" + [[ -f "$HOME/.zshrc" ]] && check_pass "Zsh configuration exists" || check_fail "Zsh configuration missing" + [[ "$SHELL" == *"zsh"* ]] && check_pass "Zsh is default shell" || check_warn "Zsh is not default shell" + command -v zsh &>/dev/null && check_pass "Zsh version: $(zsh --version | awk '{print $2}')" +} + +check_symlinks() { + print_section "Symlinks" + for symlink in ~/.zshrc ~/.gitconfig ~/.vimrc ~/.tmux.conf; do + if [[ -L "$symlink" ]]; then + [[ -e "$symlink" ]] && check_pass "$(basename $symlink) → $(readlink $symlink)" || check_fail "$(basename $symlink) is broken" + elif [[ -f "$symlink" ]]; then + check_warn "$(basename $symlink) is regular file (not symlink)" + fi + done +} + +check_pacman() { + print_section "Package Manager" + command -v pacman &>/dev/null && check_pass "Pacman available" || { check_fail "Pacman not found"; return; } + command -v paru &>/dev/null && check_pass "AUR helper: paru" || \ + command -v yay &>/dev/null && check_pass "AUR helper: yay" || check_warn "No AUR helper installed" +} + +check_optional_tools() { + print_section "Optional Tools" + command -v fzf &>/dev/null && check_pass "fzf" || check_warn "fzf not installed" + command -v bat &>/dev/null && check_pass "bat" || check_warn "bat not installed" + command -v eza &>/dev/null && check_pass "eza" || check_warn "eza not installed" + command -v tmux &>/dev/null && check_pass "tmux" || check_warn "tmux not installed" +} + +check_dotfiles_dir() { + print_section "Dotfiles Directory" + [[ -d "$DOTFILES_HOME" ]] && check_pass "Dotfiles: $DOTFILES_HOME" || { check_fail "Dotfiles not found"; return; } + [[ -f "$DOTFILES_HOME/dotfiles.conf" ]] && check_pass "Config file exists" || check_warn "Config file missing" + [[ -d "$DOTFILES_HOME/.git" ]] && check_pass "Git repo initialized" || check_warn "Not a git repository" + check_pass "Version: $DOTFILES_VERSION" + check_pass "Display width: $DF_WIDTH" +} + +print_summary() { + local width="${DF_WIDTH:-66}" + echo "" + printf "${DF_CYAN}─%.0s${DF_NC}" $(seq 1 $width); echo "" + if [[ $FAILED_CHECKS -eq 0 ]]; then + echo -e "${DF_GREEN}✓${DF_NC} All checks passed ($PASSED_CHECKS/$TOTAL_CHECKS)" + else + echo -e "${DF_RED}✗${DF_NC} Issues found: $FAILED_CHECKS failed, $WARNING_CHECKS warnings" + fi + echo "" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + print_header + check_os + check_pacman + check_shell + check_dotfiles_dir + check_symlinks + [[ "$QUICK_MODE" != true ]] && check_optional_tools + print_summary +} + +main "$@" diff --git a/refactor_backup/bin/dotfiles-stats.sh b/refactor_backup/bin/dotfiles-stats.sh new file mode 100755 index 0000000..3156113 --- /dev/null +++ b/refactor_backup/bin/dotfiles-stats.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Shell Analytics (Arch/CachyOS) +# ============================================================================ + +set -e + +readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" + +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_MAGENTA=$'\033[0;35m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' +} + +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" + +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-stats " + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local hline="" && for ((i=0; i/dev/null; then + df_print_header "dotfiles-sync" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i&2; } +print_warning() { echo -e "${DF_YELLOW}⚠${DF_NC} $1"; } +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } + +# ============================================================================ +# Sync Functions +# ============================================================================ + +check_git_repo() { + git -C "$DOTFILES_HOME" rev-parse --git-dir > /dev/null 2>&1 || { print_error "Not a git repository: $DOTFILES_HOME"; exit 1; } +} + +get_sync_status() { + cd "$DOTFILES_HOME" + local local_commits=$(git rev-list --count @{u}..HEAD 2>/dev/null || echo 0) + local remote_commits=$(git rev-list --count HEAD..@{u} 2>/dev/null || echo 0) + echo "$local_commits:$remote_commits" +} + +show_status() { + print_section "Sync Status" + cd "$DOTFILES_HOME" + print_status "Local branch: $(git rev-parse --abbrev-ref HEAD)" + print_status "Last commit: $(git log -1 --pretty=format:'%h - %s' 2>/dev/null || echo 'N/A')" + + local status=$(get_sync_status) + local local_commits="${status%:*}" + local remote_commits="${status#*:}" + + echo "" + [[ $local_commits -gt 0 ]] && print_warning "$local_commits commit(s) ahead of remote" + [[ $remote_commits -gt 0 ]] && print_warning "$remote_commits commit(s) behind remote" + [[ $local_commits -eq 0 && $remote_commits -eq 0 ]] && print_success "In sync with remote" +} + +show_status_short() { + cd "$DOTFILES_HOME" + local changes=$(git status --porcelain | wc -l) + local status=$(get_sync_status) + local local_commits="${status%:*}" + local remote_commits="${status#*:}" + + if [[ $changes -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${changes} local change(s) not pushed" + elif [[ $local_commits -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${local_commits} commit(s) not pushed" + elif [[ $remote_commits -gt 0 ]]; then + echo -e " ${DF_YELLOW}⚠${DF_NC} Dotfiles: ${remote_commits} commit(s) behind remote" + fi +} + +pull_changes() { + print_section "Pulling Changes" + cd "$DOTFILES_HOME" + print_status "Fetching from remote..." + git fetch origin + git pull origin && print_success "Changes pulled" || print_success "Already up to date" +} + +push_changes() { + local commit_msg="$1" + print_section "Pushing Changes" + cd "$DOTFILES_HOME" + + if ! git status --porcelain | grep -q .; then + print_warning "No local changes to push" + return + fi + + print_status "Staging changes..." + git add -A + + if [[ -z "$commit_msg" ]]; then + read -p "Commit message: " commit_msg + [[ -z "$commit_msg" ]] && { print_error "Commit cancelled"; return 1; } + fi + + git commit -m "$commit_msg" + git push origin + print_success "Changes pushed" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + check_git_repo + + case "${1:-status}" in + status) + [[ "$2" == "-s" || "$2" == "--short" ]] && show_status_short || { print_header; show_status; } + ;; + push) + print_header; shift; push_changes "$*" + ;; + pull) + print_header; pull_changes + ;; + -s|--short) + show_status_short + ;; + *) + echo "Usage: $0 {status [-s]|push [message]|pull}" + exit 1 + ;; + esac +} + +main "$@" diff --git a/refactor_backup/bin/dotfiles-update.sh b/refactor_backup/bin/dotfiles-update.sh new file mode 100755 index 0000000..4959d26 --- /dev/null +++ b/refactor_backup/bin/dotfiles-update.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# ============================================================================ +# Update Dotfiles Script +# ============================================================================ + +set -e + +SKIP_DEPS=true +PULL_ONLY=false + +for arg in "$@"; do + case "$arg" in + --skip-deps) SKIP_DEPS=true ;; + --with-deps) SKIP_DEPS=false ;; + --pull-only) PULL_ONLY=true ;; + --help|-h) + echo "Usage: dotfiles-update.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --skip-deps Skip dependency check (default for updates)" + echo " --with-deps Run full dependency check" + echo " --pull-only Only git pull, don't re-run install script" + echo " --help Show this help message" + exit 0 + ;; + esac +done + +# Load Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" +[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf" + +if [[ -f "$DOTFILES_CONF" ]]; then + source "$DOTFILES_CONF" +else + DOTFILES_DIR="$HOME/.dotfiles" + DOTFILES_BRANCH="main" + DOTFILES_RAW_URL="https://raw.githubusercontent.com/adlee-was-taken/dotfiles/main" +fi + +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || { + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' +} + +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" + +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-update" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local hline="" && for ((i=0; i${DF_NC} $1"; } + +# ============================================================================ +# Main +# ============================================================================ + +print_header + +if [ ! -d "$DOTFILES_DIR" ]; then + print_error "Dotfiles directory not found: $DOTFILES_DIR" + exit 1 +fi + +cd "$DOTFILES_DIR" + +print_step "Updating dotfiles from repository..." +git pull origin "$DOTFILES_BRANCH" + +if [ $? -eq 0 ]; then + print_success "Dotfiles updated successfully" + + if [[ "$PULL_ONLY" == true ]]; then + echo + print_success "Pull complete (--pull-only mode)" + exit 0 + fi + + echo + print_success "Update complete!" + echo -e "Reload your shell: ${DF_CYAN}reload${DF_NC} or ${DF_CYAN}source ~/.zshrc${DF_NC}" +else + print_error "Failed to update dotfiles" + exit 1 +fi diff --git a/refactor_backup/bin/dotfiles-vault.sh b/refactor_backup/bin/dotfiles-vault.sh new file mode 100755 index 0000000..733bc24 --- /dev/null +++ b/refactor_backup/bin/dotfiles-vault.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Secrets Vault (Arch/CachyOS) +# ============================================================================ + +set -e + +readonly DOTFILES_HOME="${DOTFILES_HOME:-$HOME/.dotfiles}" +readonly VAULT_DIR="${HOME}/.dotfiles/vault" +readonly VAULT_FILE="${VAULT_DIR}/secrets.enc" + +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_HOME/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_HOME/zsh/lib/colors.zsh" 2>/dev/null || { + DF_RED=$'\033[0;31m' DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' +} + +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" + +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-vault " + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local hline="" && for ((i=0; i&2; } +print_section() { echo ""; echo -e "${DF_BLUE}▶${DF_NC} $1"; } + +get_cipher() { + command -v age &> /dev/null && echo "age" || \ + command -v gpg &> /dev/null && echo "gpg" || \ + { print_error "No encryption tool available"; exit 1; } +} + +init_vault() { + print_section "Initializing Vault" + mkdir -p "$VAULT_DIR" + chmod 700 "$VAULT_DIR" + [[ ! -f "$VAULT_FILE" ]] && { echo "{}" > "$VAULT_FILE"; print_success "Vault initialized"; } || print_success "Vault exists" +} + +vault_list() { + print_section "Secrets" + [[ -f "$VAULT_FILE" ]] && cat "$VAULT_FILE" | grep -o '"[^"]*":' | sed 's/"//g;s/:$//' | while read key; do + echo -e " ${DF_CYAN}•${DF_NC} $key" + done || print_error "No vault file" + echo "" +} + +vault_status() { + print_section "Vault Status" + [[ -d "$VAULT_DIR" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault not initialized"; return; } + [[ -f "$VAULT_FILE" ]] || { echo -e " ${DF_YELLOW}⚠${DF_NC} Vault file not found"; return; } + echo -e " ${DF_CYAN}Location:${DF_NC} $VAULT_FILE" + echo -e " ${DF_CYAN}Encryption:${DF_NC} $(get_cipher)" + echo "" +} + +main() { + print_header + [[ ! -d "$VAULT_DIR" ]] && init_vault + case "${1:-list}" in + init) init_vault ;; + list|ls) vault_list ;; + status) vault_status ;; + *) echo "Usage: $0 {init|list|status}"; exit 1 ;; + esac +} + +main "$@" diff --git a/refactor_backup/bin/dotfiles-version.sh b/refactor_backup/bin/dotfiles-version.sh new file mode 100755 index 0000000..c73d529 --- /dev/null +++ b/refactor_backup/bin/dotfiles-version.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Version Checker +# ============================================================================ + +# ============================================================================ +# Source Configuration +# ============================================================================ + +_df_source_config() { + local locations=( + "${DOTFILES_HOME:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_CYAN=$'\033[0;36m' + DF_NC=$'\033[0m' DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-unknown}" + DOTFILES_BRANCH="${DOTFILES_BRANCH:-main}" + DF_WIDTH="${DF_WIDTH:-66}" +} + +_df_source_config + +# ============================================================================ +# Parse Arguments +# ============================================================================ + +CHECK_ONLY=false +for arg in "$@"; do + case "$arg" in + --check|-c) CHECK_ONLY=true ;; + --help|-h) echo "Usage: dotfiles-version.sh [--check]"; exit 0 ;; + esac +done + +# ============================================================================ +# Header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "dotfiles-version " + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i/dev/null || echo "unknown"; } || echo "not a git repo" +} + +get_local_date() { + [[ -d "${DOTFILES_DIR}/.git" ]] && { cd "$DOTFILES_DIR"; git log -1 --format="%ci" 2>/dev/null | cut -d' ' -f1 || echo "unknown"; } || echo "unknown" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + local local_commit=$(get_local_commit) + local local_date=$(get_local_date) + + if [[ "$CHECK_ONLY" == true ]]; then + echo "Version: $DOTFILES_VERSION ($local_commit)" + exit 0 + fi + + print_header + + echo -e "${DF_CYAN}Local:${DF_NC}" + echo -e " Version: ${DF_GREEN}${DOTFILES_VERSION}${DF_NC}" + echo -e " Commit: ${local_commit}" + echo -e " Date: ${local_date}" + echo -e " Path: ${DOTFILES_DIR}" + echo -e " Branch: ${DOTFILES_BRANCH}" + echo -e " Width: ${DF_WIDTH}" + echo +} + +main "$@" diff --git a/refactor_backup/setup/setup-espanso.sh b/refactor_backup/setup/setup-espanso.sh new file mode 100755 index 0000000..6bc40b4 --- /dev/null +++ b/refactor_backup/setup/setup-espanso.sh @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +# ============================================================================ +# Espanso Setup and Configuration Script +# ============================================================================ + +set -e + +# ============================================================================ +# Load Configuration +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DOTFILES_CONF="${SCRIPT_DIR}/../dotfiles.conf" +[[ -f "$DOTFILES_CONF" ]] || DOTFILES_CONF="$HOME/.dotfiles/dotfiles.conf" + +if [[ -f "$DOTFILES_CONF" ]]; then + source "$DOTFILES_CONF" +else + DOTFILES_DIR="$HOME/.dotfiles" + USER_FULLNAME="" + USER_EMAIL="" + USER_PHONE="" + USER_WEBSITE="" + USER_GITHUB="" +fi + +# ============================================================================ +# Colors +# ============================================================================ + +# Source shared colors and utils (provides DF_WIDTH) +source "$DOTFILES_DIR/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null || \ +source "$DOTFILES_DIR/zsh/lib/colors.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/colors.zsh" 2>/dev/null || { + typeset -g DF_NC=$'\033[0m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + typeset -g DF_BLUE=$'\033[38;5;39m' DF_CYAN=$'\033[38;5;51m' + typeset -g DF_GREEN=$'\033[38;5;82m' DF_YELLOW=$'\033[38;5;220m' + typeset -g DF_RED=$'\033[38;5;196m' DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' + typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' DF_LIGHT_GREEN=$'\033[38;5;82m' +} + +# Use DF_WIDTH from utils.zsh or default to 66 +readonly WIDTH="${DF_WIDTH:-66}" + +# ============================================================================ +# MOTD-style header +# ============================================================================ + +print_header() { + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local script_name="setup-espanso" + local datetime=$(date '+%a %b %d %H:%M') + + local hline="" + for ((i=0; i${DF_NC} $1" +} + +print_success() { + echo -e "${DF_GREEN}✓${DF_NC} $1" +} + +print_warning() { + echo -e "${DF_YELLOW}⚠${DF_NC} $1" +} + +print_error() { + echo -e "${DF_RED}✗${DF_NC} $1" +} + +ask_yes_no() { + local prompt="$1" + local default="${2:-y}" + + if [[ "$default" == "y" ]]; then + prompt="$prompt [Y/n]: " + else + prompt="$prompt [y/N]: " + fi + + read -p "$prompt" response + response=${response:-$default} + [[ "$response" =~ ^[Yy]$ ]] +} + +# ============================================================================ +# Functions +# ============================================================================ + +check_espanso() { + if ! command -v espanso &> /dev/null; then + print_error "espanso is not installed" + echo "Install it from: https://espanso.org/install/" + echo "Or run the main dotfiles install script" + exit 1 + fi + print_success "espanso is installed: $(espanso --version)" +} + +show_espanso_status() { + print_step "Checking espanso status" + + if espanso status | grep -q "running"; then + print_success "espanso service is running" + else + print_warning "espanso service is not running" + if ask_yes_no "Start espanso service?"; then + espanso service start + print_success "espanso service started" + fi + fi +} + +personalize_config() { + print_step "Personalizing espanso configuration" + + local personal_file="$HOME/.config/espanso/match/personal.yml" + + if [ ! -f "$personal_file" ]; then + print_warning "Personal config file not found, creating from template..." + mkdir -p "$(dirname "$personal_file")" + cat > "$personal_file" << 'EOF' +# ============================================================================ +# Personal Espanso Snippets +# ============================================================================ +# Edit these with your own information + +matches: + # Personal info + - trigger: "..myemail" + replace: "your.email@example.com" + + - trigger: "..myname" + replace: "Your Full Name" + + - trigger: "..myphone" + replace: "+1 (555) 123-4567" + + - trigger: "..myweb" + replace: "https://yourwebsite.com" + + - trigger: "..mygithub" + replace: "https://github.com/yourusername" + + # Email signature + - trigger: "..sig" + replace: | + Best regards, + Your Full Name + your.email@example.com + + # Address (customize as needed) + - trigger: "..myaddr" + replace: | + 123 Main Street + City, ST 12345 +EOF + print_success "Created personal.yml template" + fi + + echo + echo "Let's personalize your espanso configuration!" + echo "(Press Enter to keep existing/default values)" + echo + + # Use config values as defaults, prompt for any missing + local fullname="${USER_FULLNAME}" + local email="${USER_EMAIL}" + local phone="${USER_PHONE}" + local website="${USER_WEBSITE}" + local github="${USER_GITHUB}" + + [[ -z "$fullname" ]] && read -p "Your full name: " fullname + [[ -z "$email" ]] && read -p "Your email: " email + [[ -z "$phone" ]] && read -p "Your phone (optional): " phone + [[ -z "$website" ]] && read -p "Your website (optional): " website + [[ -z "$github" ]] && read -p "Your GitHub username (optional): " github + + # Create backup + cp "$personal_file" "$personal_file.backup" + + # Update values + [[ -n "$email" ]] && sed -i "s/your.email@example.com/$email/g" "$personal_file" + [[ -n "$fullname" ]] && sed -i "s/Your Full Name/$fullname/g" "$personal_file" + [[ -n "$phone" ]] && sed -i "s/+1 (555) 123-4567/$phone/g" "$personal_file" + [[ -n "$website" ]] && sed -i "s|https://yourwebsite.com|$website|g" "$personal_file" + [[ -n "$github" ]] && sed -i "s/yourusername/$github/g" "$personal_file" + + print_success "Personal configuration updated!" + print_warning "Backup saved to: $personal_file.backup" + + # Suggest updating dotfiles.conf for future installs + echo + echo -e "${DF_BLUE}Tip:${DF_NC} Add these to dotfiles.conf for future installs:" + echo " USER_FULLNAME=\"$fullname\"" + echo " USER_EMAIL=\"$email\"" + [[ -n "$github" ]] && echo " USER_GITHUB=\"$github\"" +} + +install_packages() { + print_step "Installing espanso packages" + + echo + echo "Available packages to install:" + echo " 1. emoji - Emoji snippets (e.g., :smile: → 😊)" + echo " 2. greek-letters - Greek letters (e.g., :alpha: → α)" + echo " 3. math - Math symbols (e.g., :sum: → ∑)" + echo + + if ask_yes_no "Install emoji package?"; then + espanso install emoji --force + print_success "Emoji package installed" + fi + + if ask_yes_no "Install greek-letters package?"; then + espanso install greek-letters --force + print_success "Greek letters package installed" + fi + + if ask_yes_no "Install math package?"; then + espanso install math --force + print_success "Math package installed" + fi +} + +list_installed_packages() { + print_step "Installed espanso packages" + echo + espanso package list + echo +} + +show_usage_tips() { + print_step "Usage tips" + + cat << EOF + +${DF_GREEN}Espanso Quick Start:${DF_NC} + +${DF_YELLOW}Toggle espanso on/off:${DF_NC} + ALT+SHIFT+E + +${DF_YELLOW}Open search menu:${DF_NC} + ALT+SPACE + +${DF_YELLOW}Basic triggers:${DF_NC} + ..date → Current date (YYYY-MM-DD) + ..time → Current time (HH:MM:SS) + ..shrug → ¯\\_(ツ)_/¯ + ..gstat → git status + ..myemail → Your email + +${DF_YELLOW}Espanso commands:${DF_NC} + espanso status - Check if running + espanso restart - Restart service + espanso log - View logs + +${DF_YELLOW}Configuration files:${DF_NC} + ~/.config/espanso/match/base.yml - Main snippets + ~/.config/espanso/match/personal.yml - Your personal snippets + +EOF +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + print_header + + check_espanso + show_espanso_status + + echo + if ask_yes_no "Personalize your configuration?"; then + personalize_config + fi + + echo + if ask_yes_no "Install additional espanso packages?"; then + install_packages + fi + + echo + list_installed_packages + + echo + show_usage_tips + + echo + print_success "Espanso setup complete!" + echo + echo "Try typing ${DF_YELLOW}..date${DF_NC} in any application to test it!" +} + +main "$@" diff --git a/refactor_backup/setup/setup-wizard.sh b/refactor_backup/setup/setup-wizard.sh new file mode 100755 index 0000000..b893fee --- /dev/null +++ b/refactor_backup/setup/setup-wizard.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# ============================================================================ +# Dotfiles Interactive Setup Wizard +# ============================================================================ + +set -e + +# ============================================================================ +# Source Configuration +# ============================================================================ + +_df_source_config() { + local locations=( + "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/utils.zsh" + "$HOME/.dotfiles/zsh/lib/utils.zsh" + ) + for loc in "${locations[@]}"; do + [[ -f "$loc" ]] && { source "$loc"; return 0; } + done + + # Fallback defaults + DF_GREEN=$'\033[0;32m' DF_YELLOW=$'\033[1;33m' DF_RED=$'\033[0;31m' + DF_BLUE=$'\033[0;34m' DF_CYAN=$'\033[0;36m' DF_NC=$'\033[0m' + DF_GREY=$'\033[38;5;242m' DF_LIGHT_BLUE=$'\033[38;5;39m' + DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' DF_LIGHT_GREEN=$'\033[38;5;82m' + DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + DOTFILES_VERSION="${DOTFILES_VERSION:-1.0.0}" + DF_WIDTH="${DF_WIDTH:-66}" +} + +_df_source_config + +# ============================================================================ +# Header +# ============================================================================ + +print_header() { + if declare -f df_print_header &>/dev/null; then + df_print_header "setup-wizard" + else + local user="${USER:-root}" + local hostname="${HOSTNAME:-$(hostname -s 2>/dev/null)}" + local datetime=$(date '+%a %b %d %H:%M') + local width="${DF_WIDTH:-66}" + local hline="" && for ((i=0; i/dev/null && HAS_GUM=true + +wizard_confirm() { + local prompt="$1" + local default="${2:-yes}" + if [[ "$HAS_GUM" == true ]]; then + [[ "$default" == "yes" ]] && gum confirm --default=yes "$prompt" || gum confirm --default=no "$prompt" + else + local yn_prompt="[Y/n]" + [[ "$default" == "no" ]] && yn_prompt="[y/N]" + read -p "$prompt $yn_prompt: " response + response=${response:-${default:0:1}} + [[ "$response" =~ ^[Yy] ]] + fi +} + +wizard_input() { + local prompt="$1" + local default="$2" + if [[ "$HAS_GUM" == true ]]; then + gum input --placeholder "$default" --value "$default" --prompt "$prompt: " + else + read -p "$prompt [$default]: " response + echo "${response:-$default}" + fi +} + +# ============================================================================ +# Wizard Steps +# ============================================================================ + +step_welcome() { + clear + print_header + echo -e "${DF_BOLD}Welcome to Dotfiles Setup Wizard${DF_NC}" + echo -e "${DF_DIM}Version: $DOTFILES_VERSION | Width: $DF_WIDTH${DF_NC}" + echo + wizard_confirm "Ready to begin?" || { echo "Cancelled."; exit 0; } +} + +step_user_info() { + echo -e "\n${DF_BLUE}▶${DF_NC} Personal Information" + USER_FULLNAME=$(wizard_input "Full Name" "${USER_FULLNAME:-}") + USER_EMAIL=$(wizard_input "Email" "${USER_EMAIL:-}") + USER_GITHUB=$(wizard_input "GitHub Username" "${USER_GITHUB:-}") +} + +step_summary() { + echo -e "\n${DF_GREEN}✓${DF_NC} Setup Complete!" + echo + echo " Name: $USER_FULLNAME" + echo " Email: $USER_EMAIL" + echo " GitHub: $USER_GITHUB" + echo + echo -e "${DF_DIM}Run 'source ~/.zshrc' to apply changes.${DF_NC}" +} + +# ============================================================================ +# Main +# ============================================================================ + +main() { + step_welcome + step_user_info + step_summary +} + +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@" diff --git a/refactor_backup/zsh/.zshrc b/refactor_backup/zsh/.zshrc new file mode 100644 index 0000000..d77513d --- /dev/null +++ b/refactor_backup/zsh/.zshrc @@ -0,0 +1,377 @@ +# ============================================================================ +# ADLee's ZSH Configuration (Optimized) +# ============================================================================ +# Optimizations: +# - Deferred/lazy loading for heavy plugins +# - Parallel background loading where possible +# - Compiled zsh files (.zwc) for faster parsing +# - Minimal command -v checks (cached) +# ============================================================================ + +# --- Profiling (uncomment to debug slow startup) --- +# zmodload zsh/zprof + +# ============================================================================ +# Instant Prompt (show prompt immediately while loading continues) +# ============================================================================ + +# Cache command existence checks +typeset -gA _cmd_cache +_has_cmd() { + if [[ -z "${_cmd_cache[$1]+x}" ]]; then + _cmd_cache[$1]=$(command -v "$1" &>/dev/null && echo 1 || echo 0) + fi + [[ "${_cmd_cache[$1]}" == "1" ]] +} + +# ============================================================================ +# Core Settings (fast, no external calls) +# ============================================================================ + +export ZSH="$HOME/.oh-my-zsh" +export EDITOR='vim' +export VISUAL='vim' +export LANG=en_US.UTF-8 +export LC_ALL=en_US.UTF-8 +export PATH="$HOME/.local/bin:$PATH" + +# History (set early) +HISTSIZE=10000 +SAVEHIST=10000 +HISTFILE=~/.zsh_history +setopt SHARE_HISTORY APPEND_HISTORY EXTENDED_HISTORY +setopt HIST_IGNORE_ALL_DUPS HIST_FIND_NO_DUPS HIST_IGNORE_SPACE + +# ============================================================================ +# Theme Configuration +# ============================================================================ + +ZSH_THEME="adlee" + +# ============================================================================ +# Oh-My-Zsh Settings (before sourcing) +# ============================================================================ + +zstyle ':omz:update' mode reminder +zstyle ':omz:update' frequency 13 +COMPLETION_WAITING_DOTS="true" +HIST_STAMPS="yyyy-mm-dd" + +# Disable oh-my-zsh auto-update check on every load (slow) +DISABLE_AUTO_UPDATE="true" + +# ============================================================================ +# Plugins - Optimized Selection +# ============================================================================ +# Removed heavy plugins that aren't always needed +# kubectl, docker-compose loaded on-demand + +plugins=( + git + sudo + zsh-autosuggestions + zsh-syntax-highlighting +) + +# Conditionally add plugins only if tools exist +[[ -d "$HOME/.fzf" || -f "/usr/share/fzf/key-bindings.zsh" ]] && plugins+=(fzf) + +# ============================================================================ +# Load Oh-My-Zsh +# ============================================================================ + +source $ZSH/oh-my-zsh.sh + +# ============================================================================ +# Aliases (inline - no external checks during definition) +# ============================================================================ + +# Navigation +alias ..='cd ..' +alias ...='cd ../..' +alias ....='cd ../../..' +alias ~='cd ~' + +# Git shortcuts +alias g='git' +alias gs='git status' +alias ga='git add' +alias gc='git commit' +alias gp='git push' +alias gl='git pull' +alias gd='git diff' +alias gco='git checkout' +alias gb='git branch' +alias glog='git log --oneline --graph --decorate --all' + +# Docker shortcuts +alias d='docker' +alias dc='docker-compose' +alias dps='docker ps' +alias dpa='docker ps -a' +alias di='docker images' +alias dex='docker exec -it' + +# System +alias h='history' +alias c='clear' +alias rm='rm -i' +alias cp='cp -i' +alias mv='mv -i' +alias myip='curl -s ifconfig.me' +alias ports='netstat -tulanp' + +# ============================================================================ +# Deferred Alias Setup (runs after prompt displays) +# ============================================================================ + +_setup_tool_aliases() { + # eza/ls aliases + if _has_cmd eza; then + alias ls='eza --icons' + alias ll='eza -lah --icons' + alias la='eza -a --icons' + alias lt='eza --tree --level=2 --icons' + else + alias ll='ls -lah' + alias la='ls -A' + fi + + # bat/cat aliases + if _has_cmd batcat; then + alias cat='batcat --paging=never' + alias bat='batcat' + elif _has_cmd bat; then + alias cat='bat --paging=never' + fi +} + +# ============================================================================ +# Functions +# ============================================================================ + +push-it() { git add . && git commit -m "$1" && git push origin; } +mkcd() { mkdir -p "$1" && cd "$1"; } +ff() { find . -type f -iname "*$1*"; } +fdir() { find . -type d -iname "*$1*"; } +backup() { cp "$1" "$1.backup-$(date +%Y%m%d-%H%M%S)"; } + +extract() { + [[ ! -f "$1" ]] && { echo "'$1' is not a valid file"; return 1; } + case "$1" in + *.tar.bz2) tar xjf "$1" ;; + *.tar.gz) tar xzf "$1" ;; + *.bz2) bunzip2 "$1" ;; + *.rar) unrar x "$1" ;; + *.gz) gunzip "$1" ;; + *.tar) tar xf "$1" ;; + *.tbz2) tar xjf "$1" ;; + *.tgz) tar xzf "$1" ;; + *.zip) unzip "$1" ;; + *.Z) uncompress "$1" ;; + *.7z) 7z x "$1" ;; + *) echo "'$1' cannot be extracted via extract()" ;; + esac +} + +# ============================================================================ +# Key Bindings! +# ============================================================================ + +bindkey "^[[1;5C" forward-word +bindkey "^[[1;5D" backward-word +bindkey "^[[H" beginning-of-line +bindkey "^[[F" end-of-line +bindkey "^[[3~" delete-char + +# Alt+R to reload +function reload-zsh() { echo "Reloading ~/.zshrc ... "; source ~/.zshrc; zle reset-prompt} +zle -N reload-zsh +bindkey "^[r" reload-zsh + +# ============================================================================ +# FZF Configuration (deferred) +# ============================================================================ + +_setup_fzf() { + export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border' + if _has_cmd fd; then + export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' + export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" + fi +} + +# ============================================================================ +# Lazy-loaded Tools +# ============================================================================ + +# NVM +export NVM_DIR="$HOME/.nvm" +if [[ -s "$NVM_DIR/nvm.sh" ]]; then + _load_nvm() { + unfunction nvm node npm npx 2>/dev/null + \. "$NVM_DIR/nvm.sh" + [[ -s "$NVM_DIR/bash_completion" ]] && \. "$NVM_DIR/bash_completion" + } + nvm() { _load_nvm; nvm "$@"; } + node() { _load_nvm; node "$@"; } + npm() { _load_nvm; npm "$@"; } + npx() { _load_nvm; npx "$@"; } +fi + +# Python virtualenvwrapper +export WORKON_HOME=$HOME/.virtualenvs +if [[ -f /usr/local/bin/virtualenvwrapper.sh ]]; then + _load_venv() { + unfunction workon mkvirtualenv rmvirtualenv 2>/dev/null + export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 + source /usr/local/bin/virtualenvwrapper.sh + } + workon() { _load_venv; workon "$@"; } + mkvirtualenv() { _load_venv; mkvirtualenv "$@"; } +fi + +# Rust cargo +[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env" + +# kubectl (lazy load - it's VERY slow to initialize) +if _has_cmd kubectl; then + kubectl() { + unfunction kubectl 2>/dev/null + source <(command kubectl completion zsh) + command kubectl "$@" + } +fi + +# ============================================================================ +# Dotfiles Configuration +# ============================================================================ + +_dotfiles_dir="$HOME/.dotfiles" + +# Load dotfiles.conf first (sets DOTFILES_DIR and other vars) +if [[ -f "$_dotfiles_dir/dotfiles.conf" ]]; then + source "$_dotfiles_dir/dotfiles.conf" +else + DOTFILES_DIR="$HOME/.dotfiles" + DOTFILES_BRANCH="main" +fi + +# Source shared colors library +[[ -f "$_dotfiles_dir/zsh/lib/colors.zsh" ]] && source "$_dotfiles_dir/zsh/lib/colors.zsh" + +# Source dotfiles aliases +[[ -f "$_dotfiles_dir/zsh/aliases.zsh" ]] && source "$_dotfiles_dir/zsh/aliases.zsh" + +# Load command-palette immediately (needed for keybindings) +[[ -f "$_dotfiles_dir/zsh/functions/command-palette.zsh" ]] && \ + source "$_dotfiles_dir/zsh/functions/command-palette.zsh" + +# ============================================================================ +# Deferred Loading (runs in background after prompt) +# ============================================================================ + +_deferred_load() { + # Setup tool aliases + _setup_tool_aliases + + # Setup FZF + _has_cmd fzf && _setup_fzf + + # ----------------------------------------------------------------------- + # Load all function files from functions directory + # Excludes command-palette.zsh (already loaded) and motd.zsh (loaded separately) + # ----------------------------------------------------------------------- + local func_dir="$_dotfiles_dir/zsh/functions" + if [[ -d "$func_dir" ]]; then + for func_file in "$func_dir"/*.zsh; do + [[ -f "$func_file" ]] || continue + + # Skip files that are loaded elsewhere + case "${func_file:t}" in + command-palette.zsh) continue ;; # Loaded early for keybindings + motd.zsh) continue ;; # Loaded after prompt + esac + + source "$func_file" + done + fi +} + +# ============================================================================ +# Background Tasks (truly async, won't block) +# ============================================================================ + +_background_tasks() { + # Check for dotfiles updates + if [[ "${DOTFILES_AUTO_SYNC_CHECK:-true}" == "true" ]]; then + $_dotfiles_dir/bin/dotfiles-sync.sh status -s 2> /dev/null + fi + _df_check_sys_updates +} + +_df_check_sys_updates() { + # Check number of available updates and export + if _has_cmd checkupdates; then + export UPDATE_PKG_COUNT=$(checkupdates 2>/dev/null | wc -l) + else + export UPDATE_PKG_COUNT=0 + fi +} + +# ============================================================================ +# Initialization Strategy +# ============================================================================ + +# Method 1: Use zsh-defer if available (best option) +if [[ -f "$_dotfiles_dir/zsh/plugins/zsh-defer/zsh-defer.plugin.zsh" ]]; then + source "$_dotfiles_dir/zsh/plugins/zsh-defer/zsh-defer.plugin.zsh" + zsh-defer _deferred_load + zsh-defer _background_tasks + zsh-defer -c '[[ -f "$_dotfiles_dir/zsh/functions/motd.zsh" ]] && source "$_dotfiles_dir/zsh/functions/motd.zsh" && show_motd' +else + # Method 2: Use sched for deferred loading (built-in) + # Runs after first prompt is displayed + zmodload zsh/sched 2>/dev/null + + _first_prompt_hook() { + # Remove this hook after first run + add-zsh-hook -d precmd _first_prompt_hook + + # Run deferred loading + _deferred_load + + # Show MOTD after prompt + if [[ -f "$_dotfiles_dir/zsh/functions/motd.zsh" ]]; then + source "$_dotfiles_dir/zsh/functions/motd.zsh" + case "${MOTD_STYLE:-compact}" in + compact) show_motd ;; + mini) show_motd_mini ;; + full) show_motd_full ;; + esac + fi + + # Background tasks + _background_tasks + } + + autoload -Uz add-zsh-hook + add-zsh-hook precmd _first_prompt_hook +fi + +# ============================================================================ +# OS-Specific +# ============================================================================ + +[[ "$(uname -s)" == "Darwin"* ]] && export HOMEBREW_NO_ANALYTICS=1 + +# ============================================================================ +# Local Configuration (always load - user overrides) +# ============================================================================ + +[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local + +# ============================================================================ +# End - Profiling output (uncomment zprof at top to use) +# ============================================================================ +# zprof diff --git a/refactor_backup/zsh/.zshrc.zwc b/refactor_backup/zsh/.zshrc.zwc new file mode 100644 index 0000000..4e89c30 Binary files /dev/null and b/refactor_backup/zsh/.zshrc.zwc differ diff --git a/refactor_backup/zsh/aliases.zsh b/refactor_backup/zsh/aliases.zsh new file mode 100644 index 0000000..c9fbc11 --- /dev/null +++ b/refactor_backup/zsh/aliases.zsh @@ -0,0 +1,149 @@ +# ============================================================================ +# Dotfiles Command Aliases +# ============================================================================ +# Convenient shortcuts for dotfiles management scripts +# +# Source this file in .zshrc (already included by default) +# ============================================================================ + +# Dotfiles directory +_df_dir="${DOTFILES_DIR:-$HOME/.dotfiles}" +_df_bin="$_df_dir/bin" + +# Helper to run dotfiles scripts (uses full path with fallback to PATH) +_df_run() { + local script="$1" + shift + if [[ -x "$_df_bin/$script" ]]; then + "$_df_bin/$script" "$@" + elif command -v "$script" &>/dev/null; then + "$script" "$@" + else + echo "Error: $script not found" >&2 + echo "Run the installer: ~/.dotfiles/install.sh" >&2 + return 1 + fi +} + +# --- Quality of Life Aliases --- +alias hist="history" + + +# --- Core Dotfiles Commands --- +alias dfdir='cd $HOME/.dotfiles' +alias c.='cd $HOME/.dotfiles' + +# Note: 'df' not aliased to avoid conflict with disk free utility + +# Doctor - health check +dfd() { _df_run dotfiles-doctor.sh "$@"; } +doctor() { _df_run dotfiles-doctor.sh "$@"; } +dffix() { _df_run dotfiles-doctor.sh --fix "$@"; } + +# Sync - multi-machine synchronization +dfs() { _df_run dotfiles-sync.sh "$@"; } +dfsync() { _df_run dotfiles-sync.sh "$@"; } +dfpush() { _df_run dotfiles-sync.sh push "${1:-Dotfiles update $(date '+%Y-%m-%d %H:%M')}"; } +dfpull() { _df_run dotfiles-sync.sh pull "$@"; } +dfstatus() { _df_run dotfiles-sync.sh status "$@"; } + +# Update - pull latest and reinstall +dfu() { _df_run dotfiles-update.sh "$@"; } +dfupdate() { _df_run dotfiles-update.sh "$@"; } + +# Version - check version info +dfv() { _df_run dotfiles-version.sh "$@"; } +dfversion() { _df_run dotfiles-version.sh "$@"; } + +# Stats - shell analytics (removed short 'stats' alias to force explicit usage) +dfstats() { _df_run dotfiles-stats.sh "$@"; } +tophist() { _df_run dotfiles-stats.sh --top "$@"; } +suggest() { _df_run dotfiles-stats.sh --suggest "$@"; } + +# Vault - secrets management +vault() { _df_run dotfiles-vault.sh "$@"; } +vls() { _df_run dotfiles-vault.sh list "$@"; } +vget() { _df_run dotfiles-vault.sh get "$@"; } +vset() { _df_run dotfiles-vault.sh set "$@"; } + +# Compile - compile zsh files for speed +dfcompile() { _df_run dotfiles-compile.sh "$@"; } + +# --- Quick Edit Aliases --- +alias v.zshrc='${EDITOR:-vim} ~/.zshrc' +alias v.conf='${EDITOR:-vim} ~/.dotfiles/dotfiles.conf' +alias v.edit='cd ~/.dotfiles && ${EDITOR:-vim} .' +alias v.alias='${EDITOR:-vim} ~/.dotfiles/zsh/aliases.zsh' +alias v.motd='${EDITOR:-vim} ~/.dotfiles/zsh/functions/motd.zsh' +alias v.theme='${EDITOR:-vim} ~/.dotfiles/zsh/themes/adlee.zsh-theme' + +# --- Reload Aliases --- +alias reload='source ~/.zshrc' +alias rl='source ~/.zshrc' + +# ============================================================================ +# Function Wrappers (for tab completion) +# ============================================================================ + +# Dotfiles main command with subcommands +dotfiles-cli() { + case "${1:-help}" in + doctor|doc|d) shift; _df_run dotfiles-doctor.sh "$@" ;; + sync|s) shift; _df_run dotfiles-sync.sh "$@" ;; + update|up|u) shift; _df_run dotfiles-update.sh "$@" ;; + version|ver|v) shift; _df_run dotfiles-version.sh "$@" ;; + stats|st) shift; _df_run dotfiles-stats.sh "$@" ;; + vault|vlt) shift; _df_run dotfiles-vault.sh "$@" ;; + compile|comp) shift; _df_run dotfiles-compile.sh "$@" ;; + edit|e) cd ~/.dotfiles && ${EDITOR:-vim} . ;; + cd) cd ~/.dotfiles ;; + help|--help|-h|*) + echo "Dotfiles CLI" + echo + echo "Usage: dotfiles-cli [args]" + echo + echo "Commands:" + echo " doctor, d Run health check (--fix to auto-repair)" + echo " sync, s Sync dotfiles across machines" + echo " update, u Pull latest and reinstall" + echo " version, v Show version info" + echo " stats, st Shell analytics dashboard" + echo " vault, vlt Secrets management" + echo " compile Compile zsh files for speed" + echo " edit, e Open dotfiles in editor" + echo " cd Change to dotfiles directory" + echo + echo "Aliases:" + echo " dfd, dffix, dfs, dfpush, dfpull, dfu, dfv, dfstats, vault" + echo + echo "Note: 'stats' alias removed - use 'dfstats' instead" + ;; + esac +} + +# Short alias for the CLI +alias dfc='dotfiles-cli' + +# Additional quality of life aliases/functions. + +# Use glow to "less" Markdown files: +alias glow='glow -p' +less() { + if ! command -v glow &>/dev/null; then + command less "$@" + return + fi + + if [[ $# -eq 1 && "$1" == *.md ]]; then + glow -p "$1" # -p for pager mode (like less) + else + command less "$@" + fi +} + +# Arch system upgrade with snapper pre/post +sys-update() { + local update_date=$(date -d "today" +"%Y-%m-%d %H:%M") + sudo snapper -c root create --description "System Update ${update_date}" --command "sudo pacman -Syu" + _df_check_sys_updates +} diff --git a/refactor_backup/zsh/aliases.zsh.zwc b/refactor_backup/zsh/aliases.zsh.zwc new file mode 100644 index 0000000..2118194 Binary files /dev/null and b/refactor_backup/zsh/aliases.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/btrfs-helpers.zsh b/refactor_backup/zsh/functions/btrfs-helpers.zsh new file mode 100644 index 0000000..3088b58 --- /dev/null +++ b/refactor_backup/zsh/functions/btrfs-helpers.zsh @@ -0,0 +1,210 @@ +# ============================================================================ +# Btrfs Helpers for Arch/CachyOS +# ============================================================================ +# Quick commands for btrfs filesystem management +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g BTRFS_DEFAULT_MOUNT="${BTRFS_DEFAULT_MOUNT:-/}" + +_btrfs_check() { + df_require_cmd btrfs btrfs-progs || return 1 + if ! df_is_btrfs; then + df_print_warning "Root filesystem is not btrfs" + return 1 + fi + return 0 +} + +btrfs-usage() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Filesystem Usage: ${mount}" + sudo btrfs filesystem usage "$mount" -h +} + +btrfs-subs() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Subvolumes" + df_print_section "Subvolume List" + sudo btrfs subvolume list "$mount" | while read -r line; do + local path=$(echo "$line" | awk '{print $NF}') + local id=$(echo "$line" | awk '{print $2}') + df_print_indent "● [$id] $path" + done + echo "" + df_print_section "Default Subvolume" + sudo btrfs subvolume get-default "$mount" +} + +btrfs-balance() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + local usage="${2:-50}" + df_print_func_name "Btrfs Balance" + df_confirm_warning "This may take a while and use significant I/O" || return 0 + echo "" + df_print_step "Balancing data chunks with <${usage}% usage..." + sudo btrfs balance start -dusage="$usage" -musage="$usage" "$mount" -v + [[ $? -eq 0 ]] && df_print_success "Balance completed" || df_print_warning "Balance finished (may have been interrupted)" +} + +btrfs-balance-status() { + _btrfs_check || return 1 + df_print_func_name "Btrfs Balance Status" + sudo btrfs balance status "${1:-$BTRFS_DEFAULT_MOUNT}" +} + +btrfs-balance-cancel() { + _btrfs_check || return 1 + df_print_step "Cancelling balance..." + sudo btrfs balance cancel "${1:-$BTRFS_DEFAULT_MOUNT}" +} + +btrfs-scrub() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Scrub" + local status=$(sudo btrfs scrub status "$mount" 2>/dev/null) + if echo "$status" | grep -q "running"; then + df_print_section "Scrub Status (running)" + echo "$status" | sed 's/^/ /' + return 0 + fi + df_print_warning "Scrub verifies data integrity and may take hours" + df_confirm "Start scrub?" || return 0 + df_print_step "Starting scrub..." + sudo btrfs scrub start "$mount" + echo "" + df_print_section "Scrub Status" + sudo btrfs scrub status "$mount" + df_print_info "Monitor with: btrfs-scrub-status" +} + +btrfs-scrub-status() { + _btrfs_check || return 1 + df_print_func_name "Btrfs Scrub Status" + sudo btrfs scrub status "${1:-$BTRFS_DEFAULT_MOUNT}" +} + +btrfs-scrub-cancel() { + _btrfs_check || return 1 + df_print_step "Cancelling scrub..." + sudo btrfs scrub cancel "${1:-$BTRFS_DEFAULT_MOUNT}" +} + +btrfs-defrag() { + _btrfs_check || return 1 + local target="${1:-.}" + [[ ! -e "$target" ]] && { df_print_error "Target not found: $target"; return 1; } + df_print_func_name "Btrfs Defragment" + if [[ -d "$target" ]]; then + df_print_warning "Recursive defrag on directory: $target" + df_confirm "Continue?" || return 0 + sudo btrfs filesystem defragment -r -v "$target" + else + df_print_step "Defragmenting: $target" + sudo btrfs filesystem defragment -v "$target" + fi + df_print_success "Defragmentation complete" +} + +btrfs-compress() { + _btrfs_check || return 1 + df_require_cmd compsize || return 1 + df_print_func_name "Btrfs Compression Statistics" + sudo compsize "${1:-$BTRFS_DEFAULT_MOUNT}" +} + +btrfs-info() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Filesystem Information" + df_print_section "Filesystem Show" + sudo btrfs filesystem show "$mount" + echo "" + df_print_section "Filesystem df" + sudo btrfs filesystem df "$mount" + echo "" + df_print_section "Device Stats" + sudo btrfs device stats "$mount" +} + +btrfs-health() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Health Check" + local issues=0 + + df_print_section "Device Errors" + local errors=$(sudo btrfs device stats "$mount" 2>/dev/null | grep -v " 0$" | grep -v "^$") + [[ -z "$errors" ]] && df_print_indent "✓ No errors" || { df_print_indent "✗ Errors detected:"; echo "$errors" | sed 's/^/ /'; ((issues++)); } + + echo "" + df_print_section "Space Allocation" + local used_pct=$(sudo btrfs filesystem usage "$mount" -b 2>/dev/null | grep "Used:" | head -1 | awk '{print $2}' | tr -d '%') + if [[ -n "$used_pct" ]]; then + (( used_pct >= 90 )) && { df_print_indent "✗ ${used_pct}% full - critical!"; ((issues++)); } || \ + (( used_pct >= 80 )) && df_print_indent "⚠ ${used_pct}% full" || df_print_indent "✓ ${used_pct}% used" + fi + + echo "" + df_print_section "Last Scrub" + local scrub=$(sudo btrfs scrub status "$mount" 2>/dev/null) + local scrub_date=$(echo "$scrub" | grep "Scrub started" | awk '{print $3, $4, $5}') + [[ -n "$scrub_date" ]] && df_print_indent "Last: $scrub_date" || df_print_indent "⚠ No scrub run yet" + + echo "" + (( issues == 0 )) && df_print_success "Filesystem healthy" || df_print_error "Found $issues issue(s)" +} + +btrfs-snap-usage() { + _btrfs_check || return 1 + df_print_func_name "Snapshot Disk Space Usage" + if [[ -d "/.snapshots" ]]; then + df_print_section "Snapshot Directory" + local size=$(timeout 10 sudo du -sh /.snapshots 2>/dev/null | cut -f1) + df_print_indent "${size:-Unable to calculate}" + echo "" + df_print_section "Individual Snapshots (top 10)" + timeout 30 sudo du -sh /.snapshots/*/ 2>/dev/null | sort -h | tail -10 | sed 's/^/ /' + else + df_print_warning "No /.snapshots directory found" + fi +} + +btrfs-maintain() { + _btrfs_check || return 1 + local mount="${1:-$BTRFS_DEFAULT_MOUNT}" + df_print_func_name "Btrfs Maintenance Routine" + echo "This will: health check, balance, scrub" + df_confirm_warning "This may take several hours" || return 0 + df_print_step "Step 1/3: Health Check" + btrfs-health "$mount" + df_print_step "Step 2/3: Balance" + sudo btrfs balance start -dusage=50 -musage=50 "$mount" + df_print_step "Step 3/3: Scrub" + sudo btrfs scrub start -B "$mount" + df_print_success "Maintenance complete" +} + +btrfs-help() { + df_print_func_name "Btrfs Helper Commands" + cat << 'EOF' + btrfs-usage [mount] Filesystem usage + btrfs-subs [mount] List subvolumes + btrfs-info [mount] Full filesystem info + btrfs-health [mount] Quick health check + btrfs-compress [path] Compression stats + btrfs-balance [mount] Start balance + btrfs-scrub [mount] Start scrub + btrfs-defrag Defragment + btrfs-snap-usage Snapshot space usage + btrfs-maintain [mount] Full maintenance +EOF +} + +alias btru='btrfs-usage' btrs='btrfs-subs' btrh='btrfs-health' btri='btrfs-info' btrc='btrfs-compress' diff --git a/refactor_backup/zsh/functions/btrfs-helpers.zsh.zwc b/refactor_backup/zsh/functions/btrfs-helpers.zsh.zwc new file mode 100644 index 0000000..05a5691 Binary files /dev/null and b/refactor_backup/zsh/functions/btrfs-helpers.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/command-palette.zsh b/refactor_backup/zsh/functions/command-palette.zsh new file mode 100644 index 0000000..57ef146 --- /dev/null +++ b/refactor_backup/zsh/functions/command-palette.zsh @@ -0,0 +1,136 @@ +# ============================================================================ +# Command Palette - Fuzzy Command Launcher for Zsh +# ============================================================================ +# A Raycast/Alfred-style command palette for the terminal +# Keybinding: Ctrl+Space (configurable) +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g PALETTE_HOTKEY="${PALETTE_HOTKEY:-^@}" +typeset -g PALETTE_HISTORY_SIZE=50 +typeset -g PALETTE_BOOKMARKS_FILE="$HOME/.dotfiles/.bookmarks" +typeset -g DOTFILES_DIR="${DOTFILES_DIR:-$HOME/.dotfiles}" + +typeset -g ICON_ALIAS="⚡" ICON_FUNC="λ" ICON_HIST="↺" ICON_DIR="📁" +typeset -g ICON_SCRIPT="⚙" ICON_ACTION="★" ICON_GIT="⎇" + +_palette_get_aliases() { + alias | sed 's/^alias //' | while IFS='=' read -r name cmd; do + cmd="${cmd#\'}"; cmd="${cmd%\'}"; cmd="${cmd#\"}"; cmd="${cmd%\"}" + printf "%s\t%s\t%s\t%s\n" "$ICON_ALIAS" "alias" "$name" "$cmd" + done +} + +_palette_get_functions() { + print -l ${(ok)functions} | grep -v "^_" | while read -r name; do + printf "%s\t%s\t%s\t%s\n" "$ICON_FUNC" "function" "$name" "" + done +} + +_palette_get_history() { + fc -ln -"$PALETTE_HISTORY_SIZE" 2>/dev/null | awk '!seen[$0]++' | while read -r cmd; do + [[ -n "$cmd" ]] && printf "%s\t%s\t%s\t%s\n" "$ICON_HIST" "history" "$cmd" "" + done +} + +_palette_get_bookmarks() { + [[ ! -f "$PALETTE_BOOKMARKS_FILE" ]] && return + while IFS='|' read -r name path desc; do + [[ "$name" =~ ^# || -z "$name" ]] && continue + printf "%s\t%s\t%s\t%s\n" "$ICON_DIR" "bookmark" "$name" "$path" + done < "$PALETTE_BOOKMARKS_FILE" +} + +_palette_get_actions() { + cat << 'EOF' +★ action reload-shell Reload zsh configuration +★ action edit-zshrc Edit ~/.zshrc +★ action dotfiles-update Update dotfiles +EOF + df_in_git_repo && cat << 'EOF' +⎇ git git-status Show git status +⎇ git git-pull Pull latest +⎇ git git-push Push commits +EOF +} + +_palette_run_action() { + case "$1" in + reload-shell) source ~/.zshrc; df_print_success "Shell reloaded" ;; + edit-zshrc) ${EDITOR:-vim} ~/.zshrc ;; + dotfiles-update) cd "$DOTFILES_DIR" && git pull ;; + git-status) git status ;; + git-pull) git pull ;; + git-push) git push ;; + *) df_print_error "Unknown action: $1" ;; + esac +} + +palette() { + df_require_cmd fzf || return 1 + local items=$(_palette_get_actions; _palette_get_aliases; _palette_get_functions; _palette_get_bookmarks; _palette_get_history) + local sel=$(echo "$items" | fzf --ansi --delimiter='\t' --with-nth=1,3,4 $(df_fzf_opts) --prompt='> ') + [[ -z "$sel" ]] && return + local type=$(echo "$sel" | cut -f2) name=$(echo "$sel" | cut -f3) detail=$(echo "$sel" | cut -f4) + case "$type" in + alias|history) print -z "$name" ;; + function) print -z "$name " ;; + bookmark) cd "$detail" && pwd ;; + action|git) _palette_run_action "$name" ;; + esac +} + +bookmark() { + local cmd="${1:-list}"; shift 2>/dev/null + case "$cmd" in + add) + local name="$1" path="${2:-$(pwd)}" desc="$3" + [[ -z "$name" ]] && { echo "Usage: bookmark add [path]"; return 1; } + df_ensure_file "$PALETTE_BOOKMARKS_FILE" "# Bookmarks: name|path|description" + grep -q "^${name}|" "$PALETTE_BOOKMARKS_FILE" 2>/dev/null && { + df_confirm "Overwrite '$name'?" || return 1 + grep -v "^${name}|" "$PALETTE_BOOKMARKS_FILE" > "${PALETTE_BOOKMARKS_FILE}.tmp" + mv "${PALETTE_BOOKMARKS_FILE}.tmp" "$PALETTE_BOOKMARKS_FILE" + } + echo "${name}|${path}|${desc}" >> "$PALETTE_BOOKMARKS_FILE" + df_print_success "Bookmarked: $name → $path" + ;; + delete|rm) + [[ -z "$1" ]] && { echo "Usage: bookmark delete "; return 1; } + grep -q "^${1}|" "$PALETTE_BOOKMARKS_FILE" 2>/dev/null || { df_print_error "Not found: $1"; return 1; } + grep -v "^${1}|" "$PALETTE_BOOKMARKS_FILE" > "${PALETTE_BOOKMARKS_FILE}.tmp" + mv "${PALETTE_BOOKMARKS_FILE}.tmp" "$PALETTE_BOOKMARKS_FILE" + df_print_success "Deleted: $1" + ;; + list|ls) + df_print_func_name "Bookmarks" + [[ ! -f "$PALETTE_BOOKMARKS_FILE" ]] && { df_print_info "No bookmarks"; return; } + while IFS='|' read -r name path desc; do + [[ "$name" =~ ^# || -z "$name" ]] && continue + df_print_indent "● $name → $path" + done < "$PALETTE_BOOKMARKS_FILE" + ;; + go) + [[ -z "$1" ]] && { echo "Usage: bookmark go "; return 1; } + local path=$(grep "^${1}|" "$PALETTE_BOOKMARKS_FILE" 2>/dev/null | cut -d'|' -f2) + [[ -n "$path" ]] && cd "$path" || df_print_error "Not found: $1" + ;; + *) echo "Usage: bookmark " ;; + esac +} + +bm() { + df_require_cmd fzf || return 1 + [[ ! -f "$PALETTE_BOOKMARKS_FILE" ]] && { df_print_info "No bookmarks"; return 1; } + local sel=$(grep -v "^#" "$PALETTE_BOOKMARKS_FILE" | grep -v "^$" | \ + fzf $(df_fzf_opts) --delimiter='|' --with-nth=1,2 --prompt='Bookmark > ') + [[ -n "$sel" ]] && cd "$(echo "$sel" | cut -d'|' -f2)" +} + +_palette_widget() { BUFFER=""; zle redisplay; palette; zle reset-prompt; } +zle -N _palette_widget +bindkey "$PALETTE_HOTKEY" _palette_widget + +alias p='palette' bml='bookmark list' bma='bookmark add' bmg='bookmark go' diff --git a/refactor_backup/zsh/functions/command-palette.zsh.zwc b/refactor_backup/zsh/functions/command-palette.zsh.zwc new file mode 100644 index 0000000..b872726 Binary files /dev/null and b/refactor_backup/zsh/functions/command-palette.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/motd.zsh b/refactor_backup/zsh/functions/motd.zsh new file mode 100644 index 0000000..8d08bce --- /dev/null +++ b/refactor_backup/zsh/functions/motd.zsh @@ -0,0 +1,201 @@ +#!/usr/bin/env zsh +# ============================================================================ +# MOTD (Message of the Day) - Dynamic System Info +# ============================================================================ +# Displays system information on shell startup +# Optimized for Arch/CachyOS with direct /proc access +# +# Functions: +# show_motd - Compact box format +# show_motd_mini - Single line format +# show_motd_full - Extended info +# ============================================================================ + +# Only run in interactive shells +[[ -o interactive ]] || return 0 + +# ============================================================================ +# Source Configuration +# ============================================================================ +# utils.zsh sources config.zsh which sources dotfiles.conf +# This gives us DF_WIDTH, MOTD_STYLE, colors, and all other settings + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null || { + # Fallback if utils.zsh not available + typeset -g DF_RESET=$'\033[0m' DF_BOLD=$'\033[1m' DF_DIM=$'\033[2m' + typeset -g DF_BLUE=$'\033[38;5;39m' DF_CYAN=$'\033[38;5;51m' + typeset -g DF_GREEN=$'\033[38;5;82m' DF_YELLOW=$'\033[38;5;220m' + typeset -g DF_RED=$'\033[38;5;196m' DF_GREY=$'\033[38;5;242m' DF_NC=$'\033[0m' + typeset -g DF_LIGHT_BLUE=$'\033[38;5;39m' DF_LIGHT_GREEN=$'\033[38;5;82m' + typeset -g DF_WIDTH="${DF_WIDTH:-66}" + typeset -g MOTD_STYLE="${MOTD_STYLE:-compact}" +} + +# ============================================================================ +# Optimized Info Gathering (using /proc directly) +# ============================================================================ + +_motd_uptime() { + local uptime_seconds=$(cut -d. -f1 /proc/uptime 2>/dev/null) + [[ -z "$uptime_seconds" ]] && { echo "?"; return; } + + local days=$((uptime_seconds / 86400)) + local hours=$(((uptime_seconds % 86400) / 3600)) + local mins=$(((uptime_seconds % 3600) / 60)) + + if (( days > 0 )); then + echo "${days}d${hours}h" + elif (( hours > 0 )); then + echo "${hours}h${mins}m" + else + echo "${mins}m" + fi +} + +_motd_load() { cut -d' ' -f1 /proc/loadavg 2>/dev/null || echo "?"; } + +_motd_mem() { + awk '/MemTotal/ {total=$2} /MemAvailable/ {avail=$2} END { + if (total > 0) { + used=(total-avail)/1024/1024 + total_gb=total/1024/1024 + printf "%.1fG/%.0fG", used, total_gb + } else { print "N/A" } + }' /proc/meminfo 2>/dev/null || echo "N/A" +} + +_motd_disk() { df -h / 2>/dev/null | awk 'NR==2 {print $3 "/" $2}' || echo "N/A"; } + +_motd_failed_services() { + local cache_file="/tmp/.motd-failed-${UID}" + local cache_age=300 + if [[ -f "$cache_file" ]]; then + local file_age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0))) + (( file_age < cache_age )) && { cat "$cache_file"; return; } + fi + local count=$(systemctl --failed --no-pager --no-legend 2>/dev/null | wc -l) + echo "$count" > "$cache_file" 2>/dev/null + echo "$count" +} + +_motd_updates() { echo "${UPDATE_PKG_COUNT:-0}"; } + +# ============================================================================ +# Box Drawing - Uses DF_WIDTH from config +# ============================================================================ + +_motd_line() { + local char="$1" + local width="${DF_WIDTH:-66}" + local line="" + for ((i=0; i/dev/null | awk -F" " '{print $1}') + local h_right="${datetime}" + local h_pad=$(((inner - ${#h_left} - ${#h_center} - ${#h_right}) / 2)) + local h_spaces="" + for ((i=0; i 0 )) && echo " ${DF_RED}⚠${DF_NC} ${failed} failed service(s)" + fi + + echo "" +} + +# ============================================================================ +# Mini Format (Single Line) +# ============================================================================ + +show_motd_mini() { + local hostname="${HOST:-$(hostname -s 2>/dev/null)}" + local uptime=$(_motd_uptime) + local load=$(_motd_load) + echo "${DF_LIGHT_BLUE}${hostname}${DF_NC} │ up ${uptime} │ load ${load}" +} + +# ============================================================================ +# Full Format (Extended Info) +# ============================================================================ + +show_motd_full() { + show_motd --force + + local width="${DF_WIDTH:-66}" + + echo "${DF_CYAN}System Details${DF_NC}" + printf "${DF_GREY}─%.0s${DF_NC}" $(seq 1 $width); echo "" + + echo " ${DF_DIM}Kernel:${DF_NC} $(uname -r)" + echo " ${DF_DIM}Shell:${DF_NC} ${SHELL##*/} ${ZSH_VERSION:-}" + + if [[ -f /etc/os-release ]]; then + local distro=$(grep '^PRETTY_NAME=' /etc/os-release | cut -d'"' -f2) + echo " ${DF_DIM}OS:${DF_NC} ${distro}" + fi + + if command -v pacman &>/dev/null; then + local pkg_count=$(pacman -Q 2>/dev/null | wc -l) + echo " ${DF_DIM}Packages:${DF_NC} ${pkg_count}" + fi + + echo "" +} + +# ============================================================================ +# Auto-display based on MOTD_STYLE from config +# ============================================================================ + +_motd_auto() { + case "${MOTD_STYLE:-compact}" in + full) show_motd_full ;; + mini) show_motd_mini ;; + none|off|false) ;; + *) show_motd ;; + esac +} + +# Run on source if ENABLE_MOTD is true +[[ "${ENABLE_MOTD:-true}" == "true" ]] && _motd_auto diff --git a/refactor_backup/zsh/functions/motd.zsh.zwc b/refactor_backup/zsh/functions/motd.zsh.zwc new file mode 100644 index 0000000..235f0dc Binary files /dev/null and b/refactor_backup/zsh/functions/motd.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/password-manager.zsh b/refactor_backup/zsh/functions/password-manager.zsh new file mode 100644 index 0000000..4778aea --- /dev/null +++ b/refactor_backup/zsh/functions/password-manager.zsh @@ -0,0 +1,84 @@ +# ============================================================================ +# Password Manager Integration (LastPass CLI) +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g PW_CLIP_TIME="${PW_CLIP_TIME:-45}" + +_pw_check() { + df_require_cmd lpass lastpass-cli || return 1 + if ! lpass status -q 2>/dev/null; then + df_print_warning "Not logged in" + df_print_step "Logging in..." + lpass login --trust "${LPASS_USER:-}" || { df_print_error "Login failed"; return 1; } + fi +} + +_pw_copy() { + local text="$1" label="${2:-Password}" + if df_cmd_exists wl-copy; then + echo -n "$text" | wl-copy + elif df_cmd_exists xclip; then + echo -n "$text" | xclip -selection clipboard + else + df_print_error "No clipboard tool (install wl-clipboard or xclip)" + return 1 + fi + df_print_success "$label copied (clears in ${PW_CLIP_TIME}s)" + (sleep "$PW_CLIP_TIME" && { wl-copy "" 2>/dev/null || xclip -selection clipboard < /dev/null 2>/dev/null; }) & +} + +pw() { + local cmd="${1:-search}" + case "$cmd" in + login) lpass login --trust "${LPASS_USER:-}" ;; + logout) lpass logout -f; df_print_success "Logged out" ;; + sync) _pw_check || return 1; df_print_step "Syncing..."; lpass sync; df_print_success "Synced" ;; + show) _pw_check || return 1; [[ -z "$2" ]] && { echo "Usage: pw show "; return 1; }; lpass show "$2" ;; + gen|generate) + local len="${2:-20}" + local pass=$(tr -dc 'A-Za-z0-9!@#$%^&*' < /dev/urandom | head -c "$len") + _pw_copy "$pass" "Generated password" + ;; + list|ls) _pw_check || return 1; df_print_func_name "Password Entries"; lpass ls --long ;; + search|*) + _pw_check || return 1 + local query="$1"; [[ "$cmd" == "search" ]] && query="$2" + if df_cmd_exists fzf && [[ -z "$query" ]]; then + local entry=$(lpass ls --format "%an (%au) [%ai]" 2>/dev/null | fzf $(df_fzf_opts) --prompt='Password > ') + [[ -z "$entry" ]] && return + local id=$(echo "$entry" | grep -oP '\[\K[^\]]+(?=\]$)') + local pass=$(lpass show --password "$id" 2>/dev/null) + [[ -n "$pass" ]] && _pw_copy "$pass" || df_print_error "Could not retrieve" + else + [[ -z "$query" ]] && { echo "Usage: pw "; return 1; } + local results=$(lpass ls --format "%an [%ai]" 2>/dev/null | grep -i "$query") + local count=$(echo "$results" | grep -c . 2>/dev/null || echo 0) + if (( count == 0 )); then + df_print_warning "No entries for: $query" + elif (( count == 1 )); then + local id=$(echo "$results" | grep -oP '\[\K[^\]]+(?=\]$)') + _pw_copy "$(lpass show --password "$id" 2>/dev/null)" + else + df_print_warning "Multiple entries:" + echo "$results" | while read -r l; do df_print_indent "$l"; done + fi + fi + ;; + help|--help|-h) + df_print_func_name "Password Manager" + cat << 'EOF' + pw Search and copy password + pw show Show entry details + pw list List all entries + pw gen [len] Generate password (default: 20) + pw sync Sync vault + pw login/logout Auth commands +EOF + ;; + esac +} + +alias pwc='pw' pws='pw show' pwg='pw gen' pwl='pw list' diff --git a/refactor_backup/zsh/functions/password-manager.zsh.zwc b/refactor_backup/zsh/functions/password-manager.zsh.zwc new file mode 100644 index 0000000..38fca1d Binary files /dev/null and b/refactor_backup/zsh/functions/password-manager.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/python-templates.zsh b/refactor_backup/zsh/functions/python-templates.zsh new file mode 100644 index 0000000..038ef12 --- /dev/null +++ b/refactor_backup/zsh/functions/python-templates.zsh @@ -0,0 +1,173 @@ +# ============================================================================ +# Python Project Template Functions +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g PY_PYTHON="${PY_PYTHON:-python3}" +typeset -g PY_VENV="${PY_VENV:-venv}" +typeset -g PY_GIT_INIT="${PY_GIT_INIT:-true}" + +_py_check_name() { + [[ -z "$1" ]] && { df_print_warning "Project name required"; return 1; } + [[ -d "$1" ]] && { df_print_warning "Directory '$1' exists"; return 1; } + return 0 +} + +_py_venv() { + df_print_step "Creating virtual environment" + "$PY_PYTHON" -m venv "$1/$PY_VENV" + df_print_success "Created: $PY_VENV" +} + +_py_gitignore() { + cat > "$1/.gitignore" << 'EOF' +__pycache__/ +*.py[cod] +*.so +build/ +dist/ +*.egg-info/ +venv/ +.venv/ +.env +*.log +.pytest_cache/ +.mypy_cache/ +EOF + df_print_success "Created .gitignore" +} + +_py_git() { + [[ "$PY_GIT_INIT" == "true" ]] && { cd "$1"; git init; git add .; git commit -m "Initial commit"; df_print_success "Git initialized"; } +} + +_py_next() { + echo "" + df_print_section "Next steps" + df_print_indent "cd $1" + df_print_indent "source $PY_VENV/bin/activate" +} + +py-new() { + _py_check_name "$1" || return 1 + df_print_func_name "Python Project: $1" + mkdir -p "$1"/{src,tests} + touch "$1/src/__init__.py" "$1/tests/__init__.py" + cat > "$1/src/main.py" << 'EOF' +#!/usr/bin/env python3 +def main(): + print("Hello!") + +if __name__ == "__main__": + main() +EOF + echo "# Dependencies" > "$1/requirements.txt" + _py_venv "$1"; _py_gitignore "$1"; _py_git "$1" + df_print_success "Created: $1" + _py_next "$1" +} + +py-flask() { + _py_check_name "$1" || return 1 + df_print_func_name "Flask Project: $1" + mkdir -p "$1"/{app/{templates,static},tests} + _py_venv "$1" + df_print_step "Installing Flask" + "$1/$PY_VENV/bin/pip" install flask -q + cat > "$1/app/__init__.py" << 'EOF' +from flask import Flask +def create_app(): + app = Flask(__name__) + from app.routes import main + app.register_blueprint(main) + return app +EOF + cat > "$1/app/routes.py" << 'EOF' +from flask import Blueprint, render_template +main = Blueprint('main', __name__) +@main.route('/') +def index(): + return render_template('index.html') +EOF + echo '

Flask

' > "$1/app/templates/index.html" + cat > "$1/app.py" << 'EOF' +#!/usr/bin/env python3 +from app import create_app +app = create_app() +if __name__ == '__main__': + app.run(debug=True) +EOF + echo "Flask>=3.0.0" > "$1/requirements.txt" + _py_gitignore "$1"; _py_git "$1" + df_print_success "Created: $1" + _py_next "$1" +} + +py-fastapi() { + _py_check_name "$1" || return 1 + df_print_func_name "FastAPI Project: $1" + mkdir -p "$1"/{app,tests} + _py_venv "$1" + df_print_step "Installing FastAPI" + "$1/$PY_VENV/bin/pip" install fastapi uvicorn -q + cat > "$1/app/main.py" << 'EOF' +from fastapi import FastAPI +app = FastAPI() +@app.get("/") +def root(): + return {"message": "Hello"} +@app.get("/health") +def health(): + return {"status": "ok"} +EOF + cat > "$1/run.py" << 'EOF' +#!/usr/bin/env python3 +import uvicorn +if __name__ == "__main__": + uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True) +EOF + echo -e "fastapi>=0.104.0\nuvicorn>=0.24.0" > "$1/requirements.txt" + _py_gitignore "$1"; _py_git "$1" + df_print_success "Created: $1" + df_print_info "Docs: http://localhost:8000/docs" + _py_next "$1" +} + +py-cli() { + _py_check_name "$1" || return 1 + df_print_func_name "CLI Project: $1" + mkdir -p "$1"/{src/$1,tests} + _py_venv "$1" + df_print_step "Installing click" + "$1/$PY_VENV/bin/pip" install click -q + echo '__version__ = "0.1.0"' > "$1/src/$1/__init__.py" + cat > "$1/src/$1/cli.py" << 'EOF' +#!/usr/bin/env python3 +import click +@click.group() +@click.version_option() +def cli(): + pass +@cli.command() +@click.argument('name', default='World') +def greet(name): + click.echo(f"Hello, {name}!") +if __name__ == '__main__': + cli() +EOF + echo "click>=8.1.0" > "$1/requirements.txt" + _py_gitignore "$1"; _py_git "$1" + df_print_success "Created: $1" + df_print_info "Install: pip install -e $1" + _py_next "$1" +} + +venv() { + [[ -d "venv" ]] && source venv/bin/activate && return + [[ -d ".venv" ]] && source .venv/bin/activate && return + df_print_error "No venv found" +} + +alias pynew='py-new' pyflask='py-flask' pyfast='py-fastapi' pycli='py-cli' diff --git a/refactor_backup/zsh/functions/python-templates.zsh.zwc b/refactor_backup/zsh/functions/python-templates.zsh.zwc new file mode 100644 index 0000000..44ce625 Binary files /dev/null and b/refactor_backup/zsh/functions/python-templates.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/smart-suggest.zsh b/refactor_backup/zsh/functions/smart-suggest.zsh new file mode 100644 index 0000000..f17685b --- /dev/null +++ b/refactor_backup/zsh/functions/smart-suggest.zsh @@ -0,0 +1,82 @@ +# ============================================================================ +# Smart Command Suggestions for Zsh +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g SMART_SUGGEST_ENABLED=true +typeset -g SMART_SUGGEST_TRACK_FILE="$HOME/.cache/smart-suggest-track" + +typeset -gA TYPO_CORRECTIONS=( + [gti]="git" [gitt]="git" [got]="git" [gi]="git" + [gitst]="git st" [gits]="git s" [gitp]="git p" + [psuh]="push" [psull]="pull" [pul]="pull" + [stauts]="status" [comit]="commit" [commti]="commit" + [chekcout]="checkout" [branhc]="branch" [marge]="merge" + [dokcer]="docker" [doker]="docker" [dcoker]="docker" + [sl]="ls" [sls]="ls" [cta]="cat" [grpe]="grep" [gerp]="grep" + [mkdri]="mkdir" [chmdo]="chmod" [suod]="sudo" [sduo]="sudo" + [pytohn]="python" [pyhton]="python" [ndoe]="node" + [vmi]="vim" [cde]="code" [clera]="clear" [exti]="exit" +) + +typeset -gA COMMAND_PACKAGES=( + [htop]="htop" [tree]="tree" [jq]="jq" [fd]="fd" [rg]="ripgrep" + [bat]="bat" [eza]="eza" [fzf]="fzf" [tldr]="tldr" [ncdu]="ncdu" + [lazygit]="lazygit" [neofetch]="neofetch" [delta]="git-delta" +) + +_ss_track() { + local cmd="$1" + [[ ${#cmd} -lt 8 ]] && return + df_ensure_dir "$(dirname "$SMART_SUGGEST_TRACK_FILE")" + echo "$cmd" >> "$SMART_SUGGEST_TRACK_FILE" + local count=$(grep -Fc "$cmd" "$SMART_SUGGEST_TRACK_FILE" 2>/dev/null || echo 0) + if (( count >= 10 && count % 10 == 0 )); then + local existing=$(alias | grep -F "='$cmd'" | head -1 | cut -d= -f1) + [[ -n "$existing" ]] && df_print_info "You have alias: $existing" || \ + df_print_info "Consider: alias xyz='$cmd'" + fi +} + +command_not_found_handler() { + local cmd="$1"; shift + [[ "$SMART_SUGGEST_ENABLED" != true ]] && { echo "zsh: command not found: $cmd"; return 127; } + + df_print_error "Command not found: $cmd" + + local correction="${TYPO_CORRECTIONS[$cmd]}" + [[ -n "$correction" ]] && { df_print_info "Did you mean: $correction?"; df_print_indent "Run: $correction $@"; } + + local pkg="${COMMAND_PACKAGES[$cmd]}" + [[ -n "$pkg" ]] && df_print_info "Install: sudo pacman -S $pkg" + + return 127 +} + +fuck() { + local last=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//') + local first="${last%% *}" + local fix="${TYPO_CORRECTIONS[$first]}" + [[ -n "$fix" ]] && { df_print_step "Running: ${last/$first/$fix}"; eval "${last/$first/$fix}"; } || df_print_warning "No fix for: $last" +} + +_ss_preexec() { _ss_track "$1"; } +_ss_precmd() { + local exit=$?; (( exit == 0 )) && return + local last=$(fc -ln -1 2>/dev/null | sed 's/^[[:space:]]*//') + [[ "${last%% *}" == "git" ]] && { + local sub=$(echo "$last" | awk '{print $2}') + local fix="${TYPO_CORRECTIONS[$sub]}" + [[ -n "$fix" ]] && df_print_info "Did you mean: git $fix?" + } +} + +_ss_setup() { + autoload -Uz add-zsh-hook + add-zsh-hook preexec _ss_preexec + add-zsh-hook precmd _ss_precmd +} + +[[ "$SMART_SUGGEST_ENABLED" == true ]] && _ss_setup diff --git a/refactor_backup/zsh/functions/smart-suggest.zsh.zwc b/refactor_backup/zsh/functions/smart-suggest.zsh.zwc new file mode 100644 index 0000000..d7c3a2f Binary files /dev/null and b/refactor_backup/zsh/functions/smart-suggest.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/snapper.zsh b/refactor_backup/zsh/functions/snapper.zsh new file mode 100644 index 0000000..65b464f --- /dev/null +++ b/refactor_backup/zsh/functions/snapper.zsh @@ -0,0 +1,98 @@ +# ============================================================================ +# Snapper Snapshot Functions for CachyOS/Arch with limine-snapper-sync +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +snap-create() { + local desc="$*" + local limine="/boot/limine.conf" + + df_print_func_name "Snapper Snapshot Creation" + + if [[ -z "$desc" ]]; then + df_print_warning "No description" + echo -n "Description: "; read desc + [[ -z "$desc" ]] && { df_print_error "Required"; return 1; } + fi + + [[ ! -f "$limine" ]] && { df_print_error "Limine not found: $limine"; return 1; } + + df_print_step "Checking limine.conf before snapshot" + local before=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine" || echo "0") + df_print_success "Before: $before entries" + + df_print_step "Creating snapshot: \"$desc\"" + local num=$(sudo snapper -c root create --description "$desc" --print-number) + [[ -z "$num" ]] && { df_print_error "Failed"; return 1; } + df_print_success "Created: #$num" + + df_print_step "Triggering limine-snapper-sync..." + sudo systemctl start limine-snapper-sync.service && df_print_success "Triggered" || df_print_warning "May run automatically" + sleep 2 + + df_print_step "Validating" + local after=$(sudo grep -cP "^\\s*///\\d+\\s*│" "$limine" || echo "0") + + if sudo grep -qP "^\\s*///$num\\s*│" "$limine"; then + df_print_success "Snapshot #$num in limine.conf" + (( after > before )) && df_print_success "Added $((after - before)) entry" + else + df_print_error "Snapshot #$num NOT in limine.conf" + return 1 + fi + + echo "" + df_print_section "Summary" + df_print_indent "Number: #$num" + df_print_indent "Description: $desc" +} + +snap-list() { + local count="${1:-10}" + df_print_func_name "Snapper Snapshots (last $count)" + sudo snapper -c root list | tail -n "$((count + 1))" +} + +snap-show() { + [[ -z "$1" ]] && { echo "Usage: snap-show "; return 1; } + df_print_func_name "Snapshot #$1" + sudo snapper -c root list | grep "^\s*$1\s" + echo "" + df_print_section "In limine.conf" + sudo grep -qP "^\\s*///$1\\s*│" /boot/limine.conf && \ + sudo grep -P "^\\s*///$1\\s*│" /boot/limine.conf || df_print_warning "Not found" +} + +snap-delete() { + [[ -z "$1" ]] && { echo "Usage: snap-delete "; return 1; } + df_print_func_name "Delete Snapshot #$1" + + local before=$(sudo grep -cP "^\\s*///\\d+\\s*│" /boot/limine.conf || echo "0") + sudo snapper -c root delete "$1" && df_print_success "Deleted #$1" || { df_print_error "Failed"; return 1; } + + df_print_step "Syncing limine..." + sudo systemctl start limine-snapper-sync.service; sleep 2 + + sudo grep -qP "^\\s*///$1\\s*│" /boot/limine.conf && df_print_error "Still in limine!" || df_print_success "Removed from limine" +} + +snap-sync() { + df_print_func_name "Limine-Snapper-Sync" + df_print_step "Triggering sync..." + sudo systemctl start limine-snapper-sync.service && { sleep 2; df_print_success "Done"; } || df_print_error "Failed" +} + +snap-check() { + df_print_func_name "Limine Snapshot Entries" + local latest=$(sudo snapper -c root list | tail -n +3 | grep -v "^\s*0\s" | tail -1 | awk '{print $1}') + [[ -z "$latest" ]] && { df_print_warning "No snapshots"; return 1; } + df_print_info "Latest: #$latest" + sudo grep -qP "^\\s*///$latest\\s*│" /boot/limine.conf && \ + df_print_success "Latest in limine.conf" || df_print_error "Latest NOT in limine.conf" + local count=$(sudo grep -cP "^\\s*///\\d+\\s*│" /boot/limine.conf || echo "0") + df_print_info "Total entries: $count" +} + +alias snap='snap-create' snapls='snap-list' snaprm='snap-delete' snapcheck='snap-check' diff --git a/refactor_backup/zsh/functions/snapper.zsh.zwc b/refactor_backup/zsh/functions/snapper.zsh.zwc new file mode 100644 index 0000000..8e845a9 Binary files /dev/null and b/refactor_backup/zsh/functions/snapper.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/ssh-manager.zsh b/refactor_backup/zsh/functions/ssh-manager.zsh new file mode 100644 index 0000000..7b0cb5c --- /dev/null +++ b/refactor_backup/zsh/functions/ssh-manager.zsh @@ -0,0 +1,96 @@ +# ============================================================================ +# SSH Session Manager with Tmux Integration +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g SSH_PROFILES_FILE="${SSH_PROFILES_FILE:-$HOME/.dotfiles/.ssh-profiles}" +typeset -g SSH_AUTO_TMUX="${SSH_AUTO_TMUX:-true}" +typeset -g SSH_TMUX_PREFIX="${SSH_TMUX_PREFIX:-ssh}" + +_ssh_init() { + df_ensure_file "$SSH_PROFILES_FILE" "# SSH Profiles: name|user@host|port|key|options|description" +} + +_ssh_parse() { + local line=$(grep "^${1}|" "$SSH_PROFILES_FILE" 2>/dev/null | head -1) + [[ -z "$line" ]] && return 1 + echo "$line" | cut -d'|' -f2- +} + +ssh-save() { + local name="$1" conn="$2" port="${3:-22}" key="${4:-}" opts="${5:-}" desc="${6:-}" + [[ -z "$name" || -z "$conn" ]] && { echo "Usage: ssh-save [port] [key] [opts] [desc]"; return 1; } + _ssh_init + grep -q "^${name}|" "$SSH_PROFILES_FILE" 2>/dev/null && { + df_confirm "Overwrite '$name'?" || return 1 + grep -v "^${name}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" + mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" + } + echo "${name}|${conn}|${port}|${key}|${opts}|${desc}" >> "$SSH_PROFILES_FILE" + df_print_success "Saved: $name → $conn" +} + +ssh-list() { + _ssh_init + df_print_func_name "SSH Profiles" + local found=false + while IFS='|' read -r name conn port key opts desc; do + [[ "$name" =~ ^# || -z "$name" ]] && continue + found=true + df_print_indent "● $name → $conn" + [[ "$port" != "22" && -n "$port" ]] && df_print_indent " Port: $port" + [[ -n "$desc" ]] && df_print_indent " $desc" + done < "$SSH_PROFILES_FILE" + [[ "$found" != true ]] && df_print_info "No profiles. Use: ssh-save name user@host" +} + +ssh-delete() { + [[ -z "$1" ]] && { echo "Usage: ssh-delete "; return 1; } + grep -q "^${1}|" "$SSH_PROFILES_FILE" 2>/dev/null || { df_print_error "Not found: $1"; return 1; } + grep -v "^${1}|" "$SSH_PROFILES_FILE" > "${SSH_PROFILES_FILE}.tmp" + mv "${SSH_PROFILES_FILE}.tmp" "$SSH_PROFILES_FILE" + df_print_success "Deleted: $1" +} + +ssh-connect() { + local name="$1" session="${2:-${SSH_TMUX_PREFIX}-${1}}" + [[ -z "$name" ]] && { ssh-list; return 1; } + _ssh_init + local data=$(_ssh_parse "$name") + [[ -z "$data" ]] && { df_print_error "Not found: $name"; return 1; } + + IFS='|' read -r conn port key opts desc <<< "$data" + df_print_step "Connecting: $name" + [[ -n "$desc" ]] && df_print_indent "$desc" + + local cmd="ssh" + [[ -n "$port" && "$port" != "22" ]] && cmd="$cmd -p $port" + [[ -n "$key" ]] && cmd="$cmd -i $key" + [[ -n "$opts" ]] && cmd="$cmd $opts" + cmd="$cmd $conn" + + if [[ "$SSH_AUTO_TMUX" == "true" ]]; then + df_print_info "Tmux session: $session" + eval "$cmd -t 'tmux attach -t $session 2>/dev/null || tmux new -s $session'" + else + eval "$cmd" + fi +} + +sshf() { + df_require_cmd fzf || return 1 + _ssh_init + local profiles=() + while IFS='|' read -r name conn port key opts desc; do + [[ "$name" =~ ^# || -z "$name" ]] && continue + profiles+=("$name|$name → $conn") + done < "$SSH_PROFILES_FILE" + [[ ${#profiles[@]} -eq 0 ]] && { df_print_info "No profiles"; return 1; } + local sel=$(printf '%s\n' "${profiles[@]}" | fzf $(df_fzf_opts) --delimiter='|' --with-nth=2 --prompt='SSH > ') + [[ -n "$sel" ]] && ssh-connect "${sel%%|*}" +} + +alias sshl='ssh-list' sshs='ssh-save' sshc='ssh-connect' sshd='ssh-delete' +_ssh_init diff --git a/refactor_backup/zsh/functions/ssh-manager.zsh.zwc b/refactor_backup/zsh/functions/ssh-manager.zsh.zwc new file mode 100644 index 0000000..cf82341 Binary files /dev/null and b/refactor_backup/zsh/functions/ssh-manager.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/systemd-helpers.zsh b/refactor_backup/zsh/functions/systemd-helpers.zsh new file mode 100644 index 0000000..2b7eba2 --- /dev/null +++ b/refactor_backup/zsh/functions/systemd-helpers.zsh @@ -0,0 +1,124 @@ +# ============================================================================ +# Systemd Integration for Arch/CachyOS +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +# Core shortcuts +sc() { sudo systemctl "$@"; } +scu() { systemctl --user "$@"; } + +scr() { + [[ -z "$1" ]] && { echo "Usage: scr "; return 1; } + df_print_step "Restarting $1..." + sudo systemctl restart "$1" && { df_print_success "Restarted"; sudo systemctl status "$1" --no-pager -l; } || df_print_error "Failed" +} + +sce() { + [[ -z "$1" ]] && { echo "Usage: sce "; return 1; } + df_print_step "Enabling $1..." + sudo systemctl enable --now "$1" && { df_print_success "Enabled"; sudo systemctl status "$1" --no-pager -l | head -15; } || df_print_error "Failed" +} + +scd() { + [[ -z "$1" ]] && { echo "Usage: scd "; return 1; } + df_print_step "Disabling $1..." + sudo systemctl disable --now "$1" && df_print_success "Disabled" || df_print_error "Failed" +} + +sclog() { + [[ -z "$1" ]] && { echo "Usage: sclog "; return 1; } + df_print_step "Following logs for $1 (Ctrl+C to exit)..." + sudo journalctl -xeu "$1" -f -n "${2:-50}" +} + +sclogs() { + [[ -z "$1" ]] && { echo "Usage: sclogs "; return 1; } + sudo journalctl -xeu "$1" -n "${2:-50}" --no-pager +} + +sc-failed() { + df_print_func_name "Failed Services" + df_print_section "System" + local sys=$(systemctl --failed --no-pager --no-legend 2>/dev/null) + [[ -z "$sys" ]] && df_print_indent "✓ None" || echo "$sys" | sed 's/^/ /' + echo "" + df_print_section "User" + local usr=$(systemctl --user --failed --no-pager --no-legend 2>/dev/null) + [[ -z "$usr" ]] && df_print_indent "✓ None" || echo "$usr" | sed 's/^/ /' +} + +sc-timers() { + df_print_func_name "Active Timers" + df_print_section "System" + systemctl list-timers --no-pager | head -15 + echo "" + df_print_section "User" + systemctl --user list-timers --no-pager 2>/dev/null | head -10 +} + +sc-boot() { + df_print_func_name "Boot Analysis" + df_print_section "Summary" + systemd-analyze + echo "" + df_print_section "Slowest (top 10)" + systemd-analyze blame --no-pager | head -10 | sed 's/^/ /' +} + +sc-search() { + [[ -z "$1" ]] && { echo "Usage: sc-search "; return 1; } + df_print_func_name "Service Search: $1" + systemctl list-unit-files --type=service --no-pager | grep -i "$1" +} + +sc-info() { + [[ -z "$1" ]] && { echo "Usage: sc-info "; return 1; } + df_print_func_name "Service: $1" + systemctl status "$1" --no-pager -l 2>/dev/null || sudo systemctl status "$1" --no-pager -l + echo "" + df_print_section "Unit File" + systemctl cat "$1" 2>/dev/null | head -30 +} + +# fzf interactive +if df_cmd_exists fzf; then + scf() { + local svc=$(systemctl list-units --type=service --no-pager --no-legend | \ + fzf $(df_fzf_opts) --prompt='Service > ' --preview='systemctl status {1} --no-pager' | awk '{print $1}') + [[ -z "$svc" ]] && return + df_print_info "Selected: $svc" + echo "[s]tatus [r]estart [l]ogs [e]nable [d]isable" + read -k 1 "act?Action: "; echo + case "$act" in + s) sudo systemctl status "$svc" --no-pager -l ;; + r) scr "$svc" ;; + l) sclog "$svc" ;; + e) sce "$svc" ;; + d) scd "$svc" ;; + esac + } +fi + +sc-help() { + df_print_func_name "Systemd Commands" + cat << 'EOF' + sc sudo systemctl + scu systemctl --user + scr Restart + status + sce Enable + start + scd Disable + stop + sclog Follow logs + sclogs Recent logs + sc-failed Failed services + sc-timers Active timers + sc-boot Boot analysis + sc-search Search services + sc-info Service details + scf Interactive (fzf) +EOF +} + +alias scs='sc status' scstart='sc start' scstop='sc stop' screload='sc daemon-reload' +alias jctl='journalctl' jctlf='journalctl -f' jctlb='journalctl -b' diff --git a/refactor_backup/zsh/functions/systemd-helpers.zsh.zwc b/refactor_backup/zsh/functions/systemd-helpers.zsh.zwc new file mode 100644 index 0000000..c91258c Binary files /dev/null and b/refactor_backup/zsh/functions/systemd-helpers.zsh.zwc differ diff --git a/refactor_backup/zsh/functions/tmux-workspaces.zsh b/refactor_backup/zsh/functions/tmux-workspaces.zsh new file mode 100644 index 0000000..ae808ae --- /dev/null +++ b/refactor_backup/zsh/functions/tmux-workspaces.zsh @@ -0,0 +1,116 @@ +# ============================================================================ +# Tmux Workspace Manager - Project Templates & Layouts +# ============================================================================ + +source "${0:A:h}/../lib/utils.zsh" 2>/dev/null || \ +source "$HOME/.dotfiles/zsh/lib/utils.zsh" 2>/dev/null + +typeset -g TW_TEMPLATES="${TW_TEMPLATES:-$HOME/.dotfiles/.tmux-templates}" +typeset -g TW_PREFIX="${TW_PREFIX:-work}" +typeset -g TW_DEFAULT="${TW_DEFAULT:-dev}" + +_tw_check() { df_require_cmd tmux || return 1; } + +_tw_init() { + df_ensure_dir "$TW_TEMPLATES" + [[ ! -f "$TW_TEMPLATES/dev.tmux" ]] && { + echo -e "# Dev layout\nsplit-window -h -p 50\nsplit-window -v -p 50\nselect-pane -t 0" > "$TW_TEMPLATES/dev.tmux" + echo -e "# Ops layout\nsplit-window -h\nsplit-window -v\nselect-pane -t 0\nsplit-window -v\nselect-pane -t 0" > "$TW_TEMPLATES/ops.tmux" + echo "# Full\n" > "$TW_TEMPLATES/full.tmux" + df_print_success "Created default templates" + } +} + +tw-templates() { + _tw_init + df_print_func_name "Tmux Templates" + for t in "$TW_TEMPLATES"/*.tmux; do + [[ -f "$t" ]] && df_print_indent "● $(basename "$t" .tmux)" + done + echo "" + df_print_info "Create: tw-create