Hi, Racket Discourse.
I thought I'd quickly share this little macro for defining paths originating from a single root (or home), which I've been using for easily referencing paths when writing scripts. Perhaps there is already a form which does exactly this, but I have not chanced upon it, yet.
The paths aren't realized, although it would probably be easy to adjust the macro to take care of this as well, or perhaps even incorporate "runtime-path" et al.
Interestingly, this was one of the very first macros I attempted, although it has evolved over time, as I have gained more experience.
#lang racket/base
(require
(for-syntax
racket/base
racket/syntax
syntax/parse))
(provide
define-paths)
(begin-for-syntax
(define-syntax-class
(path-name type)
#:attributes
([labl 0]
[path 0])
(pattern name:id
#:attr labl (case type [(file) #'("~a")] [(directory) #'("~a/")])
#:attr path #'(name)))
(define-syntax-class
file
#:attributes
([labl 0]
[path 0])
#:local-conventions
([name (path-name 'file)])
(pattern name
#:attr labl #'name.labl
#:attr path #'name.path))
(define-syntax-class
directory
#:attributes
([labls 1]
[paths 1])
#:local-conventions
([name (path-name 'directory)])
(pattern [name (~alt file:file sub:directory) ...]
#:with (labls ...) #'(("~a/" . file.labl) ... name.labl ("~a/" . sub.labls) ... ...)
#:with (paths ...) #'((name . file.path) ... name.path (name . sub.paths) ... ...)))
(define ((format-path-name stx) labl path)
(format-id stx labl #:subs? #true . apply . path))
(define (path-join names) (string-append . apply . names))
(define (format-paths-names stx labls-stx paths-stx)
(define paths* (map path-join (syntax->datum labls-stx)))
(map (format-path-name stx) paths* (map syntax->list (syntax->list paths-stx)))))
(define (norm-path path-symbol)
(define path-string (symbol->string path-symbol))
(case (system-path-convention-type)
[(unix) path-string]
[(windows) (regexp-replace* #rx"/" path-string "\\\\")]))
(define-syntax (define-paths stx)
(syntax-parse stx
[(_ #:home home:expr {~alt :file :directory} ...)
#:with once (generate-temporary #'once)
#:with (path-names ...)
(format-paths-names stx #'(labl ... labls ... ...) #'(path ... paths ... ...))
#'(define-values (path-names ...)
(let ([once home])
(values (build-path once (norm-path 'path-names)) ...)))]))
The usage then, looks something like this:
(define-paths
#:home temp/
[cmds
; command outputs
recon.txt
subdomains.txt
take-overs.txt
doppelgangers.json
load-balancing.txt
waf-signatures.json
dir-enumeration.txt
dir-traversal.txt
url-scan.json
wordpress.json]
[assm
; assessment data
assm.log
assm.json
info.json])
Directories appear at the head of a parens-group, as in [cmds ...]
and files are the atoms in such a group, as in [cmds ... recon.txt ...]
.
Happy hacking!