forked from philhofer/distill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexecline.scm
274 lines (258 loc) · 9.32 KB
/
execline.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
(define execline-shebang "#!/bin/execlineb -P")
(: tabs (fixnum --> string))
(define (tabs n)
(cond
;; fast-paths: empty string or a small
;; number of tabs become constant string refs
((eqv? n 0) "")
((<= n 8) (let ((v "\t\t\t\t\t\t\t\t"))
(##sys#substring v 0 n)))
(else (string-append "\t" (tabs (- n 1))))))
(: %dsp-string (string --> undefined))
(define (%dsp-string obj)
(let ((len (string-length obj)))
(if (= len 0)
(write obj)
(let loop ((i 0))
(if (= i len)
(display obj)
(case (string-ref obj i)
((#\space #\newline #\tab #\linefeed #\\ #\# #\" #\{ #\})
;; any obvious escape sequences or semantic characters
;; mean we encode the string into the script just
;; as it would appear as a scheme literal
(write obj))
((#\delete #\backspace #\alarm #\vtab #\nul #\esc)
;; while we're here, warn about illegal characters
(error "illegal character in execline string:" obj))
(else (loop (+ i 1)))))))))
;; given a bytevector, produce a quoted string
;; that escapes non-printable ascii chars into
;; escape sequences that execlineb will interpret
;; properly
(define (quote-bv bv)
(let ((len (u8vector-length bv))
(->int (lambda (c)
(if (integer? c) c (char->integer c))))
(->hex1 (lambda (i)
(string-ref "0123456789abcdef"
(bitwise-and i 15))))
(->hex0 (lambda (i)
(string-ref "0123456789abcdef"
(bitwise-and
(arithmetic-shift i -4) 15)))))
(list->string
(cons #\"
(let loop ((i 0))
(if (= i len)
(list #\")
(let ((v (->int (u8vector-ref bv i))))
(define-syntax cons*
(syntax-rules ()
((_ x y) (cons x y))
((_ x y rest* ...) (cons x (cons* y rest* ...)))))
(if (<= 32 v 126)
(cons (integer->char v) (loop (+ i 1)))
(cons*
#\\ #\0 #\x (->hex0 v) (->hex1 v)
(loop (+ i 1)))))))))))
;; fmt-execline produces a formatting combinator
;; from the list representation of an execline script
(define (dsp-execline lst)
(define (sep? sym)
(and
(symbol? sym)
(let* ((v '#(background
backtick cd chroot
define dollarat elgetopt elgetpositionals
elglob emptyenv envfile exec export exportall
fdblock fdclose fdmove fdreserve fdswap
forbacktickx foreground forstdin forx getcwd getpid gptimage
heredoc homeof if ifelse ifte ifthenelse
importas loopwhilex multidefine nice pipeline
piperw redirfd runblock shift su
sudo trap tryexec umask
unexport unshare wait withstdinas xargs))
(e<? (lambda (a b)
(string<? (##sys#symbol->string a) (##sys#symbol->string b))))
(ref (lambda (i) (vector-ref v i))))
(let loop ((i 0)
(j (- (vector-length v) 1)))
(and (<= i j)
(let* ((mid (+ (quotient (- j i) 2) i))
(e (ref mid)))
(or (eq? e sym)
(if (e<? sym e)
(loop i (- mid 1))
(loop (+ mid 1) j)))))))))
(define (execl-dsp obj)
(cond
;; technically there can be spaces, etc. in symbols, too...
((symbol? obj) (display obj))
((string? obj) (%dsp-string obj))
((u8vector? obj) (display (quote-bv obj)))
((integer? obj) (display obj))
((real? obj) (display obj))
;; if you write '-i it's read as a complex number;
;; this shows up in 'sed -i' for example
((complex? obj) (error "you almost certainly didn't mean to print:" obj))
(else (error "can't serialize for execline:" obj lst))))
(let loop ((lst lst) ; items to display
(indent 0)
(nl #t)) ; currently in head position
(define (cont endl indent)
(cond
((null? endl) (newline))
((sep? (car endl))
(begin
(newline)
(display (tabs indent))
(loop endl indent #t)))
(else
(begin
(display " ")
(loop endl indent #f)))))
(or (null? lst)
(let ((head (car lst))
(rest (cdr lst)))
(cond
((null? head)
(begin
(when nl
(error "unexpected null in execline form" lst))
(display "{ }")
(cont rest indent)))
((list? head)
(begin ; display a block
(when nl
;; TODO: this isn't strictly illegal,
;; but I'm not aware of any tools that
;; support {{ ... } ...} syntax
(error "unexpected list in head position" head))
;; if someone writes '(a b ,c) accidentally,
;; bail rather than producing a really strange
;; execline script
(when (memq (car head) '(quote unquote unquote-splicing quasiquote))
(error "not a deliberate execline form" head))
(display "{\n")
(display (tabs (+ indent 1)))
(loop head (+ indent 1) #t)
(display (tabs indent))
(display "}")
(cont rest indent)))
(else
(begin
(execl-dsp head)
(cont rest indent))))))))
;; write-exexpr writes an execline expression
;; as a script to current-output-port
;;
;; the "shebang:" keyword argument may specify
;; an alternate script invocation; the default
;; is #!/bin/execlineb -P
(: write-exexpr (list #!rest * -> undefined))
(define (write-exexpr expr #!key (shebang execline-shebang))
(when shebang
(begin
(display shebang)
(newline)))
(dsp-execline expr))
(: elif ((or list false) list -> list))
(define (elif head body)
(unless (pair? body)
(error "elif: unexpected tail form" body))
(cond
((not head) body)
((list? head) (cons 'if (cons head body)))
(else (error "elif: unexpected head form" head))))
(: elif* ((or list false) #!rest (or list false) -> list))
(define (elif* head . rest)
(let loop ((head head)
(rest rest))
(if (null? rest)
head
(elif head (loop (car rest) (cdr rest))))))
;; el= is a monadic execline template formatter
;; that formats its arguments as a single string
;; where elements are space-separated
(define (el= head . rest)
(lambda (conf)
(let ((tail (elexpand conf rest)))
(string-append
(cond
((string? head) head)
((symbol? head) (##sys#symbol->string head))
(else (error "unexpected head element in el=" head)))
(join-with " " tail)))))
;; elconc is a monadic execline template formatter
;; that formats its arguments as a single string
;; by simply concatenating the display'd elements
(define (elconc . args)
(lambda (conf)
(let ((lst (elexpand conf args)))
(with-output-to-string
(lambda ()
(for-each display lst))))))
;; elpath expands a list of arguments into
;; a filepath by concatenating the resolved values
;; into (apply filepath-join args ...)
(define (elpath . args)
(lambda (conf)
(apply filepath-join (elexpand conf args))))
(define (elexpand conf lst)
;; literal datum (valid in execline form):
(define (lit? x)
(or (symbol? x) (string? x) (number? x) (u8vector? x)))
;; prepend the k=v forms of kvector 'kv' to lst
(define (+kv kv lst)
(kvector-foldl
kv
(lambda (k v lst)
(if v (cons (k=v k v) lst) lst))
lst))
(define (->string x)
(cond
((string? x) x)
((symbol? x) (##sys#symbol->string x))
((keyword? x) (##sys#symbol->string x))
((number? x) (number->string x 10))
((u8vector? x) (quote-bv x))
(else (error "can't stringify" x))))
;; k=v displays a key-value pair as "k=v..."
;; (always as a single string)
(define (k=v k v)
(string-append
(##sys#symbol->string k)
"="
(if (list? v)
(join-with " " v)
(->string v))))
(if (null? lst)
'()
(let* ((head (car lst))
(rest (cdr lst))
(tail (elexpand conf rest)))
(cond
((null? head) tail)
((lit? head)
(if (eq? rest tail) lst (cons head tail)))
((kvector? head)
(+kv head tail))
((procedure? head)
(let ((e (head conf)))
(cond
((null? e) tail)
((lit? e) (cons e tail))
((pair? e) (append e tail))
((kvector? e) (+kv e tail))
;; TODO: maybe recurse on procedure?
(else (error "cannot splat" e)))))
((pair? head)
(let ((h (elexpand conf head)))
(if (and (eq? h head) (eq? rest tail))
lst
(cons h tail))))
(else (error "unexpected form in execline template" head))))))
(: eltemplate (list -> procedure))
(define (eltemplate form)
(lambda (conf) (elexpand conf form)))