-
Notifications
You must be signed in to change notification settings - Fork 11
/
tools.clj
166 lines (146 loc) · 4.86 KB
/
tools.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
(ns envoy.tools
(:require [cheshire.core :as json]
[clojure.string :as s]
[clojure.edn :as edn]))
(defn key->prop [k]
(-> k
name
;; (s/replace "-" "_") ;; TODO: think about whether it is best to simply leave dashes alone
))
(defn link [connect from [to value]]
(let [to (key->prop to)]
[(str from connect to) value]))
(defn- serialize
[v serializer]
(condp = serializer
:edn (str (vec v))
:json (json/generate-string (vec v))
(serialize v :edn)))
(defn serialize-map
[m & [serializer]]
(reduce-kv (fn [acc k v]
(cond
(map? v) (assoc acc k (serialize-map v serializer))
(sequential? v) (assoc acc k (serialize v serializer))
:else (assoc acc k (str v))))
{} m))
(defn- map->flat [m key->x connect & [serializer]]
(reduce-kv (fn [path k v]
(cond
(map? v)
(concat (map (partial link connect (key->x k))
(map->flat v key->x connect serializer))
path)
(sequential? v) (conj path [(key->x k)
(serialize v serializer)])
:else (conj path [(key->x k) v])))
[] m))
(defn map->props [m & [serializer]]
(map->flat m key->prop "/" serializer))
(defn- deserialize
[v serializer]
(condp = serializer
:edn (try
(let [parsed (read-string v)]
(if (symbol? parsed)
v
parsed))
(catch Throwable _
v))
:json (try
(json/parse-string-strict v true)
(catch Throwable _ v))
(deserialize v :edn)))
(defn- str->value [v & [deserializer]]
"consul values are strings. str->value will convert:
the numbers to longs, the alphanumeric values to strings, and will use Clojure reader for the rest
in case reader can't read OR it reads a symbol, the value will be returned as is (a string)"
(cond
(re-matches #"[0-9]+" v) (Long/parseLong v)
(re-matches #"^(true|false)$" v) (Boolean/parseBoolean v)
(re-matches #"\w+" v) v
:else (deserialize v deserializer)))
(defn- key->path [k level]
(as-> k $
;; (s/lower-case $)
(s/split $ level)
(remove #{""} $) ;; in case "/foo/bar" remove the result for the first slash
(map keyword $)))
(defn- sys->map [sys]
(reduce (fn [m [k-path v]]
(assoc-in m k-path v)) {} sys))
(defn cpath->kpath
"consul path to key path: i.e. \"/foo/bar/baz\" to [:foo :bar :baz]"
[cpath]
(if (seq cpath)
(key->path cpath #"/")
[]))
(defn remove-nils [m]
(let [remove? (fn [v]
(or (nil? v)
(= "null" v)))]
(into {}
(remove
(comp remove? second)
m))))
(defn props->map [read-from-consul & [deserializer]]
(->> (for [[k v] (-> (read-from-consul)
remove-nils)]
[(key->path k #"/")
(str->value v deserializer)])
sys->map))
;; author of "deep-merge-with" is Chris Chouser: https://github.com/clojure/clojure-contrib/commit/19613025d233b5f445b1dd3460c4128f39218741
(defn deep-merge-with
"Like merge-with, but merges maps recursively, appling the given fn
only when there's a non-map at a particular level.
(deepmerge + {:a {:b {:c 1 :d {:x 1 :y 2}} :e 3} :f 4}
{:a {:b {:c 2 :d {:z 9} :z 3} :e 100}})
-> {:a {:b {:z 3, :c 3, :d {:z 9, :x 1, :y 2}}, :e 103}, :f 4}"
[f & maps]
(apply
(fn m [& maps]
(if (every? map? maps)
(apply merge-with m maps)
(apply f maps)))
maps))
(defn merge-maps [& m]
(apply deep-merge-with
(fn [_ v] v) m))
(defn nest-map
"given a prefix in a form of [:a :b :c] and a map, nests this map under
{:a {:b {:c m}}}"
[m prefix]
(reduce (fn [nested level]
{level nested})
m
(reverse prefix)))
(defn with-slash
"adds a slash to the last position if it is not there"
[path]
(let [c (last path)]
(if (not= c \/)
(str path "/")
path)))
(defn clean-slash
[path]
(s/join "/"(remove #{""} (s/split path #"/"))))
(defn without-slash
"removes slash from either ':first' or ':last' (default) position
in case it is there"
([path]
(without-slash path {}))
([path {:keys [slash]
:or {slash :last}}]
(if-not (= :both slash)
(let [[find-slash no-slash] (case slash
:last [last drop-last]
:first [first rest]
:not-first-or-last-might-need-to-implement)]
(if (= (find-slash path) \/)
(apply str (no-slash path))
path))
(clean-slash path))))
(defn concat-with-slash [s1 s2]
(str (without-slash s1)
"/"
(without-slash s2 {:slash :first})))