Merge branch 'master' into staging
[jackhill/guix/guix.git] / build-aux / update-NEWS.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2017 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 ;;; Commentary:
20 ;;;
21 ;;; This script updates the list of new and updated packages in 'NEWS'.
22 ;;;
23 ;;; Code:
24
25 (use-modules (gnu) (guix)
26 (guix build utils)
27 ((guix ui) #:select (fill-paragraph))
28 (srfi srfi-1)
29 (srfi srfi-11)
30 (ice-9 match)
31 (ice-9 rdelim)
32 (ice-9 regex)
33 (ice-9 pretty-print))
34
35 (define %header-rx
36 (make-regexp "^\\* Changes in (version )?([0-9.]+) \\(since ([0-9.]+)\\)"))
37
38 (define (NEWS->versions port)
39 "Return two values: the previous version and the current version as read
40 from PORT, which is an input port on the 'NEWS' file."
41 (let loop ()
42 (let ((line (read-line port)))
43 (cond ((eof-object? line)
44 (error "failed to determine previous and current version"
45 port))
46 ((regexp-exec %header-rx line)
47 =>
48 (lambda (match)
49 (values (match:substring match 3)
50 (match:substring match 2))))
51 (else
52 (loop))))))
53
54 (define (skip-to-org-heading port)
55 "Read from PORT until an Org heading is found."
56 (let loop ()
57 (let ((next (peek-char port)))
58 (cond ((eqv? next #\*)
59 #t)
60 ((eof-object? next)
61 (error "next heading could not be found"))
62 (else
63 (read-line port)
64 (loop))))))
65
66 (define (rewrite-org-section input output heading-rx proc)
67 "Write to OUTPUT the text read from INPUT, but with the first Org section
68 matching HEADING-RX replaced by NEW-HEADING and CONTENTS."
69 (let loop ()
70 (let ((line (read-line input)))
71 (cond ((eof-object? line)
72 (error "failed to match heading regexp" heading-rx))
73 ((regexp-exec heading-rx line)
74 =>
75 (lambda (match)
76 (proc match output)
77 (skip-to-org-heading input)
78 (dump-port input output)
79 #t))
80 (else
81 (display line output)
82 (newline output)
83 (loop))))))
84
85 (define (enumeration->paragraph lst)
86 "Turn LST, a list of strings, into a single string that is a ready-to-print
87 paragraph."
88 (fill-paragraph (string-join (sort lst string<?) ", ")
89 75))
90
91 (define (write-packages-added news-file old new)
92 "Write to NEWS-FILE the list of packages added between OLD and NEW."
93 (let ((added (lset-difference string=? (map car new) (map car old))))
94 (with-atomic-file-replacement news-file
95 (lambda (input output)
96 (rewrite-org-section input output
97 (make-regexp "^(\\*+) (.*) new packages")
98 (lambda (match port)
99 (let ((stars (match:substring match 1)))
100 (format port
101 "~a ~a new packages~%~%~a~%~%"
102 stars (length added)
103 (enumeration->paragraph added)))))))))
104
105 (define (write-packages-updates news-file old new)
106 "Write to NEWS-FILE the list of packages upgraded between OLD and NEW."
107 (let ((upgraded (filter-map (match-lambda
108 ((package . new-version)
109 (match (assoc package old)
110 ((_ . old-version)
111 (and (version>? new-version old-version)
112 (string-append package "@"
113 new-version)))
114 (_ #f))))
115 new)))
116 (with-atomic-file-replacement news-file
117 (lambda (input output)
118 (rewrite-org-section input output
119 (make-regexp "^(\\*+) (.*) package updates")
120 (lambda (match port)
121 (let ((stars (match:substring match 1)))
122 (format port
123 "~a ~a package updates~%~%~a~%~%"
124 stars (length upgraded)
125 (enumeration->paragraph upgraded)))))))))
126
127 \f
128 (define (main . args)
129 (match args
130 ((news-file data-directory)
131 ;; Don't browse things listed in the user's $GUIX_PACKAGE_PATH. Here we
132 ;; assume that the last item in (%package-module-path) is the distro
133 ;; directory.
134 (parameterize ((%package-module-path
135 (list (last (%package-module-path)))))
136 (define (package-file version)
137 (string-append data-directory "/packages-"
138 version ".txt"))
139
140 (let-values (((previous-version new-version)
141 (call-with-input-file news-file NEWS->versions)))
142 (let* ((old (call-with-input-file (package-file previous-version)
143 read))
144 (new (fold-packages (lambda (p r)
145 (alist-cons (package-name p) (package-version p)
146 r))
147 '())))
148 (call-with-output-file (package-file new-version)
149 (lambda (port)
150 (pretty-print new port)))
151
152 (write-packages-added news-file old new)
153 (write-packages-updates news-file old new)))))
154 (x
155 (format (current-error-port) "Usage: update-NEWS NEWS-FILE DATA-DIRECTORY
156
157 Update the list of new and updated packages in NEWS-FILE using the
158 previous-version package list from DATA-DIRECTORY.\n")
159 (exit 1))))
160
161 (apply main (cdr (command-line)))