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