#lang scheme/base
(require (for-syntax scheme/base)
scheme/file
scheme/path
scheme/port
(except-in srfi/1 any)
srfi/13
"base.ss"
"debug.ss")
(define (path-contains? path1 path2)
(let ([elems1 (explode-path (simplify-path path1))]
[elems2 (explode-path (simplify-path path2))])
(and (>= (length elems2) (length elems1))
(equal? elems1 (take elems2 (length elems1))))))
(define (make-directory-tree tree)
(define (tree-fold seed tree)
(define (list->path head rest)
(apply build-path (reverse (cons head rest))))
(match tree
[(? string? here)
(make-directory* (list->path here seed))]
[(list) (void)]
[`(,(? string? head) (,children ...) . ,rest)
(make-directory* (list->path head seed))
(tree-fold (cons head seed) children)
(tree-fold seed rest)]
[`(,(? string? here) . ,rest)
(make-directory* (list->path here seed))
(tree-fold seed rest)]))
(tree-fold null tree))
(define (make-non-conflicting-path path filename)
(build-path path (make-non-conflicting-filename path filename)))
(define (make-non-conflicting-filename path filename)
(define (stem->stem-and-index stem)
(let loop ([stem stem] [index-string ""])
(if (char-numeric? (string-ref stem (sub1 (string-length stem))))
(loop (string-drop-right stem 1)
(string-append index-string (string-take-right stem 1)))
(values stem
(if (= (string-length index-string) 0)
1
(string->number index-string))))))
(if (file-exists? (build-path path filename))
(let* ([pos (string-index-right filename #\.)]
[stem (if pos (string-take filename pos) filename)]
[extension (if pos (string-drop filename pos) "")])
(let-values ([(stem index)
(stem->stem-and-index stem)])
(let loop ([index index])
(let ([filename
(string-append
stem
(number->string index)
extension)])
(if (file-exists? (build-path path filename))
(loop (add1 index))
filename)))))
filename))
(define (read-file->string path)
(let ([in (open-input-file path)]
[out (open-output-string)])
(let loop ()
(let ([buf (read-string 1024 in)])
(unless (eof-object? buf)
(display buf out)
(loop))))
(close-input-port in)
(get-output-string out)))
(define (concatenate-files des srcs)
(with-output-to-file des
(cut copy-port (apply input-port-append
#t
(map open-input-file srcs))
(current-output-port))))
(define (directory-tree
root-path+string
#:order [order 'pre]
#:filter [predicate (lambda (path) #t)]
#:follow-links? [follow-links? #t])
(define root
(if (string? root-path+string)
(string->path root-path+string)
root-path+string))
(define tree
(letrec ([process (lambda (curr)
(cond [(directory-exists? curr)
(if (link-exists? curr)
(if follow-links?
(recurse curr)
(list curr))
(recurse curr))]
[(file-exists? curr)
(list curr)]
[else null]))]
[recurse (lambda (curr)
(let*-values ([(children)
(map (cut build-path curr <>)
(directory-list curr))]
[(directory-children file-children)
(partition directory-exists? children)])
(case order
[(post)
`(,@(append-map process directory-children)
,@(append-map process file-children)
,curr)]
[(pre)
`(,curr
,@(append-map process file-children)
,@(append-map process directory-children))])))])
(process root)))
(filter predicate tree))
(define-sequence-syntax in-directory
(lambda (stx)
(raise-syntax-error #f "can only be used as a sequence" stx))
(lambda (stx)
(syntax-case stx ()
[[ids (_ args ...)]
#'[ids (in-list (directory-tree args ...))]])))
(define (file-pretty-size path)
(prettify-file-size (file-size path)))
(define (prettify-file-size size [unit 1])
(define unit->string
(match-lambda
[1 "bytes"]
[2 "KB"]
[3 "MB"]
[4 "GB"]
[5 "TB"]))
(cond [(and (>= size 1000) (< unit 5))
(prettify-file-size (/ size 1024) (add1 unit))]
[(> size 10)
(let ([size (floor size)])
(format "~a ~a" size (unit->string unit)))]
[(and (= size 1) (= unit 1)) "1 byte"]
[else (let ([whole (floor size)]
[fraction (floor (remainder (floor (* size 10)) 10))])
(if (zero? fraction)
(format "~a ~a" whole (unit->string unit))
(format "~a.~a ~a" whole fraction (unit->string unit))))]))
(provide in-directory)
(provide/contract
[path-contains? (-> path? path? boolean?)]
[make-directory-tree (-> any/c void?)] [make-non-conflicting-filename (-> (or/c path? string?) string? string?)]
[make-non-conflicting-path (-> (or/c path? string?) string? path?)]
[read-file->string (-> (or/c path? string?) string?)]
[concatenate-files (-> (or/c path? string?) (listof (or/c path? string?)) void?)]
[directory-tree (->* ((or/c path? string?))
(#:order (symbols 'pre 'post)
#:filter (-> path? boolean?)
#:follow-links? boolean?)
(listof path?))]
[file-pretty-size (-> path? string?)]
[prettify-file-size (-> natural-number/c string?)])