gnu: libarchive: Apply fixes including for CVE-2013-0211.
[jackhill/guix/guix.git] / guix / ui.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
8fa3e6b3 2;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
e49951eb 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
98eb8cbe 4;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
073c34d7 5;;;
233e7676 6;;; This file is part of GNU Guix.
073c34d7 7;;;
233e7676 8;;; GNU Guix is free software; you can redistribute it and/or modify it
073c34d7
LC
9;;; under the terms of the GNU General Public License as published by
10;;; the Free Software Foundation; either version 3 of the License, or (at
11;;; your option) any later version.
12;;;
233e7676 13;;; GNU Guix is distributed in the hope that it will be useful, but
073c34d7
LC
14;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;;; GNU General Public License for more details.
17;;;
18;;; You should have received a copy of the GNU General Public License
233e7676 19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
073c34d7
LC
20
21(define-module (guix ui)
22 #:use-module (guix utils)
23 #:use-module (guix store)
cdd5d6f9 24 #:use-module (guix config)
073c34d7 25 #:use-module (guix packages)
5d7a8584 26 #:use-module (guix profiles)
9b222abe 27 #:use-module (guix build-system)
9bb2b96a 28 #:use-module (guix derivations)
59758816 29 #:use-module ((guix build utils) #:select (mkdir-p))
299112d3
LC
30 #:use-module ((guix licenses) #:select (license? license-name))
31 #:use-module (srfi srfi-1)
32 #:use-module (srfi srfi-11)
2cd09108 33 #:use-module (srfi srfi-19)
073c34d7
LC
34 #:use-module (srfi srfi-26)
35 #:use-module (srfi srfi-34)
c1d52c71 36 #:use-module (srfi srfi-35)
a5975ced 37 #:use-module (srfi srfi-37)
e31ff8b8 38 #:autoload (ice-9 ftw) (scandir)
64fc89b6 39 #:use-module (ice-9 match)
9bb2b96a 40 #:use-module (ice-9 format)
2cd09108 41 #:use-module (ice-9 regex)
073c34d7
LC
42 #:export (_
43 N_
ee764179 44 P_
073c34d7 45 leave
cdd5d6f9 46 show-version-and-exit
3441e164 47 show-bug-report-information
969e678e 48 string->number*
1d6243cf 49 size->number
9bb2b96a 50 show-what-to-build
5d7a8584 51 show-manifest-transaction
073c34d7 52 call-with-error-handling
64fc89b6 53 with-error-handling
ac5de156 54 read/eval
eb0880e7 55 read/eval-package-expression
299112d3 56 location->string
c61b026e 57 switch-symlinks
f651b477 58 config-directory
299112d3
LC
59 fill-paragraph
60 string->recutils
e49951eb 61 package->recutils
2876b989 62 package-specification->name+version+output
2cd09108
NK
63 string->generations
64 string->duration
a5975ced 65 args-fold*
e49951eb 66 run-guix-command
a2011be5
LC
67 program-name
68 guix-warning-port
69 warning
e49951eb 70 guix-main))
073c34d7
LC
71
72;;; Commentary:
73;;;
74;;; User interface facilities for command-line tools.
75;;;
76;;; Code:
77
78(define %gettext-domain
ee764179 79 ;; Text domain for strings used in the tools.
073c34d7
LC
80 "guix")
81
ee764179
LC
82(define %package-text-domain
83 ;; Text domain for package synopses and descriptions.
84 "guix-packages")
85
073c34d7
LC
86(define _ (cut gettext <> %gettext-domain))
87(define N_ (cut ngettext <> <> <> %gettext-domain))
ee764179 88(define P_ (cut gettext <> %package-text-domain))
073c34d7 89
b2a886f6
LC
90(define-syntax-rule (define-diagnostic name prefix)
91 "Create a diagnostic macro (i.e., NAME), which will prepend PREFIX to all
92messages."
93 (define-syntax name
94 (lambda (x)
95 (define (augmented-format-string fmt)
96 (string-append "~:[~*~;guix ~a: ~]~a" (syntax->datum fmt)))
97
89d02b98
LC
98 (syntax-case x ()
99 ((name (underscore fmt) args (... ...))
100 (and (string? (syntax->datum #'fmt))
101 (free-identifier=? #'underscore #'_))
b2a886f6
LC
102 (with-syntax ((fmt* (augmented-format-string #'fmt))
103 (prefix (datum->syntax x prefix)))
104 #'(format (guix-warning-port) (gettext fmt*)
105 (program-name) (program-name) prefix
106 args (... ...))))
89d02b98 107 ((name (N-underscore singular plural n) args (... ...))
b2a886f6 108 (and (string? (syntax->datum #'singular))
89d02b98
LC
109 (string? (syntax->datum #'plural))
110 (free-identifier=? #'N-underscore #'N_))
b2a886f6
LC
111 (with-syntax ((s (augmented-format-string #'singular))
112 (p (augmented-format-string #'plural))
113 (prefix (datum->syntax x prefix)))
114 #'(format (guix-warning-port)
115 (ngettext s p n %gettext-domain)
116 (program-name) (program-name) prefix
117 args (... ...))))))))
118
119(define-diagnostic warning "warning: ") ; emit a warning
120
121(define-diagnostic report-error "error: ")
122(define-syntax-rule (leave args ...)
123 "Emit an error message and exit."
124 (begin
125 (report-error args ...)
126 (exit 1)))
127
128(define (install-locale)
129 "Install the current locale settings."
130 (catch 'system-error
131 (lambda _
132 (setlocale LC_ALL ""))
133 (lambda args
134 (warning (_ "failed to install locale: ~a~%")
135 (strerror (system-error-errno args))))))
136
e49951eb 137(define (initialize-guix)
633f045f 138 "Perform the usual initialization for stand-alone Guix commands."
e49951eb 139 (install-locale)
39e9f95d 140 (textdomain %gettext-domain)
e14c3929
LC
141
142 ;; Ignore SIGPIPE. If the daemon closes the connection, we prefer to be
143 ;; notified via an EPIPE later.
144 (sigaction SIGPIPE SIG_IGN)
145
e49951eb
MW
146 (setvbuf (current-output-port) _IOLBF)
147 (setvbuf (current-error-port) _IOLBF))
148
cdd5d6f9
LC
149(define* (show-version-and-exit #:optional (command (car (command-line))))
150 "Display version information for COMMAND and `(exit 0)'."
151 (simple-format #t "~a (~a) ~a~%"
152 command %guix-package-name %guix-version)
c5e0eb28 153 (display (_ "Copyright (C) 2014 the Guix authors
64a967cc
LC
154License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
155This is free software: you are free to change and redistribute it.
156There is NO WARRANTY, to the extent permitted by law.
157"))
cdd5d6f9
LC
158 (exit 0))
159
3441e164
LC
160(define (show-bug-report-information)
161 (format #t (_ "
162Report bugs to: ~a.") %guix-bug-report-address)
163 (format #t (_ "
164~a home page: <~a>") %guix-package-name %guix-home-page-url)
165 (display (_ "
166General help using GNU software: <http://www.gnu.org/gethelp/>"))
167 (newline))
168
969e678e
LC
169(define (string->number* str)
170 "Like `string->number', but error out with an error message on failure."
171 (or (string->number str)
172 (leave (_ "~a: invalid number~%") str)))
173
1d6243cf
LC
174(define (size->number str)
175 "Convert STR, a storage measurement representation such as \"1024\" or
176\"1MiB\", to a number of bytes. Raise an error if STR could not be
177interpreted."
178 (define unit-pos
179 (string-rindex str char-set:digit))
180
181 (define unit
182 (and unit-pos (substring str (+ 1 unit-pos))))
183
184 (let* ((numstr (if unit-pos
185 (substring str 0 (+ 1 unit-pos))
186 str))
187 (num (string->number numstr)))
188 (unless num
189 (leave (_ "invalid number: ~a~%") numstr))
190
191 ((compose inexact->exact round)
192 (* num
193 (match unit
4a44d7bb
LC
194 ((or "KiB" "K" "k") (expt 2 10))
195 ((or "MiB" "M") (expt 2 20))
196 ((or "GiB" "G") (expt 2 30))
197 ((or "TiB" "T") (expt 2 40))
198 ((or "PiB" "P") (expt 2 50))
199 ((or "EiB" "E") (expt 2 60))
200 ((or "ZiB" "Z") (expt 2 70))
201 ((or "YiB" "Y") (expt 2 80))
202 ("kB" (expt 10 3))
1d6243cf
LC
203 ("MB" (expt 10 6))
204 ("GB" (expt 10 9))
205 ("TB" (expt 10 12))
4a44d7bb
LC
206 ("PB" (expt 10 15))
207 ("EB" (expt 10 18))
208 ("ZB" (expt 10 21))
209 ("YB" (expt 10 24))
1d6243cf
LC
210 ("" 1)
211 (_
212 (leave (_ "unknown unit: ~a~%") unit)))))))
213
073c34d7
LC
214(define (call-with-error-handling thunk)
215 "Call THUNK within a user-friendly error handler."
216 (guard (c ((package-input-error? c)
217 (let* ((package (package-error-package c))
218 (input (package-error-invalid-input c))
219 (location (package-location package))
220 (file (location-file location))
221 (line (location-line location))
222 (column (location-column location)))
98eb8cbe 223 (leave (_ "~a:~a:~a: package `~a' has an invalid input: ~s~%")
073c34d7
LC
224 file line column
225 (package-full-name package) input)))
9b222abe
LC
226 ((package-cross-build-system-error? c)
227 (let* ((package (package-error-package c))
228 (loc (package-location package))
229 (system (package-build-system package)))
230 (leave (_ "~a: ~a: build system `~a' does not support cross builds~%")
231 (location->string loc)
232 (package-full-name package)
233 (build-system-name system))))
ef86c39f 234 ((nix-connection-error? c)
98eb8cbe 235 (leave (_ "failed to connect to `~a': ~a~%")
ef86c39f
LC
236 (nix-connection-error-file c)
237 (strerror (nix-connection-error-code c))))
073c34d7
LC
238 ((nix-protocol-error? c)
239 ;; FIXME: Server-provided error messages aren't i18n'd.
98eb8cbe 240 (leave (_ "build failed: ~a~%")
c1d52c71
LC
241 (nix-protocol-error-message c)))
242 ((message-condition? c)
243 ;; Normally '&message' error conditions have an i18n'd message.
12703d08
LC
244 (leave (_ "~a~%")
245 (gettext (condition-message c) %gettext-domain))))
e14c3929
LC
246 ;; Catch EPIPE and the likes.
247 (catch 'system-error
248 thunk
bde8c0e6
LC
249 (lambda (key proc format-string format-args . rest)
250 (leave (_ "~a: ~a~%") proc
251 (apply format #f format-string format-args))))))
073c34d7 252
56b82106
LC
253(define %guix-user-module
254 ;; Module in which user expressions are evaluated.
07254feb
LC
255 ;; Compute lazily to avoid circularity with (guix gexp).
256 (delay
257 (let ((module (make-module)))
258 (beautify-user-module! module)
259 ;; Use (guix gexp) so that one can use #~ & co.
260 (module-use! module (resolve-interface '(guix gexp)))
261 module)))
56b82106 262
ac5de156
LC
263(define (read/eval str)
264 "Read and evaluate STR, raising an error if something goes wrong."
eb0880e7
LC
265 (let ((exp (catch #t
266 (lambda ()
267 (call-with-input-string str read))
268 (lambda args
269 (leave (_ "failed to read expression ~s: ~s~%")
270 str args)))))
ac5de156
LC
271 (catch #t
272 (lambda ()
07254feb 273 (eval exp (force %guix-user-module)))
ac5de156
LC
274 (lambda args
275 (leave (_ "failed to evaluate expression `~a': ~s~%")
276 exp args)))))
277
278(define (read/eval-package-expression str)
279 "Read and evaluate STR and return the package it refers to, or exit an
280error."
281 (match (read/eval str)
282 ((? package? p) p)
283 (_
284 (leave (_ "expression ~s does not evaluate to a package~%")
285 str))))
eb0880e7 286
dd36b51b
LC
287(define* (show-what-to-build store drv
288 #:key dry-run? (use-substitutes? #t))
9bb2b96a 289 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a 290derivations listed in DRV. Return #t if there's something to build, #f
dd36b51b
LC
291otherwise. When USE-SUBSTITUTES?, check and report what is prerequisites are
292available for download."
52ddf2ae
LC
293 (define (built-or-substitutable? drv)
294 (let ((out (derivation->output-path drv)))
295 ;; If DRV has zero outputs, OUT is #f.
296 (or (not out)
297 (or (valid-path? store out)
298 (and use-substitutes?
299 (has-substitutes? store out))))))
300
dd36b51b 301 (let*-values (((build download)
59688fc4
LC
302 (fold2 (lambda (drv build download)
303 (let-values (((b d)
304 (derivation-prerequisites-to-build
305 store drv
306 #:use-substitutes?
307 use-substitutes?)))
308 (values (append b build)
309 (append d download))))
dd36b51b
LC
310 '() '()
311 drv))
312 ((build) ; add the DRV themselves
313 (delete-duplicates
59688fc4 314 (append (map derivation-file-name
52ddf2ae 315 (remove built-or-substitutable? drv))
dd36b51b
LC
316 (map derivation-input-path build))))
317 ((download) ; add the references of DOWNLOAD
1a8b7834
LC
318 (if use-substitutes?
319 (delete-duplicates
320 (append download
321 (remove (cut valid-path? store <>)
322 (append-map
323 substitutable-references
324 (substitutable-path-info store
325 download)))))
326 download)))
00554b2a 327 ;; TODO: Show the installed size of DOWNLOAD.
9bb2b96a 328 (if dry-run?
dd36b51b
LC
329 (begin
330 (format (current-error-port)
83e61a73
LC
331 (N_ "~:[The following derivation would be built:~%~{ ~a~%~}~;~]"
332 "~:[The following derivations would be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
333 (length build))
334 (null? build) build)
335 (format (current-error-port)
83e61a73
LC
336 (N_ "~:[The following file would be downloaded:~%~{ ~a~%~}~;~]"
337 "~:[The following files would be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
338 (length download))
339 (null? download) download))
340 (begin
341 (format (current-error-port)
83e61a73
LC
342 (N_ "~:[The following derivation will be built:~%~{ ~a~%~}~;~]"
343 "~:[The following derivations will be built:~%~{ ~a~%~}~;~]"
dd36b51b
LC
344 (length build))
345 (null? build) build)
346 (format (current-error-port)
83e61a73
LC
347 (N_ "~:[The following file will be downloaded:~%~{ ~a~%~}~;~]"
348 "~:[The following files will be downloaded:~%~{ ~a~%~}~;~]"
dd36b51b
LC
349 (length download))
350 (null? download) download)))
351 (pair? build)))
9bb2b96a 352
5d7a8584
AK
353(define (right-arrow port)
354 "Return either a string containing the 'RIGHT ARROW' character, or an ASCII
355replacement if PORT is not Unicode-capable."
356 (with-fluids ((%default-port-encoding (port-encoding port)))
357 (let ((arrow "→"))
358 (catch 'encoding-error
359 (lambda ()
360 (call-with-output-string
361 (lambda (port)
362 (set-port-conversion-strategy! port 'error)
363 (display arrow port))))
364 (lambda (key . args)
365 "->")))))
366
367(define* (show-manifest-transaction store manifest transaction
368 #:key dry-run?)
369 "Display what will/would be installed/removed from MANIFEST by TRANSACTION."
370 (define (package-strings name version output item)
371 (map (lambda (name version output item)
372 (format #f " ~a~:[:~a~;~*~]\t~a\t~a"
373 name
374 (equal? output "out") output version
375 (if (package? item)
376 (package-output store item output)
377 item)))
378 name version output item))
379
380 (define → ;an arrow that can be represented on stderr
381 (right-arrow (current-error-port)))
382
383 (define (upgrade-string name old-version new-version output item)
384 (format #f " ~a~:[:~a~;~*~]\t~a ~a ~a\t~a"
385 name (equal? output "out") output
386 old-version → new-version
387 (if (package? item)
388 (package-output store item output)
389 item)))
390
391 (let-values (((remove install upgrade)
392 (manifest-transaction-effects manifest transaction)))
393 (match remove
394 ((($ <manifest-entry> name version output item) ..1)
395 (let ((len (length name))
396 (remove (package-strings name version output item)))
397 (if dry-run?
398 (format (current-error-port)
399 (N_ "The following package would be removed:~%~{~a~%~}~%"
400 "The following packages would be removed:~%~{~a~%~}~%"
401 len)
402 remove)
403 (format (current-error-port)
404 (N_ "The following package will be removed:~%~{~a~%~}~%"
405 "The following packages will be removed:~%~{~a~%~}~%"
406 len)
407 remove))))
408 (_ #f))
409 (match upgrade
410 (((($ <manifest-entry> name old-version)
411 . ($ <manifest-entry> _ new-version output item)) ..1)
412 (let ((len (length name))
413 (upgrade (map upgrade-string
414 name old-version new-version output item)))
415 (if dry-run?
416 (format (current-error-port)
417 (N_ "The following package would be upgraded:~%~{~a~%~}~%"
418 "The following packages would be upgraded:~%~{~a~%~}~%"
419 len)
420 upgrade)
421 (format (current-error-port)
422 (N_ "The following package will be upgraded:~%~{~a~%~}~%"
423 "The following packages will be upgraded:~%~{~a~%~}~%"
424 len)
425 upgrade))))
426 (_ #f))
427 (match install
428 ((($ <manifest-entry> name version output item _) ..1)
429 (let ((len (length name))
430 (install (package-strings name version output item)))
431 (if dry-run?
432 (format (current-error-port)
433 (N_ "The following package would be installed:~%~{~a~%~}~%"
434 "The following packages would be installed:~%~{~a~%~}~%"
435 len)
436 install)
437 (format (current-error-port)
438 (N_ "The following package will be installed:~%~{~a~%~}~%"
439 "The following packages will be installed:~%~{~a~%~}~%"
440 len)
441 install))))
442 (_ #f))))
443
073c34d7
LC
444(define-syntax with-error-handling
445 (syntax-rules ()
446 "Run BODY within a user-friendly error condition handler."
447 ((_ body ...)
448 (call-with-error-handling
449 (lambda ()
450 body ...)))))
451
64fc89b6
LC
452(define (location->string loc)
453 "Return a human-friendly, GNU-standard representation of LOC."
454 (match loc
455 (#f (_ "<unknown location>"))
456 (($ <location> file line column)
457 (format #f "~a:~a:~a" file line column))))
458
c61b026e
LC
459(define (switch-symlinks link target)
460 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
461both when LINK already exists and when it does not."
462 (let ((pivot (string-append link ".new")))
463 (symlink target pivot)
464 (rename-file pivot link)))
465
f651b477
LC
466(define (config-directory)
467 "Return the name of the configuration directory, after making sure that it
468exists. Honor the XDG specs,
469<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
470 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
471 (and=> (getenv "HOME")
472 (cut string-append <> "/.config")))
473 (cut string-append <> "/guix"))))
474 (catch 'system-error
475 (lambda ()
59758816 476 (mkdir-p dir)
f651b477
LC
477 dir)
478 (lambda args
59758816
LC
479 (let ((err (system-error-errno args)))
480 ;; ERR is necessarily different from EEXIST.
481 (leave (_ "failed to create configuration directory `~a': ~a~%")
482 dir (strerror err)))))))
f651b477 483
299112d3
LC
484(define* (fill-paragraph str width #:optional (column 0))
485 "Fill STR such that each line contains at most WIDTH characters, assuming
486that the first character is at COLUMN.
487
488When STR contains a single line break surrounded by other characters, it is
489converted to a space; sequences of more than one line break are preserved."
490 (define (maybe-break chr result)
491 (match result
492 ((column newlines chars)
493 (case chr
494 ((#\newline)
495 `(,column ,(+ 1 newlines) ,chars))
496 (else
3a09e1d2
CS
497 (let* ((spaces (if (and (pair? chars) (eqv? (car chars) #\.)) 2 1))
498 (chars (case newlines
499 ((0) chars)
500 ((1)
501 (append (make-list spaces #\space) chars))
502 (else
503 (append (make-list newlines #\newline) chars))))
504 (column (case newlines
505 ((0) column)
506 ((1) (+ spaces column))
507 (else 0))))
299112d3
LC
508 (let ((chars (cons chr chars))
509 (column (+ 1 column)))
510 (if (> column width)
511 (let*-values (((before after)
512 (break (cut eqv? #\space <>) chars))
513 ((len)
514 (length before)))
515 (if (<= len width)
516 `(,len
517 0
518 ,(if (null? after)
519 before
3a09e1d2
CS
520 (append before
521 (cons #\newline
522 (drop-while (cut eqv? #\space <>)
523 after)))))
299112d3
LC
524 `(,column 0 ,chars))) ; unbreakable
525 `(,column 0 ,chars)))))))))
526
527 (match (string-fold maybe-break
528 `(,column 0 ())
529 str)
530 ((_ _ chars)
531 (list->string (reverse chars)))))
532
2876b989
LC
533\f
534;;;
535;;; Packages.
536;;;
537
299112d3
LC
538(define (string->recutils str)
539 "Return a version of STR where newlines have been replaced by newlines
540followed by \"+ \", which makes for a valid multi-line field value in the
541`recutils' syntax."
542 (list->string
543 (string-fold-right (lambda (chr result)
544 (if (eqv? chr #\newline)
545 (cons* chr #\+ #\space result)
546 (cons chr result)))
547 '()
548 str)))
549
550(define* (package->recutils p port
551 #:optional (width (or (and=> (getenv "WIDTH")
552 string->number)
553 80)))
554 "Write to PORT a `recutils' record of package P, arranging to fit within
555WIDTH columns."
556 (define (description->recutils str)
ee764179 557 (let ((str (P_ str)))
299112d3
LC
558 (string->recutils
559 (fill-paragraph str width
560 (string-length "description: ")))))
561
20ffce82
LC
562 (define (dependencies->recutils packages)
563 (let ((list (string-join (map package-full-name
564 (sort packages package<?)) " ")))
565 (string->recutils
566 (fill-paragraph list width
567 (string-length "dependencies: ")))))
568
9c0fc279
CR
569 (define (package<? p1 p2)
570 (string<? (package-full-name p1) (package-full-name p2)))
571
299112d3
LC
572 ;; Note: Don't i18n field names so that people can post-process it.
573 (format port "name: ~a~%" (package-name p))
574 (format port "version: ~a~%" (package-version p))
9c0fc279
CR
575 (format port "dependencies: ~a~%"
576 (match (package-direct-inputs p)
5e6feee6
EB
577 (((labels inputs . _) ...)
578 (dependencies->recutils (filter package? inputs)))))
299112d3
LC
579 (format port "location: ~a~%"
580 (or (and=> (package-location p) location->string)
581 (_ "unknown")))
8fa3e6b3
LC
582
583 ;; Note: Starting from version 1.6 or recutils, hyphens are not allowed in
584 ;; field identifiers.
585 (format port "homepage: ~a~%" (package-home-page p))
586
299112d3
LC
587 (format port "license: ~a~%"
588 (match (package-license p)
589 (((? license? licenses) ...)
590 (string-join (map license-name licenses)
591 ", "))
592 ((? license? license)
593 (license-name license))
594 (x
595 (_ "unknown"))))
596 (format port "synopsis: ~a~%"
597 (string-map (match-lambda
598 (#\newline #\space)
599 (chr chr))
ee764179 600 (or (and=> (package-synopsis p) P_)
299112d3
LC
601 "")))
602 (format port "description: ~a~%"
603 (and=> (package-description p) description->recutils))
604 (newline port))
605
2cd09108
NK
606(define (string->generations str)
607 "Return the list of generations matching a pattern in STR. This function
608accepts the following patterns: \"1\", \"1,2,3\", \"1..9\", \"1..\", \"..9\"."
609 (define (maybe-integer)
610 (let ((x (string->number str)))
611 (and (integer? x)
612 x)))
613
614 (define (maybe-comma-separated-integers)
615 (let ((lst (delete-duplicates
616 (map string->number
617 (string-split str #\,)))))
618 (and (every integer? lst)
619 lst)))
620
621 (cond ((maybe-integer)
622 =>
623 list)
624 ((maybe-comma-separated-integers)
625 =>
626 identity)
627 ((string-match "^([0-9]+)\\.\\.([0-9]+)$" str)
628 =>
629 (lambda (match)
630 (let ((s (string->number (match:substring match 1)))
631 (e (string->number (match:substring match 2))))
632 (and (every integer? (list s e))
633 (<= s e)
634 (iota (1+ (- e s)) s)))))
635 ((string-match "^([0-9]+)\\.\\.$" str)
636 =>
637 (lambda (match)
638 (let ((s (string->number (match:substring match 1))))
639 (and (integer? s)
640 `(>= ,s)))))
641 ((string-match "^\\.\\.([0-9]+)$" str)
642 =>
643 (lambda (match)
644 (let ((e (string->number (match:substring match 1))))
645 (and (integer? e)
646 `(<= ,e)))))
647 (else #f)))
648
649(define (string->duration str)
650 "Return the duration matching a pattern in STR. This function accepts the
651following patterns: \"1d\", \"1w\", \"1m\"."
652 (define (hours->duration hours match)
653 (make-time time-duration 0
654 (* 3600 hours (string->number (match:substring match 1)))))
655
656 (cond ((string-match "^([0-9]+)d$" str)
657 =>
658 (lambda (match)
659 (hours->duration 24 match)))
660 ((string-match "^([0-9]+)w$" str)
661 =>
662 (lambda (match)
663 (hours->duration (* 24 7) match)))
664 ((string-match "^([0-9]+)m$" str)
665 =>
666 (lambda (match)
667 (hours->duration (* 24 30) match)))
668 (else #f)))
669
2876b989
LC
670(define* (package-specification->name+version+output spec
671 #:optional (output "out"))
672 "Parse package specification SPEC and return three value: the specified
673package name, version number (or #f), and output name (or OUTPUT). SPEC may
674optionally contain a version number and an output name, as in these examples:
675
676 guile
677 guile-2.0.9
678 guile:debug
679 guile-2.0.9:debug
680"
681 (let*-values (((name sub-drv)
682 (match (string-rindex spec #\:)
683 (#f (values spec output))
684 (colon (values (substring spec 0 colon)
685 (substring spec (+ 1 colon))))))
686 ((name version)
687 (package-name->name+version name)))
688 (values name version sub-drv)))
689
690\f
691;;;
692;;; Command-line option processing.
693;;;
694
a5975ced
LC
695(define (args-fold* options unrecognized-option-proc operand-proc . seeds)
696 "A wrapper on top of `args-fold' that does proper user-facing error
697reporting."
698 (catch 'misc-error
699 (lambda ()
700 (apply args-fold options unrecognized-option-proc
701 operand-proc seeds))
702 (lambda (key proc msg args . rest)
703 ;; XXX: MSG is not i18n'd.
704 (leave (_ "invalid argument: ~a~%")
705 (apply format #f msg args)))))
706
e49951eb 707(define (show-guix-usage)
e49951eb 708 (format (current-error-port)
25c93676
LC
709 (_ "Try `guix --help' for more information.~%"))
710 (exit 1))
e49951eb 711
e31ff8b8
LC
712(define (command-files)
713 "Return the list of source files that define Guix sub-commands."
714 (define directory
715 (and=> (search-path %load-path "guix.scm")
716 (compose (cut string-append <> "/guix/scripts")
717 dirname)))
718
2b8cf44f
LC
719 (define dot-scm?
720 (cut string-suffix? ".scm" <>))
721
722 ;; In Guile 2.0.5 `scandir' would return "." and ".." regardless even though
723 ;; they don't match `dot-scm?'. Work around it by doing additional
724 ;; filtering.
e31ff8b8 725 (if directory
2b8cf44f 726 (filter dot-scm? (scandir directory dot-scm?))
e31ff8b8
LC
727 '()))
728
729(define (commands)
730 "Return the list of Guix command names."
731 (map (compose (cut string-drop-right <> 4)
732 basename)
733 (command-files)))
734
735(define (show-guix-help)
59f734f3 736 (define (internal? command)
49e6291a 737 (member command '("substitute-binary" "authenticate" "offload")))
59f734f3 738
e31ff8b8
LC
739 (format #t (_ "Usage: guix COMMAND ARGS...
740Run COMMAND with ARGS.\n"))
741 (newline)
742 (format #t (_ "COMMAND must be one of the sub-commands listed below:\n"))
743 (newline)
744 ;; TODO: Display a synopsis of each command.
59f734f3
LC
745 (format #t "~{ ~a~%~}" (sort (remove internal? (commands))
746 string<?))
e31ff8b8
LC
747 (show-bug-report-information))
748
a2011be5
LC
749(define program-name
750 ;; Name of the command-line program currently executing, or #f.
751 (make-parameter #f))
752
ec5d0a85
LC
753(define (run-guix-command command . args)
754 "Run COMMAND with the given ARGS. Report an error when COMMAND is not
755found."
756 (define module
757 (catch 'misc-error
758 (lambda ()
759 (resolve-interface `(guix scripts ,command)))
760 (lambda -
25c93676
LC
761 (format (current-error-port)
762 (_ "guix: ~a: command not found~%") command)
763 (show-guix-usage))))
ec5d0a85
LC
764
765 (let ((command-main (module-ref module
766 (symbol-append 'guix- command))))
767 (parameterize ((program-name command))
768 (apply command-main args))))
769
a2011be5
LC
770(define guix-warning-port
771 (make-parameter (current-warning-port)))
772
e49951eb
MW
773(define (guix-main arg0 . args)
774 (initialize-guix)
775 (let ()
776 (define (option? str) (string-prefix? "-" str))
777 (match args
25c93676
LC
778 (()
779 (format (current-error-port)
780 (_ "guix: missing command name~%"))
781 (show-guix-usage))
e12b3eb9 782 ((or ("-h") ("--help"))
25c93676
LC
783 (show-guix-help))
784 (("--version")
785 (show-version-and-exit "guix"))
786 (((? option? o) args ...)
787 (format (current-error-port)
788 (_ "guix: unrecognized option '~a'~%") o)
789 (show-guix-usage))
e49951eb 790 ((command args ...)
ec5d0a85
LC
791 (apply run-guix-command
792 (string->symbol command)
793 args)))))
e49951eb 794
073c34d7 795;;; ui.scm ends here