dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

kv.el (13866B)


      1 ;;; kv.el --- key/value data structure functions
      2 
      3 ;; Copyright (C) 2012  Nic Ferrier
      4 
      5 ;; Author: Nic Ferrier <nferrier@ferrier.me.uk>
      6 ;; Keywords: lisp
      7 ;; Package-Version: 20140108.1534
      8 ;; Package-Commit: 721148475bce38a70e0b678ba8aa923652e8900e
      9 ;; Version: 0.0.19
     10 ;; Maintainer: Nic Ferrier <nferrier@ferrier.me.uk>
     11 ;; Created: 7th September 2012
     12 
     13 ;; This program is free software; you can redistribute it and/or modify
     14 ;; it under the terms of the GNU General Public License as published by
     15 ;; the Free Software Foundation, either version 3 of the License, or
     16 ;; (at your option) any later version.
     17 
     18 ;; This program is distributed in the hope that it will be useful,
     19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     21 ;; GNU General Public License for more details.
     22 
     23 ;; You should have received a copy of the GNU General Public License
     24 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     25 
     26 ;;; Commentary:
     27 
     28 ;; Some routines for working with key/value data structures like
     29 ;; hash-tables and alists and plists.
     30 
     31 ;; This also takes over the dotassoc stuff and provides it separately.
     32 
     33 ;;; Code:
     34 
     35 (eval-when-compile (require 'cl))
     36 
     37 
     38 (defun kvalist->hash (alist &rest hash-table-args)
     39   "Convert ALIST to a HASH.
     40 
     41 HASH-TABLE-ARGS are passed to the hash-table creation."
     42   (let ((table (apply 'make-hash-table hash-table-args)))
     43     (mapc
     44      (lambda (pair)
     45        (puthash (car pair) (cdr pair) table))
     46      alist)
     47     table))
     48 
     49 (defun kvhash->alist (hash &optional func)
     50   "Convert HASH to an ALIST.
     51 
     52 Optionally filter through FUNC, only non-nil values returned from
     53 FUNC are stored as the resulting value against the converted
     54 key."
     55   (when hash
     56     (let (store)
     57       (maphash
     58        (lambda (key value)
     59          (when key
     60            (if (and (functionp func))
     61                (let ((res (funcall func key value)))
     62                  (when res
     63                    (setq store (acons key res store))))
     64                ;; else no filtering, just return
     65                (setq store (acons key value store)))))
     66        hash)
     67       store)))
     68 
     69 (defun kvfa (key alist receive)
     70   "Call RECEIVE with whatever comes out of ALIST for KEY.
     71 
     72 RECEIVE can do whatever destructuring you want, the first
     73 argument is always the car of the alist pair."
     74   (apply receive (let ((a (assoc key alist)))
     75                    (append (list (car a))
     76                            (if (listp (cdr a))(cdr a)(list (cdr a)))))))
     77 
     78 (defun kva (key alist)
     79   "Retrieve the value assigned to KEY in ALIST.
     80 
     81 This uses `assoc' as the lookup mechanism."
     82   (cdr (assoc key alist)))
     83 
     84 (defun kvaq (key alist)
     85   "Retrieve the value assigned to KEY in ALIST.
     86 
     87 This uses `assq' as the lookup mechanism."
     88   (cdr (assq key alist)))
     89 
     90 (defun kvaqc (key alist)
     91   "Retrieve the value assigned to KEY in ALIST.
     92 
     93 This uses first the `assq' and then `assoc' as the lookup
     94 mechanism."
     95   (cdr (or (assq key alist)
     96            (assoc key alist))))
     97 
     98 (defun kvassoc= (key value alist)
     99   "Is the value assocd to KEY in ALIST equal to VALUE?
    100 
    101 Returns the value looked up by KEY that passes, so normally:
    102 
    103   KEY . VALUE
    104 "
    105   (let ((v (assoc key alist)))
    106     (and v (equal (cdr v) value) v)))
    107 
    108 (defun kvassoqc (key alist)
    109   "String or symbol assoc."
    110   (let ((v (or
    111             (assq (if (symbolp key) key (intern key)) alist)
    112             (or (assoc key alist)
    113                 ;; not sure about this behaviour... see test
    114                 (assoc (symbol-name key) alist)))))  v))
    115 
    116 (defun kvassoq= (key value alist)
    117   "Test the VALUE with the value bound to KEY in ALIST.
    118 
    119 The lookup mechanism is to ensure the key is a symbol and then
    120 use assq.  Hence the name of the function being a mix of assoc
    121 and assq.
    122 
    123 Returns the value looked up by KEY that passes, so normally:
    124 
    125   KEY . VALUE
    126 "
    127   (let ((v (kvassoqc key alist)))
    128     (and v (equal (cdr v) value) v)))
    129 
    130 (defun kvmatch (key regex alist)
    131   "Test the value with KEY in ALIST matches REGEX."
    132   (let ((v (kvassoqc key alist)))
    133     (and v (string-match regex (cdr v)) v)))
    134 
    135 (defun* kvquery->func (query &key
    136                              (equal-func 'kvassoc=)
    137                              (match-func 'kvmatch))
    138   "Turn a simple QUERY expression into a filter function.
    139 
    140 EQUAL-FUNC is the function that implements the equality
    141 predicate.
    142 
    143 MATCH-FUNC is the function that implements the match predicate.
    144 
    145 The query language is:
    146 
    147  | a b  - true if a or b is true
    148  & a b  - true only if a and b is true
    149  = a b  - true if a equals b as per the EQUAL-FUNC
    150  ~ a b  - true if a matches b as per the MATCH-FUNC
    151 
    152 So, for example:
    153 
    154  (|(= a b)(= c d))
    155 
    156 Means: if `a' equals `b', or if `c' equals `d' then the
    157 expression is true."
    158   (flet ((query-parse (query)
    159            (let ((part (car query))
    160                  (rest (cdr query)))
    161              (cond
    162                ((eq part '|)
    163                 (cons 'or
    164                       (loop for i in rest
    165                          collect (query-parse i))))
    166                ((eq part '&)
    167                 (cons 'and
    168                       (loop for i in rest
    169                          collect (query-parse i))))
    170                ((eq part '~)
    171                 (destructuring-bind (field value) rest
    172                   (list match-func field value (quote record))))
    173                ((eq part '=)
    174                 (destructuring-bind (field value) rest
    175                   (list equal-func field value (quote record))))))))
    176     (eval `(lambda (record) ,(query-parse query)))))
    177 
    178 (defun kvplist2get (plist2 keyword value)
    179   "Get the plist with KEYWORD / VALUE from the list of plists."
    180   (loop for plist in plist2
    181      if (equal (plist-get plist keyword) value)
    182      return plist))
    183 
    184 (defun kvthing->keyword (str-or-symbol)
    185   "Convert STR-OR-SYMBOL into a keyword symbol."
    186   (let ((str
    187          (cond
    188            ((symbolp str-or-symbol) (symbol-name str-or-symbol))
    189            ((stringp str-or-symbol) str-or-symbol))))
    190     (intern
    191      (if (eq (aref str 0) ?:) str (concat ":" str)))))
    192 
    193 (defun kvalist->plist (alist)
    194   "Convert an alist to a plist."
    195   ;; Why doesn't elisp provide this?
    196   (loop for pair in alist
    197      append (list
    198              (kvthing->keyword
    199               (car pair))
    200              (cdr pair))))
    201 
    202 (defun kvacons (&rest args)
    203   "Make an alist from the plist style args."
    204   (kvplist->alist args))
    205 
    206 (defun keyword->symbol (keyword)
    207   "A keyword is a symbol leading with a :.
    208 
    209 Converting to a symbol means dropping the :."
    210   (if (keywordp keyword)
    211       (intern (substring (symbol-name keyword) 1))
    212     keyword))
    213 
    214 (defun kvplist->alist (plist &optional keys-are-keywords)
    215   "Convert PLIST to an alist.
    216 
    217 The keys are expected to be :prefixed and the colons are removed
    218 unless KEYS-ARE-KEYWORDS is `t'.
    219 
    220 The keys in the resulting alist are always symbols."
    221   (when plist
    222     (loop for (key value . rest) on plist by 'cddr
    223        collect
    224          (cons (if keys-are-keywords
    225                    key
    226                    (keyword->symbol key))
    227                value))))
    228 
    229 (defun kvalist2->plist (alist2)
    230   "Convert a list of alists too a list of plists."
    231   (loop for alist in alist2
    232        append
    233        (list (kvalist->plist alist))))
    234 
    235 (defun kvalist->keys (alist)
    236   "Get just the keys from the alist."
    237   (mapcar (lambda (pair) (car pair)) alist))
    238 
    239 (defun kvalist->values (alist)
    240   "Get just the values from the alist."
    241   (mapcar (lambda (pair) (cdr pair)) alist))
    242 
    243 (defun kvalist-sort (alist pred)
    244   "Sort ALIST (by key) with PRED."
    245   (sort alist (lambda (a b) (funcall pred (car a) (car b)))))
    246 
    247 (defun kvalist-sort-by-value (alist pred)
    248   "Sort ALIST by value with PRED."
    249   (sort alist (lambda (a b) (funcall pred (cdr a) (cdr b)))))
    250 
    251 (defun kvalist->filter-keys (alist &rest keys)
    252   "Return the ALIST filtered to the KEYS list.
    253 
    254 Only pairs where the car is a `member' of KEYS will be returned."
    255   (loop for a in alist
    256      if (member (car a) keys)
    257      collect a))
    258 
    259 (defun kvplist->filter-keys (plist &rest keys)
    260   "Filter the plist to just those matching KEYS.
    261 
    262 `kvalist->filter-keys' is actually used to do this work."
    263   (let ((symkeys
    264          (loop for k in keys
    265             collect (let ((strkey (symbol-name k)))
    266                       (if (equal (substring strkey 0 1) ":")
    267                           (intern (substring strkey 1))
    268                           k)))))
    269     (kvalist->plist
    270      (apply
    271       'kvalist->filter-keys
    272       (cons (kvplist->alist plist) symkeys)))))
    273 
    274 (defun kvplist2->filter-keys (plist2 &rest keys)
    275   "Return the PLIST2 (a list of plists) filtered to the KEYS."
    276   (loop for plist in plist2
    277      collect (apply 'kvplist->filter-keys (cons plist keys))))
    278 
    279 (defun kvalist2->filter-keys (alist2 &rest keys)
    280   "Return the ALIST2 (a list of alists) filtered to the KEYS."
    281   (loop for alist in alist2
    282      collect (apply 'kvalist->filter-keys (cons alist keys))))
    283 
    284 (defun kvalist2->alist (alist2 car-key cdr-key &optional proper)
    285   "Reduce the ALIST2 (a list of alists) to a single alist.
    286 
    287 CAR-KEY is the key of each alist to use as the resulting key and
    288 CDR-KEY is the key of each alist to user as the resulting cdr.
    289 
    290 For example, if CAR-KEY is `email' and CDR-KEY is `name' the
    291 records:
    292 
    293   '((user . \"nic\")(name . \"Nic\")(email . \"nic@domain\")
    294     (user . \"jim\")(name . \"Jim\")(email . \"jim@domain\"))
    295 
    296 could be reduced to:
    297 
    298   '((\"nic@domain\" . \"Nic\")
    299     (\"jim@domain\" . \"Jic\"))
    300 
    301 If PROPER is `t' then the alist is a list of proper lists, not
    302 cons cells."
    303   (loop for alist in alist2
    304        collect (apply (if proper 'list 'cons)
    305                       (list
    306                        (assoc-default car-key alist)
    307                        (assoc-default cdr-key alist)))))
    308 
    309 (defun kvalist-keys->* (alist fn)
    310   "Convert the keys of ALIST through FN."
    311   (mapcar
    312    (lambda (pair)
    313      (cons
    314       (funcall fn (car pair))
    315       (cdr pair)))
    316    alist))
    317 
    318 (defun* kvalist-keys->symbols (alist &key (first-fn 'identity))
    319   "Convert the keys of ALIST into symbols.
    320 
    321 If key parameter FIRST-FN is present it should be a function
    322 which will be used to first transform the string key.  A popular
    323 choice might be `downcase' for example, to cause all symbol keys
    324 to be lower-case."
    325   (kvalist-keys->*
    326    alist
    327    (lambda (key)
    328      (intern (funcall first-fn (format "%s" key))))))
    329 
    330 (defun kvalist2-filter (alist2 fn)
    331   "Filter the list of alists with FN."
    332   (let (value)
    333     (loop for rec in alist2
    334        do (setq value (funcall fn rec))
    335        if value
    336        collect rec)))
    337 
    338 (defun kvidentity (a b)
    339   "Returns a cons of A B."
    340   (cons a b))
    341 
    342 (defun kvcar (a b)
    343   "Given A B returns A."
    344   a)
    345 
    346 (defun kvcdr (a b)
    347   "Given A B returns B."
    348   b)
    349 
    350 (defun kvcmp (a b)
    351   "Do a comparison of the two values using printable syntax.
    352 
    353 Use this as the function to pass to `sort'."
    354   (string-lessp (if a (format "%S" a) "")
    355                 (if b (format "%S" b) "")))
    356 
    357 (defun kvqsort (lst)
    358   "Do a sort using `kvcmp'."
    359   (sort lst 'kvcmp))
    360 
    361 (progn
    362   (put 'kvalist-key
    363        'error-conditions
    364        '(error))
    365   (put 'kvalist-key
    366        'error-message
    367        "No such key found in alist."))
    368 
    369 (defun kvalist-set-value! (alist key value)
    370   "Destructively set the value of KEY to VALUE in ALIST.
    371 
    372 If the assoc is not found this adds it to alist."
    373   (let ((cell (assoc key alist)))
    374     (if (consp cell)
    375         (setcdr cell value)
    376         ;; Else what to do?
    377         (signal 'kvalist-key (list alist key)))))
    378 
    379 (defun kvdotassoc-fn (expr table func)
    380   "Use the dotted EXPR to access deeply nested data in TABLE.
    381 
    382 EXPR is a dot separated expression, either a symbol or a string.
    383 For example:
    384 
    385  \"a.b.c\"
    386 
    387 or:
    388 
    389  'a.b.c
    390 
    391 If the EXPR is a symbol then the keys of the alist are also
    392 expected to be symbols.
    393 
    394 TABLE is expected to be an alist currently.
    395 
    396 FUNC is some sort of `assoc' like function."
    397   (let ((state table)
    398         (parts
    399          (if (symbolp expr)
    400              (mapcar
    401               'intern
    402               (split-string (symbol-name expr) "\\."))
    403              ;; Else it's a string
    404              (split-string expr "\\."))))
    405     (catch 'break
    406       (while (listp parts)
    407         (let ((traverse (funcall func (car parts) state)))
    408           (setq parts (cdr parts))
    409           (if parts
    410               (setq state (cdr traverse))
    411               (throw 'break (cdr traverse))))))))
    412 
    413 (defun kvdotassoc (expr table)
    414   "Dotted expression handling with `assoc'."
    415   (kvdotassoc-fn expr table 'assoc))
    416 
    417 (defun kvdotassq (expr table)
    418   "Dotted expression handling with `assq'."
    419   (kvdotassoc-fn expr table 'assq))
    420 
    421 (defun kvdotassoc= (expr value table)
    422   (let ((v (kvdotassoc expr table)))
    423     (and v (equal v value) v)))
    424 
    425 (defalias 'dotassoc 'kvdotassoc)
    426 (defalias 'dotassq 'kvdotassq)
    427 
    428 ;; Thank you taylanub for this wonderful abstraction.
    429 (defmacro kv--destructuring-map (map-function args sequence &rest body)
    430   "Helper macro for `destructuring-mapcar' and `destructuring-map'."
    431   (declare (indent 3))
    432   (let ((entry (gensym)))
    433     `(,map-function (lambda (,entry)
    434                       (destructuring-bind ,args ,entry ,@body))
    435                     ,sequence)))
    436 
    437 (defmacro kvmap-bind (args sexp seq)
    438   "A hybrid of `destructuring-bind' and `mapcar'
    439 ARGS shall be of the form used with `destructuring-bind'
    440 
    441 Unlike most other mapping forms this is a macro intended to be
    442 used for structural transformations, so the expected usage will
    443 be that ARGS describes the structure of the items in SEQ, and
    444 SEXP will describe the structure desired."
    445   (declare (indent 2))
    446   `(kv--destructuring-map mapcar ,args ,seq ,sexp))
    447 
    448 (defalias 'map-bind 'kvmap-bind)
    449 
    450 (defun kvplist-merge (&rest plists)
    451   "Merge the 2nd and subsequent plists into the first.
    452 
    453 Values set by lists to the left are clobbered."
    454   (let ((result (car plists))
    455         (plists (cdr plists)))
    456     (loop for plist in plists do
    457           (loop for (key val) on plist by 'cddr do
    458                 (setq result (plist-put result key val))))
    459     result))
    460 
    461 (provide 'kv)
    462 (provide 'dotassoc)
    463 
    464 ;;; kv.el ends here