Update TODO.
[jackhill/guix/guix.git] / guix / build / utils.scm
CommitLineData
4155e2a9
LC
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org>
11996d85 3;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
c36db98c 4;;;
4155e2a9 5;;; This file is part of GNU Guix.
c36db98c 6;;;
4155e2a9 7;;; GNU Guix is free software; you can redistribute it and/or modify it
c36db98c
LC
8;;; under the terms of the GNU General Public License as published by
9;;; the Free Software Foundation; either version 3 of the License, or (at
10;;; your option) any later version.
11;;;
4155e2a9 12;;; GNU Guix is distributed in the hope that it will be useful, but
c36db98c
LC
13;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;;; GNU General Public License for more details.
16;;;
17;;; You should have received a copy of the GNU General Public License
4155e2a9 18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
c36db98c
LC
19
20(define-module (guix build utils)
21 #:use-module (srfi srfi-1)
b0e0d0e9 22 #:use-module (srfi srfi-11)
c0746cc9 23 #:use-module (ice-9 ftw)
b0e0d0e9
LC
24 #:use-module (ice-9 match)
25 #:use-module (ice-9 regex)
26 #:use-module (ice-9 rdelim)
ebe2f31f
LC
27 #:use-module (rnrs bytevectors)
28 #:use-module (rnrs io ports)
c36db98c 29 #:export (directory-exists?
d0084152 30 executable-file?
c0895112 31 call-with-ascii-input-file
b0e0d0e9 32 with-directory-excursion
7da95264 33 mkdir-p
c0746cc9 34 copy-recursively
e65df6a6 35 delete-file-recursively
4c261f41
LC
36 find-files
37
b0e0d0e9 38 set-path-environment-variable
ebe2f31f
LC
39 search-path-as-string->list
40 list->search-path-as-string
7584f822
LC
41 which
42
b0e0d0e9
LC
43 alist-cons-before
44 alist-cons-after
45 alist-replace
dcd72906 46 with-atomic-file-replacement
10c87717 47 substitute
ebe2f31f
LC
48 substitute*
49 dump-port
bc5bf85f 50 set-file-time
91133c2d 51 patch-shebang
c0895112 52 patch-makefile-SHELL
91133c2d
LC
53 fold-port-matches
54 remove-store-references))
b0e0d0e9 55
11996d85 56
b0e0d0e9
LC
57;;;
58;;; Directories.
59;;;
c36db98c
LC
60
61(define (directory-exists? dir)
62 "Return #t if DIR exists and is a directory."
9f55cf8d
LC
63 (let ((s (stat dir #f)))
64 (and s
65 (eq? 'directory (stat:type s)))))
c36db98c 66
d0084152
LC
67(define (executable-file? file)
68 "Return #t if FILE exists and is executable."
69 (let ((s (stat file #f)))
70 (and s
71 (not (zero? (logand (stat:mode s) #o100))))))
72
c0895112
LC
73(define (call-with-ascii-input-file file proc)
74 "Open FILE as an ASCII or binary file, and pass the resulting port to
75PROC. FILE is closed when PROC's dynamic extent is left. Return the
76return values of applying PROC to the port."
77 (let ((port (with-fluids ((%default-port-encoding #f))
78 ;; Use "b" so that `open-file' ignores `coding:' cookies.
79 (open-file file "rb"))))
80 (dynamic-wind
81 (lambda ()
82 #t)
83 (lambda ()
84 (proc port))
85 (lambda ()
86 (close-input-port port)))))
87
b0e0d0e9
LC
88(define-syntax-rule (with-directory-excursion dir body ...)
89 "Run BODY with DIR as the process's current directory."
90 (let ((init (getcwd)))
91 (dynamic-wind
92 (lambda ()
93 (chdir dir))
94 (lambda ()
95 body ...)
96 (lambda ()
97 (chdir init)))))
98
7da95264
LC
99(define (mkdir-p dir)
100 "Create directory DIR and all its ancestors."
101 (define absolute?
102 (string-prefix? "/" dir))
103
104 (define not-slash
105 (char-set-complement (char-set #\/)))
106
107 (let loop ((components (string-tokenize dir not-slash))
108 (root (if absolute?
109 ""
110 ".")))
111 (match components
112 ((head tail ...)
113 (let ((path (string-append root "/" head)))
114 (catch 'system-error
115 (lambda ()
116 (mkdir path)
117 (loop tail path))
118 (lambda args
119 (if (= EEXIST (system-error-errno args))
120 (loop tail path)
121 (apply throw args))))))
122 (() #t))))
123
c0746cc9 124(define* (copy-recursively source destination
12761f48
LC
125 #:key
126 (log (current-output-port))
127 (follow-symlinks? #f))
128 "Copy SOURCE directory to DESTINATION. Follow symlinks if FOLLOW-SYMLINKS?
129is true; otherwise, just preserve them. Write verbose output to the LOG port."
c0746cc9
LC
130 (define strip-source
131 (let ((len (string-length source)))
132 (lambda (file)
133 (substring file len))))
134
135 (file-system-fold (const #t) ; enter?
136 (lambda (file stat result) ; leaf
137 (let ((dest (string-append destination
138 (strip-source file))))
139 (format log "`~a' -> `~a'~%" file dest)
12761f48
LC
140 (case (stat:type stat)
141 ((symlink)
142 (let ((target (readlink file)))
143 (symlink target dest)))
144 (else
145 (copy-file file dest)))))
c0746cc9
LC
146 (lambda (dir stat result) ; down
147 (mkdir-p (string-append destination
148 (strip-source dir))))
149 (lambda (dir stat result) ; up
150 result)
151 (const #t) ; skip
152 (lambda (file stat errno result)
153 (format (current-error-port) "i/o error: ~a: ~a~%"
154 file (strerror errno))
155 #f)
156 #t
12761f48
LC
157 source
158
159 (if follow-symlinks?
160 stat
161 lstat)))
c0746cc9 162
e65df6a6
LC
163(define (delete-file-recursively dir)
164 "Delete DIR recursively, like `rm -rf', without following symlinks. Report
165but ignore errors."
166 (file-system-fold (const #t) ; enter?
167 (lambda (file stat result) ; leaf
168 (delete-file file))
169 (const #t) ; down
170 (lambda (dir stat result) ; up
171 (rmdir dir))
172 (const #t) ; skip
173 (lambda (file stat errno result)
174 (format (current-error-port)
175 "warning: failed to delete ~a: ~a~%"
176 file (strerror errno)))
177 #t
178 dir
179
180 ;; Don't follow symlinks.
181 lstat))
182
4c261f41
LC
183(define (find-files dir regexp)
184 "Return the list of files under DIR whose basename matches REGEXP."
185 (define file-rx
186 (if (regexp? regexp)
187 regexp
188 (make-regexp regexp)))
189
190 (file-system-fold (const #t)
191 (lambda (file stat result) ; leaf
192 (if (regexp-exec file-rx (basename file))
193 (cons file result)
194 result))
195 (lambda (dir stat result) ; down
196 result)
197 (lambda (dir stat result) ; up
198 result)
199 (lambda (file stat result) ; skip
200 result)
201 (lambda (file stat errno result)
202 (format (current-error-port) "find-files: ~a: ~a~%"
203 file (strerror errno))
204 #f)
205 '()
206 dir))
207
b0e0d0e9
LC
208\f
209;;;
210;;; Search paths.
211;;;
212
c36db98c
LC
213(define (search-path-as-list sub-directories input-dirs)
214 "Return the list of directories among SUB-DIRECTORIES that exist in
215INPUT-DIRS. Example:
216
217 (search-path-as-list '(\"share/emacs/site-lisp\" \"share/emacs/24.1\")
218 (list \"/package1\" \"/package2\" \"/package3\"))
219 => (\"/package1/share/emacs/site-lisp\"
220 \"/package3/share/emacs/site-lisp\")
221
222"
223 (append-map (lambda (input)
224 (filter-map (lambda (dir)
225 (let ((dir (string-append input "/"
226 dir)))
227 (and (directory-exists? dir)
228 dir)))
229 sub-directories))
230 input-dirs))
231
232(define (list->search-path-as-string lst separator)
233 (string-join lst separator))
234
ebe2f31f
LC
235(define* (search-path-as-string->list path #:optional (separator #\:))
236 (string-tokenize path (char-set-complement (char-set separator))))
237
c36db98c
LC
238(define* (set-path-environment-variable env-var sub-directories input-dirs
239 #:key (separator ":"))
240 "Look for each of SUB-DIRECTORIES in INPUT-DIRS. Set ENV-VAR to a
241SEPARATOR-separated path accordingly. Example:
242
243 (set-path-environment-variable \"PKG_CONFIG\"
244 '(\"lib/pkgconfig\")
245 (list package1 package2))
246"
9d9ef458
LC
247 (let* ((path (search-path-as-list sub-directories input-dirs))
248 (value (list->search-path-as-string path separator)))
249 (setenv env-var value)
250 (format #t "environment variable `~a' set to `~a'~%"
251 env-var value)))
b0e0d0e9 252
7584f822
LC
253(define (which program)
254 "Return the complete file name for PROGRAM as found in $PATH, or #f if
255PROGRAM could not be found."
256 (search-path (search-path-as-string->list (getenv "PATH"))
257 program))
258
b0e0d0e9
LC
259\f
260;;;
261;;; Phases.
262;;;
263;;; In (guix build gnu-build-system), there are separate phases (configure,
264;;; build, test, install). They are represented as a list of name/procedure
265;;; pairs. The following procedures make it easy to change the list of
266;;; phases.
267;;;
268
269(define* (alist-cons-before reference key value alist
270 #:optional (key=? equal?))
271 "Insert the KEY/VALUE pair before the first occurrence of a pair whose key
272is REFERENCE in ALIST. Use KEY=? to compare keys."
273 (let-values (((before after)
274 (break (match-lambda
275 ((k . _)
276 (key=? k reference)))
277 alist)))
278 (append before (alist-cons key value after))))
279
280(define* (alist-cons-after reference key value alist
281 #:optional (key=? equal?))
282 "Insert the KEY/VALUE pair after the first occurrence of a pair whose key
283is REFERENCE in ALIST. Use KEY=? to compare keys."
284 (let-values (((before after)
285 (break (match-lambda
286 ((k . _)
287 (key=? k reference)))
288 alist)))
289 (match after
290 ((reference after ...)
291 (append before (cons* reference `(,key . ,value) after)))
292 (()
293 (append before `((,key . ,value)))))))
294
295(define* (alist-replace key value alist #:optional (key=? equal?))
296 "Replace the first pair in ALIST whose car is KEY with the KEY/VALUE pair.
297An error is raised when no such pair exists."
298 (let-values (((before after)
299 (break (match-lambda
300 ((k . _)
301 (key=? k key)))
302 alist)))
303 (match after
304 ((_ after ...)
305 (append before (alist-cons key value after))))))
306
307\f
308;;;
309;;; Text substitution (aka. sed).
310;;;
311
dcd72906
LC
312(define (with-atomic-file-replacement file proc)
313 "Call PROC with two arguments: an input port for FILE, and an output
314port for the file that is going to replace FILE. Upon success, FILE is
315atomically replaced by what has been written to the output port, and
316PROC's result is returned."
317 (let* ((template (string-append file ".XXXXXX"))
d9dbab18
LC
318 (out (mkstemp! template))
319 (mode (stat:mode (stat file))))
b0e0d0e9
LC
320 (with-throw-handler #t
321 (lambda ()
322 (call-with-input-file file
323 (lambda (in)
dcd72906
LC
324 (let ((result (proc in out)))
325 (close out)
326 (chmod template mode)
327 (rename-file template file)
328 result))))
b0e0d0e9
LC
329 (lambda (key . args)
330 (false-if-exception (delete-file template))))))
331
dcd72906
LC
332(define (substitute file pattern+procs)
333 "PATTERN+PROCS is a list of regexp/two-argument procedure. For each line
334of FILE, and for each PATTERN that it matches, call the corresponding PROC
335as (PROC LINE MATCHES); PROC must return the line that will be written as a
336substitution of the original line."
337 (let ((rx+proc (map (match-lambda
338 (((? regexp? pattern) . proc)
339 (cons pattern proc))
340 ((pattern . proc)
341 (cons (make-regexp pattern regexp/extended)
342 proc)))
343 pattern+procs)))
344 (with-atomic-file-replacement file
345 (lambda (in out)
346 (let loop ((line (read-line in 'concat)))
347 (if (eof-object? line)
348 #t
349 (let ((line (fold (lambda (r+p line)
350 (match r+p
351 ((regexp . proc)
352 (match (list-matches regexp line)
353 ((and m+ (_ _ ...))
354 (proc line m+))
355 (_ line)))))
356 line
357 rx+proc)))
358 (display line out)
359 (loop (read-line in 'concat)))))))))
360
10c87717
LC
361
362(define-syntax let-matches
363 ;; Helper macro for `substitute*'.
364 (syntax-rules (_)
365 ((let-matches index match (_ vars ...) body ...)
366 (let-matches (+ 1 index) match (vars ...)
367 body ...))
368 ((let-matches index match (var vars ...) body ...)
369 (let ((var (match:substring match index)))
370 (let-matches (+ 1 index) match (vars ...)
371 body ...)))
372 ((let-matches index match () body ...)
373 (begin body ...))))
374
8773648e
LC
375(define-syntax substitute*
376 (syntax-rules ()
377 "Substitute REGEXP in FILE by the string returned by BODY. BODY is
10c87717
LC
378evaluated with each MATCH-VAR bound to the corresponding positional regexp
379sub-expression. For example:
380
4fa697e9 381 (substitute* file
20d83444
LC
382 ((\"hello\")
383 \"good morning\\n\")
384 ((\"foo([a-z]+)bar(.*)$\" all letters end)
385 (string-append \"baz\" letter end)))
4fa697e9
LC
386
387Here, anytime a line of FILE contains \"hello\", it is replaced by \"good
388morning\". Anytime a line of FILE matches the second regexp, ALL is bound to
389the complete match, LETTERS is bound to the first sub-expression, and END is
390bound to the last one.
391
392When one of the MATCH-VAR is `_', no variable is bound to the corresponding
20d83444
LC
393match substring.
394
395Alternatively, FILE may be a list of file names, in which case they are
396all subject to the substitutions."
8773648e 397 ((substitute* file ((regexp match-var ...) body ...) ...)
20d83444
LC
398 (let ()
399 (define (substitute-one-file file-name)
400 (substitute
401 file-name
402 (list (cons regexp
403 (lambda (l m+)
404 ;; Iterate over matches M+ and return the
405 ;; modified line based on L.
406 (let loop ((m* m+) ; matches
407 (o 0) ; offset in L
408 (r '())) ; result
409 (match m*
410 (()
411 (let ((r (cons (substring l o) r)))
412 (string-concatenate-reverse r)))
413 ((m . rest)
414 (let-matches 0 m (match-var ...)
415 (loop rest
416 (match:end m)
417 (cons*
418 (begin body ...)
419 (substring l o (match:start m))
420 r))))))))
421 ...)))
422
423 (match file
424 ((files (... ...))
425 (for-each substitute-one-file files))
426 ((? string? f)
427 (substitute-one-file f)))))))
10c87717 428
ebe2f31f
LC
429\f
430;;;
431;;; Patching shebangs---e.g., /bin/sh -> /nix/store/xyz...-bash/bin/sh.
432;;;
433
a18b4d08
LC
434(define* (dump-port in out
435 #:key (buffer-size 16384)
436 (progress (lambda (t k) (k))))
74baf333 437 "Read as much data as possible from IN and write it to OUT, using
a18b4d08
LC
438chunks of BUFFER-SIZE bytes. Call PROGRESS after each successful
439transfer of BUFFER-SIZE bytes or less, passing it the total number of
440bytes transferred and the continuation of the transfer as a thunk."
ebe2f31f
LC
441 (define buffer
442 (make-bytevector buffer-size))
443
a18b4d08
LC
444 (let loop ((total 0)
445 (bytes (get-bytevector-n! in buffer 0 buffer-size)))
ebe2f31f 446 (or (eof-object? bytes)
a18b4d08 447 (let ((total (+ total bytes)))
ebe2f31f 448 (put-bytevector out buffer 0 bytes)
a18b4d08
LC
449 (progress total
450 (lambda ()
451 (loop total
452 (get-bytevector-n! in buffer 0 buffer-size))))))))
ebe2f31f 453
bc5bf85f
LC
454(define (set-file-time file stat)
455 "Set the atime/mtime of FILE to that specified by STAT."
456 (utime file
457 (stat:atime stat)
458 (stat:mtime stat)
459 (stat:atimensec stat)
460 (stat:mtimensec stat)))
461
ebe2f31f 462(define patch-shebang
11996d85 463 (let ((shebang-rx (make-regexp "^[[:blank:]]*([[:graph:]]+)[[:blank:]]*([[:graph:]]*)(.*)$")))
525a59d6 464 (lambda* (file
bc5bf85f
LC
465 #:optional
466 (path (search-path-as-string->list (getenv "PATH")))
467 #:key (keep-mtime? #t))
525a59d6
LC
468 "Replace the #! interpreter file name in FILE by a valid one found in
469PATH, when FILE actually starts with a shebang. Return #t when FILE was
bc5bf85f
LC
470patched, #f otherwise. When KEEP-MTIME? is true, the atime/mtime of
471FILE are kept unchanged."
ebe2f31f
LC
472 (define (patch p interpreter rest-of-line)
473 (let* ((template (string-append file ".XXXXXX"))
474 (out (mkstemp! template))
bc5bf85f
LC
475 (st (stat file))
476 (mode (stat:mode st)))
ebe2f31f
LC
477 (with-throw-handler #t
478 (lambda ()
479 (format out "#!~a~a~%"
480 interpreter rest-of-line)
481 (dump-port p out)
482 (close out)
483 (chmod template mode)
484 (rename-file template file)
bc5bf85f
LC
485 (when keep-mtime?
486 (set-file-time file st))
ebe2f31f
LC
487 #t)
488 (lambda (key . args)
489 (format (current-error-port)
490 "patch-shebang: ~a: error: ~a ~s~%"
491 file key args)
492 (false-if-exception (delete-file template))
493 #f))))
494
c0895112
LC
495 (call-with-ascii-input-file file
496 (lambda (p)
497 (and (eq? #\# (read-char p))
498 (eq? #\! (read-char p))
499 (let ((line (false-if-exception (read-line p))))
500 (and=> (and line (regexp-exec shebang-rx line))
501 (lambda (m)
11996d85
AE
502 (let* ((interp (match:substring m 1))
503 (arg1 (match:substring m 2))
504 (rest (match:substring m 3))
505 (has-env (string-suffix? "/env" interp))
506 (cmd (if has-env arg1 (basename interp)))
507 (bin (search-path path cmd)))
c0895112 508 (if bin
11996d85 509 (if (string=? bin interp)
c0895112 510 #f ; nothing to do
11996d85
AE
511 (if has-env
512 (begin
513 (format (current-error-port)
514 "patch-shebang: ~a: changing `~a' to `~a'~%"
515 file (string-append interp " " arg1) bin)
516 (patch p bin rest))
517 (begin
518 (format (current-error-port)
519 "patch-shebang: ~a: changing `~a' to `~a'~%"
520 file interp bin)
521 (patch p bin
ca8def6e
AE
522 (if (string-null? arg1)
523 ""
524 (string-append " " arg1 rest))))))
c0895112
LC
525 (begin
526 (format (current-error-port)
527 "patch-shebang: ~a: warning: no binary for interpreter `~a' found in $PATH~%"
528 file (basename cmd))
529 #f))))))))))))
530
bc5bf85f
LC
531(define* (patch-makefile-SHELL file #:key (keep-mtime? #t))
532 "Patch the `SHELL' variable in FILE, which is supposedly a makefile.
533When KEEP-MTIME? is true, the atime/mtime of FILE are kept unchanged."
c0895112
LC
534
535 ;; For instance, Gettext-generated po/Makefile.in.in do not honor $SHELL.
536
537 ;; XXX: Unlike with `patch-shebang', FILE is always touched.
538
539 (define (find-shell name)
540 (let ((shell
541 (search-path (search-path-as-string->list (getenv "PATH"))
542 name)))
543 (unless shell
544 (format (current-error-port)
545 "patch-makefile-SHELL: warning: no binary for shell `~a' found in $PATH~%"
546 name))
547 shell))
548
bc5bf85f
LC
549 (let ((st (stat file)))
550 (substitute* file
551 (("^ *SHELL[[:blank:]]*=[[:blank:]]*([[:graph:]]*/)([[:graph:]]+)[[:blank:]]*" _ dir shell)
552 (let* ((old (string-append dir shell))
553 (new (or (find-shell shell) old)))
554 (unless (string=? new old)
555 (format (current-error-port)
556 "patch-makefile-SHELL: ~a: changing `SHELL' from `~a' to `~a'~%"
557 file old new))
558 (string-append "SHELL = " new "\n"))))
559
560 (when keep-mtime?
561 (set-file-time file st))))
10c87717 562
91133c2d
LC
563(define* (fold-port-matches proc init pattern port
564 #:optional (unmatched (lambda (_ r) r)))
565 "Read from PORT character-by-character; for each match against
566PATTERN, call (PROC MATCH RESULT), where RESULT is seeded with INIT.
567PATTERN is a list of SRFI-14 char-sets. Call (UNMATCHED CHAR RESULT)
568for each unmatched character."
569 (define initial-pattern
570 ;; The poor developer's regexp.
571 (if (string? pattern)
572 (map char-set (string->list pattern))
573 pattern))
574
93b03575
LC
575 (define (get-char p)
576 ;; We call it `get-char', but that's really a binary version
577 ;; thereof. (The real `get-char' cannot be used here because our
578 ;; bootstrap Guile is hacked to always use UTF-8.)
579 (match (get-u8 p)
580 ((? integer? x) (integer->char x))
581 (x x)))
582
91133c2d
LC
583 ;; Note: we're not really striving for performance here...
584 (let loop ((chars '())
585 (pattern initial-pattern)
586 (matched '())
587 (result init))
588 (cond ((null? chars)
589 (loop (list (get-char port))
590 pattern
591 matched
592 result))
593 ((null? pattern)
594 (loop chars
595 initial-pattern
596 '()
597 (proc (list->string (reverse matched)) result)))
598 ((eof-object? (car chars))
599 (fold-right unmatched result matched))
600 ((char-set-contains? (car pattern) (car chars))
601 (loop (cdr chars)
602 (cdr pattern)
603 (cons (car chars) matched)
604 result))
605 ((null? matched) ; common case
606 (loop (cdr chars)
607 pattern
608 matched
609 (unmatched (car chars) result)))
610 (else
611 (let ((matched (reverse matched)))
612 (loop (append (cdr matched) chars)
613 initial-pattern
614 '()
615 (unmatched (car matched) result)))))))
616
617(define* (remove-store-references file
618 #:optional (store (or (getenv "NIX_STORE")
619 "/nix/store")))
620 "Remove from FILE occurrences of file names in STORE; return #t when
621store paths were encountered in FILE, #f otherwise. This procedure is
622known as `nuke-refs' in Nixpkgs."
623 (define pattern
624 (let ((nix-base32-chars
625 '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
626 #\a #\b #\c #\d #\f #\g #\h #\i #\j #\k #\l #\m #\n
627 #\p #\q #\r #\s #\v #\w #\x #\y #\z)))
628 `(,@(map char-set (string->list store))
629 ,(char-set #\/)
630 ,@(make-list 32 (list->char-set nix-base32-chars))
631 ,(char-set #\-))))
632
633 (with-fluids ((%default-port-encoding #f))
634 (with-atomic-file-replacement file
635 (lambda (in out)
636 ;; We cannot use `regexp-exec' here because it cannot deal with
637 ;; strings containing NUL characters.
638 (format #t "removing store references from `~a'...~%" file)
639 (setvbuf in _IOFBF 65536)
640 (setvbuf out _IOFBF 65536)
641 (fold-port-matches (lambda (match result)
93b03575
LC
642 (put-bytevector out (string->utf8 store))
643 (put-u8 out (char->integer #\/))
644 (put-bytevector out
645 (string->utf8
646 "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-"))
91133c2d
LC
647 #t)
648 #f
649 pattern
650 in
651 (lambda (char result)
93b03575 652 (put-u8 out (char->integer char))
91133c2d
LC
653 result))))))
654
b0e0d0e9
LC
655;;; Local Variables:
656;;; eval: (put 'call-with-output-file/atomic 'scheme-indent-function 1)
657;;; eval: (put 'with-throw-handler 'scheme-indent-function 1)
8197c978 658;;; eval: (put 'let-matches 'scheme-indent-function 3)
dcd72906 659;;; eval: (put 'with-atomic-file-replacement 'scheme-indent-function 1)
b0e0d0e9 660;;; End: