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