;;;; directory-utils.scm ;;;; Kon Lovett, Aug '10 ;; Issues ;; ;; - See scattered 'FIXME' entries. (module directory-utils (;export pathname? check-pathname error-pathname filename? check-filename error-filename check-directory error-directory ; dot-filename-prefix? dot-pathname? dot-filename? ; directory-fold ; directory-utility-stack push-directory pop-directory pop-toplevel-directory ; create-pathname-directory make-program-filename make-shell-filename file-exists-in-directory? find-file-pathnames find-program-pathnames which-command-pathnames which-command-pathname remove-dotfiles) (import scheme ;(only (scheme file) file-exists?) ;w/ rev 42f3ff1b (scheme process-context) (chicken base) (chicken type) (only (chicken keyword) get-keyword) (only (chicken process-context) current-directory change-directory) (only (chicken string) string-split) (only (chicken pathname) make-pathname pathname-directory pathname-extension pathname-file decompose-pathname decompose-directory) (only (chicken file) file-exists? directory create-directory) (only (chicken file posix) directory?) (only (srfi 1) first fold append! filter-map remove any) (only (srfi 13) string-null? string-prefix?) (only miscmacros define-parameter until) (only list-utils not-null? ensure-list) (only stack make-stack stack? stack-empty? stack-push! stack-pop!) (only type-checks define-check+error-type check-procedure)) ;;; Helpers ;; (define-type stack (struct stack)) (define-type optional-list (or boolean list)) (define-type filename string) (define-type extension string) (define-type basename string) (define-type pathname string) ;; (define (->boolean obj) (and obj #t) ) (cond-expand (windows (define-constant PATH-DELIMITER ";")) (else (define-constant PATH-DELIMITER ":"))) (define +dot-directory+ (make-pathname "." #f)) ;; ;no . or .. since directoryname (: dot-filename-prefix? (filename -> boolean)) ; (define (dot-filename-prefix? str) (and (string-prefix? "." str) #+unix (not (or (string=? "." str) (string=? ".." str)))) ) ;;; ;; (define-check+error-type directory) ;NOTE do not type these as 'predicate', ex: (: filename? (* -> boolean : filename)) ;since the compiler will treat a literal ".." as meeting the criteria at compile time! ;; A null pathname or only extension is not a pathname, here at least ; detecting only an extension is impossible with string pathnames (: pathname? (* --> boolean)) ; (define (pathname? obj) (and (string? obj) (receive (dir fil ext) (decompose-pathname obj) (->boolean (or dir fil)))) ) (define-check+error-type pathname) ;; Just a filename, no directory (: filename? (* --> boolean)) ; (define (filename? obj) (and (string? obj) (receive (dir fil ext) (decompose-pathname obj) (not dir))) ) (define-check+error-type filename) ;; (: dot-filename? (* --> boolean)) ; (define (dot-filename? obj) (and (filename? obj) (dot-filename-prefix? obj)) ) ;; Is any pathname component is a dot-filename? (: dot-pathname? (* --> boolean)) ; (define (dot-pathname? obj) (and (string? obj) (let-values ( ((dir fil ext) (decompose-pathname obj)) ) (or (dot-filename-prefix? fil) (let-values ( ((org dir elts) (decompose-directory dir)) ) (and elts (any dot-filename-prefix? elts)))))) ) ;; Remove dot files from a directory list (: remove-dotfiles ((list-of pathname) --> (list-of pathname))) ; (define (remove-dotfiles files) (remove (lambda (pn) (dot-filename? (pathname-file pn))) files) ) ;; ;FIXME need a routine that provides filename and stat info to the fold func. ;The stat info should include platform specific info as well: the Windows Hidden ;attribute, the MacOS X birthtime, etc. (: directory-fold (procedure * pathname #!rest -> *)) ; (define (directory-fold func ident dir . opts) (check-procedure 'directory-fold func) (let* ( (dotfiles? (get-keyword #:dotfiles? opts (lambda () #f))) (dir (directory (check-directory 'directory-fold dir) dotfiles?)) ) (fold func ident dir) ) ) ;; Directory Stack (: directory-utility-stack (#!optional stack -> stack)) ; (define-parameter directory-utility-stack (make-stack) (lambda (x) (if (stack? x) x (begin (warning '+directory-stack+ "not a stack") (directory-utility-stack))))) ;; (: ignored-directory? (pathname --> boolean)) ; (define (ignored-directory? dir) (or (string-null? dir) (string=? +dot-directory+ (make-pathname dir #f))) ) (: push-directory ((or boolean pathname) -> void)) ; (define (push-directory dir) (stack-push! (directory-utility-stack) (current-directory)) ;don't cd unless necessary (when (and dir (not (ignored-directory? dir))) (change-directory dir) ) ) (: pop-directory (-> void)) ; (define (pop-directory) (unless (stack-empty? (directory-utility-stack)) (change-directory (stack-pop! (directory-utility-stack))) ) ) (: pop-toplevel-directory (-> void)) ; (define (pop-toplevel-directory) (until (stack-empty? (directory-utility-stack)) (pop-directory) ) ) ;; Ensure the directory for the specified path exists. (: create-pathname-directory (pathname -> boolean)) ; (define (create-pathname-directory pn) (->boolean (create-directory (pathname-directory (check-pathname 'create-pathname-directory pn)) #t)) ) ;; Platform specific program filename. (: make-program-filename (basename --> filename)) ; (define (make-program-filename bn) (cond-expand (windows (if (pathname-extension bn) bn (make-pathname #f bn "exe")) ) (else bn ) ) ) (: make-shell-filename (basename -> filename)) ; (define (make-shell-filename bn) (cond-expand (windows (if (pathname-extension bn) bn (make-pathname #f bn "bat")) ) (else (if (pathname-extension bn) bn (make-pathname #f bn "sh")) ) ) ) ;; Pathname if file exists in directory. (: file-exists-in-directory? (filename #!rest (list pathname) -> (or boolean pathname))) ; (define (file-exists-in-directory? fil . opts) (let* ( (dir (optional opts #f)) (path (make-pathname dir fil)) ) (and (file-exists? path) path) ) ) ;; List of all found pathnames. (: find-file-pathnames-in-directory (filename pathname -> (list-of pathname))) ; (define (find-file-pathnames-in-directory fil dir) (filter-map (cut file-exists-in-directory? fil <>) (ensure-list dir)) ) (: *find-file-pathnames (filename (list-of pathname) -> (or boolean (list-of pathname)))) ; (define (*find-file-pathnames fil dirs) (let loop ((dirs dirs) (paths '())) (if (null? dirs) (not-null? paths) (loop (cdr dirs) (append! paths (find-file-pathnames-in-directory fil (car dirs))))) ) ) (: find-file-pathnames (filename #!rest -> (or boolean (list-of pathname)))) ; (define (find-file-pathnames fil . dirs) (*find-file-pathnames fil dirs) ) ;; All found program pathname in directories. (: find-program-pathnames (filename #!rest (list-of pathname) -> optional-list)) ; (define (find-program-pathnames cmd . dirs) (cond-expand (windows (if (pathname-extension cmd) (*find-file-pathnames cmd dirs) (let ( (founds (append! (or (*find-file-pathnames (make-program-filename cmd) dirs) '()) (or (*find-file-pathnames (make-shell-filename cmd) dirs) '()))) ) (not-null? founds))) ) (else (*find-file-pathnames (make-program-filename cmd) dirs) ) ) ) ;; All found program pathname in path. (: which-command-pathnames (filename #!rest (list string) -> optional-list)) ; (define (which-command-pathnames cmd . opts) (let ( (varnam (optional opts "PATH")) ) (and-let* ( (path (get-environment-variable varnam)) ) (find-program-pathnames cmd (string-split path PATH-DELIMITER)) ) ) ) ;; First found program pathname in path. (: which-command-pathname (filename #!rest (list string) -> optional-list)) ; (define (which-command-pathname cmd . opts) (let ( (varnam (optional opts "PATH")) ) (and-let* ( (ps (which-command-pathnames cmd varnam)) ) (first ps) ) ) ) ) ;directory-utils