168 lines
7.4 KiB
Scheme
168 lines
7.4 KiB
Scheme
#lang scheme/base
|
|
|
|
(provide find-relative-path
|
|
explode-path
|
|
simple-form-path
|
|
normalize-path
|
|
filename-extension
|
|
file-name-from-path
|
|
path-only)
|
|
|
|
(define (simple-form-path p)
|
|
(unless (path-string? p)
|
|
(raise-type-error 'simple-form-path "path or string" p))
|
|
(simplify-path (path->complete-path p)))
|
|
|
|
;; Note that normalize-path does not normalize the case
|
|
(define normalize-path
|
|
(letrec ([resolve-all
|
|
(lambda (path wrt)
|
|
(let ([orig-path (if (and wrt (not (complete-path? path)))
|
|
(path->complete-path path wrt)
|
|
path)])
|
|
(let loop ([full-path orig-path][seen-paths (list orig-path)])
|
|
(let ([resolved (resolve-path full-path)])
|
|
(if (equal? resolved full-path)
|
|
(do-normalize-path resolved #f)
|
|
(let ([path (if (relative-path? resolved)
|
|
(build-path
|
|
(let-values ([(base name dir?) (split-path full-path)])
|
|
base)
|
|
resolved)
|
|
resolved)])
|
|
(if (member path seen-paths)
|
|
(error 'normalize-path "circular reference at ~s" path)
|
|
(let ([spath
|
|
;; Use simplify-path to get rid of ..s, which can
|
|
;; allow the path to grow indefinitely in a cycle.
|
|
;; An exception must mean a cycle of links.
|
|
(with-handlers ([exn:fail:filesystem?
|
|
(lambda (x)
|
|
(error 'normalize-path "circular reference at ~s" path))])
|
|
(simplify-path path))])
|
|
(loop spath (cons path seen-paths))))))))))]
|
|
[resolve
|
|
(lambda (path)
|
|
(if (equal? path (resolve-path path))
|
|
path
|
|
(resolve-all path #f)))]
|
|
[normalize-path
|
|
(case-lambda
|
|
[(orig-path) (do-normalize-path orig-path (current-directory))]
|
|
[(orig-path wrt)
|
|
(unless (complete-path? wrt)
|
|
(raise-type-error 'normalize-path "complete path" wrt))
|
|
(do-normalize-path orig-path wrt)])]
|
|
[error-not-a-dir
|
|
(lambda (path)
|
|
(error 'normalize-path
|
|
"~s (within the input path) is not a directory or does not exist"
|
|
path))]
|
|
[do-normalize-path
|
|
(lambda (orig-path wrt)
|
|
(let normalize ([path (cleanse-path orig-path)])
|
|
(let-values ([(base name dir?) (split-path path)])
|
|
(cond
|
|
[(eq? name 'up)
|
|
(let up ([base (if (eq? base 'relative)
|
|
wrt
|
|
(resolve-all base wrt))])
|
|
(if (directory-exists? base)
|
|
(let-values ([(prev name dir?) (split-path base)])
|
|
(cond
|
|
[(not prev)
|
|
(error 'normalize-path
|
|
"root has no parent directory: ~s"
|
|
orig-path)]
|
|
[else
|
|
(let ([prev
|
|
(if (eq? prev 'relative)
|
|
wrt
|
|
(normalize prev))])
|
|
(cond
|
|
[(eq? name 'same) (up prev)]
|
|
[(eq? name 'up) (up (up prev))]
|
|
[else prev]))]))
|
|
(error-not-a-dir base)))]
|
|
[(eq? name 'same)
|
|
(cond
|
|
[(eq? base 'relative) wrt]
|
|
[else (let ([n (normalize base)])
|
|
(if (directory-exists? n)
|
|
n
|
|
(error-not-a-dir n)))])]
|
|
[(not base) (path->complete-path path)]
|
|
[else
|
|
(let* ([base (if (eq? base 'relative)
|
|
(normalize wrt)
|
|
(normalize base))]
|
|
[path (if (directory-exists? base)
|
|
(build-path base name)
|
|
(error-not-a-dir base))]
|
|
[resolved (cleanse-path (resolve path))])
|
|
(cond
|
|
[(relative-path? resolved)
|
|
(normalize (build-path base resolved))]
|
|
[(complete-path? resolved)
|
|
resolved]
|
|
[else (path->complete-path resolved base)]))]))))])
|
|
normalize-path))
|
|
|
|
;; Argument must be in simple form
|
|
(define (do-explode-path who orig-path simple?)
|
|
(let loop ([path orig-path][rest '()])
|
|
(let-values ([(base name dir?) (split-path path)])
|
|
(when simple?
|
|
(when (or (and base (not (path? base)))
|
|
(not (path? name)))
|
|
(raise-type-error who
|
|
"path in simple form (absolute, complete, and with no same- or up-directory indicators)"
|
|
orig-path)))
|
|
(if (path? base)
|
|
(loop base (cons name rest))
|
|
(cons name rest)))))
|
|
|
|
(define (explode-path orig-path)
|
|
(unless (path-string? orig-path)
|
|
(raise-type-error 'explode-path "path or string" orig-path))
|
|
(do-explode-path 'explode-path orig-path #f))
|
|
|
|
;; Arguments must be in simple form
|
|
(define (find-relative-path directory filename)
|
|
(let ([dir (do-explode-path 'find-relative-path directory #t)]
|
|
[file (do-explode-path 'find-relative-path filename #t)])
|
|
(if (equal? (car dir) (car file))
|
|
(let loop ([dir (cdr dir)]
|
|
[file (cdr file)])
|
|
(cond [(null? dir) (if (null? file) filename (apply build-path file))]
|
|
[(null? file) (apply build-path (map (lambda (x) 'up) dir))]
|
|
[(equal? (car dir) (car file))
|
|
(loop (cdr dir) (cdr file))]
|
|
[else
|
|
(apply build-path (append (map (lambda (x) 'up) dir) file))]))
|
|
filename)))
|
|
|
|
(define (file-name who name)
|
|
(unless (path-string? name)
|
|
(raise-type-error who "path or string" name))
|
|
(let-values ([(base file dir?) (split-path name)])
|
|
(and (not dir?) (path? file) file)))
|
|
|
|
(define (file-name-from-path name)
|
|
(file-name 'file-name-from-path name))
|
|
|
|
(define (path-only name)
|
|
(unless (path-string? name)
|
|
(raise-type-error 'path-only "path or string" name))
|
|
(let-values ([(base file dir?) (split-path name)])
|
|
(cond [dir? name]
|
|
[(path? base) base]
|
|
[else #f])))
|
|
|
|
;; name can be any string; we just look for a dot
|
|
(define (filename-extension name)
|
|
(let* ([name (file-name 'filename-extension name)]
|
|
[name (and name (path->bytes name))])
|
|
(cond [(and name (regexp-match #rx#"[.]([^.]+)$" name)) => cadr]
|
|
[else #f])))
|