#!/usr/bin/env racket #lang racket ;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-FileCopyrightText: Copyright © 2021 Tony Garnock-Jones (require file/glob) (require racket/date) (define ((re p) i) (regexp-match p i)) (define ((re? p) i) (regexp-match? p i)) (define ((s p ins) i) (regexp-replace p i ins)) (define this-year (number->string (date-year (current-date)))) (define (get-git-config key) (string-trim (with-output-to-string (lambda () (system* "/usr/bin/env" "git" "config" "--get" key))))) (define (is-tracked? f) (call-with-output-file "/dev/null" #:exists 'append (lambda (sink) (parameterize ((current-error-port sink) (current-output-port sink)) (system* "/usr/bin/env" "git" "ls-files" "--error-unmatch" f))))) (define user-name (get-git-config "user.name")) (define user-email (get-git-config "user.email")) (define user (format "~a <~a>" user-name user-email)) (define (make-copyright who low [hi #f]) (if (and hi (not (string=? low hi))) (format "Copyright © ~a-~a ~a" low hi who) (format "Copyright © ~a ~a" low who))) (define total-file-count 0) (define total-changed-files 0) (define dry-run? #f) (define modify-untracked? #f) (define (fix-files #:file-type-name file-type-name #:file-pattern file-pattern #:front-matter-re [front-matter-re #f] #:leading-comment-re leading-comment-re #:comment-prefix comment-prefix #:file-filter [file-filter (lambda (x) #t)]) (define matched-files (filter file-filter (glob file-pattern))) (define file-count (length matched-files)) (define changed-files 0) (for [(file-number (in-naturals)) (f (in-list matched-files))] (printf "~a [~a/~a] ~a ..." file-type-name file-number file-count f) (flush-output) (define all-lines (file->lines f)) (define-values (front-matter head tail) (let*-values (((lines) all-lines) ((front-matter lines) (if front-matter-re (splitf-at lines (re? front-matter-re)) (values '() lines))) ((head tail) (splitf-at lines (re? leading-comment-re)))) (values front-matter head tail))) (let* ((head (map (s leading-comment-re "") head)) (head (map (lambda (l) (match (regexp-match "^([^:]+): (.*)$" l) [(list _ k v) (list k v)] [#f (list #f l)])) head)) (head (if (assoc "SPDX-FileCopyrightText" head) head (cons (list "SPDX-FileCopyrightText" (make-copyright user this-year)) head))) (head (if (assoc "SPDX-License-Identifier" head) head (cons (list "SPDX-License-Identifier" "LGPL-3.0-or-later") head))) (head (map (lambda (l) (match l [(list "SPDX-FileCopyrightText" (and (regexp (regexp-quote user-name)) (regexp #px"(\\d{4})-\\d{4}" (list _ low)))) (list "SPDX-FileCopyrightText" (make-copyright user low this-year))] [(list "SPDX-FileCopyrightText" (and (regexp (regexp-quote user-name)) (regexp #px"\\d{4}" (list low)))) (list "SPDX-FileCopyrightText" (make-copyright user low this-year))] [_ l])) head)) (head (map (lambda (l) (if (string=? (cadr l) "") (string-trim comment-prefix) (string-append comment-prefix (match l [(list #f v) v] [(list k v) (format "~a: ~a" k v)])))) head)) (new-lines `(,@front-matter ,@head "" ,@(dropf tail (lambda (l) (string=? (string-trim l) ""))))) (would-change-if-written? (not (equal? all-lines new-lines))) (write-needed? (and would-change-if-written? (or modify-untracked? (is-tracked? f))))) (when (and write-needed? (not dry-run?)) (call-with-atomic-output-file f (lambda (port _tmp-path) (for [(l front-matter)] (displayln l port)) (for [(l head)] (displayln l port)) (newline port) (for [(l (dropf tail (lambda (l) (string=? (string-trim l) ""))))] (displayln l port))))) (if write-needed? (begin (set! changed-files (+ changed-files 1)) (printf "\e[41mchanged\e[0m\n")) (printf "\r\e[K")))) (when (positive? changed-files) (printf "~a [~a total files, ~a changed]\n" file-type-name file-count changed-files)) (set! total-file-count (+ total-file-count file-count)) (set! total-changed-files (+ total-changed-files changed-files))) (command-line #:once-each [("-n" "--dry-run") "Do not write back changes to files" (set! dry-run? #t)] [("--modify-untracked") "Modify files not tracked by git as well as those that are" (set! modify-untracked? #t)]) (void (fix-files #:file-type-name "Racket" #:file-pattern "**.rkt" #:front-matter-re #px"^#" #:leading-comment-re #px"^;+ *" #:comment-prefix ";;; ")) (printf "fixcopyright: ~a files examined, ~a ~a\n" total-file-count total-changed-files (if dry-run? (if (zero? total-changed-files) "changes are needed" "files need to be updated") (if (zero? total-changed-files) "changes were needed" "files were updated"))) (void (system "chmod a+x fixcopyright.rkt")) (exit (if (positive? total-changed-files) 1 0))