# ============================================================================= # functions/dotfiles.fish — Dotfiles Manager # ============================================================================= # # This file is auto-loaded by Fish when you first run `dotfiles`. It defines # the `dotfiles` command and all its subcommands. The design is deliberately # monolithic (one file) so that the whole system is easy to fork, audit, and # copy around — yet each subcommand is a separate named function for clarity. # # ── How it works ──────────────────────────────────────────────────────────── # # 1. Template files live in $DOTFILES_DIR (default: ~/.config/fish/dotfiles/). # Each template is named after the dotfile it manages, e.g.: # dotfiles/tmux.conf → installed to ~/.tmux.conf # dotfiles/gitconfig → installed to ~/.gitconfig # # 2. When you run `dotfiles init`, templates are copied to $HOME with a # leading dot prepended. Existing files are backed up. # # 3. When you run `dotfiles update`, the template is re-copied (with backup). # # 4. `dotfiles sync` also pulls the latest templates from the remote repo # before re-applying them. # # 5. `dotfiles status` compares the installed file against its template using # a checksum so you can see at a glance what's drifted. # # ── Convention for template filenames ────────────────────────────────────── # # Template name Target file Typical use # ────────────────────────────────────────────────────────────────── # tmux.conf ~/.tmux.conf tmux terminal multiplexer # gitconfig ~/.gitconfig Git version control # gitignore ~/.gitignore Global Git ignore rules # bashrc ~/.bashrc Bash (when Fish is absent) # # To add support for a new file, simply drop a template in $DOTFILES_DIR # and run `dotfiles init `. That's it — no registration needed. # # ============================================================================= # ══════════════════════════════════════════════════════════════════════════════ # dotfiles — main dispatcher # ══════════════════════════════════════════════════════════════════════════════ # # Usage: dotfiles [arguments ...] # # Run `dotfiles help` (or just `dotfiles`) for a full list of subcommands. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles -d "Manage dotfiles and self-update system" # ── No argument (or explicit help) → show help ───────────────────────── set -l cmd $argv[1] switch "$cmd" case "" help --help -h dotfiles_help case version --version -v dotfiles_version case status dotfiles_status $argv[2..] case init # `dotfiles init` → init all # `dotfiles init foo bar` → init only foo and bar dotfiles_init $argv[2..] case update # Same argument pattern as init. dotfiles_update $argv[2..] case edit if set -q argv[2] dotfiles_edit $argv[2] else echo "Usage: dotfiles edit " echo "Example: dotfiles edit tmux" return 1 end case repo # `dotfiles repo` → show current URL # `dotfiles repo ` → set URL dotfiles_repo $argv[2] case sync dotfiles_sync case publish dotfiles_publish case add dotfiles_add $argv[2..] case '*' echo "dotfiles: unknown command '$cmd'" echo "Run 'dotfiles help' for usage." return 1 end end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_help # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_help -d "Show the dotfiles help text" echo "Usage: dotfiles [arguments]" echo "" echo "Commands:" echo " help Show this help message" echo " version Display version and system info" echo " status [name ...] Show checksum diff for dotfiles" echo " init [name ...] Create dotfiles from templates" echo " update [name ...] Re-apply templates (with backup)" echo " add [name] Import a file as a new template" echo " edit Open ~/. in \$EDITOR" echo " repo [url] Show or set the remote repo URL" echo " sync Git-pull and re-apply everything" echo " publish Push local changes to remote repo" echo " with a version bump" echo "" echo "Available managed files:" _dotfiles_list_managed | string collect echo "" echo "Examples:" echo " dotfiles init Create ~/.tmux.conf, ~/.gitconfig, …" echo " dotfiles init gitconfig Create only ~/.gitconfig" echo " dotfiles add ~/.vimrc Import ~/.vimrc as a template" echo " dotfiles add ~/.config/kitty/kitty.conf kitty/kitty.conf" echo " dotfiles status Check every managed file for drift" echo " dotfiles sync Pull + re-apply everything" echo " dotfiles publish Bump version, commit, and push" echo "" echo "The DOTFILES_REPO_URL environment variable controls where sync" echo "and publish operate. Set it with: dotfiles repo " end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_version # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_version -d "Show version information" echo "dotfiles v$DOTFILES_VERSION" if test -n "$DOTFILES_REPO_URL" echo "Repository : $DOTFILES_REPO_URL ($DOTFILES_REPO_BRANCH)" else echo "Repository : (none set — run 'dotfiles repo ' to enable sync)" end echo "Shell : Fish $FISH_VERSION" echo "Config dir : $__fish_config_dir" echo "Templates : $DOTFILES_DIR" echo "Backups : $DOTFILES_BACKUP_DIR" end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_status # ══════════════════════════════════════════════════════════════════════════════ # # Compares each installed dotfile against its template using MD5 checksums. # Shows three states: # ✓ up to date — template and installed file are identical # ✗ drifted — installed file differs from the template # - not installed — template exists but target has not been created # # If you give one or more names (e.g. `dotfiles status tmux`) only those # are checked. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_status -d "Show checksum diff for managed dotfiles" set -l targets (_dotfiles_resolve_targets $argv) echo "Checking managed dotfiles ..." echo "" set -l any_drift 0 for name in $targets set -l tmpl (_dotfiles_template_path "$name") set -l dest (_dotfiles_target_path "$name") if not test -f "$tmpl" echo " - $name (no template at $tmpl)" continue end if not test -f "$dest" echo " - $name (not installed — run 'dotfiles init $name')" continue end # Compare checksums (cross-platform: md5sum on Linux, md5 on macOS) set -l tmpl_md5 (_dotfiles_md5 "$tmpl") set -l dest_md5 (_dotfiles_md5 "$dest") if test "$tmpl_md5" = "$dest_md5" echo " ✓ $name (up to date)" else echo " ✗ $name (drifted — run 'dotfiles update $name')" set any_drift 1 end end echo "" if test "$any_drift" -eq 0 echo "All managed files are up to date." else echo "Some files have drifted. Run 'dotfiles update' to re-apply templates." end end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_init — create dotfiles from templates # ══════════════════════════════════════════════════════════════════════════════ # # Creates ~/. from the template at $DOTFILES_DIR/. # Existing files are backed up before being overwritten. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_init -d "Create dotfiles from templates" set -l targets (_dotfiles_resolve_targets $argv) if set -q targets[1] echo "Initialising dotfiles ..." else echo "No managed files found in $DOTFILES_DIR" echo "Add a template file there and run 'dotfiles init '." return 0 end set -l did_anything 0 for name in $targets set -l tmpl (_dotfiles_template_path "$name") set -l dest (_dotfiles_target_path "$name") if not test -f "$tmpl" echo " skipping $name — no template at $tmpl" continue end # Backup any existing file if test -f "$dest" set -l backup "$DOTFILES_BACKUP_DIR/$name" set -l backup_parent (dirname "$backup") if not test -d "$backup_parent" mkdir -p "$backup_parent" end command cp "$dest" "$backup" echo " backed up $dest → $backup" end # Create parent directory if needed (e.g. ~/.config/starship.toml) set -l parent (dirname "$dest") if not test -d "$parent" mkdir -p "$parent" end command cp "$tmpl" "$dest" echo " created $dest" set did_anything 1 end if test "$did_anything" -eq 0 echo "Nothing was created." end end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_update — re-apply templates (with backup) # ══════════════════════════════════════════════════════════════════════════════ # # Identical to `dotfiles init` except it prints "updated" rather than # "created". This distinction is useful when scripting or checking logs. # (Technically the behaviour is the same — backup + copy — but the user # intent is different.) # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_update -d "Re-apply template to existing dotfile" set -l targets (_dotfiles_resolve_targets $argv) if not set -q targets[1] echo "No managed files found in $DOTFILES_DIR" return 0 end for name in $targets set -l tmpl (_dotfiles_template_path "$name") set -l dest (_dotfiles_target_path "$name") if not test -f "$tmpl" echo " skipping $name — no template at $tmpl" continue end # Backup existing file if test -f "$dest" set -l backup "$DOTFILES_BACKUP_DIR/$name" set -l backup_parent (dirname "$backup") if not test -d "$backup_parent" mkdir -p "$backup_parent" end command cp "$dest" "$backup" echo " backed up $dest → $backup" end set -l parent (dirname "$dest") if not test -d "$parent" mkdir -p "$parent" end command cp "$tmpl" "$dest" echo " updated $dest" end end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_edit — open a dotfile in $EDITOR # ══════════════════════════════════════════════════════════════════════════════ # # Opens ~/. for editing. If the file doesn't exist yet, you can # initialise it from the template first with `dotfiles init `. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_edit -d "Open a dotfile for editing" set -l name $argv[1] set -l dest (_dotfiles_target_path "$name") if not test -f "$dest" echo "File $dest does not exist." echo "Create it first: dotfiles init $name" return 1 end set -q EDITOR; or set -l EDITOR nano $EDITOR "$dest" end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_add — import a file as a new template # ══════════════════════════════════════════════════════════════════════════════ # # Usage: # dotfiles add ~/.vimrc → imports as vimrc, deploys to ~/.vimrc # dotfiles add ~/.config/kitty/kitty.conf kitty/kitty.conf # → imports to kitty/kitty.conf, # deploys to ~/.config/kitty/kitty.conf # # When no explicit name is given, the template name is derived by stripping # the "$HOME/." prefix from the resolved source path. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_add -d "Import a file as a new dotfile template" set -l source $argv[1] if not set -q source[1] echo "Usage: dotfiles add [name]" echo "Example: dotfiles add ~/.vimrc" return 1 end if not test -f "$source" echo "Error: $source is not a regular file." return 1 end # Resolve to an absolute path (cross-platform: Linux `realpath` or # macOS `grealpath` from coreutils; fall back to basic expansion). set -l abs_source if command -v realpath &>/dev/null set abs_source (realpath "$source") else if command -v grealpath &>/dev/null set abs_source (grealpath "$source") else set abs_source (string replace -r '^~' "$HOME" -- "$source") if not string match -q '/*' "$abs_source" set abs_source "$PWD/$abs_source" end end # Derive template name: strip "$HOME/." prefix → relative dotfile path set -l name $argv[2] if not set -q name[1] set -l home_dot "$HOME/." if string match -q "$home_dot*" "$abs_source" set name (string sub -s (math (string length "$home_dot") + 1) "$abs_source") else # Fall back to basename of the source file set name (basename "$abs_source") end end set -l tmpl (_dotfiles_template_path "$name") # Create parent directories under DOTFILES_DIR if needed set -l parent (dirname "$tmpl") if not test -d "$parent" mkdir -p "$parent" end command cp "$abs_source" "$tmpl" echo " added template: $tmpl" # Deploy it immediately dotfiles_init "$name" end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_repo — show or set the remote repository URL # ══════════════════════════════════════════════════════════════════════════════ # # The URL is stored persistently in a small text file under the backup dir # so it survives shell restarts. It is also loaded into the environment # variable DOTFILES_REPO_URL by config.fish if the file exists. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_repo -d "Show or set the remote repository URL" set -l repo_file "$DOTFILES_BACKUP_DIR/.repo_url" set -l url $argv[1] if set -q url[1] # ── Set the URL ──────────────────────────────────────────────────── echo "$url" >"$repo_file" set -gx DOTFILES_REPO_URL "$url" echo "Repository URL set to: $url" else # ── Show the current URL ────────────────────────────────────────── if test -f "$repo_file" set -l stored_url (command cat "$repo_file") echo "$stored_url" else if test -n "$DOTFILES_REPO_URL" echo "$DOTFILES_REPO_URL" else echo "(no repository configured)" echo "" echo "Run: dotfiles repo https://github.com/yourname/dotfiles" end end end # Ensure there's always a default branch and dotfiles directory, even if # config.fish hasn't been reloaded after a framework update. if not set -q DOTFILES_REPO_BRANCH set -gx DOTFILES_REPO_BRANCH "main" end if not set -q DOTFILES_DIR set -gx DOTFILES_DIR "$__fish_config_dir/dotfiles" end if not set -q DOTFILES_BACKUP_DIR set -gx DOTFILES_BACKUP_DIR "$__fish_config_dir/backups" end if not set -q DOTFILES_REPO_URL set -gx DOTFILES_REPO_URL "git@gitssh.toomuchtaco.net:taco/dots.git" end # Load persisted repo URL into the environment on shell start. # This runs when the function file is sourced, so it's evaluated once per # session (before the first `dotfiles` invocation). set -l repo_file "$DOTFILES_BACKUP_DIR/.repo_url" if test -f "$repo_file" set -gx DOTFILES_REPO_URL (command cat "$repo_file") end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_sync — full sync: git pull + update all dotfiles # ══════════════════════════════════════════════════════════════════════════════ # # This is the "self-update" mechanism. It: # # 1. Checks that DOTFILES_REPO_URL is set # 2. Clones the repo into a temporary directory (or pulls if already cloned) # 3. Copies the templates from the cloned repo into DOTFILES_DIR # 4. Re-applies every template to its target location # # Sync also copies config.fish and functions/ from upstream, then # automatically restarts the shell if framework files changed. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_sync -d "Pull remote updates and re-apply all dotfiles" # ── Step 1: Ensure we have a remote URL ──────────────────────────────── if test -z "$DOTFILES_REPO_URL" echo "No repository URL configured." echo "Set one with: dotfiles repo https://github.com/yourname/dotfiles" return 1 end echo "Syncing from $DOTFILES_REPO_URL ($DOTFILES_REPO_BRANCH) ..." echo "" # ── Step 2: Clone/pull into a temporary directory ────────────────────── set -l tmpdir (mktemp -d) echo "Cloning into $tmpdir ..." if command git clone --branch "$DOTFILES_REPO_BRANCH" --depth 1 "$DOTFILES_REPO_URL" "$tmpdir" echo "Clone successful." else echo "Error: failed to clone repository." command rm -rf "$tmpdir" return 1 end # ── Step 3: Copy upstream templates into our local DOTFILES_DIR ──────── set -l upstream_templates "$tmpdir/.config/fish/dotfiles" if test -d "$upstream_templates" echo "" echo "Updating templates from upstream ..." if not command cp -r "$upstream_templates/." "$DOTFILES_DIR/" echo "Error: failed to copy templates to $DOTFILES_DIR" command rm -rf "$tmpdir" return 1 end echo " templates synced from upstream" else echo "Warning: no dotfiles/ directory found in the upstream repo." echo "Expected at: $upstream_templates" end # ── Step 4: Re-apply all templates ──────────────────────────────────── echo "" echo "Re-applying dotfiles ..." dotfiles_update # ── Step 5: Copy framework files (config.fish, functions/) ───────────── echo "" echo "Updating framework files ..." set -l had_updates 0 if test -f "$tmpdir/.config/fish/config.fish" command cp "$tmpdir/.config/fish/config.fish" "$__fish_config_dir/config.fish" echo " config.fish updated" set had_updates 1 end if test -d "$tmpdir/.config/fish/functions" command cp -r "$tmpdir/.config/fish/functions/." "$__fish_config_dir/functions/" echo " functions/ updated" set had_updates 1 end if test -d "$tmpdir/.config/fish/conf.d" command cp -r "$tmpdir/.config/fish/conf.d/." "$__fish_config_dir/conf.d/" echo " conf.d/ updated" set had_updates 1 end # ── Step 6: Clean up ─────────────────────────────────────────────────── command rm -rf "$tmpdir" echo "" echo "Sync complete." echo "" # ── Step 7: Restart shell to apply framework changes ─────────────────── if test "$had_updates" -eq 1 if status is-interactive echo "Restarting shell to apply changes ..." exec fish else echo "Framework files updated. Restart your shell: exec fish" end end end # ══════════════════════════════════════════════════════════════════════════════ # dotfiles_publish — push local changes to the remote repo # ══════════════════════════════════════════════════════════════════════════════ # # This is the counterpart to `dotfiles sync`. Instead of pulling remote # templates down, it pushes your local config, functions, templates, and # conf.d/ files back up to the remote repository. # # Workflow: # 1. Clones the remote repo into a temporary directory # 2. Copies the current local state on top of it: # config.fish, functions/, dotfiles/, conf.d/ # 3. Prompts for a version number (auto-suggested minor bump, or custom) # 4. Updates DOTFILES_VERSION in both the local and cloned config.fish # 5. Commits (with a message like "dotfiles v1.1.0") # 6. Pushes to the remote # 7. Cleans up the temporary clone # # After publish, run `dotfiles sync` on any other machine to pick up the # changes. # ══════════════════════════════════════════════════════════════════════════════ function dotfiles_publish -d "Push local changes to the remote dotfiles repo" # ── Step 1: Ensure we have a remote URL ──────────────────────────────── if test -z "$DOTFILES_REPO_URL" echo "No repository URL configured." echo "Set one with: dotfiles repo https://github.com/yourname/dotfiles" return 1 end echo "Publishing to $DOTFILES_REPO_URL ($DOTFILES_REPO_BRANCH) ..." # ── Step 2: Clone (or init) the repo ───────────────────────────────────── set -l tmpdir (mktemp -d) echo "Preparing $tmpdir ..." if command git clone --branch "$DOTFILES_REPO_BRANCH" "$DOTFILES_REPO_URL" "$tmpdir" 2>/dev/null echo "Clone successful." else # Remote may not exist yet, or it exists but has no commits. # Initialise a fresh repo locally and wire up the remote. echo "Remote branch not found — initialising new repository." command git init "$tmpdir" command git -C "$tmpdir" remote add origin "$DOTFILES_REPO_URL" echo "Local repo ready (origin → $DOTFILES_REPO_URL)." end # Make sure the expected branch exists (create it if it doesn't). if not command git -C "$tmpdir" rev-parse --verify "$DOTFILES_REPO_BRANCH" 2>/dev/null command git -C "$tmpdir" checkout -b "$DOTFILES_REPO_BRANCH" echo "Created branch '$DOTFILES_REPO_BRANCH'." else command git -C "$tmpdir" checkout "$DOTFILES_REPO_BRANCH" end # ── Step 3: Copy local state into the clone ─────────────────────────── # These are the files and directories that make up the dotfiles repo. set -l local_config "$__fish_config_dir/config.fish" set -l local_funcs "$__fish_config_dir/functions/" set -l local_tmpl "$DOTFILES_DIR/" set -l local_confd "$__fish_config_dir/conf.d/" set -l local_install "$__fish_config_dir/install.fish" set -l local_readme "$__fish_config_dir/README.md" set -l repo_config "$tmpdir/.config/fish/config.fish" set -l repo_funcs "$tmpdir/.config/fish/functions/" set -l repo_tmpl "$tmpdir/.config/fish/dotfiles/" set -l repo_confd "$tmpdir/.config/fish/conf.d/" set -l repo_install "$tmpdir/install.fish" set -l repo_readme "$tmpdir/README.md" # Ensure target directories exist for d in (dirname "$repo_config") "$repo_funcs" "$repo_tmpl" "$repo_confd" mkdir -p "$d" end echo "" echo "Copying local state into clone ..." command cp "$local_config" "$repo_config" echo " config.fish" command cp -r "$local_funcs." "$repo_funcs" echo " functions/" command cp -r "$local_tmpl." "$repo_tmpl" echo " dotfiles/" command cp -r "$local_confd." "$repo_confd" echo " conf.d/" if test -f "$local_install" command cp "$local_install" "$repo_install" echo " install.fish" else echo " (no install.fish)" end if test -f "$local_readme" command cp "$local_readme" "$repo_readme" echo " README.md" else echo " (no README.md)" end # ── Step 4: Prompt for version ───────────────────────────────────────── echo "" set -l new_version (_dotfiles_prompt_version "$DOTFILES_VERSION") # ── Step 5: Update version in both local and cloned config.fish ─────── # The version line looks like: set -g DOTFILES_VERSION "X.Y.Z" set -l version_line 'set -g DOTFILES_VERSION "'"$new_version"'"' # Update local config.fish sed -i.bak 's/^set -g DOTFILES_VERSION ".*"$/'"$version_line"'/' "$local_config" command rm -f "$local_config.bak" echo "Updated local $local_config → v$new_version" # Update cloned config.fish sed -i.bak 's/^set -g DOTFILES_VERSION ".*"$/'"$version_line"'/' "$repo_config" command rm -f "$repo_config.bak" # Set the session variable so subsequent commands in this function # and future shell sessions see the new version. set -g DOTFILES_VERSION "$new_version" # ── Step 6: Commit and push from the clone ───────────────────────────── echo "" command git -C "$tmpdir" add -A # Check if there's anything to commit if not command git -C "$tmpdir" status --porcelain | grep -q . echo "Nothing changed — skipping commit and push." command rm -rf "$tmpdir" return 0 end echo "Committing and pushing ..." command git -C "$tmpdir" commit -m "dotfiles v$new_version" if command git -C "$tmpdir" push origin "$DOTFILES_REPO_BRANCH" echo "" echo "Published v$new_version successfully." else echo "" echo "Warning: push failed. The commit exists locally in $tmpdir." echo "Check your credentials and remote URL, then push manually:" echo " cd $tmpdir && git push origin $DOTFILES_REPO_BRANCH" # Don't clean up on failure so the user can fix and push manually. return 1 end # ── Step 7: Clean up ─────────────────────────────────────────────────── command rm -rf "$tmpdir" echo "" echo "Done. Run 'dotfiles sync' on other machines to pull v$new_version." end # ══════════════════════════════════════════════════════════════════════════════ # _dotfiles_prompt_version — ask user for version, auto-suggest minor bump # ══════════════════════════════════════════════════════════════════════════════ # # Parses the current version (X.Y.Z), suggests X.(Y+1).0, and prompts. # If the user presses Enter without typing, the suggested bump is used. # If the user types a custom string (e.g. "2.0.0" or "1.5.0-beta"), that # is used instead. # ══════════════════════════════════════════════════════════════════════════════ function _dotfiles_prompt_version -d "Prompt for version, defaulting to minor bump" set -l current $argv[1] set -l suggested (_dotfiles_bump_minor "$current") echo "Current version: v$current" >&2 # Use double quotes so $suggested expands in the prompt string. read -p "echo \"New version (press Enter for v$suggested): \"" -l custom if test -z "$custom" echo "$suggested" else echo "$custom" end end # ══════════════════════════════════════════════════════════════════════════════ # _dotfiles_bump_minor — given X.Y.Z, return X.(Y+1).0 # ══════════════════════════════════════════════════════════════════════════════ # # Works with versions like "1.0.0", "2.15.3", etc. Non-numeric parts # (e.g. "1.0.0-beta") will cause the math to fail, so we fall back to # appending ".1" as a best-effort. # ══════════════════════════════════════════════════════════════════════════════ function _dotfiles_bump_minor -d "Increment the minor version component" set -l v $argv[1] # Split on dots: "1.2.3" → major=1, minor=2, patch=3 set -l parts (string split "." "$v") set -l major $parts[1] set -l minor $parts[2] set -l patch $parts[3] # Default to 0 for missing components set -q minor[1]; or set minor 0 set -q patch[1]; or set patch 0 # Only attempt arithmetic if all three components are plain integers. # Use string match to check they are digits only. if string match -rq '^\d+$' "$major" and string match -rq '^\d+$' "$minor" and string match -rq '^\d+$' "$patch" set -l new_minor (math "$minor + 1") echo "$major.$new_minor.0" else # Non-numeric version — just append a bump suffix. echo "$v.1" end end # ══════════════════════════════════════════════════════════════════════════════ # Private helper functions (prefixed with _dotfiles_) # ══════════════════════════════════════════════════════════════════════════════ # # These are not intended to be called directly from the command line. # ══════════════════════════════════════════════════════════════════════════════ # ----------------------------------------------------------------------------- # _dotfiles_list_managed # # Returns a sorted list of template names in $DOTFILES_DIR. # Used by the help text and by _dotfiles_resolve_targets. # ----------------------------------------------------------------------------- function _dotfiles_list_managed -d "List available dotfile templates" if not test -d "$DOTFILES_DIR" return 1 end # Recursively list all regular files relative to DOTFILES_DIR. # find -printf is GNU-only, so we strip the prefix portably. command find "$DOTFILES_DIR" -type f | sort | string replace "$DOTFILES_DIR/" "" end # ----------------------------------------------------------------------------- # _dotfiles_resolve_targets # # Converts a list of user-supplied names (e.g. "tmux gitconfig") into the # full list of targets. If no names are given, falls back to ALL managed # templates. Unknown names are silently skipped with a warning. # # This function is used by init, update, and status. # ----------------------------------------------------------------------------- function _dotfiles_resolve_targets -d "Resolve user-supplied names to template names" if set -q argv[1] # User specified specific names — return them as-is. # Validation happens downstream (_dotfiles_template_path). printf "%s\n" $argv else # No names given — return everything we have templates for. _dotfiles_list_managed end end # ----------------------------------------------------------------------------- # _dotfiles_md5 # # Cross-platform MD5 checksum: uses md5sum on Linux, md5 on macOS. # Returns just the hash string. # ----------------------------------------------------------------------------- function _dotfiles_md5 -d "Get MD5 checksum of a file (cross-platform)" if command -v md5sum &>/dev/null md5sum "$argv[1]" | cut -d' ' -f1 else md5 "$argv[1]" | sed 's/.*= //' end end # ----------------------------------------------------------------------------- # _dotfiles_template_path # # Returns the full filesystem path to the template for a given name. # Example: _dotfiles_template_path "tmux" → $DOTFILES_DIR/tmux.conf # ----------------------------------------------------------------------------- function _dotfiles_template_path -d "Get the template path for a dotfile name" echo "$DOTFILES_DIR/$argv[1]" end # ----------------------------------------------------------------------------- # _dotfiles_target_path # # Returns the full filesystem path where the dotfile should be installed. # # Convention: # - If the template name contains a "/", it's treated as a relative path # under $HOME (e.g. "config/starship.toml" → ~/.config/starship.toml). # - Otherwise, the template name is prefixed with a dot and placed directly # under $HOME (e.g. "tmux" → ~/.tmux). Note: tmux.conf is the *filename*, # so "tmux.conf" → ~/.tmux.conf. # # This means: # Template name Target # tmux.conf ~/.tmux.conf # gitconfig ~/.gitconfig # config/starship.toml ~/.config/starship.toml # # To add a file in a subdirectory of $HOME, simply create a template with # a "/" in its name: "vim/vimrc" → ~/.vim/vimrc # ----------------------------------------------------------------------------- function _dotfiles_target_path -d "Get the target installation path for a dotfile" set -l name $argv[1] echo "$HOME/.$name" end