Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

464 lines
14 KiB

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