style: Add support for "newline forms".

This allows us to express cases where a newline should be inserted
immediately after the head symbol of a list.

* guix/scripts/style.scm (%newline-forms): New variable.
(newline-form?): New procedure.
(pretty-print-with-comments): Handle "newline forms".
* tests/style.scm: Add test.
This commit is contained in:
Ludovic Courtès 2022-01-03 11:04:40 +01:00
parent 208a7aa17b
commit 6f892630ae
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
2 changed files with 50 additions and 6 deletions

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2021-2022 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@ -163,6 +163,19 @@
('with-output-to-file 2)
('with-input-from-file 2)))
(define %newline-forms
;; List heads that must be followed by a newline. The second argument is
;; the context in which they must appear. This is similar to a special form
;; of 1, except that indent is 1 instead of 2 columns.
(vhashq
('arguments '(package))
('sha256 '(origin source package))
('base32 '(sha256 origin))
('git-reference '(uri origin source))
('search-paths '(package))
('native-search-paths '(package))
('search-path-specification '())))
(define (prefix? candidate lst)
"Return true if CANDIDATE is a prefix of LST."
(let loop ((candidate candidate)
@ -188,6 +201,14 @@ surrounding SYMBOL."
(and (prefix? prefix context) (- level 1))))
alist))))
(define (newline-form? symbol context)
"Return true if parenthesized expressions starting with SYMBOL must be
followed by a newline."
(match (vhash-assq symbol %newline-forms)
(#f #f)
((_ . prefix)
(prefix? prefix context))))
(define (escaped-string str)
"Return STR with backslashes and double quotes escaped. Everything else, in
particular newlines, is left as is."
@ -377,6 +398,7 @@ Lists longer than LONG-LIST are written as one element per line."
(column (if overflow?
(+ indent 1)
(+ column (if delimited? 1 2))))
(newline? (newline-form? head context))
(context (cons head context)))
(if overflow?
(begin
@ -384,17 +406,26 @@ Lists longer than LONG-LIST are written as one element per line."
(display (make-string indent #\space) port))
(unless delimited? (display " " port)))
(display "(" port)
(let* ((new-column (loop column column #t context head))
(indent (if (or (>= new-column max-width)
(not (symbol? head))
(sequence-would-protrude?
(+ new-column 1) tail))
(+ new-column 1) tail)
newline?)
column
(+ new-column 1))))
(define column
(print-sequence context indent new-column tail #f))
(when newline?
;; Insert a newline right after HEAD.
(newline port)
(display (make-string indent #\space) port))
(let ((column
(print-sequence context indent
(if newline? indent new-column)
tail newline?)))
(display ")" port)
(+ column 1))))
(+ column 1)))))
(_
(let* ((str (if (string? obj)
(escaped-string obj)

@ -465,6 +465,19 @@ mnopqrstuvwxyz.\")"
;; Regular indentation for 'replace' here.
(replace \"gmp\" gmp))")
(test-pretty-print "\
(package
;; Here 'sha256', 'base32', and 'arguments' must be
;; immediately followed by a newline.
(source (origin
(method url-fetch)
(sha256
(base32
\"not a real base32 string\"))))
(arguments
'(#:phases %standard-phases
#:tests? #f)))")
(test-end)
;; Local Variables: