# walkmap/utils.clj

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)))))