diff --git a/README.md b/README.md index 68e0f5d..ac14179 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Stash +# Stash - Enhanced GNU Stow Replacement -`stash` is a command-line utility written in Guile Scheme that helps organize your files by moving them to a target location and creating symbolic links (symlinks) in their original location. While it's great for managing dotfiles, it works with any directories you want to organize. +`stash` is a powerful command-line utility written in Guile Scheme that serves as an enhanced replacement for GNU Stow. It helps organize your files by moving them to a target location and creating symbolic links (symlinks) in their original location. With intelligent stashing, restoration capabilities, and GNU Stow-like deployment features, it's perfect for managing dotfiles and any other directory organization needs. ## Installation @@ -78,48 +78,70 @@ After installation, you might want to ensure the `stash` command is easily acces ## Key Features +### GNU Stow Replacement Capabilities +- **Deploy Mode**: Batch deployment of all packages (`stash -d`) +- **Package Deployment**: Deploy specific packages (`stash package-name`) +- **Dot Syntax**: Reverse symlinking from current directory (`stash .`) +- **Intelligent Stashing**: Automatically detects existing symlinks and adapts behavior +- **Restoration**: Complete file restoration with metadata tracking (`stash -R`) + +### Core Functionality - **Flexible Usage**: Works with any directories, not just config files - **Interactive Mode**: Option to interactively specify target directory - **Recursive Processing**: Can process entire directory trees - **Advanced Path Handling**: Supports home directory expansion and relative paths - **Symlink Management**: Creates and manages symlinks while maintaining directory structure - **Ignore Patterns**: Supports local and global ignore patterns +- **Metadata Tracking**: Each stashed file includes restoration metadata ## Usage -Stash offers several ways to organize your files: +### GNU Stow-like Deployment (Recommended) -1. **Interactive Mode** (easiest for beginners): +```sh +# Deploy all packages from dotfiles repository +cd ~/.dotfiles && stash -d - ```sh - # Move Pictures directory to a backup location - guile -L . stash.scm --source ~/Pictures --interactive - ``` +# Deploy specific package +cd ~/.dotfiles && stash shell -2. **Explicit Paths**: +# Using dot syntax (reverse symlinking) +cd ~/.dotfiles/shell && stash . +``` - ```sh - # Move Documents to backup while keeping symlink - guile -L . stash.scm --source ~/Documents/notes --target ~/backup/notes - - # Move project to code archive - guile -L . stash.scm --source ~/projects/webapp --target ~/code/archive/webapp - ``` +### Traditional Stashing (File Organization) -3. **Recursive Mode** (for entire directory trees): +```sh +# Stash individual files +stash -s ~/.zshrc -t ~/.files - ```sh - # Archive entire projects directory - guile -L . stash.scm --source ~/projects --target ~/archive/projects --recursive - ``` +# Stash with interactive target selection +stash -s ~/.config -i -4. **Dot Syntax** (after files are stashed): +# Recursive stashing +stash -s ~/.config -t ~/.files -r +``` - ```sh - # Recreate symlink for previously stashed directory - cd ~/backup/notes - guile -L . stash.scm . - ``` +### Restoration + +```sh +# Restore stashed files back to original locations +stash -R -s ~/.files/config/app/config.yml +``` + +### Complete Dotfiles Workflow + +```sh +# 1. Stash your configurations +stash -s ~/.zshrc -t ~/.files +stash -s ~/.config/app -t ~/.files + +# 2. On a new machine, deploy everything +cd ~/.files && stash -d + +# 3. If needed, restore individual files +stash -R -s ~/.files/config/app/config.yml +``` ## Common Use Cases diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 7316480..84736fb 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -1,4 +1,4 @@ -# Stash User Guide +# Stash User Guide - Enhanced GNU Stow Replacement ## Table of Contents @@ -7,20 +7,22 @@ - [Using Guix](#using-guix) - [Manual Installation](#manual-installation) 3. [Shell Configuration](#shell-configuration) - - [Fish Shell](#fish-shell) - - [Bash Shell](#bash-shell) - - [Zsh Shell](#zsh-shell) -4. [Basic Concepts](#basic-concepts) -5. [Usage Patterns](#usage-patterns) -6. [Common Use Cases](#common-use-cases) -7. [Advanced Features](#advanced-features) -8. [Configuration](#configuration) -9. [Troubleshooting](#troubleshooting) -10. [Tips and Best Practices](#tips-and-best-practices) +4. [GNU Stow-like Features](#gnu-stow-like-features) + - [Deploy Mode](#deploy-mode) + - [Dot Syntax](#dot-syntax) + - [Package Management](#package-management) +5. [Traditional Stashing](#traditional-stashing) +6. [Restoration](#restoration) +7. [Intelligent Stashing](#intelligent-stashing) +8. [Common Use Cases](#common-use-cases) +9. [Advanced Features](#advanced-features) +10. [Configuration](#configuration) +11. [Troubleshooting](#troubleshooting) +12. [Tips and Best Practices](#tips-and-best-practices) ## Introduction -Stash is a powerful symlink management utility that helps you organize your files by moving them to a target location while maintaining easy access through symbolic links. While it's excellent for managing dotfiles, Stash can organize any type of files or directories. +Stash is a powerful symlink management utility written in Guile Scheme that serves as an enhanced replacement for GNU Stow. It helps you organize your files by moving them to a target location while maintaining easy access through symbolic links. With intelligent stashing, restoration capabilities, and GNU Stow-like deployment features, it's perfect for managing dotfiles and any other directory organization needs. ### Why Use Stash? @@ -124,7 +126,104 @@ if [ -f "$GUIX_PROFILE/etc/profile" ]; then fi ``` -## Basic Concepts +## GNU Stow-like Features + +Stash provides enhanced GNU Stow replacement functionality with intelligent behavior and additional features. + +### Deploy Mode + +Deploy mode (`-d`) allows you to batch deploy all packages from a dotfiles repository, similar to GNU Stow: + +```sh +# Deploy all packages from current directory +cd ~/.dotfiles && stash -d + +# Deploy all packages with explicit path +stash -d /path/to/dotfiles +``` + +**How it works:** +- Scans the current directory for subdirectories (packages) +- Creates symlinks from each package's contents to the home directory +- Automatically handles directory structure creation +- Skips system files like `.git`, `README.md`, etc. + +### Dot Syntax + +The dot syntax (`.`) provides reverse symlinking - creating a symlink from the current directory back to its corresponding location in the home directory: + +```sh +# From within a dotfiles package directory +cd ~/.dotfiles/shell +stash . +# Creates: ~/.shell -> ~/.dotfiles/shell +``` + +**Use cases:** +- Quick reverse linking from dotfiles repo +- Testing configurations before full deployment +- Selective package activation + +### Package Management + +Deploy specific packages by name: + +```sh +# Deploy only the shell package +cd ~/.dotfiles && stash shell + +# Deploy only the emacs package +cd ~/.dotfiles && stash emacs +``` + +**Package structure example:** +``` +~/.dotfiles/ +├── shell/ +│ ├── zshrc +│ └── bashrc +├── emacs/ +│ ├── config.org +│ └── init.el +└── git/ + └── gitconfig +``` + +## Restoration + +Stash includes complete restoration capabilities with metadata tracking: + +```sh +# Restore a specific file +stash -R -s ~/.files/config/app/config.yml + +# Restore using metadata +stash -R -s /path/to/stashed/file +``` + +**Features:** +- Each stashed file includes `.stash-meta` metadata +- Metadata contains original path information +- Automatic directory structure recreation +- Safe restoration with conflict detection + +## Intelligent Stashing + +Stash automatically detects existing symlinks and adapts its behavior: + +**File-level stashing:** When a directory already contains symlinks, stash operates at the file level to avoid "symlink to symlink" issues. + +**Directory-level stashing:** When no symlinks exist, stash can move entire directories. + +```sh +# First file - directory level stashing +stash -s ~/.config/app/config.yml -t ~/.files + +# Second file - detects existing symlink, uses file-level stashing +stash -s ~/.config/app/theme.json -t ~/.files +``` + +## Traditional Stashing ### How Stash Works diff --git a/stash-enhanced.scm b/stash-enhanced.scm deleted file mode 100755 index ff201c1..0000000 --- a/stash-enhanced.scm +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env guile -!# - -;; Enhanced Stash with GNU Stow-like functionality -;; This version includes our enhancements merged with the dev branch improvements - -(add-to-load-path ".") - -(use-modules (ice-9 getopt-long) - (ice-9 ftw) - (ice-9 rdelim) - (srfi srfi-1) - (srfi srfi-19)) - -;; Load stash modules directly to avoid module conflicts -(load "modules/stash/colors.scm") -(load "modules/stash/log.scm") -(load "modules/stash/paths.scm") -(load "modules/stash/conflict.scm") -(load "modules/stash/package.scm") -(load "modules/stash/file-ops.scm") -(load "modules/stash/tree.scm") - -;;; Enhanced help function -(define (display-help) - "Display help message explaining how to use the program." - (display "\ -Usage: stash-enhanced [OPTION...] [PACKAGE|.] - -Enhanced Stash with GNU Stow-like functionality for dotfiles management. - -Options: - -s, --source=DIR Source directory to stash - -t, --target=DIR Target directory where files will be stashed - -r, --recursive Recursively process directories under source - -i, --interactive Interactively prompt for target directory - -d, --deploy Deploy mode: create symlinks from dotfiles repo - -h, --help Display this help - -v, --version Display version information - -Stashing Examples (moving files to storage): - # Stash a single directory: - stash-enhanced -s ~/Documents/notes -t ~/backup/notes - - # Stash with interactive target selection: - stash-enhanced -s ~/Pictures -i - - # Recursively stash an entire directory: - stash-enhanced -s ~/.config -t ~/.dotfiles/config -r - -Deployment Examples (GNU Stow-like functionality): - # Deploy all packages from dotfiles directory: - cd ~/.dotfiles && stash-enhanced -d - - # Deploy specific package: - cd ~/.dotfiles && stash-enhanced shell - - # Using dot syntax (create symlink from current dir to home): - cd ~/.dotfiles/shell && stash-enhanced . - -Dotfiles Workflow: - 1. Collect dotfiles: ~/.files/collect-dotfiles.sh - 2. Stash to dotfiles repo: stash-enhanced -s ~/.zshrc -t ~/.dotfiles/shell/zshrc - 3. On new machine: cd ~/.dotfiles && stash-enhanced -d - -For more information, visit: https://codeberg.org/glenneth/stash -")) - -(define (display-version) - (display "stash-enhanced version 0.2.0-alpha (with GNU Stow-like functionality)\n")) - -;;; Command-line options -(define %options - '((target (value #t) (single-char #\t)) - (source (value #t) (single-char #\s)) - (recursive (value #f) (single-char #\r)) - (interactive (value #f) (single-char #\i)) - (deploy (value #f) (single-char #\d)) - (help (value #f) (single-char #\h)) - (version (value #f) (single-char #\v)))) - -;;; Enhanced handler functions for GNU Stow-like functionality - -(define (handle-interactive-stash source recursive?) - "Handle interactive stashing where user selects target directory." - (display "Interactive target selection:\n") - (display (format #f "Source: ~a\n" source)) - (display "Enter target directory: ") - (let ((target (read-line))) - (if (string-null? target) - (begin - (display "No target specified. Exiting.\n") - (exit 1)) - (handle-explicit-stash source target recursive?)))) - -(define (handle-dot-syntax) - "Handle dot syntax: create symlink from current directory to home." - (let* ((current-dir (getcwd)) - (home-dir (getenv "HOME")) - (relative-path (string-drop current-dir (+ (string-length home-dir) 1))) - (target-path (string-append home-dir "/" relative-path))) - (display (format #f "Creating symlink: ~a -> ~a\n" target-path current-dir)) - (when (file-exists? target-path) - (if (file-is-symlink? target-path) - (delete-file target-path) - (begin - (display (format #f "Error: ~a already exists and is not a symlink\n" target-path)) - (exit 1)))) - (let ((target-dir (dirname target-path))) - (when (not (file-exists? target-dir)) - (mkdir-p target-dir))) - (symlink current-dir target-path) - (display (format #f "Created symlink: ~a -> ~a\n" target-path current-dir)))) - -(define (handle-package-deploy package-name) - "Deploy a specific package from current directory." - (let* ((package-dir (string-append (getcwd) "/" package-name)) - (home-dir (getenv "HOME"))) - (if (file-exists? package-dir) - (deploy-package package-dir home-dir) - (begin - (display (format #f "Error: Package directory ~a not found\n" package-dir)) - (exit 1))))) - -(define (handle-deploy-mode args) - "Handle deploy mode: deploy all packages or specific package." - (let ((current-dir (getcwd)) - (home-dir (getenv "HOME"))) - (if (null? args) - ;; Deploy all packages in current directory - (deploy-all-packages current-dir home-dir) - ;; Deploy specific package - (handle-package-deploy (car args))))) - -(define (deploy-all-packages dotfiles-dir home-dir) - "Deploy all packages from dotfiles directory." - (display (format #f "Deploying all packages from ~a to ~a\n" dotfiles-dir home-dir)) - (let ((entries (scandir dotfiles-dir))) - (for-each - (lambda (entry) - (let ((entry-path (string-append dotfiles-dir "/" entry))) - (when (and (not (member entry '("." ".." ".git" "README.md" "collect-dotfiles.sh" "STASH_GUIDE.md"))) - (file-is-directory? entry-path)) - (display (format #f "Deploying package: ~a\n" entry)) - (deploy-package entry-path home-dir)))) - entries))) - -(define (deploy-package package-dir home-dir) - "Deploy a single package by creating symlinks." - (let ((package-name (basename package-dir))) - (display (format #f "Deploying package: ~a\n" package-name)) - (deploy-directory-contents package-dir home-dir))) - -(define (deploy-directory-contents source-dir target-base) - "Recursively deploy directory contents by creating symlinks." - (let ((entries (scandir source-dir))) - (for-each - (lambda (entry) - (when (not (member entry '("." ".."))) - (let* ((source-path (string-append source-dir "/" entry)) - (target-path (string-append target-base "/." entry))) - (cond - ((file-is-directory? source-path) - ;; For directories, create the directory and recurse - (when (not (file-exists? target-path)) - (mkdir target-path #o755)) - (deploy-directory-contents source-path (dirname target-path))) - (else - ;; For files, create symlink - (when (file-exists? target-path) - (if (file-is-symlink? target-path) - (delete-file target-path) - (display (format #f "Warning: ~a exists and is not a symlink, skipping\n" target-path)))) - (let ((target-dir (dirname target-path))) - (when (not (file-exists? target-dir)) - (mkdir-p target-dir))) - (symlink source-path target-path) - (display (format #f "Created symlink: ~a -> ~a\n" target-path source-path))))))) - entries))) - -;;; Original stash functionality (adapted for dev branch improvements) - -(define (handle-explicit-stash source target recursive?) - "Handle stashing with explicit source and target paths." - (let* ((source-path (normalize-path source)) - (target-path (normalize-path target)) - (package-name (basename source-path)) - (ignore-patterns (read-ignore-patterns source-path)) - (package (make-package package-name source-path target-path ignore-patterns))) - (if recursive? - (handle-recursive-stash source-path target-path) - (begin - (process-package package) - #t)))) - -(define (handle-recursive-stash source-path target-path) - "Handle recursive stashing of directories." - (let* ((base-name (basename source-path)) - (ignore-patterns (read-ignore-patterns source-path)) - (package (make-package base-name source-path target-path ignore-patterns))) - (process-package package) - #t)) - -(define (process-package package) - "Process a single package for stashing." - (let* ((tree (analyze-tree package)) - (operations (plan-operations tree package))) - (execute-operations operations) - #t)) - -;;; Main function -(define (main args) - "Main function to parse arguments and execute the program." - (setenv "GUILE_AUTO_COMPILE" "0") - - (let* ((options (getopt-long args %options)) - (help-wanted? (option-ref options 'help #f)) - (version-wanted? (option-ref options 'version #f)) - (source (option-ref options 'source #f)) - (target (option-ref options 'target #f)) - (recursive? (option-ref options 'recursive #f)) - (interactive? (option-ref options 'interactive #f)) - (deploy? (option-ref options 'deploy #f)) - (remaining-args (option-ref options '() '()))) - - (cond - (help-wanted? - (display-help) - (exit 0)) - (version-wanted? - (display-version) - (exit 0)) - (deploy? - (handle-deploy-mode remaining-args) - #t) - ((and source target) - (handle-explicit-stash source target recursive?) - #t) - ((and source interactive?) - (handle-interactive-stash source recursive?) - #t) - ;; Handle dot syntax: stash . - ((and (= (length remaining-args) 1) (string=? (car remaining-args) ".")) - (handle-dot-syntax) - #t) - ;; Handle package deployment: stash package-name - ((and (= (length remaining-args) 1) (not (string=? (car remaining-args) "."))) - (handle-package-deploy (car remaining-args)) - #t) - (else - (display-help) - (exit 1))))) - -;; Entry point for stash-enhanced -(let ((result (main (command-line)))) - (if result - (exit 0) - (exit 1)))