(define-module (ttn-do zzz filesystem)
#:export (safe-rewind
temporary-file-port
directory-vicinity
dir-exists?
mkdir-p
directory-files
filtered-files
filtered-files-in-vicinity
not-dot-not-dotdot
extract-stem-proc
filename-sans-end-sep-proc
file-newer-than-file?
save-cwd
with-cwd
filename-absolute-or-in-vicinity
filename-sans-end-slash
filename-components
filename-components-append
expand-file-name
reset-tilde-cache!
substitute-env-vars
expand-file-name-substituting-env-vars)
#:autoload (ttn-do zzz 0gx temporary-file) (temporary-file)
#:use-module ((ice-9 common-list) #:select (pick-mappings))
#:use-module ((srfi srfi-14) #:select (char-set-complement
char-set))
#:use-module ((srfi srfi-13) #:select (string-suffix?
substring/shared
string-take
string-drop-right
string-join
string-tokenize))
#:use-module ((ice-9 regex) #:select (match:start
match:substring
match:end)))
(define subs substring/shared)
(define (safe-rewind port)
(and (output-port? port) (force-output port))
(seek port 0 SEEK_SET)
port)
(define (temporary-file-port)
(temporary-file))
(define ((directory-vicinity directory) filename)
(in-vicinity directory filename))
(define (dir-exists? name)
(and (file-exists? name)
(file-is-directory? name)))
(define (mkdir-p dir)
(let ((parent (dirname dir)))
(or (dir-exists? parent)
(mkdir-p parent)))
(or (dir-exists? dir)
(mkdir dir)))
(define (directory-files dir)
(let ((dir-stream (opendir dir)))
(let loop ((new (readdir dir-stream))
(acc '()))
(if (eof-object? new)
acc
(loop (readdir dir-stream) (cons new acc))))))
(define (filtered-files filter dir)
(pick-mappings filter (directory-files dir)))
(define (filtered-files-in-vicinity dir filter . options)
(let* ((bef? (memq #:filter-prefixed options))
(aft? (not (memq #:collect-nodir options)))
(w/dir (directory-vicinity dir))
(check (if bef?
(lambda (file) (filter (w/dir file)))
filter))
(collect (if aft? w/dir identity)))
(filtered-files (lambda (file)
(let ((val (check file)))
(and val (if (eq? #t val)
(collect file)
val))))
dir)))
(define (not-dot-not-dotdot file)
(let ((base (cond ((string-rindex file #\/)
=> (lambda (cut)
(subs file (1+ cut))))
(else file))))
(and (not (string=? "." base))
(not (string=? ".." base))
file)))
(define (extract-stem-proc ext . transform)
(let* ((full-ext (string-append "." ext))
(fe-len (string-length full-ext))
(ret (cond ((or (null? transform) (not (car transform)))
subs)
((eq? #t (car transform))
(lambda args (car args)))
((string? (car transform))
(set! transform (car transform))
(lambda args (string-append (apply subs args) transform)))
((procedure? (car transform))
(set! transform (car transform))
(lambda args (transform (apply subs args)))))))
(lambda (filename)
(let ((cut (- (string-length filename) fe-len)))
(and (< 0 cut)
(string-suffix? full-ext filename 0 fe-len cut)
(ret filename 0 cut))))))
(define (filename-sans-end-sep-proc sep-char)
(lambda (filename)
(let* ((len (string-length filename))
(new (1- len)))
(if (and (positive? new) (char=? sep-char (string-ref filename new)))
(string-take filename new)
filename))))
(define (file-newer-than-file? file1 file2)
(cond ((not (file-exists? file1)) #f)
((not (file-exists? file2)) #t)
(else (let ((m1 (stat:mtime (stat file1)))
(m2 (stat:mtime (stat file2))))
(> m1 m2)))))
(define-macro (save-cwd . body)
(and (null? body)
(error "save-cwd: Null body"))
(let ((sym (gensym)))
`(let ((,sym (list (getcwd))))
(set-cdr! ,sym (let () ,@body))
(chdir (car ,sym))
(cdr ,sym))))
(define-macro (with-cwd dir . body)
(and (null? body)
(error "with-cwd: Null body"))
(let ((sym (gensym)))
`(let ((,sym (list (getcwd))))
(chdir ,dir)
(set-cdr! ,sym (let () ,@body))
(chdir (car ,sym))
(cdr ,sym))))
(define (filename-absolute-or-in-vicinity name dir)
(or (and (not (string-null? name))
(char=? #\/ (string-ref name 0))
name)
(in-vicinity dir name)))
(define (filename-sans-end-slash name)
(if (string-suffix? "/" name)
(string-drop-right name 1)
name))
(define filename-components
(let ((split (char-set-complement (char-set #\/))))
(lambda (string)
(string-tokenize string split))))
(define (filename-components-append ls)
(string-join ls "/" 'prefix))
(define (elide-dot-and-dot-dots abs-name need-trailing-sep?)
(let loop ((comps (reverse! (filename-components abs-name)))
(omit 0)
(acc (if need-trailing-sep? (list "")
(list))))
(if (null? comps)
(filename-components-append acc)
(cond ((string=? "." (car comps))
(loop (cdr comps) omit acc))
((string=? ".." (car comps))
(loop (cdr comps) (1+ omit) acc))
((< 0 omit)
(loop (cdr comps) (1- omit) acc))
(else
(loop (cdr comps) omit (cons (car comps) acc)))))))
(define *tilde-cache* 7)
(define (expand-file-name name . default-directory)
(and (number? *tilde-cache*)
(set! *tilde-cache* (make-hash-table *tilde-cache*)))
(let* ((~? (lambda (key pw-ent-thunk)
(or (hash-ref *tilde-cache* key)
(let ((val (passwd:dir (pw-ent-thunk))))
(hash-set! *tilde-cache* key val)
val))))
(dd (or (and (pair? default-directory)
(car default-directory))
(getcwd)))
(under-dd (directory-vicinity dd))
(len (string-length name)))
(elide-dot-and-dot-dots
(case (string-ref name 0)
((#\/) name)
((#\~) (let ((end (or (string-index name #\/) len)))
(string-append
(cond ((= 1 end)
(~? "~" (lambda () (getpwuid (getuid)))))
((false-if-exception
(let ((user (subs name 1 end)))
(~? user (lambda () (getpwnam user))))))
(else
(under-dd (string-take name end))))
(subs name end len))))
(else (under-dd name)))
(char=? #\/ (string-ref name (1- len))))))
(define (reset-tilde-cache! . size)
(set! *tilde-cache* (or (and (pair? size)
(number? (car size))
(car size))
7)))
(define substitute-env-vars
(let ((double (make-regexp "[$][$]"))
(rx (make-regexp "[$][{]*([A-Za-z0-9_][A-Za-z0-9_]*)[}]*")))
(lambda (string)
(let ((answer (list #f)))
(let loop ((start 0) (tp answer))
(define (hang! . x)
(append! tp x))
(cond ((regexp-exec double string start)
=> (lambda (m)
(let ((one (1+ (match:start m))))
(loop (1+ one)
(cdr (hang! (subs string start one)))))))
((regexp-exec rx string start)
=> (lambda (m)
(let ((var (match:substring m 1)))
(loop (match:end m)
(cddr (hang! (subs string start (match:start m))
(or (getenv var) "")))))))
(else
(append! tp (list (subs string start))))))
(apply string-append (cdr answer))))))
(define (expand-file-name-substituting-env-vars name . rest)
(apply expand-file-name (substitute-env-vars name) rest))