(module file mzscheme
(require (lib "contract.ss"))
(require (lib "etc.ss"))
(require (lib "file.ss"))
(require (all-except (lib "list.ss" "srfi" "1") any))
(define relative-path/c
(and/c (union path? string?) relative-path?))
(define relative-file-path/c
(and/c relative-path/c file-exists?))
(define relative-directory-path/c
(and/c relative-path/c directory-exists?))
(define complete-path/c
(and/c (union path? string?) complete-path?))
(define complete-file-path/c
(and/c complete-path/c file-exists?))
(define complete-directory-path/c
(and/c complete-path/c directory-exists?))
(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))))))))
(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))]))))
(define (telescope-exploded-path alop)
(fold (lambda (this rest)
(cons (if (null? rest)
this
(build-path (car rest) this))
rest))
null
alop))
(define (telescope-path path)
(telescope-exploded-path (explode-path path)))
(define (telescope-relative-path path)
(telescope-exploded-path (explode-relative-path path)))
(define (collect-subdirectories path)
(let ([telescoped (telescope-relative-path path)])
(if (directory-exists? (car telescoped))
telescoped
(cdr telescoped))))
(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))))))
(define (path=? path1 path2 . paths)
(andmap (lambda (path2)
(path=?/2 path1 path2))
(cons path2 paths)))
(define (path-normalized=? path1 path2 . paths)
(apply bytes=? (map (compose path->bytes normalize-path)
(cons path1 (cons path2 paths)))))
(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)))))
(define (empty-directory? p)
(and (directory-exists? p)
(null? (directory-list p))))
(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])))
(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?])
(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?))]
))