;; -*- mode: scheme -*- (import (only chicken.platform repository-path include-path) (only chicken.io read-line read-string read-list) (only chicken.process process-run process-wait) (only chicken.process-context command-line-arguments) (only chicken.string string-intersperse string-split ->string) (only chicken.file directory) (only chicken.pathname make-pathname pathname-file pathname-extension) (only chicken.condition condition-case print-error-message) (only chicken.irregex string->irregex irregex-search irregex-split irregex-extract) (only chicken.condition handle-exceptions print-error-message) (only chicken.port with-output-to-port) (only srfi-1 filter append-map lset-union) (only http-client call-with-input-request) (only uri-common uri-reference) (only optimism parse-command-line)) (cond-expand ((not chicken-5) (import (only (scheme base) make-parameter))) (else)) (define upstream-egg-list-uri (cond-expand (chicken-5 (make-parameter "https://eggs.call-cc.org/rss-5.xml")) (chicken-6 (make-parameter "https://eggs.call-cc.org/rss-6.xml")))) (define pin-regex (make-parameter #f (lambda (param) (and param (string->irregex param))))) (define options (make-parameter '())) (define opts `((-h) ((--help -h)) (--pin . ,pin-regex) (--upstream-egg-list-uri . ,upstream-egg-list-uri) (--local-only) (--unknown))) (define (usage status) (print "Usage: chicken-update [OPTION ...]") (print "All other options are passed directly to chicken-install.") (print " --unknown try updating eggs with unknown version --upstream-egg-list-uri location of the egg list RSS feed, by default https://eggs.call-cc.org/rss-5.xml --pin=REGEX pin eggs matching REGEX -h, --help Display this text") ;; --local-only skip the network check, use only local directories ;; specified in setup.defaults (cond-expand ((or chicken-script compiling) (exit status)) (else))) ;; (define (location-upstream-eggs) ;; (let* ((setup.defaults (call-with-input-file (make-pathname (include-path) "setup.defaults") read-list)) ;; (locations (append-map cdr (filter (lambda (kv) (eqv? (car kv) 'location)) setup.defaults)))) ;; (for-each print locations) ;; (if (null? locations) locations ;; (append-map ;; (lambda (dir) ;; (let ((eggs (directory dir))) ;; (map (lambda (egg) ;; (cons egg (car (sort (directory (make-pathname dir egg)) version>=?)))) ;; eggs))) ;; locations)))) (define (try-get-rss-xml) (let loop ((count 1) (exn #f)) (if (= count 3) (with-output-to-port (current-error-port) (begin (display "Unable to fetch eggs after 3 tries" (current-error-port)) (print-error-message exn (current-error-port)) (exit 1))) (condition-case (call-with-input-request (uri-reference (upstream-egg-list-uri)) #f (cut read-string #f <>)) [exn (exn i/o net) (loop (+ count 1) exn)] )))) (define (rss-upstream-eggs) (let ((raw-titles (cdr (irregex-extract "[^<]+" (try-get-rss-xml))))) (map (lambda (s) (let ((parts (string-split s "> "))) (cons (cadr parts) (caddr parts)))) raw-titles))) ;; (define (upstream-eggs) ;; (let* ((local-upstream (location-upstream-eggs)) ;; (rss-upstream (if (assv '--local-only (options)) ;; '() ;; (rss-upstream-eggs)))) ;; (lset-union (lambda (a b) (equal? (car a) (car b))) ;; local-upstream ;; rss-upstream))) (define (installed-eggs) (define (is-egginfo? f) (let ((ext (pathname-extension f))) (and ext (string=? (pathname-extension f) "egg-info")))) (define (get-egginfo-abspath d) (map (cut make-pathname d <> ) (filter is-egginfo? (directory d)))) (define (egginfo-version f) (and-let* ((version (alist-ref 'version (call-with-input-file f read) eqv?))) (car version))) (let* ((egginfo-files (join (map get-egginfo-abspath (repository-path)))) (egg-names (map pathname-file egginfo-files)) (egg-versions (map egginfo-version egginfo-files))) (map cons egg-names egg-versions))) (define (version>=? v1 v2) (define (version->list v) (map (lambda (x) (or (string->number x) x)) (irregex-split "[-\\._]" (->string v)))) (let loop ((p1 (version->list v1)) (p2 (version->list v2))) (cond ((null? p1) (null? p2)) ((null? p2)) ((number? (car p1)) (and (number? (car p2)) (or (> (car p1) (car p2)) (and (= (car p1) (car p2)) (loop (cdr p1) (cdr p2)))))) ((number? (car p2))) ((string>? (car p1) (car p2))) (else (and (string=? (car p1) (car p2)) (loop (cdr p1) (cdr p2))))))) (define (outdated-eggs) (let ((try-unknown? (assv '--unknown (options)))) (define (try-update-egg? egg) (let ((pin (pin-regex))) (and (if try-unknown? #t (cdr egg)) (if pin (not (irregex-search pin (car egg))) #t)))) (let* ((installed (filter try-update-egg? (installed-eggs))) (upstream (rss-upstream-eggs))) (apply values (let loop ((installed installed) (outdated '()) (not-upstream '())) (if (null? installed) (list outdated not-upstream) (let* ((egg (car installed)) (upstream-egg (assoc (car egg) upstream)) (egg-outdated? (and upstream-egg (or (and try-unknown? (not (cdr egg))) (not (version>=? (cdr egg) (cdr upstream-egg))))))) (loop (cdr installed) (if egg-outdated? (cons (list (car egg) (cdr egg) (cdr upstream-egg)) outdated) outdated) (if upstream-egg not-upstream (cons (car egg) not-upstream)))))))))) (define (run-chicken-install args) (let* ((pid (process-run "chicken-install" args))) (receive (pid normal-exit exit-status) (process-wait pid) (if (not (zero? exit-status)) (begin (display "chicken-install finished with a non-zero exit status.\n" (current-error-port)) (exit 1)) (print "Done updating eggs."))))) (define (print-table rows column-spacing) (let* ((width-info (let loop ((rows rows) (max-lengths '(0 0 0)) (collect '())) (if (null? rows) (cons max-lengths collect) (let* ((row (car rows)) (lengths (map string-length row)) (max-lengths (map max max-lengths lengths))) (loop (cdr rows) max-lengths (cons (map cons row lengths) collect)))))) (widths (car width-info)) (rows (cdr width-info))) (for-each (lambda (row) (let loop ((widths widths) (cells row)) (if (null? widths) #t (begin (display (caar cells)) (display (make-string (- (car widths) (cdar cells) (- column-spacing)) #\space)) (loop (cdr widths) (cdr cells))))) (newline)) (reverse rows)))) (define (print-outdated-eggs eggs) (let* ((num-eggs (length eggs)) (eggs (cons `(,(string-append "Eggs (" (number->string num-eggs) ")") "Old Version" "New Version") eggs))) (print-table eggs 2))) (define (main args) (options (handle-exceptions exn (begin (print-error-message exn (current-error-port)) (newline (current-error-port)) (usage 1)) (parse-command-line args opts))) (when (or (assv '--help (options)) (assv '-h (options))) (usage 0)) (receive (to-update not-upstream) (outdated-eggs) (when (not (null? not-upstream)) (print "The following eggs were not found upstream:") (newline) (print " " (string-intersperse not-upstream " ")) (newline)) (if (null? to-update) (print "Nothing to update.") (begin (print-outdated-eggs to-update) (display "Proceed? [y/n]: ") (when (string=? (read-string 1) "y") (run-chicken-install (append (alist-ref '-- (options)) (map car to-update)))))))) (main (command-line-arguments))