dotemacs

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

nrepl-dict.el (7219B)


      1 ;;; nrepl-dict.el --- Dictionary functions for Clojure nREPL -*- lexical-binding: t -*-
      2 
      3 ;; Copyright © 2012-2013 Tim King, Phil Hagelberg, Bozhidar Batsov
      4 ;; Copyright © 2013-2023 Bozhidar Batsov, Artur Malabarba and CIDER contributors
      5 ;;
      6 ;; Author: Tim King <kingtim@gmail.com>
      7 ;;         Phil Hagelberg <technomancy@gmail.com>
      8 ;;         Bozhidar Batsov <bozhidar@batsov.dev>
      9 ;;         Artur Malabarba <bruce.connor.am@gmail.com>
     10 ;;         Hugo Duncan <hugo@hugoduncan.org>
     11 ;;         Steve Purcell <steve@sanityinc.com>
     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 ;; This file is not part of GNU Emacs.
     27 ;;
     28 ;;; Commentary:
     29 ;;
     30 ;; Provides functions to interact with and create `nrepl-dict's.  These are
     31 ;; simply plists with an extra element at the head.
     32 
     33 ;;; Code:
     34 (require 'cl-lib)
     35 
     36 
     37 (defun nrepl-dict (&rest key-vals)
     38   "Create nREPL dict from KEY-VALS."
     39   (cons 'dict key-vals))
     40 
     41 (defun nrepl-dict-from-hash (hash)
     42   "Create nREPL dict from HASH."
     43   (let ((dict (nrepl-dict)))
     44     (maphash (lambda (k v) (nrepl-dict-put dict k v)) hash)
     45     dict))
     46 
     47 (defun nrepl-dict-p (object)
     48   "Return t if OBJECT is an nREPL dict."
     49   (and (listp object)
     50        (eq (car object) 'dict)))
     51 
     52 (defun nrepl-dict-empty-p (dict)
     53   "Return t if nREPL dict DICT is empty."
     54   (null (cdr dict)))
     55 
     56 (defun nrepl-dict-contains (dict key)
     57   "Return nil if nREPL dict DICT doesn't contain KEY.
     58 If DICT does contain KEY, then a non-nil value is returned.  Due to the
     59 current implementation, this return value is the tail of DICT's key-list
     60 whose car is KEY.  Comparison is done with `equal'."
     61   (member key (nrepl-dict-keys dict)))
     62 
     63 (defun nrepl-dict-get (dict key &optional default)
     64   "Get from DICT value associated with KEY, optional DEFAULT if KEY not in DICT.
     65 If dict is nil, return nil.  If DEFAULT not provided, and KEY not in DICT,
     66 return nil.  If DICT is not an nREPL dict object, an error is thrown."
     67   (when dict
     68     (if (nrepl-dict-p dict)
     69         (if (nrepl-dict-contains dict key)
     70             (lax-plist-get (cdr dict) key)
     71           default)
     72       (error "Not an nREPL dict object: %s" dict))))
     73 
     74 (defun nrepl-dict-put (dict key value)
     75   "Associate in DICT, KEY to VALUE.
     76 Return new dict.  Dict is modified by side effects."
     77   (if (null dict)
     78       `(dict ,key ,value)
     79     (if (not (nrepl-dict-p dict))
     80         (error "Not an nREPL dict object: %s" dict)
     81       (setcdr dict (lax-plist-put (cdr dict) key value))
     82       dict)))
     83 
     84 (defun nrepl-dict-keys (dict)
     85   "Return all the keys in the nREPL DICT."
     86   (if (nrepl-dict-p dict)
     87       (cl-loop for l on (cdr dict) by #'cddr
     88                collect (car l))
     89     (error "Not an nREPL dict")))
     90 
     91 (defun nrepl-dict-vals (dict)
     92   "Return all the values in the nREPL DICT."
     93   (if (nrepl-dict-p dict)
     94       (cl-loop for l on (cdr dict) by #'cddr
     95                collect (cadr l))
     96     (error "Not an nREPL dict")))
     97 
     98 (defun nrepl-dict-map (fn dict)
     99   "Map FN on nREPL DICT.
    100 FN must accept two arguments key and value."
    101   (if (nrepl-dict-p dict)
    102       (cl-loop for l on (cdr dict) by #'cddr
    103                collect (funcall fn (car l) (cadr l)))
    104     (error "Not an nREPL dict")))
    105 
    106 (defun nrepl-dict-merge (dict1 dict2)
    107   "Destructively merge DICT2 into DICT1.
    108 Keys in DICT2 override those in DICT1."
    109   (let ((base (or dict1 '(dict))))
    110     (nrepl-dict-map (lambda (k v)
    111                       (nrepl-dict-put base k v))
    112                     (or dict2 '(dict)))
    113     base))
    114 
    115 (defun nrepl-dict-get-in (dict keys)
    116   "Return the value in a nested DICT.
    117 KEYS is a list of keys.  Return nil if any of the keys is not present or if
    118 any of the values is nil."
    119   (let ((out dict))
    120     (while (and keys out)
    121       (setq out (nrepl-dict-get out (pop keys))))
    122     out))
    123 
    124 (defun nrepl-dict-flat-map (function dict)
    125   "Map FUNCTION over DICT and flatten the result.
    126 FUNCTION follows the same restrictions as in `nrepl-dict-map', and it must
    127 also always return a sequence (since the result will be flattened)."
    128   (when dict
    129     (apply #'append (nrepl-dict-map function dict))))
    130 
    131 (defun nrepl-dict-filter (function dict)
    132   "For all key-values of DICT, return new dict where FUNCTION returns non-nil.
    133 
    134 FUNCTION should be a function taking two arguments, key and value."
    135   (let ((new-map (nrepl-dict))
    136         (keys (nrepl-dict-keys dict)))
    137     (dolist (key keys)
    138       (let ((val (nrepl-dict-get dict key)))
    139         (when (funcall function key val)
    140           (nrepl-dict-put new-map key val))))
    141     new-map))
    142 
    143 
    144 ;;; More specific functions
    145 (defun nrepl--cons (car list-or-dict)
    146   "Generic cons of CAR to LIST-OR-DICT."
    147   (if (eq (car list-or-dict) 'dict)
    148       (cons 'dict (cons car (cdr list-or-dict)))
    149     (cons car list-or-dict)))
    150 
    151 (defun nrepl--nreverse (list-or-dict)
    152   "Generic `nreverse' which works on LIST-OR-DICT."
    153   (if (eq (car list-or-dict) 'dict)
    154       (cons 'dict (nreverse (cdr list-or-dict)))
    155     (nreverse list-or-dict)))
    156 
    157 (defun nrepl--push (obj stack)
    158   "Cons OBJ to the top element of the STACK."
    159   ;; stack is assumed to be a list
    160   (if (eq (caar stack) 'dict)
    161       (cons (cons 'dict (cons obj (cdar stack)))
    162             (cdr stack))
    163     (cons (if (null stack)
    164               obj
    165             (cons obj (car stack)))
    166           (cdr stack))))
    167 
    168 (defun nrepl--merge (dict1 dict2 &optional no-join)
    169   "Join nREPL dicts DICT1 and DICT2 in a meaningful way.
    170 String values for non \"id\" and \"session\" keys are concatenated. Lists
    171 are appended. nREPL dicts merged recursively. All other objects are
    172 accumulated into a list. DICT1 is modified destructively and
    173 then returned.
    174 If NO-JOIN is given, return the first non nil dict."
    175   (if no-join
    176       (or dict1 dict2)
    177     (cond ((null dict1) dict2)
    178           ((null dict2) dict1)
    179           ((stringp dict1) (concat dict1 dict2))
    180           ((nrepl-dict-p dict1)
    181            (nrepl-dict-map
    182             (lambda (k2 v2)
    183               (nrepl-dict-put dict1 k2
    184                               (nrepl--merge (nrepl-dict-get dict1 k2) v2
    185                                             (member k2 '("id" "session")))))
    186             dict2)
    187            dict1)
    188           ((and (listp dict2) (listp dict1)) (append dict1 dict2))
    189           ((listp dict1) (append dict1 (list dict2)))
    190           (t `(,dict1 ,dict2)))))
    191 
    192 
    193 ;;; Dbind
    194 (defmacro nrepl-dbind-response (response keys &rest body)
    195   "Destructure an nREPL RESPONSE dict.
    196 Bind the value of the provided KEYS and execute BODY."
    197   (declare (debug (form (&rest symbolp) body)))
    198   `(let ,(cl-loop for key in keys
    199                   collect `(,key (nrepl-dict-get ,response ,(format "%s" key))))
    200      ,@body))
    201 (put 'nrepl-dbind-response 'lisp-indent-function 2)
    202 
    203 (provide 'nrepl-dict)
    204 
    205 ;;; nrepl-dict.el ends here