diff options
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | guix/read-print.scm | 490 | ||||
| -rw-r--r-- | guix/scripts/import.scm | 4 | ||||
| -rw-r--r-- | guix/scripts/style.scm | 457 | ||||
| -rw-r--r-- | tests/read-print.scm | 209 | ||||
| -rw-r--r-- | tests/style.scm | 181 |
6 files changed, 705 insertions, 638 deletions
diff --git a/Makefile.am b/Makefile.am index e5363140fb7..2cda20e61c1 100644 --- a/Makefile.am +++ b/Makefile.am | |||
| @@ -130,6 +130,7 @@ MODULES = \ | |||
| 130 | guix/cve.scm \ | 130 | guix/cve.scm \ |
| 131 | guix/workers.scm \ | 131 | guix/workers.scm \ |
| 132 | guix/least-authority.scm \ | 132 | guix/least-authority.scm \ |
| 133 | guix/read-print.scm \ | ||
| 133 | guix/ipfs.scm \ | 134 | guix/ipfs.scm \ |
| 134 | guix/platform.scm \ | 135 | guix/platform.scm \ |
| 135 | guix/platforms/arm.scm \ | 136 | guix/platforms/arm.scm \ |
| @@ -524,6 +525,7 @@ SCM_TESTS = \ | |||
| 524 | tests/profiles.scm \ | 525 | tests/profiles.scm \ |
| 525 | tests/publish.scm \ | 526 | tests/publish.scm \ |
| 526 | tests/pypi.scm \ | 527 | tests/pypi.scm \ |
| 528 | tests/read-print.scm \ | ||
| 527 | tests/records.scm \ | 529 | tests/records.scm \ |
| 528 | tests/scripts.scm \ | 530 | tests/scripts.scm \ |
| 529 | tests/search-paths.scm \ | 531 | tests/search-paths.scm \ |
diff --git a/guix/read-print.scm b/guix/read-print.scm new file mode 100644 index 00000000000..69ab8ac8b3d --- /dev/null +++ b/guix/read-print.scm | |||
| @@ -0,0 +1,490 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2021-2022 Ludovic Courtès <ludo@gnu.org> | ||
| 3 | ;;; | ||
| 4 | ;;; This file is part of GNU Guix. | ||
| 5 | ;;; | ||
| 6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 7 | ;;; under the terms of the GNU General Public License as published by | ||
| 8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 9 | ;;; your option) any later version. | ||
| 10 | ;;; | ||
| 11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;;; GNU General Public License for more details. | ||
| 15 | ;;; | ||
| 16 | ;;; You should have received a copy of the GNU General Public License | ||
| 17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | |||
| 19 | (define-module (guix read-print) | ||
| 20 | #:use-module (ice-9 control) | ||
| 21 | #:use-module (ice-9 match) | ||
| 22 | #:use-module (ice-9 rdelim) | ||
| 23 | #:use-module (ice-9 vlist) | ||
| 24 | #:use-module (srfi srfi-1) | ||
| 25 | #:use-module (srfi srfi-9) | ||
| 26 | #:export (pretty-print-with-comments | ||
| 27 | read-with-comments | ||
| 28 | object->string* | ||
| 29 | |||
| 30 | comment? | ||
| 31 | comment->string | ||
| 32 | comment-margin? | ||
| 33 | canonicalize-comment)) | ||
| 34 | |||
| 35 | ;;; Commentary: | ||
| 36 | ;;; | ||
| 37 | ;;; This module provides a comment-preserving reader and a comment-preserving | ||
| 38 | ;;; pretty-printer smarter than (ice-9 pretty-print). | ||
| 39 | ;;; | ||
| 40 | ;;; Code: | ||
| 41 | |||
| 42 | |||
| 43 | ;;; | ||
| 44 | ;;; Comment-preserving reader. | ||
| 45 | ;;; | ||
| 46 | |||
| 47 | ;; A comment. | ||
| 48 | (define-record-type <comment> | ||
| 49 | (comment str margin?) | ||
| 50 | comment? | ||
| 51 | (str comment->string) | ||
| 52 | (margin? comment-margin?)) | ||
| 53 | |||
| 54 | (define (read-with-comments port) | ||
| 55 | "Like 'read', but include <comment> objects when they're encountered." | ||
| 56 | ;; Note: Instead of implementing this functionality in 'read' proper, which | ||
| 57 | ;; is the best approach long-term, this code is a layer on top of 'read', | ||
| 58 | ;; such that we don't have to rely on a specific Guile version. | ||
| 59 | (define dot (list 'dot)) | ||
| 60 | (define (dot? x) (eq? x dot)) | ||
| 61 | |||
| 62 | (define (reverse/dot lst) | ||
| 63 | ;; Reverse LST and make it an improper list if it contains DOT. | ||
| 64 | (let loop ((result '()) | ||
| 65 | (lst lst)) | ||
| 66 | (match lst | ||
| 67 | (() result) | ||
| 68 | (((? dot?) . rest) | ||
| 69 | (let ((dotted (reverse rest))) | ||
| 70 | (set-cdr! (last-pair dotted) (car result)) | ||
| 71 | dotted)) | ||
| 72 | ((x . rest) (loop (cons x result) rest))))) | ||
| 73 | |||
| 74 | (let loop ((blank-line? #t) | ||
| 75 | (return (const 'unbalanced))) | ||
| 76 | (match (read-char port) | ||
| 77 | ((? eof-object? eof) | ||
| 78 | eof) ;oops! | ||
| 79 | (chr | ||
| 80 | (cond ((eqv? chr #\newline) | ||
| 81 | (loop #t return)) | ||
| 82 | ((char-set-contains? char-set:whitespace chr) | ||
| 83 | (loop blank-line? return)) | ||
| 84 | ((memv chr '(#\( #\[)) | ||
| 85 | (let/ec return | ||
| 86 | (let liip ((lst '())) | ||
| 87 | (liip (cons (loop (match lst | ||
| 88 | (((? comment?) . _) #t) | ||
| 89 | (_ #f)) | ||
| 90 | (lambda () | ||
| 91 | (return (reverse/dot lst)))) | ||
| 92 | lst))))) | ||
| 93 | ((memv chr '(#\) #\])) | ||
| 94 | (return)) | ||
| 95 | ((eq? chr #\') | ||
| 96 | (list 'quote (loop #f return))) | ||
| 97 | ((eq? chr #\`) | ||
| 98 | (list 'quasiquote (loop #f return))) | ||
| 99 | ((eq? chr #\,) | ||
| 100 | (list (match (peek-char port) | ||
| 101 | (#\@ | ||
| 102 | (read-char port) | ||
| 103 | 'unquote-splicing) | ||
| 104 | (_ | ||
| 105 | 'unquote)) | ||
| 106 | (loop #f return))) | ||
| 107 | ((eqv? chr #\;) | ||
| 108 | (unread-char chr port) | ||
| 109 | (comment (read-line port 'concat) | ||
| 110 | (not blank-line?))) | ||
| 111 | (else | ||
| 112 | (unread-char chr port) | ||
| 113 | (match (read port) | ||
| 114 | ((and token '#{.}#) | ||
| 115 | (if (eq? chr #\.) dot token)) | ||
| 116 | (token token)))))))) | ||
| 117 | |||
| 118 | ;;; | ||
| 119 | ;;; Comment-preserving pretty-printer. | ||
| 120 | ;;; | ||
| 121 | |||
| 122 | (define-syntax vhashq | ||
| 123 | (syntax-rules (quote) | ||
| 124 | ((_) vlist-null) | ||
| 125 | ((_ (key (quote (lst ...))) rest ...) | ||
| 126 | (vhash-consq key '(lst ...) (vhashq rest ...))) | ||
| 127 | ((_ (key value) rest ...) | ||
| 128 | (vhash-consq key '((() . value)) (vhashq rest ...))))) | ||
| 129 | |||
| 130 | (define %special-forms | ||
| 131 | ;; Forms that are indented specially. The number is meant to be understood | ||
| 132 | ;; like Emacs' 'scheme-indent-function' symbol property. When given an | ||
| 133 | ;; alist instead of a number, the alist gives "context" in which the symbol | ||
| 134 | ;; is a special form; for instance, context (modify-phases) means that the | ||
| 135 | ;; symbol must appear within a (modify-phases ...) expression. | ||
| 136 | (vhashq | ||
| 137 | ('begin 1) | ||
| 138 | ('lambda 2) | ||
| 139 | ('lambda* 2) | ||
| 140 | ('match-lambda 1) | ||
| 141 | ('match-lambda* 2) | ||
| 142 | ('define 2) | ||
| 143 | ('define* 2) | ||
| 144 | ('define-public 2) | ||
| 145 | ('define*-public 2) | ||
| 146 | ('define-syntax 2) | ||
| 147 | ('define-syntax-rule 2) | ||
| 148 | ('define-module 2) | ||
| 149 | ('define-gexp-compiler 2) | ||
| 150 | ('let 2) | ||
| 151 | ('let* 2) | ||
| 152 | ('letrec 2) | ||
| 153 | ('letrec* 2) | ||
| 154 | ('match 2) | ||
| 155 | ('when 2) | ||
| 156 | ('unless 2) | ||
| 157 | ('package 1) | ||
| 158 | ('origin 1) | ||
| 159 | ('operating-system 1) | ||
| 160 | ('modify-inputs 2) | ||
| 161 | ('modify-phases 2) | ||
| 162 | ('add-after '(((modify-phases) . 3))) | ||
| 163 | ('add-before '(((modify-phases) . 3))) | ||
| 164 | ('replace '(((modify-phases) . 2))) ;different from 'modify-inputs' | ||
| 165 | ('substitute* 2) | ||
| 166 | ('substitute-keyword-arguments 2) | ||
| 167 | ('call-with-input-file 2) | ||
| 168 | ('call-with-output-file 2) | ||
| 169 | ('with-output-to-file 2) | ||
| 170 | ('with-input-from-file 2))) | ||
| 171 | |||
| 172 | (define %newline-forms | ||
| 173 | ;; List heads that must be followed by a newline. The second argument is | ||
| 174 | ;; the context in which they must appear. This is similar to a special form | ||
| 175 | ;; of 1, except that indent is 1 instead of 2 columns. | ||
| 176 | (vhashq | ||
| 177 | ('arguments '(package)) | ||
| 178 | ('sha256 '(origin source package)) | ||
| 179 | ('base32 '(sha256 origin)) | ||
| 180 | ('git-reference '(uri origin source)) | ||
| 181 | ('search-paths '(package)) | ||
| 182 | ('native-search-paths '(package)) | ||
| 183 | ('search-path-specification '()))) | ||
| 184 | |||
| 185 | (define (prefix? candidate lst) | ||
| 186 | "Return true if CANDIDATE is a prefix of LST." | ||
| 187 | (let loop ((candidate candidate) | ||
| 188 | (lst lst)) | ||
| 189 | (match candidate | ||
| 190 | (() #t) | ||
| 191 | ((head1 . rest1) | ||
| 192 | (match lst | ||
| 193 | (() #f) | ||
| 194 | ((head2 . rest2) | ||
| 195 | (and (equal? head1 head2) | ||
| 196 | (loop rest1 rest2)))))))) | ||
| 197 | |||
| 198 | (define (special-form-lead symbol context) | ||
| 199 | "If SYMBOL is a special form in the given CONTEXT, return its number of | ||
| 200 | arguments; otherwise return #f. CONTEXT is a stack of symbols lexically | ||
| 201 | surrounding SYMBOL." | ||
| 202 | (match (vhash-assq symbol %special-forms) | ||
| 203 | (#f #f) | ||
| 204 | ((_ . alist) | ||
| 205 | (any (match-lambda | ||
| 206 | ((prefix . level) | ||
| 207 | (and (prefix? prefix context) (- level 1)))) | ||
| 208 | alist)))) | ||
| 209 | |||
| 210 | (define (newline-form? symbol context) | ||
| 211 | "Return true if parenthesized expressions starting with SYMBOL must be | ||
| 212 | followed by a newline." | ||
| 213 | (match (vhash-assq symbol %newline-forms) | ||
| 214 | (#f #f) | ||
| 215 | ((_ . prefix) | ||
| 216 | (prefix? prefix context)))) | ||
| 217 | |||
| 218 | (define (escaped-string str) | ||
| 219 | "Return STR with backslashes and double quotes escaped. Everything else, in | ||
| 220 | particular newlines, is left as is." | ||
| 221 | (list->string | ||
| 222 | `(#\" | ||
| 223 | ,@(string-fold-right (lambda (chr lst) | ||
| 224 | (match chr | ||
| 225 | (#\" (cons* #\\ #\" lst)) | ||
| 226 | (#\\ (cons* #\\ #\\ lst)) | ||
| 227 | (_ (cons chr lst)))) | ||
| 228 | '() | ||
| 229 | str) | ||
| 230 | #\"))) | ||
| 231 | |||
| 232 | (define (string-width str) | ||
| 233 | "Return the \"width\" of STR--i.e., the width of the longest line of STR." | ||
| 234 | (apply max (map string-length (string-split str #\newline)))) | ||
| 235 | |||
| 236 | (define (canonicalize-comment c) | ||
| 237 | "Canonicalize comment C, ensuring it has the \"right\" number of leading | ||
| 238 | semicolons." | ||
| 239 | (let ((line (string-trim-both | ||
| 240 | (string-trim (comment->string c) (char-set #\;))))) | ||
| 241 | (comment (string-append | ||
| 242 | (if (comment-margin? c) | ||
| 243 | ";" | ||
| 244 | (if (string-null? line) | ||
| 245 | ";;" ;no trailing space | ||
| 246 | ";; ")) | ||
| 247 | line "\n") | ||
| 248 | (comment-margin? c)))) | ||
| 249 | |||
| 250 | (define* (pretty-print-with-comments port obj | ||
| 251 | #:key | ||
| 252 | (format-comment identity) | ||
| 253 | (indent 0) | ||
| 254 | (max-width 78) | ||
| 255 | (long-list 5)) | ||
| 256 | "Pretty-print OBJ to PORT, attempting to at most MAX-WIDTH character columns | ||
| 257 | and assuming the current column is INDENT. Comments present in OBJ are | ||
| 258 | included in the output. | ||
| 259 | |||
| 260 | Lists longer than LONG-LIST are written as one element per line. Comments are | ||
| 261 | passed through FORMAT-COMMENT before being emitted; a useful value for | ||
| 262 | FORMAT-COMMENT is 'canonicalize-comment'." | ||
| 263 | (define (list-of-lists? head tail) | ||
| 264 | ;; Return true if HEAD and TAIL denote a list of lists--e.g., a list of | ||
| 265 | ;; 'let' bindings. | ||
| 266 | (match head | ||
| 267 | ((thing _ ...) ;proper list | ||
| 268 | (and (not (memq thing | ||
| 269 | '(quote quasiquote unquote unquote-splicing))) | ||
| 270 | (pair? tail))) | ||
| 271 | (_ #f))) | ||
| 272 | |||
| 273 | (let loop ((indent indent) | ||
| 274 | (column indent) | ||
| 275 | (delimited? #t) ;true if comes after a delimiter | ||
| 276 | (context '()) ;list of "parent" symbols | ||
| 277 | (obj obj)) | ||
| 278 | (define (print-sequence context indent column lst delimited?) | ||
| 279 | (define long? | ||
| 280 | (> (length lst) long-list)) | ||
| 281 | |||
| 282 | (let print ((lst lst) | ||
| 283 | (first? #t) | ||
| 284 | (delimited? delimited?) | ||
| 285 | (column column)) | ||
| 286 | (match lst | ||
| 287 | (() | ||
| 288 | column) | ||
| 289 | ((item . tail) | ||
| 290 | (define newline? | ||
| 291 | ;; Insert a newline if ITEM is itself a list, or if TAIL is long, | ||
| 292 | ;; but only if ITEM is not the first item. Also insert a newline | ||
| 293 | ;; before a keyword. | ||
| 294 | (and (or (pair? item) long? | ||
| 295 | (and (keyword? item) | ||
| 296 | (not (eq? item #:allow-other-keys)))) | ||
| 297 | (not first?) (not delimited?) | ||
| 298 | (not (comment? item)))) | ||
| 299 | |||
| 300 | (when newline? | ||
| 301 | (newline port) | ||
| 302 | (display (make-string indent #\space) port)) | ||
| 303 | (let ((column (if newline? indent column))) | ||
| 304 | (print tail | ||
| 305 | (keyword? item) ;keep #:key value next to one another | ||
| 306 | (comment? item) | ||
| 307 | (loop indent column | ||
| 308 | (or newline? delimited?) | ||
| 309 | context | ||
| 310 | item))))))) | ||
| 311 | |||
| 312 | (define (sequence-would-protrude? indent lst) | ||
| 313 | ;; Return true if elements of LST written at INDENT would protrude | ||
| 314 | ;; beyond MAX-WIDTH. This is implemented as a cheap test with false | ||
| 315 | ;; negatives to avoid actually rendering all of LST. | ||
| 316 | (find (match-lambda | ||
| 317 | ((? string? str) | ||
| 318 | (>= (+ (string-width str) 2 indent) max-width)) | ||
| 319 | ((? symbol? symbol) | ||
| 320 | (>= (+ (string-width (symbol->string symbol)) indent) | ||
| 321 | max-width)) | ||
| 322 | ((? boolean?) | ||
| 323 | (>= (+ 2 indent) max-width)) | ||
| 324 | (() | ||
| 325 | (>= (+ 2 indent) max-width)) | ||
| 326 | (_ ;don't know | ||
| 327 | #f)) | ||
| 328 | lst)) | ||
| 329 | |||
| 330 | (define (special-form? head) | ||
| 331 | (special-form-lead head context)) | ||
| 332 | |||
| 333 | (match obj | ||
| 334 | ((? comment? comment) | ||
| 335 | (if (comment-margin? comment) | ||
| 336 | (begin | ||
| 337 | (display " " port) | ||
| 338 | (display (comment->string (format-comment comment)) | ||
| 339 | port)) | ||
| 340 | (begin | ||
| 341 | ;; When already at the beginning of a line, for example because | ||
| 342 | ;; COMMENT follows a margin comment, no need to emit a newline. | ||
| 343 | (unless (= column indent) | ||
| 344 | (newline port) | ||
| 345 | (display (make-string indent #\space) port)) | ||
| 346 | (display (comment->string (format-comment comment)) | ||
| 347 | port))) | ||
| 348 | (display (make-string indent #\space) port) | ||
| 349 | indent) | ||
| 350 | (('quote lst) | ||
| 351 | (unless delimited? (display " " port)) | ||
| 352 | (display "'" port) | ||
| 353 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 354 | (('quasiquote lst) | ||
| 355 | (unless delimited? (display " " port)) | ||
| 356 | (display "`" port) | ||
| 357 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 358 | (('unquote lst) | ||
| 359 | (unless delimited? (display " " port)) | ||
| 360 | (display "," port) | ||
| 361 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 362 | (('unquote-splicing lst) | ||
| 363 | (unless delimited? (display " " port)) | ||
| 364 | (display ",@" port) | ||
| 365 | (loop indent (+ column (if delimited? 2 3)) #t context lst)) | ||
| 366 | (('gexp lst) | ||
| 367 | (unless delimited? (display " " port)) | ||
| 368 | (display "#~" port) | ||
| 369 | (loop indent (+ column (if delimited? 2 3)) #t context lst)) | ||
| 370 | (('ungexp obj) | ||
| 371 | (unless delimited? (display " " port)) | ||
| 372 | (display "#$" port) | ||
| 373 | (loop indent (+ column (if delimited? 2 3)) #t context obj)) | ||
| 374 | (('ungexp-native obj) | ||
| 375 | (unless delimited? (display " " port)) | ||
| 376 | (display "#+" port) | ||
| 377 | (loop indent (+ column (if delimited? 2 3)) #t context obj)) | ||
| 378 | (('ungexp-splicing lst) | ||
| 379 | (unless delimited? (display " " port)) | ||
| 380 | (display "#$@" port) | ||
| 381 | (loop indent (+ column (if delimited? 3 4)) #t context lst)) | ||
| 382 | (('ungexp-native-splicing lst) | ||
| 383 | (unless delimited? (display " " port)) | ||
| 384 | (display "#+@" port) | ||
| 385 | (loop indent (+ column (if delimited? 3 4)) #t context lst)) | ||
| 386 | (((? special-form? head) arguments ...) | ||
| 387 | ;; Special-case 'let', 'lambda', 'modify-inputs', etc. so the second | ||
| 388 | ;; and following arguments are less indented. | ||
| 389 | (let* ((lead (special-form-lead head context)) | ||
| 390 | (context (cons head context)) | ||
| 391 | (head (symbol->string head)) | ||
| 392 | (total (length arguments))) | ||
| 393 | (unless delimited? (display " " port)) | ||
| 394 | (display "(" port) | ||
| 395 | (display head port) | ||
| 396 | (unless (zero? lead) | ||
| 397 | (display " " port)) | ||
| 398 | |||
| 399 | ;; Print the first LEAD arguments. | ||
| 400 | (let* ((indent (+ column 2 | ||
| 401 | (if delimited? 0 1))) | ||
| 402 | (column (+ column 1 | ||
| 403 | (if (zero? lead) 0 1) | ||
| 404 | (if delimited? 0 1) | ||
| 405 | (string-length head))) | ||
| 406 | (initial-indent column)) | ||
| 407 | (define new-column | ||
| 408 | (let inner ((n lead) | ||
| 409 | (arguments (take arguments (min lead total))) | ||
| 410 | (column column)) | ||
| 411 | (if (zero? n) | ||
| 412 | (begin | ||
| 413 | (newline port) | ||
| 414 | (display (make-string indent #\space) port) | ||
| 415 | indent) | ||
| 416 | (match arguments | ||
| 417 | (() column) | ||
| 418 | ((head . tail) | ||
| 419 | (inner (- n 1) tail | ||
| 420 | (loop initial-indent column | ||
| 421 | (= n lead) | ||
| 422 | context | ||
| 423 | head))))))) | ||
| 424 | |||
| 425 | ;; Print the remaining arguments. | ||
| 426 | (let ((column (print-sequence | ||
| 427 | context indent new-column | ||
| 428 | (drop arguments (min lead total)) | ||
| 429 | #t))) | ||
| 430 | (display ")" port) | ||
| 431 | (+ column 1))))) | ||
| 432 | ((head tail ...) | ||
| 433 | (let* ((overflow? (>= column max-width)) | ||
| 434 | (column (if overflow? | ||
| 435 | (+ indent 1) | ||
| 436 | (+ column (if delimited? 1 2)))) | ||
| 437 | (newline? (or (newline-form? head context) | ||
| 438 | (list-of-lists? head tail))) ;'let' bindings | ||
| 439 | (context (cons head context))) | ||
| 440 | (if overflow? | ||
| 441 | (begin | ||
| 442 | (newline port) | ||
| 443 | (display (make-string indent #\space) port)) | ||
| 444 | (unless delimited? (display " " port))) | ||
| 445 | (display "(" port) | ||
| 446 | |||
| 447 | (let* ((new-column (loop column column #t context head)) | ||
| 448 | (indent (if (or (>= new-column max-width) | ||
| 449 | (not (symbol? head)) | ||
| 450 | (sequence-would-protrude? | ||
| 451 | (+ new-column 1) tail) | ||
| 452 | newline?) | ||
| 453 | column | ||
| 454 | (+ new-column 1)))) | ||
| 455 | (when newline? | ||
| 456 | ;; Insert a newline right after HEAD. | ||
| 457 | (newline port) | ||
| 458 | (display (make-string indent #\space) port)) | ||
| 459 | |||
| 460 | (let ((column | ||
| 461 | (print-sequence context indent | ||
| 462 | (if newline? indent new-column) | ||
| 463 | tail newline?))) | ||
| 464 | (display ")" port) | ||
| 465 | (+ column 1))))) | ||
| 466 | (_ | ||
| 467 | (let* ((str (if (string? obj) | ||
| 468 | (escaped-string obj) | ||
| 469 | (object->string obj))) | ||
| 470 | (len (string-width str))) | ||
| 471 | (if (and (> (+ column 1 len) max-width) | ||
| 472 | (not delimited?)) | ||
| 473 | (begin | ||
| 474 | (newline port) | ||
| 475 | (display (make-string indent #\space) port) | ||
| 476 | (display str port) | ||
| 477 | (+ indent len)) | ||
| 478 | (begin | ||
| 479 | (unless delimited? (display " " port)) | ||
| 480 | (display str port) | ||
| 481 | (+ column (if delimited? 0 1) len)))))))) | ||
| 482 | |||
| 483 | (define (object->string* obj indent . args) | ||
| 484 | "Pretty-print OBJ with INDENT columns as the initial indent. ARGS are | ||
| 485 | passed as-is to 'pretty-print-with-comments'." | ||
| 486 | (call-with-output-string | ||
| 487 | (lambda (port) | ||
| 488 | (apply pretty-print-with-comments port obj | ||
| 489 | #:indent indent | ||
| 490 | args)))) | ||
diff --git a/guix/scripts/import.scm b/guix/scripts/import.scm index 71ab4b4fedd..bd3cfd2dc3e 100644 --- a/guix/scripts/import.scm +++ b/guix/scripts/import.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2012, 2013, 2014, 2020, 2021 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2012-2014, 2020-2022 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; Copyright © 2014 David Thompson <davet@gnu.org> | 3 | ;;; Copyright © 2014 David Thompson <davet@gnu.org> |
| 4 | ;;; Copyright © 2018 Kyle Meyer <kyle@kyleam.com> | 4 | ;;; Copyright © 2018 Kyle Meyer <kyle@kyleam.com> |
| 5 | ;;; Copyright © 2019, 2022 Ricardo Wurmus <rekado@elephly.net> | 5 | ;;; Copyright © 2019, 2022 Ricardo Wurmus <rekado@elephly.net> |
| @@ -25,7 +25,7 @@ | |||
| 25 | (define-module (guix scripts import) | 25 | (define-module (guix scripts import) |
| 26 | #:use-module (guix ui) | 26 | #:use-module (guix ui) |
| 27 | #:use-module (guix scripts) | 27 | #:use-module (guix scripts) |
| 28 | #:use-module (guix scripts style) | 28 | #:use-module (guix read-print) |
| 29 | #:use-module (guix utils) | 29 | #:use-module (guix utils) |
| 30 | #:use-module (srfi srfi-1) | 30 | #:use-module (srfi srfi-1) |
| 31 | #:use-module (srfi srfi-11) | 31 | #:use-module (srfi srfi-11) |
diff --git a/guix/scripts/style.scm b/guix/scripts/style.scm index 9fd652beb1f..e2530e80c03 100644 --- a/guix/scripts/style.scm +++ b/guix/scripts/style.scm | |||
| @@ -37,468 +37,15 @@ | |||
| 37 | #:use-module (guix utils) | 37 | #:use-module (guix utils) |
| 38 | #:use-module (guix i18n) | 38 | #:use-module (guix i18n) |
| 39 | #:use-module (guix diagnostics) | 39 | #:use-module (guix diagnostics) |
| 40 | #:use-module (guix read-print) | ||
| 40 | #:use-module (ice-9 control) | 41 | #:use-module (ice-9 control) |
| 41 | #:use-module (ice-9 match) | 42 | #:use-module (ice-9 match) |
| 42 | #:use-module (ice-9 rdelim) | ||
| 43 | #:use-module (ice-9 vlist) | ||
| 44 | #:use-module (srfi srfi-1) | 43 | #:use-module (srfi srfi-1) |
| 45 | #:use-module (srfi srfi-9) | 44 | #:use-module (srfi srfi-9) |
| 46 | #:use-module (srfi srfi-26) | 45 | #:use-module (srfi srfi-26) |
| 47 | #:use-module (srfi srfi-34) | 46 | #:use-module (srfi srfi-34) |
| 48 | #:use-module (srfi srfi-37) | 47 | #:use-module (srfi srfi-37) |
| 49 | #:export (pretty-print-with-comments | 48 | #:export (guix-style)) |
| 50 | read-with-comments | ||
| 51 | canonicalize-comment | ||
| 52 | |||
| 53 | guix-style)) | ||
| 54 | |||
| 55 | |||
| 56 | ;;; | ||
| 57 | ;;; Comment-preserving reader. | ||
| 58 | ;;; | ||
| 59 | |||
| 60 | ;; A comment. | ||
| 61 | (define-record-type <comment> | ||
| 62 | (comment str margin?) | ||
| 63 | comment? | ||
| 64 | (str comment->string) | ||
| 65 | (margin? comment-margin?)) | ||
| 66 | |||
| 67 | (define (read-with-comments port) | ||
| 68 | "Like 'read', but include <comment> objects when they're encountered." | ||
| 69 | ;; Note: Instead of implementing this functionality in 'read' proper, which | ||
| 70 | ;; is the best approach long-term, this code is a layer on top of 'read', | ||
| 71 | ;; such that we don't have to rely on a specific Guile version. | ||
| 72 | (define dot (list 'dot)) | ||
| 73 | (define (dot? x) (eq? x dot)) | ||
| 74 | |||
| 75 | (define (reverse/dot lst) | ||
| 76 | ;; Reverse LST and make it an improper list if it contains DOT. | ||
| 77 | (let loop ((result '()) | ||
| 78 | (lst lst)) | ||
| 79 | (match lst | ||
| 80 | (() result) | ||
| 81 | (((? dot?) . rest) | ||
| 82 | (let ((dotted (reverse rest))) | ||
| 83 | (set-cdr! (last-pair dotted) (car result)) | ||
| 84 | dotted)) | ||
| 85 | ((x . rest) (loop (cons x result) rest))))) | ||
| 86 | |||
| 87 | (let loop ((blank-line? #t) | ||
| 88 | (return (const 'unbalanced))) | ||
| 89 | (match (read-char port) | ||
| 90 | ((? eof-object? eof) | ||
| 91 | eof) ;oops! | ||
| 92 | (chr | ||
| 93 | (cond ((eqv? chr #\newline) | ||
| 94 | (loop #t return)) | ||
| 95 | ((char-set-contains? char-set:whitespace chr) | ||
| 96 | (loop blank-line? return)) | ||
| 97 | ((memv chr '(#\( #\[)) | ||
| 98 | (let/ec return | ||
| 99 | (let liip ((lst '())) | ||
| 100 | (liip (cons (loop (match lst | ||
| 101 | (((? comment?) . _) #t) | ||
| 102 | (_ #f)) | ||
| 103 | (lambda () | ||
| 104 | (return (reverse/dot lst)))) | ||
| 105 | lst))))) | ||
| 106 | ((memv chr '(#\) #\])) | ||
| 107 | (return)) | ||
| 108 | ((eq? chr #\') | ||
| 109 | (list 'quote (loop #f return))) | ||
| 110 | ((eq? chr #\`) | ||
| 111 | (list 'quasiquote (loop #f return))) | ||
| 112 | ((eq? chr #\,) | ||
| 113 | (list (match (peek-char port) | ||
| 114 | (#\@ | ||
| 115 | (read-char port) | ||
| 116 | 'unquote-splicing) | ||
| 117 | (_ | ||
| 118 | 'unquote)) | ||
| 119 | (loop #f return))) | ||
| 120 | ((eqv? chr #\;) | ||
| 121 | (unread-char chr port) | ||
| 122 | (comment (read-line port 'concat) | ||
| 123 | (not blank-line?))) | ||
| 124 | (else | ||
| 125 | (unread-char chr port) | ||
| 126 | (match (read port) | ||
| 127 | ((and token '#{.}#) | ||
| 128 | (if (eq? chr #\.) dot token)) | ||
| 129 | (token token)))))))) | ||
| 130 | |||
| 131 | ;;; | ||
| 132 | ;;; Comment-preserving pretty-printer. | ||
| 133 | ;;; | ||
| 134 | |||
| 135 | (define-syntax vhashq | ||
| 136 | (syntax-rules (quote) | ||
| 137 | ((_) vlist-null) | ||
| 138 | ((_ (key (quote (lst ...))) rest ...) | ||
| 139 | (vhash-consq key '(lst ...) (vhashq rest ...))) | ||
| 140 | ((_ (key value) rest ...) | ||
| 141 | (vhash-consq key '((() . value)) (vhashq rest ...))))) | ||
| 142 | |||
| 143 | (define %special-forms | ||
| 144 | ;; Forms that are indented specially. The number is meant to be understood | ||
| 145 | ;; like Emacs' 'scheme-indent-function' symbol property. When given an | ||
| 146 | ;; alist instead of a number, the alist gives "context" in which the symbol | ||
| 147 | ;; is a special form; for instance, context (modify-phases) means that the | ||
| 148 | ;; symbol must appear within a (modify-phases ...) expression. | ||
| 149 | (vhashq | ||
| 150 | ('begin 1) | ||
| 151 | ('lambda 2) | ||
| 152 | ('lambda* 2) | ||
| 153 | ('match-lambda 1) | ||
| 154 | ('match-lambda* 2) | ||
| 155 | ('define 2) | ||
| 156 | ('define* 2) | ||
| 157 | ('define-public 2) | ||
| 158 | ('define*-public 2) | ||
| 159 | ('define-syntax 2) | ||
| 160 | ('define-syntax-rule 2) | ||
| 161 | ('define-module 2) | ||
| 162 | ('define-gexp-compiler 2) | ||
| 163 | ('let 2) | ||
| 164 | ('let* 2) | ||
| 165 | ('letrec 2) | ||
| 166 | ('letrec* 2) | ||
| 167 | ('match 2) | ||
| 168 | ('when 2) | ||
| 169 | ('unless 2) | ||
| 170 | ('package 1) | ||
| 171 | ('origin 1) | ||
| 172 | ('operating-system 1) | ||
| 173 | ('modify-inputs 2) | ||
| 174 | ('modify-phases 2) | ||
| 175 | ('add-after '(((modify-phases) . 3))) | ||
| 176 | ('add-before '(((modify-phases) . 3))) | ||
| 177 | ('replace '(((modify-phases) . 2))) ;different from 'modify-inputs' | ||
| 178 | ('substitute* 2) | ||
| 179 | ('substitute-keyword-arguments 2) | ||
| 180 | ('call-with-input-file 2) | ||
| 181 | ('call-with-output-file 2) | ||
| 182 | ('with-output-to-file 2) | ||
| 183 | ('with-input-from-file 2))) | ||
| 184 | |||
| 185 | (define %newline-forms | ||
| 186 | ;; List heads that must be followed by a newline. The second argument is | ||
| 187 | ;; the context in which they must appear. This is similar to a special form | ||
| 188 | ;; of 1, except that indent is 1 instead of 2 columns. | ||
| 189 | (vhashq | ||
| 190 | ('arguments '(package)) | ||
| 191 | ('sha256 '(origin source package)) | ||
| 192 | ('base32 '(sha256 origin)) | ||
| 193 | ('git-reference '(uri origin source)) | ||
| 194 | ('search-paths '(package)) | ||
| 195 | ('native-search-paths '(package)) | ||
| 196 | ('search-path-specification '()))) | ||
| 197 | |||
| 198 | (define (prefix? candidate lst) | ||
| 199 | "Return true if CANDIDATE is a prefix of LST." | ||
| 200 | (let loop ((candidate candidate) | ||
| 201 | (lst lst)) | ||
| 202 | (match candidate | ||
| 203 | (() #t) | ||
| 204 | ((head1 . rest1) | ||
| 205 | (match lst | ||
| 206 | (() #f) | ||
| 207 | ((head2 . rest2) | ||
| 208 | (and (equal? head1 head2) | ||
| 209 | (loop rest1 rest2)))))))) | ||
| 210 | |||
| 211 | (define (special-form-lead symbol context) | ||
| 212 | "If SYMBOL is a special form in the given CONTEXT, return its number of | ||
| 213 | arguments; otherwise return #f. CONTEXT is a stack of symbols lexically | ||
| 214 | surrounding SYMBOL." | ||
| 215 | (match (vhash-assq symbol %special-forms) | ||
| 216 | (#f #f) | ||
| 217 | ((_ . alist) | ||
| 218 | (any (match-lambda | ||
| 219 | ((prefix . level) | ||
| 220 | (and (prefix? prefix context) (- level 1)))) | ||
| 221 | alist)))) | ||
| 222 | |||
| 223 | (define (newline-form? symbol context) | ||
| 224 | "Return true if parenthesized expressions starting with SYMBOL must be | ||
| 225 | followed by a newline." | ||
| 226 | (match (vhash-assq symbol %newline-forms) | ||
| 227 | (#f #f) | ||
| 228 | ((_ . prefix) | ||
| 229 | (prefix? prefix context)))) | ||
| 230 | |||
| 231 | (define (escaped-string str) | ||
| 232 | "Return STR with backslashes and double quotes escaped. Everything else, in | ||
| 233 | particular newlines, is left as is." | ||
| 234 | (list->string | ||
| 235 | `(#\" | ||
| 236 | ,@(string-fold-right (lambda (chr lst) | ||
| 237 | (match chr | ||
| 238 | (#\" (cons* #\\ #\" lst)) | ||
| 239 | (#\\ (cons* #\\ #\\ lst)) | ||
| 240 | (_ (cons chr lst)))) | ||
| 241 | '() | ||
| 242 | str) | ||
| 243 | #\"))) | ||
| 244 | |||
| 245 | (define (string-width str) | ||
| 246 | "Return the \"width\" of STR--i.e., the width of the longest line of STR." | ||
| 247 | (apply max (map string-length (string-split str #\newline)))) | ||
| 248 | |||
| 249 | (define (canonicalize-comment c) | ||
| 250 | "Canonicalize comment C, ensuring it has the \"right\" number of leading | ||
| 251 | semicolons." | ||
| 252 | (let ((line (string-trim-both | ||
| 253 | (string-trim (comment->string c) (char-set #\;))))) | ||
| 254 | (comment (string-append | ||
| 255 | (if (comment-margin? c) | ||
| 256 | ";" | ||
| 257 | (if (string-null? line) | ||
| 258 | ";;" ;no trailing space | ||
| 259 | ";; ")) | ||
| 260 | line "\n") | ||
| 261 | (comment-margin? c)))) | ||
| 262 | |||
| 263 | (define* (pretty-print-with-comments port obj | ||
| 264 | #:key | ||
| 265 | (format-comment identity) | ||
| 266 | (indent 0) | ||
| 267 | (max-width 78) | ||
| 268 | (long-list 5)) | ||
| 269 | "Pretty-print OBJ to PORT, attempting to at most MAX-WIDTH character columns | ||
| 270 | and assuming the current column is INDENT. Comments present in OBJ are | ||
| 271 | included in the output. | ||
| 272 | |||
| 273 | Lists longer than LONG-LIST are written as one element per line. Comments are | ||
| 274 | passed through FORMAT-COMMENT before being emitted; a useful value for | ||
| 275 | FORMAT-COMMENT is 'canonicalize-comment'." | ||
| 276 | (define (list-of-lists? head tail) | ||
| 277 | ;; Return true if HEAD and TAIL denote a list of lists--e.g., a list of | ||
| 278 | ;; 'let' bindings. | ||
| 279 | (match head | ||
| 280 | ((thing _ ...) ;proper list | ||
| 281 | (and (not (memq thing | ||
| 282 | '(quote quasiquote unquote unquote-splicing))) | ||
| 283 | (pair? tail))) | ||
| 284 | (_ #f))) | ||
| 285 | |||
| 286 | (let loop ((indent indent) | ||
| 287 | (column indent) | ||
| 288 | (delimited? #t) ;true if comes after a delimiter | ||
| 289 | (context '()) ;list of "parent" symbols | ||
| 290 | (obj obj)) | ||
| 291 | (define (print-sequence context indent column lst delimited?) | ||
| 292 | (define long? | ||
| 293 | (> (length lst) long-list)) | ||
| 294 | |||
| 295 | (let print ((lst lst) | ||
| 296 | (first? #t) | ||
| 297 | (delimited? delimited?) | ||
| 298 | (column column)) | ||
| 299 | (match lst | ||
| 300 | (() | ||
| 301 | column) | ||
| 302 | ((item . tail) | ||
| 303 | (define newline? | ||
| 304 | ;; Insert a newline if ITEM is itself a list, or if TAIL is long, | ||
| 305 | ;; but only if ITEM is not the first item. Also insert a newline | ||
| 306 | ;; before a keyword. | ||
| 307 | (and (or (pair? item) long? | ||
| 308 | (and (keyword? item) | ||
| 309 | (not (eq? item #:allow-other-keys)))) | ||
| 310 | (not first?) (not delimited?) | ||
| 311 | (not (comment? item)))) | ||
| 312 | |||
| 313 | (when newline? | ||
| 314 | (newline port) | ||
| 315 | (display (make-string indent #\space) port)) | ||
| 316 | (let ((column (if newline? indent column))) | ||
| 317 | (print tail | ||
| 318 | (keyword? item) ;keep #:key value next to one another | ||
| 319 | (comment? item) | ||
| 320 | (loop indent column | ||
| 321 | (or newline? delimited?) | ||
| 322 | context | ||
| 323 | item))))))) | ||
| 324 | |||
| 325 | (define (sequence-would-protrude? indent lst) | ||
| 326 | ;; Return true if elements of LST written at INDENT would protrude | ||
| 327 | ;; beyond MAX-WIDTH. This is implemented as a cheap test with false | ||
| 328 | ;; negatives to avoid actually rendering all of LST. | ||
| 329 | (find (match-lambda | ||
| 330 | ((? string? str) | ||
| 331 | (>= (+ (string-width str) 2 indent) max-width)) | ||
| 332 | ((? symbol? symbol) | ||
| 333 | (>= (+ (string-width (symbol->string symbol)) indent) | ||
| 334 | max-width)) | ||
| 335 | ((? boolean?) | ||
| 336 | (>= (+ 2 indent) max-width)) | ||
| 337 | (() | ||
| 338 | (>= (+ 2 indent) max-width)) | ||
| 339 | (_ ;don't know | ||
| 340 | #f)) | ||
| 341 | lst)) | ||
| 342 | |||
| 343 | (define (special-form? head) | ||
| 344 | (special-form-lead head context)) | ||
| 345 | |||
| 346 | (match obj | ||
| 347 | ((? comment? comment) | ||
| 348 | (if (comment-margin? comment) | ||
| 349 | (begin | ||
| 350 | (display " " port) | ||
| 351 | (display (comment->string (format-comment comment)) | ||
| 352 | port)) | ||
| 353 | (begin | ||
| 354 | ;; When already at the beginning of a line, for example because | ||
| 355 | ;; COMMENT follows a margin comment, no need to emit a newline. | ||
| 356 | (unless (= column indent) | ||
| 357 | (newline port) | ||
| 358 | (display (make-string indent #\space) port)) | ||
| 359 | (display (comment->string (format-comment comment)) | ||
| 360 | port))) | ||
| 361 | (display (make-string indent #\space) port) | ||
| 362 | indent) | ||
| 363 | (('quote lst) | ||
| 364 | (unless delimited? (display " " port)) | ||
| 365 | (display "'" port) | ||
| 366 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 367 | (('quasiquote lst) | ||
| 368 | (unless delimited? (display " " port)) | ||
| 369 | (display "`" port) | ||
| 370 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 371 | (('unquote lst) | ||
| 372 | (unless delimited? (display " " port)) | ||
| 373 | (display "," port) | ||
| 374 | (loop indent (+ column (if delimited? 1 2)) #t context lst)) | ||
| 375 | (('unquote-splicing lst) | ||
| 376 | (unless delimited? (display " " port)) | ||
| 377 | (display ",@" port) | ||
| 378 | (loop indent (+ column (if delimited? 2 3)) #t context lst)) | ||
| 379 | (('gexp lst) | ||
| 380 | (unless delimited? (display " " port)) | ||
| 381 | (display "#~" port) | ||
| 382 | (loop indent (+ column (if delimited? 2 3)) #t context lst)) | ||
| 383 | (('ungexp obj) | ||
| 384 | (unless delimited? (display " " port)) | ||
| 385 | (display "#$" port) | ||
| 386 | (loop indent (+ column (if delimited? 2 3)) #t context obj)) | ||
| 387 | (('ungexp-native obj) | ||
| 388 | (unless delimited? (display " " port)) | ||
| 389 | (display "#+" port) | ||
| 390 | (loop indent (+ column (if delimited? 2 3)) #t context obj)) | ||
| 391 | (('ungexp-splicing lst) | ||
| 392 | (unless delimited? (display " " port)) | ||
| 393 | (display "#$@" port) | ||
| 394 | (loop indent (+ column (if delimited? 3 4)) #t context lst)) | ||
| 395 | (('ungexp-native-splicing lst) | ||
| 396 | (unless delimited? (display " " port)) | ||
| 397 | (display "#+@" port) | ||
| 398 | (loop indent (+ column (if delimited? 3 4)) #t context lst)) | ||
| 399 | (((? special-form? head) arguments ...) | ||
| 400 | ;; Special-case 'let', 'lambda', 'modify-inputs', etc. so the second | ||
| 401 | ;; and following arguments are less indented. | ||
| 402 | (let* ((lead (special-form-lead head context)) | ||
| 403 | (context (cons head context)) | ||
| 404 | (head (symbol->string head)) | ||
| 405 | (total (length arguments))) | ||
| 406 | (unless delimited? (display " " port)) | ||
| 407 | (display "(" port) | ||
| 408 | (display head port) | ||
| 409 | (unless (zero? lead) | ||
| 410 | (display " " port)) | ||
| 411 | |||
| 412 | ;; Print the first LEAD arguments. | ||
| 413 | (let* ((indent (+ column 2 | ||
| 414 | (if delimited? 0 1))) | ||
| 415 | (column (+ column 1 | ||
| 416 | (if (zero? lead) 0 1) | ||
| 417 | (if delimited? 0 1) | ||
| 418 | (string-length head))) | ||
| 419 | (initial-indent column)) | ||
| 420 | (define new-column | ||
| 421 | (let inner ((n lead) | ||
| 422 | (arguments (take arguments (min lead total))) | ||
| 423 | (column column)) | ||
| 424 | (if (zero? n) | ||
| 425 | (begin | ||
| 426 | (newline port) | ||
| 427 | (display (make-string indent #\space) port) | ||
| 428 | indent) | ||
| 429 | (match arguments | ||
| 430 | (() column) | ||
| 431 | ((head . tail) | ||
| 432 | (inner (- n 1) tail | ||
| 433 | (loop initial-indent column | ||
| 434 | (= n lead) | ||
| 435 | context | ||
| 436 | head))))))) | ||
| 437 | |||
| 438 | ;; Print the remaining arguments. | ||
| 439 | (let ((column (print-sequence | ||
| 440 | context indent new-column | ||
| 441 | (drop arguments (min lead total)) | ||
| 442 | #t))) | ||
| 443 | (display ")" port) | ||
| 444 | (+ column 1))))) | ||
| 445 | ((head tail ...) | ||
| 446 | (let* ((overflow? (>= column max-width)) | ||
| 447 | (column (if overflow? | ||
| 448 | (+ indent 1) | ||
| 449 | (+ column (if delimited? 1 2)))) | ||
| 450 | (newline? (or (newline-form? head context) | ||
| 451 | (list-of-lists? head tail))) ;'let' bindings | ||
| 452 | (context (cons head context))) | ||
| 453 | (if overflow? | ||
| 454 | (begin | ||
| 455 | (newline port) | ||
| 456 | (display (make-string indent #\space) port)) | ||
| 457 | (unless delimited? (display " " port))) | ||
| 458 | (display "(" port) | ||
| 459 | |||
| 460 | (let* ((new-column (loop column column #t context head)) | ||
| 461 | (indent (if (or (>= new-column max-width) | ||
| 462 | (not (symbol? head)) | ||
| 463 | (sequence-would-protrude? | ||
| 464 | (+ new-column 1) tail) | ||
| 465 | newline?) | ||
| 466 | column | ||
| 467 | (+ new-column 1)))) | ||
| 468 | (when newline? | ||
| 469 | ;; Insert a newline right after HEAD. | ||
| 470 | (newline port) | ||
| 471 | (display (make-string indent #\space) port)) | ||
| 472 | |||
| 473 | (let ((column | ||
| 474 | (print-sequence context indent | ||
| 475 | (if newline? indent new-column) | ||
| 476 | tail newline?))) | ||
| 477 | (display ")" port) | ||
| 478 | (+ column 1))))) | ||
| 479 | (_ | ||
| 480 | (let* ((str (if (string? obj) | ||
| 481 | (escaped-string obj) | ||
| 482 | (object->string obj))) | ||
| 483 | (len (string-width str))) | ||
| 484 | (if (and (> (+ column 1 len) max-width) | ||
| 485 | (not delimited?)) | ||
| 486 | (begin | ||
| 487 | (newline port) | ||
| 488 | (display (make-string indent #\space) port) | ||
| 489 | (display str port) | ||
| 490 | (+ indent len)) | ||
| 491 | (begin | ||
| 492 | (unless delimited? (display " " port)) | ||
| 493 | (display str port) | ||
| 494 | (+ column (if delimited? 0 1) len)))))))) | ||
| 495 | |||
| 496 | (define (object->string* obj indent . args) | ||
| 497 | (call-with-output-string | ||
| 498 | (lambda (port) | ||
| 499 | (apply pretty-print-with-comments port obj | ||
| 500 | #:indent indent | ||
| 501 | args)))) | ||
| 502 | 49 | ||
| 503 | 50 | ||
| 504 | ;;; | 51 | ;;; |
diff --git a/tests/read-print.scm b/tests/read-print.scm new file mode 100644 index 00000000000..e9ba1127d4f --- /dev/null +++ b/tests/read-print.scm | |||
| @@ -0,0 +1,209 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2021-2022 Ludovic Courtès <ludo@gnu.org> | ||
| 3 | ;;; | ||
| 4 | ;;; This file is part of GNU Guix. | ||
| 5 | ;;; | ||
| 6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 7 | ;;; under the terms of the GNU General Public License as published by | ||
| 8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 9 | ;;; your option) any later version. | ||
| 10 | ;;; | ||
| 11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;;; GNU General Public License for more details. | ||
| 15 | ;;; | ||
| 16 | ;;; You should have received a copy of the GNU General Public License | ||
| 17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | |||
| 19 | (define-module (tests-style) | ||
| 20 | #:use-module (guix read-print) | ||
| 21 | #:use-module (guix gexp) ;for the reader extensions | ||
| 22 | #:use-module (srfi srfi-64)) | ||
| 23 | |||
| 24 | (define-syntax-rule (test-pretty-print str args ...) | ||
| 25 | "Test equality after a round-trip where STR is passed to | ||
| 26 | 'read-with-comments' and the resulting sexp is then passed to | ||
| 27 | 'pretty-print-with-comments'." | ||
| 28 | (test-equal str | ||
| 29 | (call-with-output-string | ||
| 30 | (lambda (port) | ||
| 31 | (let ((exp (call-with-input-string str | ||
| 32 | read-with-comments))) | ||
| 33 | (pretty-print-with-comments port exp args ...)))))) | ||
| 34 | |||
| 35 | |||
| 36 | (test-begin "read-print") | ||
| 37 | |||
| 38 | (test-equal "read-with-comments: dot notation" | ||
| 39 | (cons 'a 'b) | ||
| 40 | (call-with-input-string "(a . b)" | ||
| 41 | read-with-comments)) | ||
| 42 | |||
| 43 | (test-pretty-print "(list 1 2 3 4)") | ||
| 44 | (test-pretty-print "((a . 1) (b . 2))") | ||
| 45 | (test-pretty-print "(a b c . boom)") | ||
| 46 | (test-pretty-print "(list 1 | ||
| 47 | 2 | ||
| 48 | 3 | ||
| 49 | 4)" | ||
| 50 | #:long-list 3 | ||
| 51 | #:indent 20) | ||
| 52 | (test-pretty-print "\ | ||
| 53 | (list abc | ||
| 54 | def)" | ||
| 55 | #:max-width 11) | ||
| 56 | (test-pretty-print "\ | ||
| 57 | (#:foo | ||
| 58 | #:bar)" | ||
| 59 | #:max-width 10) | ||
| 60 | |||
| 61 | (test-pretty-print "\ | ||
| 62 | (#:first 1 | ||
| 63 | #:second 2 | ||
| 64 | #:third 3)") | ||
| 65 | |||
| 66 | (test-pretty-print "\ | ||
| 67 | ((x | ||
| 68 | 1) | ||
| 69 | (y | ||
| 70 | 2) | ||
| 71 | (z | ||
| 72 | 3))" | ||
| 73 | #:max-width 3) | ||
| 74 | |||
| 75 | (test-pretty-print "\ | ||
| 76 | (let ((x 1) | ||
| 77 | (y 2) | ||
| 78 | (z 3) | ||
| 79 | (p 4)) | ||
| 80 | (+ x y))" | ||
| 81 | #:max-width 11) | ||
| 82 | |||
| 83 | (test-pretty-print "\ | ||
| 84 | (lambda (x y) | ||
| 85 | ;; This is a procedure. | ||
| 86 | (let ((z (+ x y))) | ||
| 87 | (* z z)))") | ||
| 88 | |||
| 89 | (test-pretty-print "\ | ||
| 90 | #~(string-append #$coreutils \"/bin/uname\")") | ||
| 91 | |||
| 92 | (test-pretty-print "\ | ||
| 93 | (package | ||
| 94 | (inherit coreutils) | ||
| 95 | (version \"42\"))") | ||
| 96 | |||
| 97 | (test-pretty-print "\ | ||
| 98 | (modify-phases %standard-phases | ||
| 99 | (add-after 'unpack 'post-unpack | ||
| 100 | (lambda _ | ||
| 101 | #t)) | ||
| 102 | (add-before 'check 'pre-check | ||
| 103 | (lambda* (#:key inputs #:allow-other-keys) | ||
| 104 | do things ...)))") | ||
| 105 | |||
| 106 | (test-pretty-print "\ | ||
| 107 | (#:phases (modify-phases sdfsdf | ||
| 108 | (add-before 'x 'y | ||
| 109 | (lambda _ | ||
| 110 | xyz))))") | ||
| 111 | |||
| 112 | (test-pretty-print "\ | ||
| 113 | (description \"abcdefghijkl | ||
| 114 | mnopqrstuvwxyz.\")" | ||
| 115 | #:max-width 30) | ||
| 116 | |||
| 117 | (test-pretty-print "\ | ||
| 118 | (description | ||
| 119 | \"abcdefghijkl | ||
| 120 | mnopqrstuvwxyz.\")" | ||
| 121 | #:max-width 12) | ||
| 122 | |||
| 123 | (test-pretty-print "\ | ||
| 124 | (description | ||
| 125 | \"abcdefghijklmnopqrstuvwxyz\")" | ||
| 126 | #:max-width 33) | ||
| 127 | |||
| 128 | (test-pretty-print "\ | ||
| 129 | (modify-phases %standard-phases | ||
| 130 | (replace 'build | ||
| 131 | ;; Nicely indented in 'modify-phases' context. | ||
| 132 | (lambda _ | ||
| 133 | #t)))") | ||
| 134 | |||
| 135 | (test-pretty-print "\ | ||
| 136 | (modify-inputs inputs | ||
| 137 | ;; Regular indentation for 'replace' here. | ||
| 138 | (replace \"gmp\" gmp))") | ||
| 139 | |||
| 140 | (test-pretty-print "\ | ||
| 141 | (package | ||
| 142 | ;; Here 'sha256', 'base32', and 'arguments' must be | ||
| 143 | ;; immediately followed by a newline. | ||
| 144 | (source (origin | ||
| 145 | (method url-fetch) | ||
| 146 | (sha256 | ||
| 147 | (base32 | ||
| 148 | \"not a real base32 string\")))) | ||
| 149 | (arguments | ||
| 150 | '(#:phases %standard-phases | ||
| 151 | #:tests? #f)))") | ||
| 152 | |||
| 153 | ;; '#:key value' is kept on the same line. | ||
| 154 | (test-pretty-print "\ | ||
| 155 | (package | ||
| 156 | (name \"keyword-value-same-line\") | ||
| 157 | (arguments | ||
| 158 | (list #:phases #~(modify-phases %standard-phases | ||
| 159 | (add-before 'x 'y | ||
| 160 | (lambda* (#:key inputs #:allow-other-keys) | ||
| 161 | (foo bar baz)))) | ||
| 162 | #:make-flags #~'(\"ANSWER=42\") | ||
| 163 | #:tests? #f)))") | ||
| 164 | |||
| 165 | (test-pretty-print "\ | ||
| 166 | (let ((x 1) | ||
| 167 | (y 2) | ||
| 168 | (z (let* ((a 3) | ||
| 169 | (b 4)) | ||
| 170 | (+ a b)))) | ||
| 171 | (list x y z))") | ||
| 172 | |||
| 173 | (test-pretty-print "\ | ||
| 174 | (substitute-keyword-arguments (package-arguments x) | ||
| 175 | ((#:phases phases) | ||
| 176 | `(modify-phases ,phases | ||
| 177 | (add-before 'build 'do-things | ||
| 178 | (lambda _ | ||
| 179 | #t)))) | ||
| 180 | ((#:configure-flags flags) | ||
| 181 | `(cons \"--without-any-problem\" | ||
| 182 | ,flags)))") | ||
| 183 | |||
| 184 | (test-equal "pretty-print-with-comments, canonicalize-comment" | ||
| 185 | "\ | ||
| 186 | (list abc | ||
| 187 | ;; Not a margin comment. | ||
| 188 | ;; Ditto. | ||
| 189 | ;; | ||
| 190 | ;; There's a blank line above. | ||
| 191 | def ;margin comment | ||
| 192 | ghi)" | ||
| 193 | (let ((sexp (call-with-input-string | ||
| 194 | "\ | ||
| 195 | (list abc | ||
| 196 | ;Not a margin comment. | ||
| 197 | ;;; Ditto. | ||
| 198 | ;;;;; | ||
| 199 | ; There's a blank line above. | ||
| 200 | def ;; margin comment | ||
| 201 | ghi)" | ||
| 202 | read-with-comments))) | ||
| 203 | (call-with-output-string | ||
| 204 | (lambda (port) | ||
| 205 | (pretty-print-with-comments port sexp | ||
| 206 | #:format-comment | ||
| 207 | canonicalize-comment))))) | ||
| 208 | |||
| 209 | (test-end) | ||
diff --git a/tests/style.scm b/tests/style.scm index 55bad2b3ba1..4ac5ae7c092 100644 --- a/tests/style.scm +++ b/tests/style.scm | |||
| @@ -113,17 +113,6 @@ | |||
| 113 | (lambda (port) | 113 | (lambda (port) |
| 114 | (read-lines port line count))))) | 114 | (read-lines port line count))))) |
| 115 | 115 | ||
| 116 | (define-syntax-rule (test-pretty-print str args ...) | ||
| 117 | "Test equality after a round-trip where STR is passed to | ||
| 118 | 'read-with-comments' and the resulting sexp is then passed to | ||
| 119 | 'pretty-print-with-comments'." | ||
| 120 | (test-equal str | ||
| 121 | (call-with-output-string | ||
| 122 | (lambda (port) | ||
| 123 | (let ((exp (call-with-input-string str | ||
| 124 | read-with-comments))) | ||
| 125 | (pretty-print-with-comments port exp args ...)))))) | ||
| 126 | |||
| 127 | 116 | ||
| 128 | (test-begin "style") | 117 | (test-begin "style") |
| 129 | 118 | ||
| @@ -377,176 +366,6 @@ | |||
| 377 | (list (package-inputs (@ (my-packages) my-coreutils)) | 366 | (list (package-inputs (@ (my-packages) my-coreutils)) |
| 378 | (read-package-field (@ (my-packages) my-coreutils) 'inputs 4))))) | 367 | (read-package-field (@ (my-packages) my-coreutils) 'inputs 4))))) |
| 379 | 368 | ||
| 380 | (test-equal "read-with-comments: dot notation" | ||
| 381 | (cons 'a 'b) | ||
| 382 | (call-with-input-string "(a . b)" | ||
| 383 | read-with-comments)) | ||
| 384 | |||
| 385 | (test-pretty-print "(list 1 2 3 4)") | ||
| 386 | (test-pretty-print "((a . 1) (b . 2))") | ||
| 387 | (test-pretty-print "(a b c . boom)") | ||
| 388 | (test-pretty-print "(list 1 | ||
| 389 | 2 | ||
| 390 | 3 | ||
| 391 | 4)" | ||
| 392 | #:long-list 3 | ||
| 393 | #:indent 20) | ||
| 394 | (test-pretty-print "\ | ||
| 395 | (list abc | ||
| 396 | def)" | ||
| 397 | #:max-width 11) | ||
| 398 | (test-pretty-print "\ | ||
| 399 | (#:foo | ||
| 400 | #:bar)" | ||
| 401 | #:max-width 10) | ||
| 402 | |||
| 403 | (test-pretty-print "\ | ||
| 404 | (#:first 1 | ||
| 405 | #:second 2 | ||
| 406 | #:third 3)") | ||
| 407 | |||
| 408 | (test-pretty-print "\ | ||
| 409 | ((x | ||
| 410 | 1) | ||
| 411 | (y | ||
| 412 | 2) | ||
| 413 | (z | ||
| 414 | 3))" | ||
| 415 | #:max-width 3) | ||
| 416 | |||
| 417 | (test-pretty-print "\ | ||
| 418 | (let ((x 1) | ||
| 419 | (y 2) | ||
| 420 | (z 3) | ||
| 421 | (p 4)) | ||
| 422 | (+ x y))" | ||
| 423 | #:max-width 11) | ||
| 424 | |||
| 425 | (test-pretty-print "\ | ||
| 426 | (lambda (x y) | ||
| 427 | ;; This is a procedure. | ||
| 428 | (let ((z (+ x y))) | ||
| 429 | (* z z)))") | ||
| 430 | |||
| 431 | (test-pretty-print "\ | ||
| 432 | #~(string-append #$coreutils \"/bin/uname\")") | ||
| 433 | |||
| 434 | (test-pretty-print "\ | ||
| 435 | (package | ||
| 436 | (inherit coreutils) | ||
| 437 | (version \"42\"))") | ||
| 438 | |||
| 439 | (test-pretty-print "\ | ||
| 440 | (modify-phases %standard-phases | ||
| 441 | (add-after 'unpack 'post-unpack | ||
| 442 | (lambda _ | ||
| 443 | #t)) | ||
| 444 | (add-before 'check 'pre-check | ||
| 445 | (lambda* (#:key inputs #:allow-other-keys) | ||
| 446 | do things ...)))") | ||
| 447 | |||
| 448 | (test-pretty-print "\ | ||
| 449 | (#:phases (modify-phases sdfsdf | ||
| 450 | (add-before 'x 'y | ||
| 451 | (lambda _ | ||
| 452 | xyz))))") | ||
| 453 | |||
| 454 | (test-pretty-print "\ | ||
| 455 | (description \"abcdefghijkl | ||
| 456 | mnopqrstuvwxyz.\")" | ||
| 457 | #:max-width 30) | ||
| 458 | |||
| 459 | (test-pretty-print "\ | ||
| 460 | (description | ||
| 461 | \"abcdefghijkl | ||
| 462 | mnopqrstuvwxyz.\")" | ||
| 463 | #:max-width 12) | ||
| 464 | |||
| 465 | (test-pretty-print "\ | ||
| 466 | (description | ||
| 467 | \"abcdefghijklmnopqrstuvwxyz\")" | ||
| 468 | #:max-width 33) | ||
| 469 | |||
| 470 | (test-pretty-print "\ | ||
| 471 | (modify-phases %standard-phases | ||
| 472 | (replace 'build | ||
| 473 | ;; Nicely indented in 'modify-phases' context. | ||
| 474 | (lambda _ | ||
| 475 | #t)))") | ||
| 476 | |||
| 477 | (test-pretty-print "\ | ||
| 478 | (modify-inputs inputs | ||
| 479 | ;; Regular indentation for 'replace' here. | ||
| 480 | (replace \"gmp\" gmp))") | ||
| 481 | |||
| 482 | (test-pretty-print "\ | ||
| 483 | (package | ||
| 484 | ;; Here 'sha256', 'base32', and 'arguments' must be | ||
| 485 | ;; immediately followed by a newline. | ||
| 486 | (source (origin | ||
| 487 | (method url-fetch) | ||
| 488 | (sha256 | ||
| 489 | (base32 | ||
| 490 | \"not a real base32 string\")))) | ||
| 491 | (arguments | ||
| 492 | '(#:phases %standard-phases | ||
| 493 | #:tests? #f)))") | ||
| 494 | |||
| 495 | ;; '#:key value' is kept on the same line. | ||
| 496 | (test-pretty-print "\ | ||
| 497 | (package | ||
| 498 | (name \"keyword-value-same-line\") | ||
| 499 | (arguments | ||
| 500 | (list #:phases #~(modify-phases %standard-phases | ||
| 501 | (add-before 'x 'y | ||
| 502 | (lambda* (#:key inputs #:allow-other-keys) | ||
| 503 | (foo bar baz)))) | ||
| 504 | #:make-flags #~'(\"ANSWER=42\") | ||
| 505 | #:tests? #f)))") | ||
| 506 | |||
| 507 | (test-pretty-print "\ | ||
| 508 | (let ((x 1) | ||
| 509 | (y 2) | ||
| 510 | (z (let* ((a 3) | ||
| 511 | (b 4)) | ||
| 512 | (+ a b)))) | ||
| 513 | (list x y z))") | ||
| 514 | |||
| 515 | (test-pretty-print "\ | ||
| 516 | (substitute-keyword-arguments (package-arguments x) | ||
| 517 | ((#:phases phases) | ||
| 518 | `(modify-phases ,phases | ||
| 519 | (add-before 'build 'do-things | ||
| 520 | (lambda _ | ||
| 521 | #t)))) | ||
| 522 | ((#:configure-flags flags) | ||
| 523 | `(cons \"--without-any-problem\" | ||
| 524 | ,flags)))") | ||
| 525 | |||
| 526 | (test-equal "pretty-print-with-comments, canonicalize-comment" | ||
| 527 | "\ | ||
| 528 | (list abc | ||
| 529 | ;; Not a margin comment. | ||
| 530 | ;; Ditto. | ||
| 531 | ;; | ||
| 532 | ;; There's a blank line above. | ||
| 533 | def ;margin comment | ||
| 534 | ghi)" | ||
| 535 | (let ((sexp (call-with-input-string | ||
| 536 | "\ | ||
| 537 | (list abc | ||
| 538 | ;Not a margin comment. | ||
| 539 | ;;; Ditto. | ||
| 540 | ;;;;; | ||
| 541 | ; There's a blank line above. | ||
| 542 | def ;; margin comment | ||
| 543 | ghi)" | ||
| 544 | read-with-comments))) | ||
| 545 | (call-with-output-string | ||
| 546 | (lambda (port) | ||
| 547 | (pretty-print-with-comments port sexp | ||
| 548 | #:format-comment | ||
| 549 | canonicalize-comment))))) | ||
| 550 | 369 | ||
| 551 | (test-end) | 370 | (test-end) |
| 552 | 371 | ||
