diff --git a/packages/lab-tool/REFACTOR_PLAN.md b/packages/lab-tool/REFACTOR_PLAN.md new file mode 100644 index 0000000..6efdb0a --- /dev/null +++ b/packages/lab-tool/REFACTOR_PLAN.md @@ -0,0 +1,95 @@ +# Lab Tool Refactoring Plan - Functional Programming Approach + +## Current Problems +- Deep nested parentheses causing syntax errors +- Functions doing too many things +- Hard to debug and maintain +- Mixed pure/impure logic + +## New Structure + +``` +lab-tool/ +├── core/ +│ ├── config.scm # Pure config access +│ ├── machine.scm # Pure machine data structures +│ └── commands.scm # Pure command building +├── io/ +│ ├── ssh.scm # Pure SSH command building +│ ├── rsync.scm # Pure rsync command building +│ └── shell.scm # Impure shell execution +├── deploy/ +│ ├── ssh-strategy.scm # Pure deployment strategy +│ ├── deploy-rs-strategy.scm # Pure deploy-rs strategy +│ └── executor.scm # Impure deployment execution +├── health/ +│ ├── checks.scm # Pure health check logic +│ └── monitor.scm # Impure health monitoring +└── main/ + ├── cli.scm # Pure CLI parsing + ├── dispatcher.scm # Pure command dispatch + └── runner.scm # Impure main execution +``` + +## Functional Principles + +### 1. Single Responsibility Functions +```scheme +;; Instead of one complex function doing everything: +(define (deploy-complex machine options) ...) + +;; Break into focused functions: +(define (build-rsync-command source dest ssh-config) ...) +(define (build-nixos-rebuild-command flake-path machine mode) ...) +(define (execute-command command) ...) +(define (compose-ssh-deployment rsync-cmd rebuild-cmd) ...) +``` + +### 2. Pure vs Impure Separation +```scheme +;; Pure: No side effects, testable +(define (make-ssh-target user hostname) + (format #f "~a@~a" user hostname)) + +;; Impure: Clear side effects +(define (execute-ssh-command ssh-config command) + (system (build-ssh-command ssh-config command))) +``` + +### 3. Function Composition +```scheme +;; Instead of deep nesting: +(let ((config (get-ssh-config machine)) + (command (build-command ...)) + (result (execute (format ...)))) + (if (success? result) ...)) + +;; Use composition: +(-> machine + get-ssh-config + (build-deployment-commands flake-path) + execute-deployment + handle-result) +``` + +### 4. Error Handling as Values +```scheme +;; Instead of exceptions in nested calls: +(catch #t (lambda () (complex-nested-operation)) error-handler) + +;; Return result types: +(define (safe-ssh-connect machine) + (if (valid-config? machine) + `(success . ,(make-connection machine)) + `(error . "Invalid SSH config"))) +``` + +## Implementation Steps + +1. **Extract SSH utilities** (no more parentheses hell) +2. **Create pure command builders** +3. **Separate execution layer** +4. **Build composable deployment strategies** +5. **Clean CLI interface** + +This will make debugging much easier - each small function can be tested independently! \ No newline at end of file diff --git a/packages/lab-tool/io/shell.scm b/packages/lab-tool/io/shell.scm new file mode 100644 index 0000000..dd2eb2b --- /dev/null +++ b/packages/lab-tool/io/shell.scm @@ -0,0 +1,36 @@ +;; io/shell.scm - Impure shell execution functions + +(define-module (io shell) + #:use-module (ice-9 popen) + #:use-module (ice-9 textual-ports) + #:use-module (utils logging) + #:export (execute-command + execute-with-output + test-command)) + +;; Impure function: Execute command and return success/failure +(define (execute-command command) + "Execute shell command, return true if successful" + (log-debug "Executing: ~a" command) + (let ((status (system command))) + (zero? status))) + +;; Impure function: Execute command and capture output +(define (execute-with-output command) + "Execute command and return (success . output) pair" + (log-debug "Executing with output: ~a" command) + (let* ((port (open-pipe* OPEN_READ "/bin/sh" "-c" command)) + (output (get-string-all port)) + (status (close-pipe port)) + (success (zero? status))) + (log-debug "Command ~a: exit=~a" (if success "succeeded" "failed") status) + (cons success output))) + +;; Impure function: Test if command succeeds (no output) +(define (test-command command) + "Test if command succeeds, return boolean" + (log-debug "Testing command: ~a" command) + (let* ((port (open-pipe* OPEN_READ "/bin/sh" "-c" command)) + (output (get-string-all port)) + (status (close-pipe port))) + (zero? status))) \ No newline at end of file diff --git a/packages/lab-tool/io/ssh.scm b/packages/lab-tool/io/ssh.scm new file mode 100644 index 0000000..88e27d9 --- /dev/null +++ b/packages/lab-tool/io/ssh.scm @@ -0,0 +1,37 @@ +;; io/ssh.scm - Pure SSH command building functions + +(define-module (io ssh) + #:use-module (ice-9 format) + #:export (make-ssh-target + build-ssh-command + build-rsync-command + make-ssh-options)) + +;; Pure function: Build SSH target string +(define (make-ssh-target user hostname) + "Build SSH target string from user and hostname" + (if user + (format #f "~a@~a" user hostname) + hostname)) + +;; Pure function: Build SSH options string +(define (make-ssh-options identity-file timeout) + "Build SSH options string" + (let ((opts '())) + (when identity-file + (set! opts (cons (format #f "-i ~a" identity-file) opts))) + (when timeout + (set! opts (cons (format #f "-o ConnectTimeout=~a" timeout) opts))) + (set! opts (cons "-o BatchMode=yes" opts)) + (string-join (reverse opts) " "))) + +;; Pure function: Build SSH command +(define (build-ssh-command target options command) + "Build complete SSH command string" + (format #f "ssh ~a ~a '~a'" options target command)) + +;; Pure function: Build rsync command +(define (build-rsync-command source-path target dest-path ssh-options) + "Build rsync command with SSH transport" + (format #f "rsync -avz --delete -e 'ssh ~a' ~a/ ~a:~a/" + ssh-options source-path target dest-path)) \ No newline at end of file