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