mirror of https://codeberg.org/glenneth/stash.git
Added backup function
This commit is contained in:
parent
9bf54a5e47
commit
1222db82ee
20
stash.log
20
stash.log
|
|
@ -6,3 +6,23 @@ Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
|||
Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 11:08:52] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 11:08:52] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 13:52:41] Skipped moving directory: /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 13:52:45] Operation cancelled by user.
|
||||
[2024-09-22 14:00:22] Deleted directory: /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 14:00:22] Overwritten directory: /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 14:00:22] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-13-00] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-13-00] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-13-09] Backed up /home/glenn/.dotfiles/.config/test to /home/glenn/.dotfiles/.config/test.backup.2024-09-22-14-13-09
|
||||
[2024-09-22-14-13-09] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test after backup
|
||||
[2024-09-22-14-13-09] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-20-03] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-20-03] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-14-20-09] Backed up /home/glenn/.dotfiles/.config/test to /home/glenn/.dotfiles/.config/test.backup.2024-09-22-14-20-09
|
||||
[2024-09-22-14-45-22] Operation cancelled by user.
|
||||
[2024-09-22 15:08:02] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22 15:08:02] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-15-10-35] Operation cancelled by user.
|
||||
[2024-09-22-15-11-04] Moved /home/glenn/.config/test to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-15-11-04] Symlink created at /home/glenn/.config/test pointing to /home/glenn/.dotfiles/.config/test
|
||||
[2024-09-22-15-11-19] Operation cancelled by user.
|
||||
|
|
|
|||
123
stash.scm
123
stash.scm
|
|
@ -1,21 +1,21 @@
|
|||
;;; stash.scm --- A Guile script for moving directories and creating symlinks with conflict resolution
|
||||
;;;
|
||||
;;; Author: Glenn Thompson <glenn@kirstol.org>
|
||||
;;; Version: 1.1
|
||||
;;; Version: 1.2
|
||||
;;; Created: 2024-12-03
|
||||
;;; Compatibility: Guile 3.0.9
|
||||
;;; Keywords: symlink, file management, conflict resolution
|
||||
;;; 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 or skip the creation of new symlinks.
|
||||
;;; 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 or skip).
|
||||
;;; - 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.
|
||||
;;;
|
||||
|
|
@ -31,27 +31,6 @@
|
|||
;;; This software is licensed under the MIT License.
|
||||
;;;
|
||||
;;; Code:
|
||||
;; TODO: Refactor Repeated Logic: The logic for checking if a path is a file or directory and appending the basename is repeated in the create-symlink function. This could be encapsulatedin a helper function to improve readability and avoid redundancy. e.g.
|
||||
|
||||
;; (define (build-target-path source target)
|
||||
;; (string-append target "/" (basename source)))
|
||||
|
||||
;; TODO: Modularize Conflict Handling: Separating conflict handling into its own module to keep the main functionality more focused. This will also help to extend the conflict resolution mechanism with more options (e.g., backup, replace).
|
||||
|
||||
;; TODO: Add Logging Capabilities: It might be useful to log actions like creating symlinks, skipping conflicts, or canceling operations to a file for auditing purposes. Adding a logging function that appends these actions to a log file. e.g.
|
||||
|
||||
;; (define (log-action message)
|
||||
;; (with-output-to-file "guile-stash.log"
|
||||
;; (lambda () (display message) (newline))
|
||||
;; #:append #t))
|
||||
|
||||
;; TODO: Improve Error Handling: While catch is used, it's broad and might not capture the exact error. Using more specific error types (like system-error and file-error) could improve the granularity of error reporting. Additionally, return appropriate exit codes for different failure conditions to support integration with other scripts.
|
||||
|
||||
;; TODO: Optimize the delete-directory Function: Using rm -r is risky for recursive deletion in case of directory traversal attacks. You could implement a Scheme-native recursive delete function to avoid this.
|
||||
|
||||
;; #TODO Addition of a Non-Interactive Mode: Adding a non-interactive mode that automatically skips or overwrites conflicts (based on a command-line flag) could make the script more flexible for use in automation workflows. e.g.
|
||||
|
||||
;;; CODE
|
||||
|
||||
;; Import necessary modules
|
||||
(use-modules (ice-9 getopt-long)
|
||||
|
|
@ -75,8 +54,8 @@
|
|||
(define (current-timestamp)
|
||||
"Return the current date and time as a formatted string."
|
||||
(let* ((time (current-time))
|
||||
(seconds (time-second time))) ;; Extract the seconds from the time object
|
||||
(strftime "%Y-%m-%d %H:%M:%S" (localtime seconds))))
|
||||
(seconds (time-second time)))
|
||||
(strftime "%Y-%m-%d-%H-%M-%S" (localtime seconds))))
|
||||
|
||||
;;; Improved logging function with timestamp
|
||||
(define (log-action message)
|
||||
|
|
@ -93,6 +72,14 @@
|
|||
(string-append (getenv "HOME") (substring path 1))
|
||||
path))
|
||||
|
||||
;;; Helper function to back up a directory or symlink
|
||||
(define (backup-path path)
|
||||
"Back up a directory or symlink by renaming it with a timestamp."
|
||||
(let ((backup-path (string-append path ".backup." (current-timestamp))))
|
||||
(rename-file path backup-path)
|
||||
(display (color-message (format #f "Backed up ~a to ~a\n" path backup-path) green-text))
|
||||
(log-action (format #f "Backed up ~a to ~a" path backup-path))))
|
||||
|
||||
;;; Function to handle command-line arguments
|
||||
(define (parse-arguments args)
|
||||
"Parse command-line arguments."
|
||||
|
|
@ -101,6 +88,22 @@
|
|||
(source (value #t) (required? #t))))))
|
||||
opts))
|
||||
|
||||
;;; Helper function to quote shell arguments
|
||||
(define (shell-quote-argument arg)
|
||||
"Quote shell argument to handle spaces or special characters."
|
||||
(if (string-contains arg " ")
|
||||
(string-append "\"" (string-replace arg "\"" "\\\"") "\"")
|
||||
arg))
|
||||
|
||||
;;; Helper function to delete a directory recursively
|
||||
(define (delete-directory path)
|
||||
"Delete a directory recursively."
|
||||
(if (file-is-directory? path)
|
||||
(begin
|
||||
(system (string-append "rm -rf " (shell-quote-argument path))) ;; Use rm -rf to delete recursively
|
||||
(log-action (format #f "Deleted directory: ~a" path))) ;; Log the deletion
|
||||
(display (color-message "Error: Path is not a directory.\n" red-text))))
|
||||
|
||||
;;; Helper function to move source to target
|
||||
(define (move-source-to-target source-dir target-dir)
|
||||
"Move the entire source directory to the target directory."
|
||||
|
|
@ -108,10 +111,35 @@
|
|||
(target-dir (expand-home target-dir))
|
||||
(source-name (basename source-dir))
|
||||
(target-source-dir (string-append target-dir "/" source-name)))
|
||||
;; Move the source directory into the target directory
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (color-message (format #f "Moved ~a to ~a\n" source-dir target-source-dir) green-text))
|
||||
(log-action (format #f "Moved ~a to ~a" source-dir target-source-dir))
|
||||
;; Check if the target directory already exists
|
||||
(if (file-exists? target-source-dir)
|
||||
(begin
|
||||
(display (color-message (format #f "Directory ~a already exists.\n" target-source-dir) yellow-text))
|
||||
;; Handle the conflict: overwrite, backup, or skip
|
||||
(let ((choice (prompt-user-for-backup-action)))
|
||||
(cond
|
||||
((eq? choice 'overwrite)
|
||||
(delete-directory target-source-dir)
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (color-message (format #f "Overwriting directory ~a.\n" target-source-dir) green-text))
|
||||
(log-action (format #f "Overwritten directory: ~a" target-source-dir)))
|
||||
((eq? choice 'backup)
|
||||
(backup-path target-source-dir)
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (color-message (format #f "Moved ~a to ~a\n" source-dir target-source-dir) green-text))
|
||||
(log-action (format #f "Moved ~a to ~a after backup" source-dir target-source-dir)))
|
||||
((eq? choice 'skip)
|
||||
(display (color-message "Skipping move operation.\n" green-text))
|
||||
(log-action (format #f "Skipped moving directory: ~a" target-source-dir)))
|
||||
((eq? choice 'cancel)
|
||||
(display (color-message "Operation cancelled by user.\n" yellow-text))
|
||||
(log-action "Operation cancelled by user.")
|
||||
(exit 0)))))
|
||||
;; If the target directory doesn't exist, proceed with the move
|
||||
(begin
|
||||
(rename-file source-dir target-source-dir)
|
||||
(display (color-message (format #f "Moved ~a to ~a\n" source-dir target-source-dir) green-text))
|
||||
(log-action (format #f "Moved ~a to ~a" source-dir target-source-dir))))
|
||||
target-source-dir)) ;; Return the path of the moved source directory
|
||||
|
||||
;;; Helper function to create a symlink in the parent directory of the original source
|
||||
|
|
@ -129,35 +157,25 @@
|
|||
(display (color-message (format #f "Symlink created at ~a pointing to ~a\n" symlink-target target-dir) green-text))
|
||||
(log-action (format #f "Symlink created at ~a pointing to ~a" symlink-target target-dir))))))
|
||||
|
||||
;;; Conflict resolution handler
|
||||
(define (prompt-user-for-action)
|
||||
"Prompt the user to decide how to handle a conflict: overwrite (o), skip (s), or cancel (c)."
|
||||
(display (color-message "A conflict was detected. Choose action - Overwrite (o), Skip (s), or Cancel (c): " yellow-text))
|
||||
;;; Conflict resolution handler with backup option
|
||||
(define (prompt-user-for-backup-action)
|
||||
"Prompt the user to decide how to handle a conflict: overwrite (o), backup (b), skip (s), or cancel (c)."
|
||||
(display (color-message "A conflict was detected. Choose action - Overwrite (o), Backup (b), Skip (s), or Cancel (c): " yellow-text))
|
||||
(let ((response (read-line)))
|
||||
(cond
|
||||
((string-ci=? response "o") 'overwrite)
|
||||
((string-ci=? response "b") 'backup)
|
||||
((string-ci=? response "s") 'skip)
|
||||
((string-ci=? response "c") 'cancel)
|
||||
(else
|
||||
(display (color-message "Invalid input. Please try again.\n" red-text))
|
||||
(prompt-user-for-action)))))
|
||||
(prompt-user-for-backup-action)))))
|
||||
|
||||
;;; Function to check if a file is a symlink
|
||||
(define (file-is-symlink? path)
|
||||
"Check if a given file path is a symlink."
|
||||
(let ((stat (lstat path)))
|
||||
(eqv? (stat:type stat) 'symlink)))
|
||||
|
||||
;;; Helper function to build the target symlink path
|
||||
(define (build-target-path source target)
|
||||
"Builds the symlink path for the source file or directory under the target directory."
|
||||
(string-append target "/" (basename source)))
|
||||
|
||||
;;; Function to handle conflicts
|
||||
;;; Function to handle conflicts with symlinks and directories
|
||||
(define (handle-conflicts source target)
|
||||
"Handle conflicts when a symlink or file already exists at the target location."
|
||||
(display (color-message (format #f "Conflict detected! A file or symlink already exists at ~a.\n" target) yellow-text))
|
||||
(let ((choice (prompt-user-for-action))) ;; Prompt the user for action
|
||||
(let ((choice (prompt-user-for-backup-action))) ;; Prompt for backup or overwrite
|
||||
(cond
|
||||
((eq? choice 'overwrite)
|
||||
(if (file-is-symlink? target)
|
||||
|
|
@ -165,20 +183,19 @@
|
|||
(delete-file target)
|
||||
(display (color-message "Removed the existing symbolic link.\n" green-text))
|
||||
(log-action (format #f "Removed the existing symbolic link at ~a" target))
|
||||
;; Recreate the symlink after removing the old one
|
||||
(create-symlink-in-parent source target))
|
||||
(display (color-message "Error: Target is not a symbolic link.\n" red-text))))
|
||||
((eq? choice 'backup)
|
||||
(backup-path target) ;; Backup the existing directory or symlink
|
||||
(create-symlink-in-parent source target)) ;; After backup, create the new symlink
|
||||
((eq? choice 'skip)
|
||||
;; Do nothing, log skipping the conflict
|
||||
(log-action (format #f "Skipped creating symlink at ~a" target))
|
||||
(display (color-message "Keeping existing symbolic link.\n" green-text)))
|
||||
((eq? choice 'cancel)
|
||||
;; Cancel the operation
|
||||
(log-action "Operation cancelled by user.")
|
||||
(display (color-message "Operation cancelled.\n" yellow-text))
|
||||
(exit 0))
|
||||
(else
|
||||
;; Invalid input
|
||||
(display (color-message "Invalid choice. Please try again.\n" red-text))
|
||||
(handle-conflicts source target)))))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue