diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2017-09-21 01:06:12 -0300 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2017-09-21 01:06:12 -0300 |
commit | ab1f47b0bdefe640dd16a185da5ffef2930a7ebe (patch) | |
tree | 33f47d6940bf7f62016226bab9429b6c72ed4474 |
Import emacs-kv_0.0.19+git20140108.7211484.orig.tar.xz
[dgit import orig emacs-kv_0.0.19+git20140108.7211484.orig.tar.xz]
-rw-r--r-- | README.creole | 155 | ||||
-rw-r--r-- | kv-tests.el | 256 | ||||
-rw-r--r-- | kv.el | 462 |
3 files changed, 873 insertions, 0 deletions
diff --git a/README.creole b/README.creole new file mode 100644 index 0000000..1dfd1aa --- /dev/null +++ b/README.creole @@ -0,0 +1,155 @@ +A collection of tools for dealing with key/value data structures such +as plists, alists and hash-tables. + +=== kvalist->filter-keys alist &rest keys === + +Return the //alist// filtered to the //keys// list. + +Only pairs where the car is a [[member]] of //keys// will be returned. + + +=== kvalist->hash alist &rest hash-table-args === + +Convert //alist// to a HASH. + +//hash-table-args// are passed to the hash-table creation. + + +=== kvalist->keys alist === + +Get just the keys from the alist. + + +=== kvalist->plist alist === + +Convert an alist to a plist. + + +=== kvalist->values alist === + +Get just the values from the alist. + + +=== kvalist-keys->* alist fn === + +Convert the keys of //alist// through //fn//. + + +=== kvalist-keys->symbols alist === + +Convert the keys of //alist// into symbols. + + +=== kvalist-sort alist pred === + +Sort //alist// (by key) with //pred//. + + +=== kvalist-sort-by-value alist pred === + +Sort //alist// by value with //pred//. + + +=== kvalist2->alist alist2 car-key cdr-key &optional proper === + +Reduce the //alist2// (a list of alists) to a single alist. + +//car-key// is the key of each alist to use as the resulting key and +//cdr-key// is the key of each alist to user as the resulting cdr. + +If //proper// is [[t]] then the alist is a list of proper lists, not +cons cells. + + +=== kvalist2->filter-keys alist2 &rest keys === + +Return the //alist2// (a list of alists) filtered to the //keys//. + + +=== kvalist2->plist alist2 === + +Convert a list of alists too a list of plists. + + +=== kvcmp a b === + +Do a comparison of the two values using printable syntax. + +Use this as the function to pass to [[sort]]. + + +=== kvdotassoc expr table === + +Dotted expression handling with [[assoc]]. + + +=== kvdotassoc-fn expr table func === + +Use the dotted //expr// to access deeply nested data in //table//. + +//expr// is a dot separated expression, either a symbol or a string. +For example: + +{{{ + "a.b.c" +}}} + +or: + +{{{ + 'a.b.c +}}} + +If the //expr// is a symbol then the keys of the alist are also +expected to be symbols. + +//table// is expected to be an alist currently. + +//func// is some sort of [[assoc]] like function. + + +=== kvdotassq expr table === + +Dotted expression handling with [[assq]]. + + +=== kvhash->alist hash === + +Convert //hash// to an ALIST. + + +=== kvmap-bind args sexp seq === + +A hybrid of [[destructuring-bind]] and [[mapcar]] +//args// shall be of the form used with [[destructuring-bind]] + +Unlike most other mapping forms this is a macro intended to be +used for structural transformations, so the expected usage will +be that //args// describes the structure of the items in //seq//, and +//sexp// will describe the structure desired. + + +=== kvplist->alist plist === + +Convert //plist// to an alist. + +The keys are expected to be :prefixed and the colons are removed. +The keys in the resulting alist are symbols. + + +=== kvplist->filter-keys plist &rest keys === + +Filter the plist to just those matching //keys//. + +//keys// must actually be :-less symbols. + +[[kvalist->filter-keys]] is actually used to do this work. + +=== kvplist->merge &rest plists === + +Merge the 2nd and subsequent plists into the first, clobbering values set +by lists to the left. + +=== kvplist2->filter-keys plist2 &rest keys === + +Return the //plist2// (a list of plists) filtered to the //keys//. diff --git a/kv-tests.el b/kv-tests.el new file mode 100644 index 0000000..1713e5e --- /dev/null +++ b/kv-tests.el @@ -0,0 +1,256 @@ +(require 'kv) +(require 'ert) + +(ert-deftest kvhash->alist () + "Test making alists from hashes." + (should + (equal + (sort + (kvhash->alist + (kvalist->hash '((name1 . value1) + (name2 . value2)))) + (lambda (a b) + (string-lessp (symbol-name (car a)) + (symbol-name (car b))))) + '((name1 . value1) + (name2 . value2)))) + (should + (equal + (sort '((a . 1) + (c . 3)) 'kvcmp) + (sort (kvhash->alist + (kvalist->hash '((a . 1)(b . 2)(c . 3))) + (lambda (k v) (and (memq k '(a c)) v))) 'kvcmp)))) + +(ert-deftest kvalist-sort () + (should + (equal + (kvalist-sort + (list '("z" . 20) + '("a" . 20) + '("b" . 17)) + 'string-lessp) + '(("a" . 20) + ("b" . 17) + ("z" . 20))))) + +(ert-deftest kvalist-sort-by-value () + (should + (equal + (kvalist-sort-by-value + (list '("z" . 20) + '("a" . 20) + '("b" . 17)) + '<) + '(("b" . 17) + ("z" . 20) + ("a" . 20))))) + +(ert-deftest kvcmp () + "Test the general cmp function." + (should + (equal + '((a . 10)(b . 20)(c . 5)) + (sort '((a . 10)(b . 20)(c . 5)) 'kvcmp))) + (should + (equal + '((a . 10)(b . 20)(c . 5)) + (sort '((b . 20)(c . 5)(a . 10)) 'kvcmp)))) + +(ert-deftest kvalist-keys->symbols () + "Test the key transformation." + (should + (equal + '((a . 10)(\10 . 20)(\(a\ b\ c\) . 30)) + (kvalist-keys->symbols + '(("a" . 10)(10 . 20)((a b c) . 30))))) + (should + (equal + '((a . 10)(\10 . 20)(\(a\ b\ c\) . 30)) + (kvalist-keys->symbols + '(("A" . 10)(10 . 20)((a b c) . 30)) + :first-fn 'downcase)))) + +(ert-deftest kvfa () + "Destructuring kva through functions." + (should + (equal '("b") + (kvfa "a" '((:a :b)("a" "b")) + (lambda (key &rest result) result)))) + (should + (equal "b" + (kvfa "a" '((:a :b)("a" "b")) + (lambda (k v &rest any) v)))) + (should + (equal "b" + (kvfa "a" '((:a . :b)("a" . "b")) + (cl-function + (lambda (k v &rest any) v))))) + (should + (equal 1 + (kvfa "a" '((:a :b :c 1)("a" "b" :a 1)) + (cl-function + (lambda (k v &key a) a)))))) + +(ert-deftest kva () + "Test the simple assoc." + (should (equal :b (kva :a '((:a . :b)("a" . "b"))))) + (should (equal "b" (kva "a" '((:a . :b)("a" . "b"))))) + (should-not (kva "b" '((:a . :b)("a" . "b"))))) + +(ert-deftest kvaq () + "Test the simple assq." + (should (equal :b (kvaq :a '((:a . :b)("a" . "b"))))) + (should (equal 2 (kvaq 1 '((1 . 2)("a" . "b"))))) + (should-not (equal "b" (kvaq "a" '((:a . :b)("a" . "b"))))) + (should-not (kvaq "b" '((:a . :b)("a" . "b"))))) + +(ert-deftest kvaq () + "Test the simple assq." + (should (equal :b (kvaq :a '((:a . :b)("a" . "b"))))) + (should (equal 2 (kvaq 1 '((1 . 2)("a" . "b"))))) + (should-not (equal "b" (kvaq "a" '((:a . :b)("a" . "b"))))) + (should-not (kvaq "b" '((:a . :b)("a" . "b"))))) + +(ert-deftest kvaqc () + "Test the simple assq." + (should (equal :b (kvaqc :a '((:a . :b)("a" . "b"))))) + (should (equal 2 (kvaqc 1 '((1 . 2)("a" . "b"))))) + (should (equal "b" (kvaqc "a" '((:a . :b)("a" . "b"))))) + (should-not (kvaqc "b" '((:a . :b)("a" . "b"))))) + +(ert-deftest kvassoc= () + (should + (equal + '("testkey" . "testvalue") + (kvassoc= "testkey" "testvalue" '(("testkey" . "testvalue")))))) + +(ert-deftest kvassoq= () + (should + (equal + '(testkey . "testvalue") + (kvassoq= 'testkey "testvalue" '((testkey . "testvalue"))))) + (should + (equal + '("testkey" . "testvalue") + (kvassoq= "testkey" "testvalue" '(("testkey" . "testvalue"))))) + ;; Not sure about this - should we really find strings with symbols? + (should + (equal + '("testkey" . "testvalue") + (kvassoq= 'testkey "testvalue" '(("testkey" . "testvalue"))))) + ;; The nil case, the key isn't present + (should + (equal + nil + (kvassoq= 'blah "testvalue" '(("testkey" . "testvalue")))))) + +(ert-deftest kvalist2-filter () + (should + (equal + '(((a . 1)(b . 2))) + (kvalist2-filter + '(((a . 1)(b . 2))((c . 1)(d . 2))) + (lambda (alist) + (or + (memq 'a (kvalist->keys alist)) + (memq 'b (kvalist->keys alist)))))))) + +(ert-deftest kvquery->func () + "Test the query language." + (should + (equal + '((("a" . 1)("b" . 2))(("c" . 1)("d" . 2))) + (kvalist2-filter + '((("a" . 1)("b" . 2))(("c" . 1)("d" . 2))) + (kvquery->func '(|(= "a" 1)(= "d" 2)))))) + (should + (equal + '((("a" . 1)("b" . 2))) + (kvalist2-filter + '((("a" . 1)("b" . 2))(("c" . 1)("d" . 2))) + (kvquery->func '(= "a" 1))))) + (should + (equal + '() + (kvalist2-filter + '((("a" . 1)("b" . 2))(("c" . 1)("d" . 2))) + (kvquery->func '(&(= "a" 1)(= "c" 1)))))) + (should + (equal + '((("a" . 1)("b" . 2))) + (kvalist2-filter + '((("a" . 1)("b" . 2))(("c" . 1)("d" . 2))) + (kvquery->func '(&(= "a" 1)(= "b" 2))))))) + +(ert-deftest kvdotassoc () + (should + (equal + (dotassoc "a.b.c" '(("a" . (("b" . (("c" . 10))))))) + 10))) + +(ert-deftest kvdotassq () + (should + (equal + (dotassq 'a.b.c '((a . ((b . ((c . 10))))))) + 10))) + +(ert-deftest keyword->symbol () + "Convert keyword into a symbol without the leading `:'" + (should + (eq + 'key + (keyword->symbol :key))) + (should + (eq + 'key + (keyword->symbol 'key))) + (let ((sym (gensym))) + (should + (eq + sym + (keyword->symbol sym))))) + + +(ert-deftest kvthing->keyword () + (should (equal :one (kvthing->keyword "one"))) + (should (equal :one (kvthing->keyword ":one")))) + +(ert-deftest kvalist->plist () + "Make alists into plists." + (should + (equal + '(:a1 value1 :a2 value2) + (kvalist->plist '((a1 . value1) (a2 . value2)))))) + +(ert-deftest kvplist->alist () + "Make plists into alists." + (should + (equal + '((a1 . value1) (a2 . value2)) + (kvplist->alist '(:a1 value1 :a2 value2))))) + +(ert-deftest kvplist->filter-keys () + (should + (equal + (list :key1 "value1" :key4 10) + (kvplist->filter-keys + (list :key1 "value1" :key2 t :key3 '(big list of symbols) :key4 10) + 'key1 'key4)))) + +(ert-deftest kvplist-merge () + (should + (equal + '(:key1 "value1" :key2 "new value" :key3 "entirely new") + (kvplist-merge '(:key1 "value1" :key2 "old value") + '(:key2 "new value" :key3 "entirely new"))))) + +(ert-deftest kvplist-merge-multiple () + (should + (equal + '(:key1 "value1" :key2 "new value" :key3 "overwritten new one" :key4 "second entirely new") + (kvplist-merge '(:key1 "value1" :key2 "old value") + '(:key2 "new value" :key3 "entirely new") + '(:key3 "overwritten new one" :key4 "second entirely new"))))) + +;;; kv-tests.el ends here @@ -0,0 +1,462 @@ +;;; kv.el --- key/value data structure functions + +;; Copyright (C) 2012 Nic Ferrier + +;; Author: Nic Ferrier <nferrier@ferrier.me.uk> +;; Keywords: lisp +;; Version: 0.0.19 +;; Maintainer: Nic Ferrier <nferrier@ferrier.me.uk> +;; Created: 7th September 2012 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Some routines for working with key/value data structures like +;; hash-tables and alists and plists. + +;; This also takes over the dotassoc stuff and provides it separately. + +;;; Code: + +(eval-when-compile (require 'cl)) + + +(defun kvalist->hash (alist &rest hash-table-args) + "Convert ALIST to a HASH. + +HASH-TABLE-ARGS are passed to the hash-table creation." + (let ((table (apply 'make-hash-table hash-table-args))) + (mapc + (lambda (pair) + (puthash (car pair) (cdr pair) table)) + alist) + table)) + +(defun kvhash->alist (hash &optional func) + "Convert HASH to an ALIST. + +Optionally filter through FUNC, only non-nil values returned from +FUNC are stored as the resulting value against the converted +key." + (when hash + (let (store) + (maphash + (lambda (key value) + (when key + (if (and (functionp func)) + (let ((res (funcall func key value))) + (when res + (setq store (acons key res store)))) + ;; else no filtering, just return + (setq store (acons key value store))))) + hash) + store))) + +(defun kvfa (key alist receive) + "Call RECEIVE with whatever comes out of ALIST for KEY. + +RECEIVE can do whatever destructuring you want, the first +argument is always the car of the alist pair." + (apply receive (let ((a (assoc key alist))) + (append (list (car a)) + (if (listp (cdr a))(cdr a)(list (cdr a))))))) + +(defun kva (key alist) + "Retrieve the value assigned to KEY in ALIST. + +This uses `assoc' as the lookup mechanism." + (cdr (assoc key alist))) + +(defun kvaq (key alist) + "Retrieve the value assigned to KEY in ALIST. + +This uses `assq' as the lookup mechanism." + (cdr (assq key alist))) + +(defun kvaqc (key alist) + "Retrieve the value assigned to KEY in ALIST. + +This uses first the `assq' and then `assoc' as the lookup +mechanism." + (cdr (or (assq key alist) + (assoc key alist)))) + +(defun kvassoc= (key value alist) + "Is the value assocd to KEY in ALIST equal to VALUE? + +Returns the value looked up by KEY that passes, so normally: + + KEY . VALUE +" + (let ((v (assoc key alist))) + (and v (equal (cdr v) value) v))) + +(defun kvassoqc (key alist) + "String or symbol assoc." + (let ((v (or + (assq (if (symbolp key) key (intern key)) alist) + (or (assoc key alist) + ;; not sure about this behaviour... see test + (assoc (symbol-name key) alist))))) v)) + +(defun kvassoq= (key value alist) + "Test the VALUE with the value bound to KEY in ALIST. + +The lookup mechanism is to ensure the key is a symbol and then +use assq. Hence the name of the function being a mix of assoc +and assq. + +Returns the value looked up by KEY that passes, so normally: + + KEY . VALUE +" + (let ((v (kvassoqc key alist))) + (and v (equal (cdr v) value) v))) + +(defun kvmatch (key regex alist) + "Test the value with KEY in ALIST matches REGEX." + (let ((v (kvassoqc key alist))) + (and v (string-match regex (cdr v)) v))) + +(defun* kvquery->func (query &key + (equal-func 'kvassoc=) + (match-func 'kvmatch)) + "Turn a simple QUERY expression into a filter function. + +EQUAL-FUNC is the function that implements the equality +predicate. + +MATCH-FUNC is the function that implements the match predicate. + +The query language is: + + | a b - true if a or b is true + & a b - true only if a and b is true + = a b - true if a equals b as per the EQUAL-FUNC + ~ a b - true if a matches b as per the MATCH-FUNC + +So, for example: + + (|(= a b)(= c d)) + +Means: if `a' equals `b', or if `c' equals `d' then the +expression is true." + (flet ((query-parse (query) + (let ((part (car query)) + (rest (cdr query))) + (cond + ((eq part '|) + (cons 'or + (loop for i in rest + collect (query-parse i)))) + ((eq part '&) + (cons 'and + (loop for i in rest + collect (query-parse i)))) + ((eq part '~) + (destructuring-bind (field value) rest + (list match-func field value (quote record)))) + ((eq part '=) + (destructuring-bind (field value) rest + (list equal-func field value (quote record)))))))) + (eval `(lambda (record) ,(query-parse query))))) + +(defun kvplist2get (plist2 keyword value) + "Get the plist with KEYWORD / VALUE from the list of plists." + (loop for plist in plist2 + if (equal (plist-get plist keyword) value) + return plist)) + +(defun kvthing->keyword (str-or-symbol) + "Convert STR-OR-SYMBOL into a keyword symbol." + (let ((str + (cond + ((symbolp str-or-symbol) (symbol-name str-or-symbol)) + ((stringp str-or-symbol) str-or-symbol)))) + (intern + (if (eq (aref str 0) ?:) str (concat ":" str))))) + +(defun kvalist->plist (alist) + "Convert an alist to a plist." + ;; Why doesn't elisp provide this? + (loop for pair in alist + append (list + (kvthing->keyword + (car pair)) + (cdr pair)))) + +(defun kvacons (&rest args) + "Make an alist from the plist style args." + (kvplist->alist args)) + +(defun keyword->symbol (keyword) + "A keyword is a symbol leading with a :. + +Converting to a symbol means dropping the :." + (if (keywordp keyword) + (intern (substring (symbol-name keyword) 1)) + keyword)) + +(defun kvplist->alist (plist &optional keys-are-keywords) + "Convert PLIST to an alist. + +The keys are expected to be :prefixed and the colons are removed +unless KEYS-ARE-KEYWORDS is `t'. + +The keys in the resulting alist are always symbols." + (when plist + (loop for (key value . rest) on plist by 'cddr + collect + (cons (if keys-are-keywords + key + (keyword->symbol key)) + value)))) + +(defun kvalist2->plist (alist2) + "Convert a list of alists too a list of plists." + (loop for alist in alist2 + append + (list (kvalist->plist alist)))) + +(defun kvalist->keys (alist) + "Get just the keys from the alist." + (mapcar (lambda (pair) (car pair)) alist)) + +(defun kvalist->values (alist) + "Get just the values from the alist." + (mapcar (lambda (pair) (cdr pair)) alist)) + +(defun kvalist-sort (alist pred) + "Sort ALIST (by key) with PRED." + (sort alist (lambda (a b) (funcall pred (car a) (car b))))) + +(defun kvalist-sort-by-value (alist pred) + "Sort ALIST by value with PRED." + (sort alist (lambda (a b) (funcall pred (cdr a) (cdr b))))) + +(defun kvalist->filter-keys (alist &rest keys) + "Return the ALIST filtered to the KEYS list. + +Only pairs where the car is a `member' of KEYS will be returned." + (loop for a in alist + if (member (car a) keys) + collect a)) + +(defun kvplist->filter-keys (plist &rest keys) + "Filter the plist to just those matching KEYS. + +`kvalist->filter-keys' is actually used to do this work." + (let ((symkeys + (loop for k in keys + collect (let ((strkey (symbol-name k))) + (if (equal (substring strkey 0 1) ":") + (intern (substring strkey 1)) + k))))) + (kvalist->plist + (apply + 'kvalist->filter-keys + (cons (kvplist->alist plist) symkeys))))) + +(defun kvplist2->filter-keys (plist2 &rest keys) + "Return the PLIST2 (a list of plists) filtered to the KEYS." + (loop for plist in plist2 + collect (apply 'kvplist->filter-keys (cons plist keys)))) + +(defun kvalist2->filter-keys (alist2 &rest keys) + "Return the ALIST2 (a list of alists) filtered to the KEYS." + (loop for alist in alist2 + collect (apply 'kvalist->filter-keys (cons alist keys)))) + +(defun kvalist2->alist (alist2 car-key cdr-key &optional proper) + "Reduce the ALIST2 (a list of alists) to a single alist. + +CAR-KEY is the key of each alist to use as the resulting key and +CDR-KEY is the key of each alist to user as the resulting cdr. + +For example, if CAR-KEY is `email' and CDR-KEY is `name' the +records: + + '((user . \"nic\")(name . \"Nic\")(email . \"nic@domain\") + (user . \"jim\")(name . \"Jim\")(email . \"jim@domain\")) + +could be reduced to: + + '((\"nic@domain\" . \"Nic\") + (\"jim@domain\" . \"Jic\")) + +If PROPER is `t' then the alist is a list of proper lists, not +cons cells." + (loop for alist in alist2 + collect (apply (if proper 'list 'cons) + (list + (assoc-default car-key alist) + (assoc-default cdr-key alist))))) + +(defun kvalist-keys->* (alist fn) + "Convert the keys of ALIST through FN." + (mapcar + (lambda (pair) + (cons + (funcall fn (car pair)) + (cdr pair))) + alist)) + +(defun* kvalist-keys->symbols (alist &key (first-fn 'identity)) + "Convert the keys of ALIST into symbols. + +If key parameter FIRST-FN is present it should be a function +which will be used to first transform the string key. A popular +choice might be `downcase' for example, to cause all symbol keys +to be lower-case." + (kvalist-keys->* + alist + (lambda (key) + (intern (funcall first-fn (format "%s" key)))))) + +(defun kvalist2-filter (alist2 fn) + "Filter the list of alists with FN." + (let (value) + (loop for rec in alist2 + do (setq value (funcall fn rec)) + if value + collect rec))) + +(defun kvidentity (a b) + "Returns a cons of A B." + (cons a b)) + +(defun kvcar (a b) + "Given A B returns A." + a) + +(defun kvcdr (a b) + "Given A B returns B." + b) + +(defun kvcmp (a b) + "Do a comparison of the two values using printable syntax. + +Use this as the function to pass to `sort'." + (string-lessp (if a (format "%S" a) "") + (if b (format "%S" b) ""))) + +(defun kvqsort (lst) + "Do a sort using `kvcmp'." + (sort lst 'kvcmp)) + +(progn + (put 'kvalist-key + 'error-conditions + '(error)) + (put 'kvalist-key + 'error-message + "No such key found in alist.")) + +(defun kvalist-set-value! (alist key value) + "Destructively set the value of KEY to VALUE in ALIST. + +If the assoc is not found this adds it to alist." + (let ((cell (assoc key alist))) + (if (consp cell) + (setcdr cell value) + ;; Else what to do? + (signal 'kvalist-key (list alist key))))) + +(defun kvdotassoc-fn (expr table func) + "Use the dotted EXPR to access deeply nested data in TABLE. + +EXPR is a dot separated expression, either a symbol or a string. +For example: + + \"a.b.c\" + +or: + + 'a.b.c + +If the EXPR is a symbol then the keys of the alist are also +expected to be symbols. + +TABLE is expected to be an alist currently. + +FUNC is some sort of `assoc' like function." + (let ((state table) + (parts + (if (symbolp expr) + (mapcar + 'intern + (split-string (symbol-name expr) "\\.")) + ;; Else it's a string + (split-string expr "\\.")))) + (catch 'break + (while (listp parts) + (let ((traverse (funcall func (car parts) state))) + (setq parts (cdr parts)) + (if parts + (setq state (cdr traverse)) + (throw 'break (cdr traverse)))))))) + +(defun kvdotassoc (expr table) + "Dotted expression handling with `assoc'." + (kvdotassoc-fn expr table 'assoc)) + +(defun kvdotassq (expr table) + "Dotted expression handling with `assq'." + (kvdotassoc-fn expr table 'assq)) + +(defun kvdotassoc= (expr value table) + (let ((v (kvdotassoc expr table))) + (and v (equal v value) v))) + +(defalias 'dotassoc 'kvdotassoc) +(defalias 'dotassq 'kvdotassq) + +;; Thank you taylanub for this wonderful abstraction. +(defmacro kv--destructuring-map (map-function args sequence &rest body) + "Helper macro for `destructuring-mapcar' and `destructuring-map'." + (declare (indent 3)) + (let ((entry (gensym))) + `(,map-function (lambda (,entry) + (destructuring-bind ,args ,entry ,@body)) + ,sequence))) + +(defmacro kvmap-bind (args sexp seq) + "A hybrid of `destructuring-bind' and `mapcar' +ARGS shall be of the form used with `destructuring-bind' + +Unlike most other mapping forms this is a macro intended to be +used for structural transformations, so the expected usage will +be that ARGS describes the structure of the items in SEQ, and +SEXP will describe the structure desired." + (declare (indent 2)) + `(kv--destructuring-map mapcar ,args ,seq ,sexp)) + +(defalias 'map-bind 'kvmap-bind) + +(defun kvplist-merge (&rest plists) + "Merge the 2nd and subsequent plists into the first. + +Values set by lists to the left are clobbered." + (let ((result (car plists)) + (plists (cdr plists))) + (loop for plist in plists do + (loop for (key val) on plist by 'cddr do + (setq result (plist-put result key val)))) + result)) + +(provide 'kv) +(provide 'dotassoc) + +;;; kv.el ends here |