001  (ns walkmap.utils
002    "Miscellaneous utility functions."
003    (:require [clojure.math.numeric-tower :as m]
004              [clojure.string :as s]))
005  
006  (defn deep-merge
007    "Recursively merges maps. If vals are not maps, the last value wins."
008    ;; TODO: not my implementation, not sure I entirely trust it.
009    ;; TODO TODO: if we are to successfully merge walkmap objects, we must
010    ;; return, on each object, the union of its tags if any.
011    [& vals]
012    (if (every? map? vals)
013      (apply merge-with deep-merge vals)
014      (last vals)))
015  
016  (defn truncate
017    "If string `s` is more than `n` characters long, return the first `n`
018    characters; otherwise, return `s`."
019    [s n]
020    (if (and (string? s) (number? n) (> (count s) n))
021      (subs s 0 n)
022      s))
023  
024  (defn kind-type
025    "Identify the type of an `object`, e.g. for logging. If it has a `:kind` key,
026    it's one of ours, and that's what we want. Otherwise, we want its type; but
027    the type of `nil` is `nil`, which doesn't get printed when assembling error
028    ,essages, so return \"nil\"."
029    [object]
030    (or (:kind object) (type object) "nil"))
031  
032  (defn =ish
033    "True if numbers `n1`, `n2` are roughly equal; that is to say, equal to
034    within `tolerance` (defaults to one part in one hundred thousand)."
035    ([n1 n2]
036     (if (and (number? n1) (number? n2))
037       (let [m (m/abs (min n1 n2))
038             t (if (zero? m) 0.00001 (* 0.00001 m))]
039         (=ish n1 n2 t))
040       (= n1 n2)))
041    ([n1 n2 tolerance]
042     (if (and (number? n1) (number? n2))
043       (< (m/abs (- n1 n2)) tolerance)
044       (= n1 n2))))
045  
046  (defmacro check-kind-type
047    "If `object` is not of kind-type `expected`, throws an
048    IllegalArgumentException with an appropriate message; otherwise, returns
049    `object`. If `checkfn` is supplied, it should be a function which tests
050    whether the object is of the expected kind-type.
051  
052    Macro, so that the exception is thrown from the calling function."
053    ([object expected]
054     `(if-not (= (kind-type ~object) ~expected)
055        (throw
056          (IllegalArgumentException.
057            (s/join
058              " "
059              ["Expected" ~expected "but found" (kind-type ~object)])))
060        ~object))
061    ([object checkfn expected]
062     `(if-not (~checkfn ~object)
063        (throw
064          (IllegalArgumentException.
065            (s/join
066              " "
067              ["Expected" ~expected "but found" (kind-type ~object)])))
068        ~object)))
069  
070  (defmacro check-kind-type-seq
071    "If some item on sequence `s` is not of the `expected` kind-type, throws an
072    IllegalArgumentException with an appropriate message; otherwise, returns
073    `object`. If `checkfn` is supplied, it should be a function which tests
074    whether the object is of the expected kind-type.
075  
076    Macro, so that the exception is thrown from the calling function."
077    ([s expected]
078    `(if-not (every? #(= (kind-type %) ~expected) ~s)
079       (throw
080         (IllegalArgumentException.
081           (s/join
082             " "
083             ["Expected sequence of"
084              ~expected
085              "but found ("
086              (s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
087              ")"])))
088       ~s))
089    ([s checkfn expected]
090    `(if-not (every? #(~checkfn %) ~s)
091       (throw
092         (IllegalArgumentException.
093           (s/join
094             " "
095             ["Expected sequence of"
096              ~expected
097              "but found ("
098              (s/join ", " (remove #(= ~expected %) (map kind-type ~s)))
099              ")"])))
100       ~s)))
101