guix package: Add `--install-from-expression'.
[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
299112d3 41 location->string
c61b026e
LC
42 call-with-temporary-output-file
43 switch-symlinks
f651b477 44 config-directory
299112d3
LC
45 fill-paragraph
46 string->recutils
e49951eb
MW
47 package->recutils
48 run-guix-command
49 guix-main))
073c34d7
LC
50
51;;; Commentary:
52;;;
53;;; User interface facilities for command-line tools.
54;;;
55;;; Code:
56
57(define %gettext-domain
58 "guix")
59
60(define _ (cut gettext <> %gettext-domain))
61(define N_ (cut ngettext <> <> <> %gettext-domain))
62
473b03b3
LC
63(define (install-locale)
64 "Install the current locale settings."
65 (catch 'system-error
66 (lambda _
67 (setlocale LC_ALL ""))
68 (lambda args
69 (format (current-error-port)
70 (_ "warning: failed to install locale: ~a~%")
71 (strerror (system-error-errno args))))))
72
e49951eb 73(define (initialize-guix)
633f045f 74 "Perform the usual initialization for stand-alone Guix commands."
e49951eb
MW
75 (install-locale)
76 (textdomain "guix")
77 (setvbuf (current-output-port) _IOLBF)
78 (setvbuf (current-error-port) _IOLBF))
79
073c34d7
LC
80(define-syntax-rule (leave fmt args ...)
81 "Format FMT and ARGS to the error port and exit."
82 (begin
83 (format (current-error-port) fmt args ...)
84 (exit 1)))
85
cdd5d6f9
LC
86(define* (show-version-and-exit #:optional (command (car (command-line))))
87 "Display version information for COMMAND and `(exit 0)'."
88 (simple-format #t "~a (~a) ~a~%"
89 command %guix-package-name %guix-version)
90 (exit 0))
91
3441e164
LC
92(define (show-bug-report-information)
93 (format #t (_ "
94Report bugs to: ~a.") %guix-bug-report-address)
95 (format #t (_ "
96~a home page: <~a>") %guix-package-name %guix-home-page-url)
97 (display (_ "
98General help using GNU software: <http://www.gnu.org/gethelp/>"))
99 (newline))
100
073c34d7
LC
101(define (call-with-error-handling thunk)
102 "Call THUNK within a user-friendly error handler."
103 (guard (c ((package-input-error? c)
104 (let* ((package (package-error-package c))
105 (input (package-error-invalid-input c))
106 (location (package-location package))
107 (file (location-file location))
108 (line (location-line location))
109 (column (location-column location)))
110 (leave (_ "~a:~a:~a: error: package `~a' has an invalid input: ~s~%")
111 file line column
112 (package-full-name package) input)))
113 ((nix-protocol-error? c)
114 ;; FIXME: Server-provided error messages aren't i18n'd.
115 (leave (_ "error: build failed: ~a~%")
116 (nix-protocol-error-message c))))
117 (thunk)))
118
9bb2b96a
LC
119(define* (show-what-to-build store drv #:optional dry-run?)
120 "Show what will or would (depending on DRY-RUN?) be built in realizing the
4d60610a
LC
121derivations listed in DRV. Return #t if there's something to build, #f
122otherwise."
9bb2b96a
LC
123 (let* ((req (append-map (lambda (drv-path)
124 (let ((d (call-with-input-file drv-path
125 read-derivation)))
126 (derivation-prerequisites-to-build
127 store d)))
128 drv))
129 (req* (delete-duplicates
130 (append (remove (compose (cute valid-path? store <>)
131 derivation-path->output-path)
132 drv)
133 (map derivation-input-path req)))))
134 (if dry-run?
135 (format (current-error-port)
136 (N_ "~:[the following derivation would be built:~%~{ ~a~%~}~;~]"
137 "~:[the following derivations would be built:~%~{ ~a~%~}~;~]"
138 (length req*))
139 (null? req*) req*)
140 (format (current-error-port)
141 (N_ "~:[the following derivation will be built:~%~{ ~a~%~}~;~]"
142 "~:[the following derivations will be built:~%~{ ~a~%~}~;~]"
143 (length req*))
4d60610a
LC
144 (null? req*) req*))
145 (pair? req*)))
9bb2b96a 146
073c34d7
LC
147(define-syntax with-error-handling
148 (syntax-rules ()
149 "Run BODY within a user-friendly error condition handler."
150 ((_ body ...)
151 (call-with-error-handling
152 (lambda ()
153 body ...)))))
154
64fc89b6
LC
155(define (location->string loc)
156 "Return a human-friendly, GNU-standard representation of LOC."
157 (match loc
158 (#f (_ "<unknown location>"))
159 (($ <location> file line column)
160 (format #f "~a:~a:~a" file line column))))
161
c61b026e
LC
162(define (call-with-temporary-output-file proc)
163 "Call PROC with a name of a temporary file and open output port to that
164file; close the file and delete it when leaving the dynamic extent of this
165call."
166 (let* ((template (string-copy "guix-file.XXXXXX"))
167 (out (mkstemp! template)))
168 (dynamic-wind
169 (lambda ()
170 #t)
171 (lambda ()
172 (proc template out))
173 (lambda ()
174 (false-if-exception (close out))
175 (false-if-exception (delete-file template))))))
176
177(define (switch-symlinks link target)
178 "Atomically switch LINK, a symbolic link, to point to TARGET. Works
179both when LINK already exists and when it does not."
180 (let ((pivot (string-append link ".new")))
181 (symlink target pivot)
182 (rename-file pivot link)))
183
f651b477
LC
184(define (config-directory)
185 "Return the name of the configuration directory, after making sure that it
186exists. Honor the XDG specs,
187<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>."
188 (let ((dir (and=> (or (getenv "XDG_CONFIG_HOME")
189 (and=> (getenv "HOME")
190 (cut string-append <> "/.config")))
191 (cut string-append <> "/guix"))))
192 (catch 'system-error
193 (lambda ()
194 (mkdir dir)
195 dir)
196 (lambda args
197 (match (system-error-errno args)
198 ((or EEXIST 0)
199 dir)
200 (err
201 (leave (_ "failed to create configuration directory `~a': ~a~%")
202 dir (strerror err))))))))
203
299112d3
LC
204(define* (fill-paragraph str width #:optional (column 0))
205 "Fill STR such that each line contains at most WIDTH characters, assuming
206that the first character is at COLUMN.
207
208When STR contains a single line break surrounded by other characters, it is
209converted to a space; sequences of more than one line break are preserved."
210 (define (maybe-break chr result)
211 (match result
212 ((column newlines chars)
213 (case chr
214 ((#\newline)
215 `(,column ,(+ 1 newlines) ,chars))
216 (else
217 (let ((chars (case newlines
218 ((0) chars)
219 ((1) (cons #\space chars))
220 (else
221 (append (make-list newlines #\newline) chars))))
222 (column (case newlines
223 ((0) column)
224 ((1) (+ 1 column))
225 (else 0))))
226 (let ((chars (cons chr chars))
227 (column (+ 1 column)))
228 (if (> column width)
229 (let*-values (((before after)
230 (break (cut eqv? #\space <>) chars))
231 ((len)
232 (length before)))
233 (if (<= len width)
234 `(,len
235 0
236 ,(if (null? after)
237 before
238 (append before (cons #\newline (cdr after)))))
239 `(,column 0 ,chars))) ; unbreakable
240 `(,column 0 ,chars)))))))))
241
242 (match (string-fold maybe-break
243 `(,column 0 ())
244 str)
245 ((_ _ chars)
246 (list->string (reverse chars)))))
247
248(define (string->recutils str)
249 "Return a version of STR where newlines have been replaced by newlines
250followed by \"+ \", which makes for a valid multi-line field value in the
251`recutils' syntax."
252 (list->string
253 (string-fold-right (lambda (chr result)
254 (if (eqv? chr #\newline)
255 (cons* chr #\+ #\space result)
256 (cons chr result)))
257 '()
258 str)))
259
260(define* (package->recutils p port
261 #:optional (width (or (and=> (getenv "WIDTH")
262 string->number)
263 80)))
264 "Write to PORT a `recutils' record of package P, arranging to fit within
265WIDTH columns."
266 (define (description->recutils str)
267 (let ((str (_ str)))
268 (string->recutils
269 (fill-paragraph str width
270 (string-length "description: ")))))
271
272 ;; Note: Don't i18n field names so that people can post-process it.
273 (format port "name: ~a~%" (package-name p))
274 (format port "version: ~a~%" (package-version p))
275 (format port "location: ~a~%"
276 (or (and=> (package-location p) location->string)
277 (_ "unknown")))
278 (format port "home-page: ~a~%" (package-home-page p))
279 (format port "license: ~a~%"
280 (match (package-license p)
281 (((? license? licenses) ...)
282 (string-join (map license-name licenses)
283 ", "))
284 ((? license? license)
285 (license-name license))
286 (x
287 (_ "unknown"))))
288 (format port "synopsis: ~a~%"
289 (string-map (match-lambda
290 (#\newline #\space)
291 (chr chr))
292 (or (and=> (package-synopsis p) _)
293 "")))
294 (format port "description: ~a~%"
295 (and=> (package-description p) description->recutils))
296 (newline port))
297
e49951eb
MW
298(define (show-guix-usage)
299 ;; TODO: Dynamically generate a summary of available commands.
300 (format (current-error-port)
301 (_ "Usage: guix COMMAND ARGS...~%")))
302
303(define (run-guix-command command . args)
304 ;; TODO: Gracefully report errors
305 (let* ((module (resolve-interface `(guix scripts ,command)))
306 (command-main (module-ref module
307 (symbol-append 'guix- command))))
308 (apply command-main args)))
309
310(define (guix-main arg0 . args)
311 (initialize-guix)
312 (let ()
313 (define (option? str) (string-prefix? "-" str))
314 (match args
315 (() (show-guix-usage) (exit 1))
316 (("--help") (show-guix-usage))
317 (("--version") (show-version-and-exit "guix"))
318 (((? option? arg1) args ...) (show-guix-usage) (exit 1))
319 ((command args ...)
320 (apply run-guix-command
321 (string->symbol command)
322 args)))))
323
073c34d7 324;;; ui.scm ends here