package: allow users to upgrade the whole system by not providing a regexp.
[jackhill/guix/guix.git] / guix / ui.scm
CommitLineData
233e7676
LC
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org>
e49951eb 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
073c34d7 4;;;
233e7676 5;;; This file is part of GNU Guix.
073c34d7 6;;;
233e7676 7;;; GNU Guix is free software; you can redistribute it and/or modify it
073c34d7
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;;;
233e7676 12;;; GNU Guix is distributed in the hope that it will be useful, but
073c34d7
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
233e7676 18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
073c34d7
LC
19
20(define-module (guix ui)
21 #:use-module (guix utils)
22 #:use-module (guix store)
cdd5d6f9 23 #:use-module (guix config)
073c34d7 24 #:use-module (guix packages)
9bb2b96a 25 #:use-module (guix derivations)
299112d3
LC
26 #:use-module ((guix licenses) #:select (license? license-name))
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-11)
073c34d7
LC
29 #:use-module (srfi srfi-26)
30 #:use-module (srfi srfi-34)
64fc89b6 31 #:use-module (ice-9 match)
9bb2b96a 32 #:use-module (ice-9 format)
073c34d7
LC
33 #:export (_
34 N_
35 leave
cdd5d6f9 36 show-version-and-exit
3441e164 37 show-bug-report-information
9bb2b96a 38 show-what-to-build
073c34d7 39 call-with-error-handling
64fc89b6 40 with-error-handling
eb0880e7 41 read/eval-package-expression
299112d3 42 location->string
c61b026e
LC
43 call-with-temporary-output-file
44 switch-symlinks
f651b477 45 config-directory
299112d3
LC
46 fill-paragraph
47 string->recutils
e49951eb
MW
48 package->recutils
49 run-guix-command
a2011be5
LC
50 program-name
51 guix-warning-port
52 warning
e49951eb 53 guix-main))
073c34d7
LC
54
55;;; Commentary:
56;;;
57;;; User interface facilities for command-line tools.
58;;;
59;;; Code:
60
61(define %gettext-domain
62 "guix")
63
64(define _ (cut gettext <> %gettext-domain))
65(define N_ (cut ngettext <> <> <> %gettext-domain))
66
473b03b3
LC
67(define (install-locale)
68 "Install the current locale settings."
69 (catch 'system-error
70 (lambda _
71 (setlocale LC_ALL ""))
72 (lambda args
73 (format (current-error-port)
74 (_ "warning: failed to install locale: ~a~%")
75 (strerror (system-error-errno args))))))
76
e49951eb 77(define (initialize-guix)
633f045f 78 "Perform the usual initialization for stand-alone Guix commands."
e49951eb
MW
79 (install-locale)
80 (textdomain "guix")
81 (setvbuf (current-output-port) _IOLBF)
82 (setvbuf (current-error-port) _IOLBF))
83
073c34d7
LC
84(define-syntax-rule (leave fmt args ...)
85 "Format FMT and ARGS to the error port and exit."
86 (begin
87 (format (current-error-port) fmt args ...)
88 (exit 1)))
89
cdd5d6f9
LC
90(define* (show-version-and-exit #:optional (command (car (command-line))))
91 "Display version information for COMMAND and `(exit 0)'."
92 (simple-format #t "~a (~a) ~a~%"
93 command %guix-package-name %guix-version)
94 (exit 0))
95
3441e164
LC
96(define (show-bug-report-information)
97 (format #t (_ "
98Report bugs to: ~a.") %guix-bug-report-address)
99 (format #t (_ "
100~a home page: <~a>") %guix-package-name %guix-home-page-url)
101 (display (_ "
102General help using GNU software: <http://www.gnu.org/gethelp/>"))
103 (newline))
104
073c34d7
LC
105(define (call-with-error-handling thunk)
106 "Call THUNK within a user-friendly error handler."
107 (guard (c ((package-input-error? c)
108 (let* ((package (package-error-package c))
109 (input (package-error-invalid-input c))
110 (location (package-location package))
111 (file (location-file location))
112 (line (location-line location))
113 (column (location-column location)))
114 (leave (_ "~a:~a:~a: error: package `~a' has an invalid input: ~s~%")
115 file line column
116 (package-full-name package) input)))
ef86c39f
LC
117 ((nix-connection-error? c)
118 (leave (_ "error: failed to connect to `~a': ~a~%")
119 (nix-connection-error-file c)
120 (strerror (nix-connection-error-code c))))
073c34d7
LC
121 ((nix-protocol-error? c)
122 ;; FIXME: Server-provided error messages aren't i18n'd.
123 (leave (_ "error: build failed: ~a~%")
124 (nix-protocol-error-message c))))
125 (thunk)))
126
eb0880e7
LC
127(define (read/eval-package-expression str)
128 "Read and evaluate STR and return the package it refers to, or exit an
129error."
130 (let ((exp (catch #t
131 (lambda ()
132 (call-with-input-string str read))
133 (lambda args
134 (leave (_ "failed to read expression ~s: ~s~%")
135 str args)))))
136 (let ((p (catch #t
137 (lambda ()
138 (eval exp the-scm-module))
139 (lambda args
140 (leave (_ "failed to evaluate expression `~a': ~s~%")
141 exp args)))))
142 (if (package? p)
143 p
144 (leave (_ "expression `~s' does not evaluate to a package~%")
145 exp)))))
146
9bb2b96a
LC
147(define* (show-what-to-build store drv #:optional dry-run?)
148 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a
LC
149derivations listed in DRV. Return #t if there's something to build, #f
150otherwise."
9bb2b96a
LC
151 (let* ((req (append-map (lambda (drv-path)
152 (let ((d (call-with-input-file drv-path
153 read-derivation)))
154 (derivation-prerequisites-to-build
155 store d)))
156 drv))
157 (req* (delete-duplicates
158 (append (remove (compose (cute valid-path? store <>)
159 derivation-path->output-path)
160 drv)
161 (map derivation-input-path req)))))
162 (if dry-run?
163 (format (current-error-port)
164 (N_ "~:[the following derivation would be built:~%~{ ~a~%~}~;~]"
165 "~:[the following derivations would be built:~%~{ ~a~%~}~;~]"
166 (length req*))
167 (null? req*) req*)
168 (format (current-error-port)
169 (N_ "~:[the following derivation will be built:~%~{ ~a~%~}~;~]"
170 "~:[the following derivations will be built:~%~{ ~a~%~}~;~]"
171 (length req*))
4d60610a
LC
172 (null? req*) req*))
173 (pair? req*)))
9bb2b96a 174
073c34d7
LC
175(define-syntax with-error-handling
176 (syntax-rules ()
177 "Run BODY within a user-friendly error condition handler."
178 ((_ body ...)
179 (call-with-error-handling
180 (lambda ()
181 body ...)))))
182
64fc89b6
LC
183(define (location->string loc)
184 "Return a human-friendly, GNU-standard representation of LOC."
185 (match loc
186 (#f (_ "<unknown location>"))
187 (($ <location> file line column)
188 (format #f "~a:~a:~a" file line column))))
189
c61b026e
LC
190(define (call-with-temporary-output-file proc)
191 "Call PROC with a name of a temporary file and open output port to that
192file; close the file and delete it when leaving the dynamic extent of this
193call."
194 (let* ((template (string-copy "guix-file.XXXXXX"))
195 (out (mkstemp! template)))
196 (dynamic-wind
197 (lambda ()
198 #t)
199 (lambda ()
200 (proc template out))
201 (lambda ()
202 (false-if-exception (close out))
203 (false-if-exception (delete-file template))))))
204
205(define (switch-symlinks link target)
206 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
207both when LINK already exists and when it does not."
208 (let ((pivot (string-append link ".new")))
209 (symlink target pivot)
210 (rename-file pivot link)))
211
f651b477
LC
212(define (config-directory)
213 "Return the name of the configuration directory, after making sure that it
214exists. Honor the XDG specs,
215<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
216 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
217 (and=> (getenv "HOME")
218 (cut string-append <> "/.config")))
219 (cut string-append <> "/guix"))))
220 (catch 'system-error
221 (lambda ()
222 (mkdir dir)
223 dir)
224 (lambda args
225 (match (system-error-errno args)
226 ((or EEXIST 0)
227 dir)
228 (err
229 (leave (_ "failed to create configuration directory `~a': ~a~%")
230 dir (strerror err))))))))
231
299112d3
LC
232(define* (fill-paragraph str width #:optional (column 0))
233 "Fill STR such that each line contains at most WIDTH characters, assuming
234that the first character is at COLUMN.
235
236When STR contains a single line break surrounded by other characters, it is
237converted to a space; sequences of more than one line break are preserved."
238 (define (maybe-break chr result)
239 (match result
240 ((column newlines chars)
241 (case chr
242 ((#\newline)
243 `(,column ,(+ 1 newlines) ,chars))
244 (else
245 (let ((chars (case newlines
246 ((0) chars)
247 ((1) (cons #\space chars))
248 (else
249 (append (make-list newlines #\newline) chars))))
250 (column (case newlines
251 ((0) column)
252 ((1) (+ 1 column))
253 (else 0))))
254 (let ((chars (cons chr chars))
255 (column (+ 1 column)))
256 (if (> column width)
257 (let*-values (((before after)
258 (break (cut eqv? #\space <>) chars))
259 ((len)
260 (length before)))
261 (if (<= len width)
262 `(,len
263 0
264 ,(if (null? after)
265 before
266 (append before (cons #\newline (cdr after)))))
267 `(,column 0 ,chars))) ; unbreakable
268 `(,column 0 ,chars)))))))))
269
270 (match (string-fold maybe-break
271 `(,column 0 ())
272 str)
273 ((_ _ chars)
274 (list->string (reverse chars)))))
275
276(define (string->recutils str)
277 "Return a version of STR where newlines have been replaced by newlines
278followed by \"+ \", which makes for a valid multi-line field value in the
279`recutils' syntax."
280 (list->string
281 (string-fold-right (lambda (chr result)
282 (if (eqv? chr #\newline)
283 (cons* chr #\+ #\space result)
284 (cons chr result)))
285 '()
286 str)))
287
288(define* (package->recutils p port
289 #:optional (width (or (and=> (getenv "WIDTH")
290 string->number)
291 80)))
292 "Write to PORT a `recutils' record of package P, arranging to fit within
293WIDTH columns."
294 (define (description->recutils str)
295 (let ((str (_ str)))
296 (string->recutils
297 (fill-paragraph str width
298 (string-length "description: ")))))
299
300 ;; Note: Don't i18n field names so that people can post-process it.
301 (format port "name: ~a~%" (package-name p))
302 (format port "version: ~a~%" (package-version p))
303 (format port "location: ~a~%"
304 (or (and=> (package-location p) location->string)
305 (_ "unknown")))
306 (format port "home-page: ~a~%" (package-home-page p))
307 (format port "license: ~a~%"
308 (match (package-license p)
309 (((? license? licenses) ...)
310 (string-join (map license-name licenses)
311 ", "))
312 ((? license? license)
313 (license-name license))
314 (x
315 (_ "unknown"))))
316 (format port "synopsis: ~a~%"
317 (string-map (match-lambda
318 (#\newline #\space)
319 (chr chr))
320 (or (and=> (package-synopsis p) _)
321 "")))
322 (format port "description: ~a~%"
323 (and=> (package-description p) description->recutils))
324 (newline port))
325
e49951eb
MW
326(define (show-guix-usage)
327 ;; TODO: Dynamically generate a summary of available commands.
328 (format (current-error-port)
329 (_ "Usage: guix COMMAND ARGS...~%")))
330
331(define (run-guix-command command . args)
332 ;; TODO: Gracefully report errors
333 (let* ((module (resolve-interface `(guix scripts ,command)))
334 (command-main (module-ref module
335 (symbol-append 'guix- command))))
336 (apply command-main args)))
337
a2011be5
LC
338(define program-name
339 ;; Name of the command-line program currently executing, or #f.
340 (make-parameter #f))
341
342(define guix-warning-port
343 (make-parameter (current-warning-port)))
344
345(define-syntax warning
346 (lambda (s)
347 "Emit a warming. The macro assumes that `_' is bound to `gettext'."
348 ;; All this just to preserve `-Wformat' warnings. Too much?
349
350 (define (augmented-format-string fmt)
351 (string-append "~:[~;guix ~a: ~]~a" (syntax->datum fmt)))
352
353 (define prefix
354 #'(_ "warning: "))
355
356 (syntax-case s (N_ _) ; these are literals, yeah...
357 ((warning (_ fmt) args ...)
358 (string? (syntax->datum #'fmt))
359 (with-syntax ((fmt* (augmented-format-string #'fmt))
360 (prefix prefix))
361 #'(format (guix-warning-port) (gettext fmt*)
362 (program-name) (program-name) prefix
363 args ...)))
364 ((warning (N_ singular plural n) args ...)
365 (and (string? (syntax->datum #'singular))
366 (string? (syntax->datum #'plural)))
367 (with-syntax ((s (augmented-format-string #'singular))
368 (p (augmented-format-string #'plural))
369 (b prefix))
370 #'(format (guix-warning-port)
371 (ngettext s p n %gettext-domain)
372 (program-name) (program-name) b
373 args ...))))))
374
e49951eb
MW
375(define (guix-main arg0 . args)
376 (initialize-guix)
377 (let ()
378 (define (option? str) (string-prefix? "-" str))
379 (match args
380 (() (show-guix-usage) (exit 1))
381 (("--help") (show-guix-usage))
382 (("--version") (show-version-and-exit "guix"))
a2011be5 383 (((? option?) args ...) (show-guix-usage) (exit 1))
e49951eb 384 ((command args ...)
a2011be5
LC
385 (parameterize ((program-name command))
386 (apply run-guix-command
387 (string->symbol command)
388 args))))))
e49951eb 389
073c34d7 390;;; ui.scm ends here