utils: 'current-source-directory' gracefully handles lack of source info.
[jackhill/guix/guix.git] / guix / utils.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2013, 2014, 2015 Mark H Weaver <mhw@netris.org>
4 ;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
5 ;;; Copyright © 2014 Ian Denhardt <ian@zenhack.net>
6 ;;; Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
7 ;;; Copyright © 2015 David Thompson <davet@gnu.org>
8 ;;;
9 ;;; This file is part of GNU Guix.
10 ;;;
11 ;;; GNU Guix is free software; you can redistribute it and/or modify it
12 ;;; under the terms of the GNU General Public License as published by
13 ;;; the Free Software Foundation; either version 3 of the License, or (at
14 ;;; your option) any later version.
15 ;;;
16 ;;; GNU Guix is distributed in the hope that it will be useful, but
17 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;;; GNU General Public License for more details.
20 ;;;
21 ;;; You should have received a copy of the GNU General Public License
22 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24 (define-module (guix utils)
25 #:use-module (guix config)
26 #:use-module (srfi srfi-1)
27 #:use-module (srfi srfi-9)
28 #:use-module (srfi srfi-11)
29 #:use-module (srfi srfi-26)
30 #:use-module (srfi srfi-39)
31 #:use-module (srfi srfi-60)
32 #:use-module (rnrs bytevectors)
33 #:use-module (rnrs io ports)
34 #:use-module ((rnrs bytevectors) #:select (bytevector-u8-set!))
35 #:use-module (guix combinators)
36 #:use-module ((guix build utils) #:select (dump-port))
37 #:use-module ((guix build syscalls) #:select (mkdtemp! fdatasync))
38 #:use-module (ice-9 vlist)
39 #:use-module (ice-9 format)
40 #:autoload (ice-9 popen) (open-pipe*)
41 #:autoload (ice-9 rdelim) (read-line)
42 #:use-module (ice-9 regex)
43 #:use-module (ice-9 match)
44 #:use-module (ice-9 format)
45 #:use-module ((ice-9 iconv) #:select (bytevector->string))
46 #:use-module (system foreign)
47 #:re-export (memoize) ; for backwards compatibility
48 #:export (bytevector->base16-string
49 base16-string->bytevector
50
51 strip-keyword-arguments
52 default-keyword-arguments
53 substitute-keyword-arguments
54 ensure-keyword-arguments
55
56 current-source-directory
57
58 <location>
59 location
60 location?
61 location-file
62 location-line
63 location-column
64 source-properties->location
65 location->source-properties
66
67 nix-system->gnu-triplet
68 gnu-triplet->nix-system
69 %current-system
70 %current-target-system
71 package-name->name+version
72 version-compare
73 version>?
74 version>=?
75 version-prefix
76 version-major+minor
77 guile-version>?
78 string-replace-substring
79 arguments-from-environment-variable
80 file-extension
81 file-sans-extension
82 switch-symlinks
83 call-with-temporary-output-file
84 call-with-temporary-directory
85 with-atomic-file-output
86 cache-directory
87 readlink*
88 edit-expression
89
90 filtered-port
91 compressed-port
92 decompressed-port
93 call-with-decompressed-port
94 compressed-output-port
95 call-with-compressed-output-port
96 canonical-newline-port))
97
98 \f
99 ;;;
100 ;;; Base 16.
101 ;;;
102
103 (define (bytevector->base16-string bv)
104 "Return the hexadecimal representation of BV's contents."
105 (define len
106 (bytevector-length bv))
107
108 (let-syntax ((base16-chars (lambda (s)
109 (syntax-case s ()
110 (_
111 (let ((v (list->vector
112 (unfold (cut > <> 255)
113 (lambda (n)
114 (format #f "~2,'0x" n))
115 1+
116 0))))
117 v))))))
118 (define chars base16-chars)
119 (let loop ((i len)
120 (r '()))
121 (if (zero? i)
122 (string-concatenate r)
123 (let ((i (- i 1)))
124 (loop i
125 (cons (vector-ref chars (bytevector-u8-ref bv i)) r)))))))
126
127 (define base16-string->bytevector
128 (let ((chars->value (fold (lambda (i r)
129 (vhash-consv (string-ref (number->string i 16)
130 0)
131 i r))
132 vlist-null
133 (iota 16))))
134 (lambda (s)
135 "Return the bytevector whose hexadecimal representation is string S."
136 (define bv
137 (make-bytevector (quotient (string-length s) 2) 0))
138
139 (string-fold (lambda (chr i)
140 (let ((j (quotient i 2))
141 (v (and=> (vhash-assv chr chars->value) cdr)))
142 (if v
143 (if (zero? (logand i 1))
144 (bytevector-u8-set! bv j
145 (arithmetic-shift v 4))
146 (let ((w (bytevector-u8-ref bv j)))
147 (bytevector-u8-set! bv j (logior v w))))
148 (error "invalid hexadecimal character" chr)))
149 (+ i 1))
150 0
151 s)
152 bv)))
153
154
155 \f
156 ;;;
157 ;;; Filtering & pipes.
158 ;;;
159
160 (define (filtered-port command input)
161 "Return an input port where data drained from INPUT is filtered through
162 COMMAND (a list). In addition, return a list of PIDs that the caller must
163 wait. When INPUT is a file port, it must be unbuffered; otherwise, any
164 buffered data is lost."
165 (let loop ((input input)
166 (pids '()))
167 (if (file-port? input)
168 (match (pipe)
169 ((in . out)
170 (match (primitive-fork)
171 (0
172 (dynamic-wind
173 (const #f)
174 (lambda ()
175 (close-port in)
176 (close-port (current-input-port))
177 (dup2 (fileno input) 0)
178 (close-port (current-output-port))
179 (dup2 (fileno out) 1)
180 (catch 'system-error
181 (lambda ()
182 (apply execl (car command) command))
183 (lambda args
184 (format (current-error-port)
185 "filtered-port: failed to execute '~{~a ~}': ~a~%"
186 command (strerror (system-error-errno args))))))
187 (lambda ()
188 (primitive-_exit 1))))
189 (child
190 (close-port out)
191 (values in (cons child pids))))))
192
193 ;; INPUT is not a file port, so fork just for the sake of tunneling it
194 ;; through a file port.
195 (match (pipe)
196 ((in . out)
197 (match (primitive-fork)
198 (0
199 (dynamic-wind
200 (const #t)
201 (lambda ()
202 (close-port in)
203 (dump-port input out))
204 (lambda ()
205 (false-if-exception (close out))
206 (primitive-_exit 0))))
207 (child
208 (close-port out)
209 (loop in (cons child pids)))))))))
210
211 (define (decompressed-port compression input)
212 "Return an input port where INPUT is decompressed according to COMPRESSION,
213 a symbol such as 'xz."
214 (match compression
215 ((or #f 'none) (values input '()))
216 ('bzip2 (filtered-port `(,%bzip2 "-dc") input))
217 ('xz (filtered-port `(,%xz "-dc") input))
218 ('gzip (filtered-port `(,%gzip "-dc") input))
219 (else (error "unsupported compression scheme" compression))))
220
221 (define (compressed-port compression input)
222 "Return an input port where INPUT is decompressed according to COMPRESSION,
223 a symbol such as 'xz."
224 (match compression
225 ((or #f 'none) (values input '()))
226 ('bzip2 (filtered-port `(,%bzip2 "-c") input))
227 ('xz (filtered-port `(,%xz "-c") input))
228 ('gzip (filtered-port `(,%gzip "-c") input))
229 (else (error "unsupported compression scheme" compression))))
230
231 (define (call-with-decompressed-port compression port proc)
232 "Call PROC with a wrapper around PORT, a file port, that decompresses data
233 read from PORT according to COMPRESSION, a symbol such as 'xz."
234 (let-values (((decompressed pids)
235 (decompressed-port compression port)))
236 (dynamic-wind
237 (const #f)
238 (lambda ()
239 (proc decompressed))
240 (lambda ()
241 (close-port decompressed)
242 (unless (every (compose zero? cdr waitpid) pids)
243 (error "decompressed-port failure" pids))))))
244
245 (define (filtered-output-port command output)
246 "Return an output port. Data written to that port is filtered through
247 COMMAND and written to OUTPUT, an output file port. In addition, return a
248 list of PIDs to wait for. OUTPUT must be unbuffered; otherwise, any buffered
249 data is lost."
250 (match (pipe)
251 ((in . out)
252 (match (primitive-fork)
253 (0
254 (dynamic-wind
255 (const #f)
256 (lambda ()
257 (close-port out)
258 (close-port (current-input-port))
259 (dup2 (fileno in) 0)
260 (close-port (current-output-port))
261 (dup2 (fileno output) 1)
262 (catch 'system-error
263 (lambda ()
264 (apply execl (car command) command))
265 (lambda args
266 (format (current-error-port)
267 "filtered-output-port: failed to execute '~{~a ~}': ~a~%"
268 command (strerror (system-error-errno args))))))
269 (lambda ()
270 (primitive-_exit 1))))
271 (child
272 (close-port in)
273 (values out (list child)))))))
274
275 (define* (compressed-output-port compression output
276 #:key (options '()))
277 "Return an output port whose input is compressed according to COMPRESSION,
278 a symbol such as 'xz, and then written to OUTPUT. In addition return a list
279 of PIDs to wait for. OPTIONS is a list of strings passed to the compression
280 program--e.g., '(\"--fast\")."
281 (match compression
282 ((or #f 'none) (values output '()))
283 ('bzip2 (filtered-output-port `(,%bzip2 "-c" ,@options) output))
284 ('xz (filtered-output-port `(,%xz "-c" ,@options) output))
285 ('gzip (filtered-output-port `(,%gzip "-c" ,@options) output))
286 (else (error "unsupported compression scheme" compression))))
287
288 (define* (call-with-compressed-output-port compression port proc
289 #:key (options '()))
290 "Call PROC with a wrapper around PORT, a file port, that compresses data
291 that goes to PORT according to COMPRESSION, a symbol such as 'xz. OPTIONS is
292 a list of command-line arguments passed to the compression program."
293 (let-values (((compressed pids)
294 (compressed-output-port compression port
295 #:options options)))
296 (dynamic-wind
297 (const #f)
298 (lambda ()
299 (proc compressed))
300 (lambda ()
301 (close-port compressed)
302 (unless (every (compose zero? cdr waitpid) pids)
303 (error "compressed-output-port failure" pids))))))
304
305 (define* (edit-expression source-properties proc #:key (encoding "UTF-8"))
306 "Edit the expression specified by SOURCE-PROPERTIES using PROC, which should
307 be a procedure that takes the original expression in string and returns a new
308 one. ENCODING will be used to interpret all port I/O, it default to UTF-8.
309 This procedure returns #t on success."
310 (with-fluids ((%default-port-encoding encoding))
311 (let* ((file (assq-ref source-properties 'filename))
312 (line (assq-ref source-properties 'line))
313 (column (assq-ref source-properties 'column))
314 (in (open-input-file file))
315 ;; The start byte position of the expression.
316 (start (begin (while (not (and (= line (port-line in))
317 (= column (port-column in))))
318 (when (eof-object? (read-char in))
319 (error (format #f "~a: end of file~%" in))))
320 (ftell in)))
321 ;; The end byte position of the expression.
322 (end (begin (read in) (ftell in))))
323 (seek in 0 SEEK_SET) ; read from the beginning of the file.
324 (let* ((pre-bv (get-bytevector-n in start))
325 ;; The expression in string form.
326 (str (bytevector->string
327 (get-bytevector-n in (- end start))
328 (port-encoding in)))
329 (post-bv (get-bytevector-all in))
330 (str* (proc str)))
331 ;; Verify the edited expression is still a scheme expression.
332 (call-with-input-string str* read)
333 ;; Update the file with edited expression.
334 (with-atomic-file-output file
335 (lambda (out)
336 (put-bytevector out pre-bv)
337 (display str* out)
338 ;; post-bv maybe the end-of-file object.
339 (when (not (eof-object? post-bv))
340 (put-bytevector out post-bv))
341 #t))))))
342
343 \f
344 ;;;
345 ;;; Keyword arguments.
346 ;;;
347
348 (define (strip-keyword-arguments keywords args)
349 "Remove all of the keyword arguments listed in KEYWORDS from ARGS."
350 (let loop ((args args)
351 (result '()))
352 (match args
353 (()
354 (reverse result))
355 (((? keyword? kw) arg . rest)
356 (loop rest
357 (if (memq kw keywords)
358 result
359 (cons* arg kw result))))
360 ((head . tail)
361 (loop tail (cons head result))))))
362
363 (define (default-keyword-arguments args defaults)
364 "Return ARGS augmented with any keyword/value from DEFAULTS for
365 keywords not already present in ARGS."
366 (let loop ((defaults defaults)
367 (args args))
368 (match defaults
369 ((kw value rest ...)
370 (loop rest
371 (if (assoc-ref kw args)
372 args
373 (cons* kw value args))))
374 (()
375 args))))
376
377 (define-syntax substitute-keyword-arguments
378 (syntax-rules ()
379 "Return a new list of arguments where the value for keyword arg KW is
380 replaced by EXP. EXP is evaluated in a context where VAR is boud to the
381 previous value of the keyword argument."
382 ((_ original-args ((kw var) exp) ...)
383 (let loop ((args original-args)
384 (before '()))
385 (match args
386 ((kw var rest (... ...))
387 (loop rest (cons* exp kw before)))
388 ...
389 ((x rest (... ...))
390 (loop rest (cons x before)))
391 (()
392 (reverse before)))))))
393
394 (define (delkw kw lst)
395 "Remove KW and its associated value from LST, a keyword/value list such
396 as '(#:foo 1 #:bar 2)."
397 (let loop ((lst lst)
398 (result '()))
399 (match lst
400 (()
401 (reverse result))
402 ((kw? value rest ...)
403 (if (eq? kw? kw)
404 (append (reverse result) rest)
405 (loop rest (cons* value kw? result)))))))
406
407 (define (ensure-keyword-arguments args kw/values)
408 "Force the keywords arguments KW/VALUES in the keyword argument list ARGS.
409 For instance:
410
411 (ensure-keyword-arguments '(#:foo 2) '(#:foo 2))
412 => (#:foo 2)
413
414 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3))
415 => (#:foo 2 #:bar 3)
416
417 (ensure-keyword-arguments '(#:foo 2) '(#:bar 3 #:foo 42))
418 => (#:foo 42 #:bar 3)
419 "
420 (let loop ((args args)
421 (kw/values kw/values)
422 (result '()))
423 (match args
424 (()
425 (append (reverse result) kw/values))
426 ((kw value rest ...)
427 (match (memq kw kw/values)
428 ((_ value . _)
429 (loop rest (delkw kw kw/values) (cons* value kw result)))
430 (#f
431 (loop rest kw/values (cons* value kw result))))))))
432
433 \f
434 ;;;
435 ;;; System strings.
436 ;;;
437
438 (define* (nix-system->gnu-triplet
439 #:optional (system (%current-system)) (vendor "unknown"))
440 "Return a guess of the GNU triplet corresponding to Nix system
441 identifier SYSTEM."
442 (match system
443 ("armhf-linux"
444 (string-append "arm-" vendor "-linux-gnueabihf"))
445 (_
446 (let* ((dash (string-index system #\-))
447 (arch (substring system 0 dash))
448 (os (substring system (+ 1 dash))))
449 (string-append arch
450 "-" vendor "-"
451 (if (string=? os "linux")
452 "linux-gnu"
453 os))))))
454
455 (define (gnu-triplet->nix-system triplet)
456 "Return the Nix system type corresponding to TRIPLET, a GNU triplet as
457 returned by `config.guess'."
458 (let ((triplet (cond ((string-match "^i[345]86-(.*)$" triplet)
459 =>
460 (lambda (m)
461 (string-append "i686-" (match:substring m 1))))
462 (else triplet))))
463 (cond ((string-match "^arm[^-]*-([^-]+-)?linux-gnueabihf" triplet)
464 "armhf-linux")
465 ((string-match "^([^-]+)-([^-]+-)?linux-gnu.*" triplet)
466 =>
467 (lambda (m)
468 ;; Nix omits `-gnu' for GNU/Linux.
469 (string-append (match:substring m 1) "-linux")))
470 ((string-match "^([^-]+)-([^-]+-)?([[:alpha:]]+)([0-9]+\\.?)*$" triplet)
471 =>
472 (lambda (m)
473 ;; Nix strip the version number from names such as `gnu0.3',
474 ;; `darwin10.2.0', etc., and always strips the vendor part.
475 (string-append (match:substring m 1) "-"
476 (match:substring m 3))))
477 (else triplet))))
478
479 (define %current-system
480 ;; System type as expected by Nix, usually ARCHITECTURE-KERNEL.
481 ;; By default, this is equal to (gnu-triplet->nix-system %host-type).
482 (make-parameter %system))
483
484 (define %current-target-system
485 ;; Either #f or a GNU triplet representing the target system we are
486 ;; cross-building to.
487 (make-parameter #f))
488
489 (define (package-name->name+version spec)
490 "Given SPEC, a package name like \"foo@0.9.1b\", return two values: \"foo\"
491 and \"0.9.1b\". When the version part is unavailable, SPEC and #f are
492 returned. Both parts must not contain any '@'."
493 (match (string-rindex spec #\@)
494 (#f (values spec #f))
495 (idx (values (substring spec 0 idx)
496 (substring spec (1+ idx))))))
497
498 (define version-compare
499 (let ((strverscmp
500 (let ((sym (or (dynamic-func "strverscmp" (dynamic-link))
501 (error "could not find `strverscmp' (from GNU libc)"))))
502 (pointer->procedure int sym (list '* '*)))))
503 (lambda (a b)
504 "Return '> when A denotes a newer version than B,
505 '< when A denotes a older version than B,
506 or '= when they denote equal versions."
507 (let ((result (strverscmp (string->pointer a) (string->pointer b))))
508 (cond ((positive? result) '>)
509 ((negative? result) '<)
510 (else '=))))))
511
512 (define (version-prefix version-string num-parts)
513 "Truncate version-string to the first num-parts components of the version.
514 For example, (version-prefix \"2.1.47.4.23\" 3) returns \"2.1.47\""
515 (string-join (take (string-split version-string #\.) num-parts) "."))
516
517
518 (define (version-major+minor version-string)
519 "Return \"<major>.<minor>\", where major and minor are the major and
520 minor version numbers from version-string."
521 (version-prefix version-string 2))
522
523 (define (version>? a b)
524 "Return #t when A denotes a version strictly newer than B."
525 (eq? '> (version-compare a b)))
526
527 (define (version>=? a b)
528 "Return #t when A denotes a version newer or equal to B."
529 (case (version-compare a b)
530 ((> =) #t)
531 (else #f)))
532
533 (define (guile-version>? str)
534 "Return #t if the running Guile version is greater than STR."
535 ;; Note: Using (version>? (version) "2.0.5") or similar doesn't work,
536 ;; because the result of (version) can have a prefix, like "2.0.5-deb1".
537 (version>? (string-append (major-version) "."
538 (minor-version) "."
539 (micro-version))
540 str))
541
542 (define (file-extension file)
543 "Return the extension of FILE or #f if there is none."
544 (let ((dot (string-rindex file #\.)))
545 (and dot (substring file (+ 1 dot) (string-length file)))))
546
547 (define (file-sans-extension file)
548 "Return the substring of FILE without its extension, if any."
549 (let ((dot (string-rindex file #\.)))
550 (if dot
551 (substring file 0 dot)
552 file)))
553
554 (define (switch-symlinks link target)
555 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
556 both when LINK already exists and when it does not."
557 (let ((pivot (string-append link ".new")))
558 (symlink target pivot)
559 (rename-file pivot link)))
560
561 (define* (string-replace-substring str substr replacement
562 #:optional
563 (start 0)
564 (end (string-length str)))
565 "Replace all occurrences of SUBSTR in the START--END range of STR by
566 REPLACEMENT."
567 (match (string-length substr)
568 (0
569 (error "string-replace-substring: empty substring"))
570 (substr-length
571 (let loop ((start start)
572 (pieces (list (substring str 0 start))))
573 (match (string-contains str substr start end)
574 (#f
575 (string-concatenate-reverse
576 (cons (substring str start) pieces)))
577 (index
578 (loop (+ index substr-length)
579 (cons* replacement
580 (substring str start index)
581 pieces))))))))
582
583 (define (arguments-from-environment-variable variable)
584 "Retrieve value of environment variable denoted by string VARIABLE in the
585 form of a list of strings (`char-set:graphic' tokens) suitable for consumption
586 by `args-fold', if VARIABLE is defined, otherwise return an empty list."
587 (let ((env (getenv variable)))
588 (if env
589 (string-tokenize env char-set:graphic)
590 '())))
591
592 (define (call-with-temporary-output-file proc)
593 "Call PROC with a name of a temporary file and open output port to that
594 file; close the file and delete it when leaving the dynamic extent of this
595 call."
596 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
597 (template (string-append directory "/guix-file.XXXXXX"))
598 (out (mkstemp! template)))
599 (dynamic-wind
600 (lambda ()
601 #t)
602 (lambda ()
603 (proc template out))
604 (lambda ()
605 (false-if-exception (close out))
606 (false-if-exception (delete-file template))))))
607
608 (define (call-with-temporary-directory proc)
609 "Call PROC with a name of a temporary directory; close the directory and
610 delete it when leaving the dynamic extent of this call."
611 (let* ((directory (or (getenv "TMPDIR") "/tmp"))
612 (template (string-append directory "/guix-directory.XXXXXX"))
613 (tmp-dir (mkdtemp! template)))
614 (dynamic-wind
615 (const #t)
616 (lambda ()
617 (proc tmp-dir))
618 (lambda ()
619 (false-if-exception (rmdir tmp-dir))))))
620
621 (define (with-atomic-file-output file proc)
622 "Call PROC with an output port for the file that is going to replace FILE.
623 Upon success, FILE is atomically replaced by what has been written to the
624 output port, and PROC's result is returned."
625 (let* ((template (string-append file ".XXXXXX"))
626 (out (mkstemp! template)))
627 (with-throw-handler #t
628 (lambda ()
629 (let ((result (proc out)))
630 (fdatasync out)
631 (close-port out)
632 (rename-file template file)
633 result))
634 (lambda (key . args)
635 (false-if-exception (delete-file template))
636 (close-port out)))))
637
638 (define (cache-directory)
639 "Return the cache directory for Guix, by default ~/.cache/guix."
640 (or (getenv "XDG_CONFIG_HOME")
641 (and=> (or (getenv "HOME")
642 (passwd:dir (getpwuid (getuid))))
643 (cut string-append <> "/.cache/guix"))))
644
645 (define (readlink* file)
646 "Call 'readlink' until the result is not a symlink."
647 (define %max-symlink-depth 50)
648
649 (let loop ((file file)
650 (depth 0))
651 (define (absolute target)
652 (if (absolute-file-name? target)
653 target
654 (string-append (dirname file) "/" target)))
655
656 (if (>= depth %max-symlink-depth)
657 file
658 (call-with-values
659 (lambda ()
660 (catch 'system-error
661 (lambda ()
662 (values #t (readlink file)))
663 (lambda args
664 (let ((errno (system-error-errno args)))
665 (if (or (= errno EINVAL))
666 (values #f file)
667 (apply throw args))))))
668 (lambda (success? target)
669 (if success?
670 (loop (absolute target) (+ depth 1))
671 file))))))
672
673 (define (canonical-newline-port port)
674 "Return an input port that wraps PORT such that all newlines consist
675 of a single carriage return."
676 (define (get-position)
677 (if (port-has-port-position? port) (port-position port) #f))
678 (define (set-position! position)
679 (if (port-has-set-port-position!? port)
680 (set-port-position! position port)
681 #f))
682 (define (close) (close-port port))
683 (define (read! bv start n)
684 (let loop ((count 0)
685 (byte (get-u8 port)))
686 (cond ((eof-object? byte) count)
687 ((= count (- n 1))
688 (bytevector-u8-set! bv (+ start count) byte)
689 n)
690 ;; XXX: consume all LFs even if not followed by CR.
691 ((eqv? byte (char->integer #\return)) (loop count (get-u8 port)))
692 (else
693 (bytevector-u8-set! bv (+ start count) byte)
694 (loop (+ count 1) (get-u8 port))))))
695 (make-custom-binary-input-port "canonical-newline-port"
696 read!
697 get-position
698 set-position!
699 close))
700 \f
701 ;;;
702 ;;; Source location.
703 ;;;
704
705 (define (absolute-dirname file)
706 "Return the absolute name of the directory containing FILE, or #f upon
707 failure."
708 (match (search-path %load-path file)
709 (#f #f)
710 ((? string? file)
711 ;; If there are relative names in %LOAD-PATH, FILE can be relative and
712 ;; needs to be canonicalized.
713 (if (string-prefix? "/" file)
714 (dirname file)
715 (canonicalize-path (dirname file))))))
716
717 (define-syntax current-source-directory
718 (lambda (s)
719 "Return the absolute name of the current directory, or #f if it could not
720 be determined."
721 (syntax-case s ()
722 ((_)
723 (match (assq 'filename (syntax-source s))
724 (('filename . (? string? file-name))
725 ;; If %FILE-PORT-NAME-CANONICALIZATION is 'relative, then FILE-NAME
726 ;; can be relative. In that case, we try to find out at run time
727 ;; the absolute file name by looking at %LOAD-PATH; doing this at
728 ;; run time rather than expansion time is necessary to allow files
729 ;; to be moved on the file system.
730 (cond ((not file-name)
731 #f) ;raising an error would upset Geiser users
732 ((string-prefix? "/" file-name)
733 (dirname file-name))
734 (else
735 #`(absolute-dirname #,file-name))))
736 (_
737 #f))))))
738
739 ;; A source location.
740 (define-record-type <location>
741 (make-location file line column)
742 location?
743 (file location-file) ; file name
744 (line location-line) ; 1-indexed line
745 (column location-column)) ; 0-indexed column
746
747 (define location
748 (memoize
749 (lambda (file line column)
750 "Return the <location> object for the given FILE, LINE, and COLUMN."
751 (and line column file
752 (make-location file line column)))))
753
754 (define (source-properties->location loc)
755 "Return a location object based on the info in LOC, an alist as returned
756 by Guile's `source-properties', `frame-source', `current-source-location',
757 etc."
758 (let ((file (assq-ref loc 'filename))
759 (line (assq-ref loc 'line))
760 (col (assq-ref loc 'column)))
761 ;; In accordance with the GCS, start line and column numbers at 1. Note
762 ;; that unlike LINE and `port-column', COL is actually 1-indexed here...
763 (location file (and line (+ line 1)) col)))
764
765 (define (location->source-properties loc)
766 "Return the source property association list based on the info in LOC,
767 a location object."
768 `((line . ,(and=> (location-line loc) 1-))
769 (column . ,(location-column loc))
770 (filename . ,(location-file loc))))