>From d42829fe03271e633e43cc35cf277705203e6080 Mon Sep 17 00:00:00 2001 From: Alex Kost
Date: Thu, 18 Sep 2014 16:24:02 +0400 Subject: [PATCH 2/3] emacs: Rewrite scheme side in a functional manner. * emacs/guix-main.scm: Rewrite in a functional way. Add support for output entries. (%current-manifest, %current-manifest-entries-table, set-current-manifest-maybe!): Replace with... (mentries->hash-table, manifest->hash-table): ... this. (manifest-entries-by-name+version): Replace with... (mentries-by-name): ... this. (fold-manifest-entries): Rename to... (fold-manifest-by-name): ... this. (package-installed-param-alist): Rename to... (%mentry-param-alist): ... this. (package-param-alist): Rename to... (%package-param-alist): this. (manifest-entry->installed-entry): Rename to... (mentry->alist): ... this. (matching-generation-entries): Replace with... (matching-generations): ... this. (last-generation-entries): Replace with... (last-generations): ... this. (manifest-entries->installed-entries, installed-entries-by-name+version, installed-entries-by-package, matching-package-entries, fold-object, package-entries-by-name+version, package-entries-by-spec, package-entries-by-regexp, package-entries-by-ids, newest-available-package-entries, all-available-package-entries, manifest-package-entries, installed-package-entries, generation-package-entries, obsolete-package-entries, all-generation-entries, generation-entries-by-ids, %package-entries-functions, %generation-entries-functions): Remove. (manifest=?, mentry->name+version+output, mentry-by-output, list-maybe, matching-packages, filter-packages-by-output, packages-by-name, mentry->packages, all-available-packages, newest-available-packages, spec->package-pattern, spec->output-pattern, id->package-pattern, id->output-pattern, specs->package-patterns, specs->output-patterns, ids->package-patterns, ids->output-patterns, obsolete-package-patterns, obsolete-output-patterns, manifest-package-patterns, manifest-output-patterns, make-installed-alists, make-package-entry, make-output-entry, make-obsolete-output-entry, package-pattern-transformer, output-pattern-transformer, entry-type-error, search-type-error, pattern-transformer, patterns-maker, get-package/output-entries, find-generations, get-generation-entries): New procedures. (%pattern-transformers, %patterns-makers): New variables. (get-entries): Use 'get-package/output-entries', 'get-generation-entries'. * emacs/guix-base.el (guix-continue-package-operation-p): Adjust accordingly. * emacs/guix-info.el (guix-package-info-insert-action-button): Likewise. --- emacs/guix-base.el | 6 +- emacs/guix-info.el | 3 +- emacs/guix-main.scm | 772 ++++++++++++++++++++++++++++++++-------------------- 3 files changed, 480 insertions(+), 301 deletions(-) diff --git a/emacs/guix-base.el b/emacs/guix-base.el index d4ac643..1959814 100644 --- a/emacs/guix-base.el +++ b/emacs/guix-base.el @@ -563,9 +563,9 @@ See `guix-process-package-actions' for details." (or (null guix-operation-confirm) (let* ((entries (guix-get-entries 'package 'id - (list (append (mapcar #'car install) - (mapcar #'car upgrade) - (mapcar #'car remove))) + (append (mapcar #'car install) + (mapcar #'car upgrade) + (mapcar #'car remove)) '(id name version location))) (install-strings (guix-get-package-strings install entries)) (upgrade-strings (guix-get-package-strings upgrade entries)) diff --git a/emacs/guix-info.el b/emacs/guix-info.el index e7fc7f0..05281e7 100644 --- a/emacs/guix-info.el +++ b/emacs/guix-info.el @@ -512,7 +512,8 @@ ENTRY is an alist with package info." (button-get btn 'output))))) (concat type-str " '" full-name "'") 'action-type type - 'id (guix-get-key-val entry 'id) + 'id (or (guix-get-key-val entry 'package-id) + (guix-get-key-val entry 'id)) 'output output))) (defun guix-package-info-insert-output-path (path &optional _) diff --git a/emacs/guix-main.scm b/emacs/guix-main.scm index 1383d08..9295894 100644 --- a/emacs/guix-main.scm +++ b/emacs/guix-main.scm @@ -24,11 +24,12 @@ ;; this code. So to distinguish, just "package" in the name of a ;; function means a guile object ("package" record) while ;; "package entry" means alist of package parameters and values (see -;; ‘package-param-alist’). +;; ‘%package-param-alist’). ;; ;; "Entry" is probably not the best name for such alists, because there ;; already exists "manifest-entry" which has nothing to do with the -;; "entry" described above. Do not be confused :) +;; "entry" described above. Do not be confused. "Manifest entries" are +;; shortened to "mentries" in this file. ;; ‘get-entries’ function is the “entry point” for the elisp side to get ;; information about packages and generations. @@ -46,7 +47,7 @@ ;; ;; ‘installed’ parameter of a package entry contains information about ;; installed outputs. It is a list of "installed entries" (see -;; ‘package-installed-param-alist’). +;; ‘%mentry-param-alist’). ;; To speed-up the process of getting information, the following ;; auxiliary variables are used: @@ -55,10 +56,6 @@ ;; ;; - `%package-table' - Hash table of ;; "name+version key"/"list of packages" pairs. -;; -;; - `%current-manifest-entries-table' - Hash table of -;; "name+version key"/"list of manifest entries" pairs. This variable -;; is set by `set-current-manifest-maybe!' when it is needed. ;;; Code: @@ -97,9 +94,6 @@ (define name+version->key cons) (define key->name+version car+cdr) -(define %current-manifest #f) -(define %current-manifest-entries-table #f) - (define %packages (fold-packages (lambda (pkg res) (vhash-consq (object-address pkg) pkg res)) @@ -119,90 +113,74 @@ %packages) table)) -;; FIXME get rid of this function! -(define (set-current-manifest-maybe! profile) - (define (manifest-entries->hash-table entries) - (let ((entries-table (make-hash-table (length entries)))) - (for-each (lambda (entry) - (let* ((key (name+version->key - (manifest-entry-name entry) - (manifest-entry-version entry))) - (ref (hash-ref entries-table key))) - (hash-set! entries-table key - (if ref (cons entry ref) (list entry))))) - entries) - entries-table)) - - (when profile - (let ((manifest (profile-manifest profile))) - (unless (and (manifest? %current-manifest) - (equal? manifest %current-manifest)) - (set! %current-manifest manifest) - (set! %current-manifest-entries-table - (manifest-entries->hash-table - (manifest-entries manifest))))))) - -(define (manifest-entries-by-name+version name version) - (or (hash-ref %current-manifest-entries-table - (name+version->key name version)) - '())) - -(define (packages-by-name+version name version) - (or (hash-ref %package-table - (name+version->key name version)) - '())) - -(define (packages-by-full-name full-name) - (call-with-values - (lambda () (full-name->name+version full-name)) - packages-by-name+version)) - -(define (package-by-address address) - (and=> (vhash-assq address %packages) - cdr)) - -(define (packages-by-id id) - (if (integer? id) - (let ((pkg (package-by-address id))) - (if pkg (list pkg) '())) - (packages-by-full-name id))) - -(define (package-by-id id) - (first-or-false (packages-by-id id))) - -(define (newest-package-by-id id) - (and=> (id->name+version id) - (lambda (name) - (first-or-false (find-best-packages-by-name name #f))))) - -(define (id->name+version id) - (if (integer? id) - (and=> (package-by-address id) - (lambda (pkg) - (values (package-name pkg) - (package-version pkg)))) - (full-name->name+version id))) +(define (mentry->name+version+output mentry) + (values + (manifest-entry-name mentry) + (manifest-entry-version mentry) + (manifest-entry-output mentry))) + +(define (mentries->hash-table mentries) + "Return hash table of name keys and lists of matching MENTRIES." + (let ((table (make-hash-table (length mentries)))) + (for-each (lambda (mentry) + (let* ((key (manifest-entry-name mentry)) + (ref (hash-ref table key))) + (hash-set! table key + (if ref (cons mentry ref) (list mentry))))) + mentries) + table)) -(define (fold-manifest-entries proc init) - "Fold over `%current-manifest-entries-table'. -Call (PROC NAME VERSION ENTRIES RESULT) for each element of the hash -table, using INIT as the initial value of RESULT." - (hash-fold (lambda (key entries res) - (let-values (((name version) (key->name+version key))) - (proc name version entries res))) +(define (manifest=? m1 m2) + (or (eq? m1 m2) + (equal? m1 m2))) + +(define manifest->hash-table + (let ((current-manifest #f) + (current-table #f)) + (lambda (manifest) + "Return hash table of name keys and lists of matching MANIFEST entries." + (unless (manifest=? manifest current-manifest) + (set! current-manifest manifest) + (set! current-table (mentries->hash-table + (manifest-entries manifest)))) + current-table))) + +(define* (mentries-by-name manifest name #:optional version output) + "Return list of MANIFEST entries matching NAME, VERSION and OUTPUT." + (let ((mentries (or (hash-ref (manifest->hash-table manifest) name) + '()))) + (if (or version output) + (filter (lambda (mentry) + (and (or (not version) + (equal? version (manifest-entry-version mentry))) + (or (not output) + (equal? output (manifest-entry-output mentry))))) + mentries) + mentries))) + +(define (mentry-by-output mentries output) + (find (lambda (mentry) + (string= output (manifest-entry-output mentry))) + mentries)) + +(define (fold-manifest-by-name manifest proc init) + "Fold over MANIFEST entries. +Call (PROC NAME VERSION MENTRIES RESULT), using INIT as the initial value +of RESULT. MENTRIES is a list of manifest entries with NAME/VERSION." + (hash-fold (lambda (name mentries res) + (proc name (manifest-entry-version (car mentries)) + mentries res)) init - %current-manifest-entries-table)) + (manifest->hash-table manifest))) -(define (fold-object proc init obj) - (fold proc init - (if (list? obj) obj (list obj)))) +(define (list-maybe obj) + (if (list? obj) obj (list obj))) (define* (object-transformer param-alist #:optional (params '())) - "Return function for transforming an object into alist of parameters/values. + "Return function for transforming objects into alist of parameters/values. -PARAM-ALIST is alist of available object parameters (symbols) and functions -returning values of these parameters. Each function is called with object as -a single argument. +PARAM-ALIST is alist of available parameters (symbols) and functions returning +values of these parameters. Each function is applied to objects. PARAMS is list of parameters from PARAM-ALIST that should be returned by a resulting function. If PARAMS is not specified or is an empty list, use all @@ -224,31 +202,19 @@ Example: (cons param fun))) (_ #f)) param-alist)))) - (lambda (object) + (lambda objects (map (match-lambda ((param . fun) - (cons param (fun object)))) + (cons param (apply fun objects)))) alist)))) -(define package-installed-param-alist - (list - (cons 'output manifest-entry-output) - (cons 'path manifest-entry-item) - (cons 'dependencies manifest-entry-dependencies))) - -(define manifest-entry->installed-entry - (object-transformer package-installed-param-alist)) - -(define (manifest-entries->installed-entries entries) - (map manifest-entry->installed-entry entries)) - -(define (installed-entries-by-name+version name version) - (manifest-entries->installed-entries - (manifest-entries-by-name+version name version))) +(define %mentry-param-alist + `((output . ,manifest-entry-output) + (path . ,manifest-entry-item) + (dependencies . ,manifest-entry-dependencies))) -(define (installed-entries-by-package package) - (installed-entries-by-name+version (package-name package) - (package-version package))) +(define mentry->alist + (object-transformer %mentry-param-alist)) (define (package-inputs-names inputs) "Return list of full names of the packages from package INPUTS." @@ -260,89 +226,112 @@ Example: (define (package-license-names package) "Return list of license names of the PACKAGE." - (fold-object (lambda (license res) - (if (license? license) - (cons (license-name license) res) - res)) - '() - (package-license package))) + (filter-map (lambda (license) + (and (license? license) + (license-name license))) + (list-maybe (package-license package)))) (define (package-unique? package) "Return #t if PACKAGE is a single package with such name/version." - (null? (cdr (packages-by-name+version (package-name package) - (package-version package))))) - -(define package-param-alist - (list - (cons 'id object-address) - (cons 'name package-name) - (cons 'version package-version) - (cons 'license package-license-names) - (cons 'synopsis package-synopsis) - (cons 'description package-description) - (cons 'home-url package-home-page) - (cons 'outputs package-outputs) - (cons 'non-unique (negate package-unique?)) - (cons 'inputs (lambda (pkg) (package-inputs-names - (package-inputs pkg)))) - (cons 'native-inputs (lambda (pkg) (package-inputs-names - (package-native-inputs pkg)))) - (cons 'propagated-inputs (lambda (pkg) (package-inputs-names - (package-propagated-inputs pkg)))) - (cons 'location (lambda (pkg) (location->string - (package-location pkg)))) - (cons 'installed installed-entries-by-package))) + (null? (cdr (packages-by-name (package-name package) + (package-version package))))) + +(define %package-param-alist + `((id . ,object-address) + (package-id . ,object-address) + (name . ,package-name) + (version . ,package-version) + (license . ,package-license-names) + (synopsis . ,package-synopsis) + (description . ,package-description) + (home-url . ,package-home-page) + (outputs . ,package-outputs) + (non-unique . ,(negate package-unique?)) + (inputs . ,(lambda (pkg) + (package-inputs-names + (package-inputs pkg)))) + (native-inputs . ,(lambda (pkg) + (package-inputs-names + (package-native-inputs pkg)))) + (propagated-inputs . ,(lambda (pkg) + (package-inputs-names + (package-propagated-inputs pkg)))) + (location . ,(lambda (pkg) + (location->string (package-location pkg)))))) (define (package-param package param) "Return the value of a PACKAGE PARAM." - (define (accessor param) - (and=> (assq param package-param-alist) - cdr)) - (and=> (accessor param) + (and=> (assq-ref %package-param-alist param) (cut <> package))) -(define (matching-package-entries ->entry predicate) - "Return list of package entries for the matching packages. -PREDICATE is called on each package." + +;;; Finding packages + +(define (package-by-address address) + (and=> (vhash-assq address %packages) + cdr)) + +(define (packages-by-name+version name version) + (or (hash-ref %package-table + (name+version->key name version)) + '())) + +(define (packages-by-full-name full-name) + (call-with-values + (lambda () (full-name->name+version full-name)) + packages-by-name+version)) + +(define (packages-by-id id) + (if (integer? id) + (let ((pkg (package-by-address id))) + (if pkg (list pkg) '())) + (packages-by-full-name id))) + +(define (id->name+version id) + (if (integer? id) + (and=> (package-by-address id) + (lambda (pkg) + (values (package-name pkg) + (package-version pkg)))) + (full-name->name+version id))) + +(define (package-by-id id) + (first-or-false (packages-by-id id))) + +(define (newest-package-by-id id) + (and=> (id->name+version id) + (lambda (name) + (first-or-false (find-best-packages-by-name name #f))))) + +(define (matching-packages predicate) (fold-packages (lambda (pkg res) (if (predicate pkg) - (cons (->entry pkg) res) + (cons pkg res) res)) '())) -(define (make-obsolete-package-entry name version entries) - "Return package entry for an obsolete package with NAME and VERSION. -ENTRIES is a list of manifest entries used to get installed info." - `((id . ,(name+version->full-name name version)) - (name . ,name) - (version . ,version) - (outputs . ,(map manifest-entry-output entries)) - (obsolete . #t) - (installed . ,(manifest-entries->installed-entries entries)))) - -(define (package-entries-by-name+version ->entry name version) - "Return list of package entries for packages with NAME and VERSION." - (let ((packages (packages-by-name+version name version))) - (if (null? packages) - (let ((entries (manifest-entries-by-name+version name version))) - (if (null? entries) - '() - (list (make-obsolete-package-entry name version entries)))) - (map ->entry packages)))) +(define (filter-packages-by-output packages output) + (filter (lambda (package) + (member output (package-outputs package))) + packages)) + +(define* (packages-by-name name #:optional version output) + "Return list of packages matching NAME, VERSION and OUTPUT." + (let ((packages (if version + (packages-by-name+version name version) + (matching-packages + (lambda (pkg) (string=? name (package-name pkg))))))) + (if output + (filter-packages-by-output packages output) + packages))) -(define (package-entries-by-spec profile ->entry spec) - "Return list of package entries for packages with name specification SPEC." - (set-current-manifest-maybe! profile) - (let-values (((name version) - (full-name->name+version spec))) - (if version - (package-entries-by-name+version ->entry name version) - (matching-package-entries - ->entry - (lambda (pkg) (string=? name (package-name pkg))))))) +(define (mentry->packages mentry) + (call-with-values + (lambda () (mentry->name+version+output mentry)) + packages-by-name)) -(define (package-entries-by-regexp profile ->entry regexp match-params) - "Return list of package entries for packages matching REGEXP string. +(define (packages-by-regexp regexp match-params) + "Return list of packages matching REGEXP string. MATCH-PARAMS is a list of parameters that REGEXP can match." (define (package-match? package regexp) (any (lambda (param) @@ -350,81 +339,297 @@ MATCH-PARAMS is a list of parameters that REGEXP can match." (and (string? val) (regexp-exec regexp val)))) match-params)) - (set-current-manifest-maybe! profile) (let ((re (make-regexp regexp regexp/icase))) - (matching-package-entries ->entry (cut package-match? <> re)))) - -(define (package-entries-by-ids profile ->entry ids) - "Return list of package entries for packages matching KEYS. -IDS may be an object-address, a full-name or a list of such elements." - (set-current-manifest-maybe! profile) - (fold-object - (lambda (id res) - (if (integer? id) - (let ((pkg (package-by-address id))) - (if pkg - (cons (->entry pkg) res) - res)) - (let ((entries (package-entries-by-spec #f ->entry id))) - (if (null? entries) - res - (append res entries))))) - '() - ids)) - -(define (newest-available-package-entries profile ->entry) - "Return list of package entries for the newest available packages." - (set-current-manifest-maybe! profile) + (matching-packages (cut package-match? <> re)))) + +(define (all-available-packages) + "Return list of all available packages." + (matching-packages (const #t))) + +(define (newest-available-packages) + "Return list of the newest available packages." (vhash-fold (lambda (name elem res) (match elem - ((version newest pkgs ...) - (cons (->entry newest) res)))) + ((_ newest pkgs ...) + (cons newest res)))) '() (find-newest-available-packages))) -(define (all-available-package-entries profile ->entry) - "Return list of package entries for all available packages." - (set-current-manifest-maybe! profile) - (matching-package-entries ->entry (const #t))) + +;;; Making package/output patterns -(define (manifest-package-entries ->entry) - "Return list of package entries for the current manifest." - (fold-manifest-entries - (lambda (name version entries res) - ;; We don't care about duplicates for the list of - ;; installed packages, so just take any package (car) - ;; matching name+version - (cons (car (package-entries-by-name+version ->entry name version)) - res)) - '())) +(define (spec->package-pattern spec) + (call-with-values + (lambda () (full-name->name+version spec)) + list)) + +(define (spec->output-pattern spec) + (call-with-values + (lambda () (package-specification->name+version+output spec #f)) + list)) + +(define (id->package-pattern id) + (if (integer? id) + (package-by-address id) + (spec->package-pattern id))) + +(define (id->output-pattern id) + ;; id should be "