#lang racket/base (require racket/system racket/port racket/cmdline racket/function racket/match racket/string racket/list) #| String helper functions |# (define (split-prefix str) (define components (string-split str)) (define head (car components)) (define tail (string-join (rest components))) (cons head tail)) (define (file-string file) (format "\"~a\"" file)) (define non-blank-string? (compose1 non-empty-string? string-trim)) #| Git commands |# (define (git . args) (string-split (with-output-to-string (λ () (system (string-join (cons "git" args))))) "\n")) (define (ls-files . args) (map split-prefix (git "ls-files" "-v" (string-join args)))) (define (update-index . args) (git "update-index" (string-join args))) (define (modified-files) (map cdr (ls-files "-m"))) (define (skipped-files) (map cdr (ls-files "|" "findstr" "\"^S\""))) (define (skip-file file) (update-index "--skip-worktree" (file-string file))) (define (no-skip-file file) (update-index "--no-skip-worktree" (file-string file))) (define (skip-modified) (for-each skip-file (modified-files))) (define (no-skip-all) (for-each no-skip-file (skipped-files))) #| Interactive mode helper methods |# (define (find-numbered-list number items) (map cdr (filter (compose1 (curry equal? number) number->string car) items))) (define (numbered-list items) (map cons (inclusive-range 1 (length items)) items)) (define (format-pair pair) (format "~a - ~a" (car pair) (cdr pair))) (define (format-numbered-list items) (map format-pair (numbered-list items))) (define (parse-input input) (regexp-match* #px"([sn])(\\d+)" input #:match-select rest)) (define (interpret-input skipped-file-list modified-file-list pair) (match (car pair) ("s" (for-each skip-file (find-numbered-list (cadr pair) (numbered-list modified-file-list)))) ("n" (for-each no-skip-file (find-numbered-list (cadr pair) (numbered-list skipped-file-list)))))) (define (handle-input input skipped-file-list modified-file-list) (if (non-blank-string? input) (for-each (curry interpret-input skipped-file-list modified-file-list) (parse-input input)) #f)) (define (display-files skipped-file-list modified-file-list) (displayln "Skipped Files:") (for-each displayln (format-numbered-list skipped-file-list)) (displayln "Modified Files:") (for-each displayln (format-numbered-list modified-file-list))) (define (interactive-prompt skipped-file-list modified-file-list) (display-files skipped-file-list modified-file-list) (displayln "Enter 's' or 'n' followed by a file number to skip or unskip a file respectively.") (displayln "e.g. 's3 n12' skips the third file under Modified Files then unskips the twelfth file under Skipped Files.") (displayln "Enter a blank line to quit.") (display "Input: ") (read-line)) (define (interactive-loop) (define skipped-file-list (skipped-files)) (define modified-file-list (modified-files)) (if (handle-input (interactive-prompt skipped-file-list modified-file-list) skipped-file-list modified-file-list) (interactive-loop) #f)) #| Programs |# (define (program-display-file-status) (display-files (skipped-files) (modified-files))) (define (program-skip-file file) (displayln (format "Skipping file: '~a'" file)) (skip-file file)) (define (program-no-skip-file file) (displayln (format "Unskipping file: '~a'" file)) (no-skip-file file)) (define (program-no-skip-all) (displayln "Unskipping every file") (no-skip-all)) (define (program-skip-modified) (displayln "Skipping every modified file") (skip-modified)) (define (program-interactive) (displayln "Interactive mode") (interactive-loop)) (command-line #:once-any (("-i" "--interactive") "Interactive mode" (program-interactive)) (("-l" "--list") "View skipped and modified file status" (program-display-file-status)) (("-s" "--skip") file "Skip single file" (program-skip-file file)) (("-n" "--no-skip") file "Unskip single file" (program-no-skip-file file)) (("-p" "--no-skip-all") "Unskip every file" (program-no-skip-all)) (("-m" "--skip-modified") "Skip every modified file" (program-skip-modified)))