(module file mzscheme (require (lib "contract.ss")) (require (lib "etc.ss")) (require (lib "file.ss")) (require (lib "list.ss" "srfi" "1")) ;; =========================================================================== ;; CONTRACTS ;; =========================================================================== ;; TODO: expanded-path?, simple-path? ;; A relative-path is a path for which relative-path? is #t. A ;; relative-file-path or relative-directory-path is a relative-path for ;. which file-exists? or directory-exists? is #t, respectively. ;; relative-path/c : contract (define relative-path/c (and/c (union path? string?) relative-path?)) ;; relative-file-path/c : contract (define relative-file-path/c (and/c relative-path/c file-exists?)) ;; relative-directory-path/c : contract (define relative-directory-path/c (and/c relative-path/c directory-exists?)) ;; A complete-path is a path for which complete-path? is #t. A ;; complete-file-path or complete-directory-path is a complete-path for ;. which file-exists? or directory-exists? is #t, respectively. ;; complete-path/c : contract (define complete-path/c (and/c (union path? string?) complete-path?)) ;; complete-file-path/c : contract (define complete-file-path/c (and/c complete-path/c file-exists?)) ;; complete-directory-path/c : contract (define complete-directory-path/c (and/c complete-path/c directory-exists?)) ;; =========================================================================== ;; PROCEDURES ;; =========================================================================== ;; path->relative-path : (union string path) [(union string path)] -> relative-path ;; converts a path to a relative path (define path->relative-path (opt-lambda (path [relative-to (current-directory)]) (let-values ([(base name must-be-dir?) (split-path path)]) (let loop ([base base] [name name] [rest (list name)]) (if (path=? base relative-to) (apply build-path rest) (let-values ([(base name must-be-dir?) (split-path base)]) (loop base name (cons name rest)))))))) ;; explode-relative-path : relative-path -> (listof path) ;; computes the list of directories in a relative path (define (explode-relative-path path) (let loop ([path path] [rest '()]) (let-values ([(base name dir?) (split-path path)]) (cond [(eq? base 'relative) (cons name rest)] [(or (not (path? name)) (not path)) (raise-type-error 'explode-relative-path "relative-path in normal form" path)] [else (loop base (cons name rest))])))) ;; telescope-exploded-path : (listof path) -> (listof path) (define (telescope-exploded-path alop) (fold (lambda (this rest) (cons (if (null? rest) this (build-path (car rest) this)) rest)) null alop)) ;; telescope-path : complete-path -> (listof complete-path) (define (telescope-path path) (telescope-exploded-path (explode-path path))) ;; telescope-relative-path : relative-path -> (listof relative-path) (define (telescope-relative-path path) (telescope-exploded-path (explode-relative-path path))) ;; TODO: this is deprecated -- delete in next major version ;; collect-subdirectories : relative-path -> (listof relative-path) (define (collect-subdirectories path) (let ([telescoped (telescope-relative-path path)]) (if (directory-exists? (car telescoped)) telescoped (cdr telescoped)))) ;; path=?/2 : (union path #f 'relative) (union path #f 'relative) -> boolean (define (path=?/2 path1 path2) (or (and (not path1) (not path2)) (and (eq? path1 'relative) (eq? path2 'relative)) (and (path? path1) (path? path2) (let-values ([(base1 name1 dir1?) (split-path path1)] [(base2 name2 dir2?) (split-path path2)]) (and (or (and (symbol? name1) (symbol? name2) (eq? name1 name2)) (and (path? name1) (path? name2) (bytes=? (path->bytes name1) (path->bytes name2)))) (path=?/2 base1 base2)))))) ;; path=? : path path ... -> boolean ;; determines whether two paths contain exactly the same elements (define (path=? path1 path2 . paths) (andmap (lambda (path2) (path=?/2 path1 path2)) (cons path2 paths))) ;; path-normalized=? : path path ... -> boolean ;; determines whether two paths refer to the same normalized path (define (path-normalized=? path1 path2 . paths) (apply bytes=? (map (compose path->bytes normalize-path) (cons path1 (cons path2 paths))))) ;; directory-list/all : [(union string path)] -> (listof relative-path) ;; returns the list of all files and subdirectories, relative to base-dir (define directory-list/all (opt-lambda ([base-dir (current-directory)]) (let all-from ([dir base-dir] [prefix #f]) (append-map (lambda (p) (let ([p* (if prefix (build-path prefix p) p)] [entry (build-path dir p)]) (if (directory-exists? entry) (cons p* (all-from entry p*)) (list p*)))) (directory-list dir))))) ;; empty-directory? : (union string path) -> boolean (define (empty-directory? p) (and (directory-exists? p) (null? (directory-list p)))) ;; dirname : path -> path (define (dirname p) (let-values ([(parent name must-be-dir?) (split-path p)]) (cond [(not parent) (build-path p)] [(eq? parent 'relative) (build-path 'same)] [else parent]))) ;; basename : path -> relative-path (define (basename p) (let-values ([(parent name must-be-dir?) (split-path p)]) (if (symbol? name) (build-path name) name))) (provide/contract [relative-path/c contract?] [relative-file-path/c contract?] [relative-directory-path/c contract?] [complete-path/c contract?] [complete-file-path/c contract?] [complete-directory-path/c contract?]) ;; TODO: can we make these contracts a little more precise? -- namely, the ;; relative-paths must be simplified and expanded (provide/contract [dirname ((union string? path?) . -> . path?)] [basename ((union string? path?) . -> . path?)] [empty-directory? ((union string? path?) . -> . boolean?)] [directory-list/all (() ((union string? path?)) . opt-> . (listof relative-path/c))] [path->relative-path (((union string? path?)) ((union string? path?)) . opt-> . relative-path/c)] [explode-relative-path (relative-path/c . -> . (listof path?))] [telescope-path (complete-path/c . -> . (listof complete-path/c))] [telescope-relative-path (relative-path/c . -> . (listof relative-path/c))] [collect-subdirectories (relative-path/c . -> . (listof relative-path/c))] [path=? ((path? path?) (listof path?) . ->* . (boolean?))] [path-normalized=? ((path? path?) (listof path?) . ->* . (boolean?))] ))