diff --git a/.guix-channel/stash/packages/stash.scm b/.guix-channel/stash/packages/stash.scm index bfb6e0c..907aaf0 100644 --- a/.guix-channel/stash/packages/stash.scm +++ b/.guix-channel/stash/packages/stash.scm @@ -1,7 +1,7 @@ (define-module (stash packages stash) #:use-module (guix packages) - #:use-module (guix git-download) #:use-module (guix build-system gnu) + #:use-module (guix gexp) #:use-module ((guix licenses) #:prefix license:) #:use-module (gnu packages guile)) @@ -22,7 +22,6 @@ (arguments `(#:phases (modify-phases %standard-phases - (delete 'bootstrap) (delete 'configure) (replace 'build (lambda _ @@ -40,21 +39,16 @@ (chmod (string-append bin "/stash") #o755) ;; Install modules (for-each - (lambda (f) - (install-file f - (string-append site "/stash"))) + (lambda (file) + (install-file file + (string-append site "/stash"))) (find-files "modules/stash" "\\.scm$")) - #t))) - (delete 'check)))) ; No tests yet + #t)))))) (inputs (list guile-3.0)) (native-inputs (list guile-3.0)) ; For compilation - (home-page "https://codeberg.org/glenneth/stash") (synopsis "Symlink management utility") - (description - "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 in -their original location. While it's great for managing dotfiles, it works with -any directories you want to organize.") + (description "Stash is a command-line utility for managing symbolic links") + (home-page "https://codeberg.org/glenneth/stash") (license license:gpl3+))) diff --git a/README.md b/README.md index 3379f1e..03b778b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,31 @@ ## Installation +There are two ways to install Stash: + +### Method 1: Using Guix (Recommended) + +```sh +# Install from the local package definition +guix package --install-from-file=minimal-package.scm + +# Configure your shell environment: + +# For Fish shell (add to ~/.config/fish/config.fish): +set -gx GUIX_PROFILE $HOME/.guix-profile +set -gx PATH $GUIX_PROFILE/bin $PATH + +# For Bash (add to ~/.bashrc): +export GUIX_PROFILE="$HOME/.guix-profile" +. "$GUIX_PROFILE/etc/profile" + +# For Zsh (add to ~/.zshrc): +export GUIX_PROFILE="$HOME/.guix-profile" +. "$GUIX_PROFILE/etc/profile" +``` + +### Method 2: Manual Installation + 1. **Prerequisites**: - Guile Scheme 3.0.9 or later - A Unix-like environment (Linux/macOS) @@ -31,6 +56,24 @@ stash --help ``` +## Shell Integration + +After installation, you might want to ensure the `stash` command is easily accessible: + +1. **Using Guix Installation**: + - The command should be available after setting up your shell environment as shown above + - If not, create a symbolic link: `ln -sf ~/.guix-profile/bin/stash ~/.local/bin/stash` + +2. **Using Manual Installation**: + - Add an alias to your shell config: + ```sh + # For Fish (in ~/.config/fish/config.fish): + alias stash="guile -L /path/to/stash /path/to/stash/stash.scm" + + # For Bash/Zsh: + alias stash='guile -L /path/to/stash /path/to/stash/stash.scm' + ``` + ## Key Features - **Flexible Usage**: Works with any directories, not just config files diff --git a/USER_GUIDE.md b/USER_GUIDE.md new file mode 100644 index 0000000..15aa9af --- /dev/null +++ b/USER_GUIDE.md @@ -0,0 +1,173 @@ +# Stash User Guide + +## Table of Contents +1. [Installation](#installation) + - [Using Guix](#using-guix) + - [Manual Installation](#manual-installation) +2. [Shell Configuration](#shell-configuration) + - [Fish Shell](#fish-shell) + - [Bash Shell](#bash-shell) + - [Zsh Shell](#zsh-shell) +3. [Basic Usage](#basic-usage) +4. [Advanced Features](#advanced-features) +5. [Configuration](#configuration) +6. [Troubleshooting](#troubleshooting) + +## Installation + +### Using Guix + +The recommended way to install Stash is using the Guix package manager: + +```sh +# Install from local package definition +guix package --install-from-file=minimal-package.scm +``` + +After installation, the `stash` executable will be available in your Guix profile at `~/.guix-profile/bin/stash`. + +### Manual Installation + +If you're not using Guix, you can install Stash manually: + +1. Install prerequisites: + ```sh + # Debian/Ubuntu + sudo apt-get install guile-3.0 + + # Fedora + sudo dnf install guile30 + + # Arch Linux + sudo pacman -S guile + ``` + +2. Clone and set up the repository: + ```sh + git clone https://github.com/yourusername/stash.git + cd stash + mkdir -p ~/.guile.d/site/3.0 + ln -s $(pwd)/modules/stash ~/.guile.d/site/3.0/ + ``` + +## Shell Configuration + +### Fish Shell + +1. Add to `~/.config/fish/config.fish`: + ```fish + # Guix environment setup + set -gx GUIX_PROFILE $HOME/.guix-profile + set -gx PATH $GUIX_PROFILE/bin $PATH + + # Load Guix environment variables + if test -f $GUIX_PROFILE/etc/profile + for line in (cat $GUIX_PROFILE/etc/profile | grep '^export' | string replace 'export ' '') + set var (string split '=' $line) + set -gx $var[1] (eval echo $var[2]) + end + end + ``` + +2. Alternative method using symlink: + ```fish + ln -sf ~/.guix-profile/bin/stash ~/.local/bin/stash + ``` + +### Bash Shell + +Add to `~/.bashrc`: +```bash +# Guix environment setup +export GUIX_PROFILE="$HOME/.guix-profile" +if [ -f "$GUIX_PROFILE/etc/profile" ]; then + . "$GUIX_PROFILE/etc/profile" +fi +``` + +### Zsh Shell + +Add to `~/.zshrc`: +```zsh +# Guix environment setup +export GUIX_PROFILE="$HOME/.guix-profile" +if [ -f "$GUIX_PROFILE/etc/profile" ]; then + . "$GUIX_PROFILE/etc/profile" +fi +``` + +## Basic Usage + +1. Simple file stashing: + ```sh + # Move a directory to backup location + stash -s ~/Documents/notes -t ~/backup/notes + ``` + +2. Recursive stashing: + ```sh + # Move entire config directory + stash -s ~/.config -t ~/.dotfiles/config -r + ``` + +3. Interactive mode: + ```sh + # Let stash prompt for target location + stash -s ~/Pictures -i + ``` + +## Advanced Features + +1. **Ignore Patterns** + - Create `.stashignore` in source directory + - Add patterns similar to `.gitignore` + ``` + *.tmp + .DS_Store + node_modules/ + ``` + +2. **Recursive Mode** + - Use `-r` flag for entire directory trees + - Preserves directory structure + - Creates symlinks for all subdirectories + +3. **Conflict Resolution** + - Automatically detects existing files/symlinks + - Interactive prompts for resolution + - Options to skip, replace, or backup + +## Configuration + +1. **Environment Variables** + - `GUILE_AUTO_COMPILE=0`: Disable auto-compilation + - `GUILE_LOAD_PATH`: Add custom module paths + - `GUIX_PROFILE`: Set Guix profile location + +2. **Global Configuration** + - System-wide ignore patterns in `/etc/stash/ignore` + - User-specific patterns in `~/.config/stash/ignore` + +## Troubleshooting + +1. **Command Not Found** + - Verify Guix profile is sourced correctly + - Check PATH includes `~/.guix-profile/bin` + - Try creating symlink in `~/.local/bin` + +2. **Module Loading Issues** + - Ensure GUILE_LOAD_PATH includes module directory + - Check module permissions and ownership + - Verify Guile version compatibility + +3. **Permission Errors** + - Check file/directory permissions + - Ensure write access to target location + - Verify symlink creation permissions + +4. **Common Warnings** + - "canonicalize-path override": Normal, can be ignored + - "auto-compilation enabled": Set GUILE_AUTO_COMPILE=0 + +For more information or to report issues, visit: +https://codeberg.org/glenneth/stash diff --git a/channels.scm.example b/channels.scm.example index 51ad287..c8f83f7 100644 --- a/channels.scm.example +++ b/channels.scm.example @@ -1,12 +1,6 @@ (list (channel (name 'guix) - (url "https://git.savannah.gnu.org/git/guix.git") - (branch "master") - (introduction - (make-channel-introduction - "9edb3f66fd807b096b48283debdcddccfea34bad" - (openpgp-fingerprint - "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))) + (url "https://git.savannah.gnu.org/git/guix.git")) (channel (name 'stash) (url "https://codeberg.org/glenneth/stash") diff --git a/minimal-package.scm b/minimal-package.scm new file mode 100644 index 0000000..7d450d6 --- /dev/null +++ b/minimal-package.scm @@ -0,0 +1,46 @@ +(define-module (stash-package) + #:use-module (guix packages) + #:use-module (guix build-system copy) + #:use-module (guix licenses) + #:use-module (guix gexp) + #:use-module (gnu packages guile)) + +(define-public stash + (package + (name "stash") + (version "0.1.0") + (source (local-file "." "stash-checkout" + #:recursive? #t)) + (build-system copy-build-system) + (arguments + '(#:install-plan + '(("stash.scm" "share/guile/site/3.0/stash.scm") + ("stash.scm" "bin/stash") + ("modules/stash" "share/guile/site/3.0/stash")) + #:phases + (modify-phases %standard-phases + (add-after 'install 'make-executable + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (bin (string-append out "/bin")) + (script (string-append bin "/stash"))) + ;; Make script executable + (chmod script #o755) + ;; Add proper shebang + (substitute* script + ((".*") + (string-append + "#! /usr/bin/env -S guile" + " -L " out "/share/guile/site/3.0" + " -C " out "/share/guile/site/3.0" + " -e '(begin (use-modules (stash)) (main (command-line)))'" + " -s\n!#\n"))) + #t)))))) + (inputs + (list guile-3.0)) + (synopsis "Symlink management utility") + (description "Stash is a command-line utility for managing symlinks.") + (home-page "https://codeberg.org/glenneth/stash") + (license gpl3+))) + +stash diff --git a/modules/stash/tree.scm b/modules/stash/tree.scm index bc0ebd7..5dcc089 100644 --- a/modules/stash/tree.scm +++ b/modules/stash/tree.scm @@ -21,7 +21,7 @@ (make-tree-node path type children) tree-node? (path node-path) - (type node-type) ; 'file or 'directory + (type node-type) ; 'file, 'directory, or 'symlink (children node-children)) ;; Check if a path should be ignored based on patterns @@ -37,16 +37,20 @@ (let analyze ((path root-path)) (if (should-ignore? path ignore-patterns) #f - (if (file-is-directory? path) - (let* ((entries (scandir path)) - (children (filter-map - (lambda (entry) - (if (member entry '("." "..")) - #f - (analyze (string-append path "/" entry)))) - entries))) - (make-tree-node path 'directory children)) - (make-tree-node path 'file '())))))) + (cond + ((file-is-symlink? path) + (make-tree-node path 'symlink '())) + ((file-is-directory? path) + (let* ((entries (scandir path)) + (children (filter-map + (lambda (entry) + (if (member entry '("." "..")) + #f + (analyze (string-append path "/" entry)))) + entries))) + (make-tree-node path 'directory children))) + (else + (make-tree-node path 'file '()))))))) ;; Determine if a directory tree can be folded (define (can-fold-tree? node target-base) diff --git a/stash.scm b/stash.scm index 135dc1b..be7aece 100644 --- a/stash.scm +++ b/stash.scm @@ -1,38 +1,3 @@ -;;; stash.scm --- A Guile script for moving directories and creating symlinks with conflict resolution -;;; -;;; Author: Glenn Thompson -;;; Version: 0.1.0-alpha.1 -;;; Created: 2024-12-03 -;;; Compatibility: Guile 3.0.9 -;;; Keywords: symlink, file management, conflict resolution, backup -;;; -;;; Commentary: -;;; -;;; Stash is a command-line utility written in Guile Scheme designed to facilitate the movement of directories and -;;; creation of symbolic links (symlinks) for files and directories. It allows users to specify a source directory -;;; and a target directory where the symlink should be created. The utility handles potential conflicts with existing -;;; files or directories at the target location, offering users the choice to overwrite, back up, or skip the creation of new symlinks. -;;; -;;; Main Features: -;;; - Command-line argument parsing for specifying source and target paths. -;;; - Conflict detection with interactive user resolution options (overwrite, backup, skip, or cancel). -;;; - Moving directories and creating symlinks. -;;; - Simple and interactive user interface for easy use. -;;; -;;; Usage: -;;; -;;; guile -L . stash.scm --target= --source= -;;; -;;; Replace with the directory where you want the symlink to be created, -;;; and with the path to the source directory. -;;; -;;; License: -;;; -;;; This project is licensed under the GNU General Public License v3. -;;; - -;;; CODE - (define-module (stash) #:use-module (ice-9 getopt-long) #:use-module (ice-9 ftw) @@ -46,12 +11,8 @@ #:use-module (stash package) #:use-module (stash tree) #:use-module (srfi srfi-1) - #:use-module (srfi srfi-19)) - -;;; Color constants -(define blue-text "\x1b[0;34m") -(define yellow-text "\x1b[0;33m") -(define red-text "\x1b[0;31m") + #:use-module (srfi srfi-19) + #:export (main)) ;;; Command-line options (define %options @@ -62,89 +23,23 @@ (help (value #f) (single-char #\h)) (version (value #f) (single-char #\v)))) -;;; Function to handle dot directory stashing -(define (handle-dot-stash) - "Handle stashing when using the '.' syntax. Uses parent directory as target." - (let* ((current-dir (canonicalize-path (getcwd))) - (pkg-dir (if (file-is-symlink? current-dir) - (canonicalize-path (readlink current-dir)) - current-dir)) - (pkg-name (basename pkg-dir)) - (stow-dir (dirname pkg-dir)) - (target-dir (dirname stow-dir)) - (ignore-patterns (read-ignore-patterns pkg-dir))) - (format #t "Package directory: ~a~%" pkg-dir) - (format #t "Stow directory: ~a~%" stow-dir) - (format #t "Target directory: ~a~%" target-dir) - (if (file-is-symlink? current-dir) - (format #t "Directory is already stashed at: ~a~%" pkg-dir) - (let ((package (make-package pkg-name pkg-dir target-dir ignore-patterns))) - (process-package package))))) - -;;; Version function -(define (display-version) - "Display the current version of the program." - (newline) - (display "Stash version 0.1.0-alpha.1\n") - (exit 0)) - -;;; Helper function to check for version flag and display version -(define (check-for-version args) - "Check if --version or -v is in the arguments list." - (if (or (member "--version" args) - (member "-v" args)) - (display-version))) - -;;; Main function to handle stowing operations -(define (handle-stow package options) - (let* ((tree (analyze-tree package)) - (simulate? (assoc-ref options 'simulate)) - (no-folding? (assoc-ref options 'no-folding))) - (plan-operations tree package))) - -;;; Prompt user for target directory path -(define (prompt-for-target source-path) - "Prompt user for target directory path." - (display (color-message (string-append "\nSource directory: " source-path "\n") blue-text)) - (display (color-message "Enter target directory path (where files will be stashed): " yellow-text)) - (let ((input (read-line))) - (if (string-null? input) - (begin - (display (color-message "Target directory cannot be empty. Please try again.\n" red-text)) - (prompt-for-target source-path)) - (canonicalize-path (expand-home input))))) - -;;; Main entry point (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)) - (recursive? (option-ref options 'recursive #f)) - (interactive? (option-ref options 'interactive #f)) (source (option-ref options 'source #f)) - (target (option-ref options 'target #f))) + (target (option-ref options 'target #f)) + (recursive? (option-ref options 'recursive #f))) (cond - (help-wanted? (display-help) (exit 0)) - (version-wanted? (display-version) (exit 0)) - - ;; Handle dot syntax - ((and (= (length (option-ref options '() '())) 1) - (string=? (car (option-ref options '() '())) ".")) - (handle-dot-stash)) - - ;; Handle interactive mode - ((and source interactive?) - (let ((target-path (prompt-for-target (canonicalize-path source)))) - (handle-explicit-stash source target-path recursive?))) - - ;; Handle explicit paths with optional recursion + (help-wanted? + (display-help) + (exit 0)) ((and source target) - (handle-explicit-stash source target recursive?)) - + (handle-explicit-stash source target recursive?) + #t) (else (display-help) (exit 1))))) @@ -158,39 +53,26 @@ (if recursive? (handle-recursive-stash source-path target-path) (let ((package (make-package package-name source-path target-path ignore-patterns))) - (process-package package))))) + (process-package package) + #t)))) -(define (handle-recursive-stash source target) - "Recursively process directories under source." - (let* ((source-path (canonicalize-path source)) - (target-path (canonicalize-path target)) - (source-name (basename source-path)) - (target-config-path (string-append target-path "/" source-name)) - (entries (if (file-is-directory? source-path) - (scandir source-path) - (list (basename source-path)))) - (valid-entries (filter (lambda (entry) - (and (not (member entry '("." ".."))) - (file-is-directory? (string-append source-path "/" entry)))) - entries))) - ;; First ensure the config directory exists in target - (if (not (file-exists? target-config-path)) - (mkdir-p target-config-path)) - ;; Then process each subdirectory - (for-each - (lambda (entry) - (let* ((source-dir (string-append source-path "/" entry)) - (package-name entry) - (ignore-patterns (read-ignore-patterns source-dir))) - (let ((package (make-package package-name source-dir target-config-path ignore-patterns))) - (process-package package)))) - valid-entries))) +(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))) + (execute-operations operations) + #t)) ;; Entry point for stash -(main (command-line)) +(let ((result (main (command-line)))) + (if result + (exit 0) + (exit 1))) diff --git a/test-source/dir1/file1.txt b/test-source/dir1/file1.txt new file mode 100644 index 0000000..ac3e272 --- /dev/null +++ b/test-source/dir1/file1.txt @@ -0,0 +1 @@ +content1 diff --git a/test-source/dir1/subdir1/file2.txt b/test-source/dir1/subdir1/file2.txt new file mode 100644 index 0000000..637f034 --- /dev/null +++ b/test-source/dir1/subdir1/file2.txt @@ -0,0 +1 @@ +content2 diff --git a/test-source/dir2/file3.txt b/test-source/dir2/file3.txt new file mode 100644 index 0000000..27d10cc --- /dev/null +++ b/test-source/dir2/file3.txt @@ -0,0 +1 @@ +content3 diff --git a/test-source/dir2/subdir2/file4.txt b/test-source/dir2/subdir2/file4.txt new file mode 100644 index 0000000..9cce852 --- /dev/null +++ b/test-source/dir2/subdir2/file4.txt @@ -0,0 +1 @@ +content4