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