-
Notifications
You must be signed in to change notification settings - Fork 2
/
config.clj
208 lines (177 loc) · 5.85 KB
/
config.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
(ns ciste.config
"Ciste uses the config function in ciste.config to perform all the
configuration. Config takes a variable number of key values and will
either return a non-nil value if that option is defined, or will raise
an exception if it is not.
The config information is read from the file \"config.clj\" at the base
of the project's directory. The config file should contain a hash-map.
The top-level keys will be the names of environments. The values of
these keys will be an arbitrarily complex structure of hashes,
vectors, and other data.
Example:
(use 'ciste.config)
(load-config)
(set-environment! :default)
(config :option1) => \"foo\"
(config :option3) => [\"foo\" \"bar\" \"baz\"]
(config :option2 :title) => \"BAR\""
(:require [clojure.java.io :as io]
[clojure.string :as string]
[clojure.tools.logging :as log]
[slingshot.slingshot :refer [throw+ try+]])
(:import java.io.FileNotFoundException
java.net.InetAddress))
;; TODO: read from env var
(defonce
^{:dynamic true
:doc "The current environment. use the set-environment!,
environment, and with"}
*environment* (atom nil))
(defonce
^{:dynamic true
:doc "The full config map for all environments is stored
in this ref"}
*environments* (ref {}))
(defonce
^{:dynamic true
:doc "This is where config docs are kept"}
*doc-maps*
(ref {}))
(defonce
^{:doc "By default, the runner will look for a file with this name at the root
of the project directory."}
default-site-config-filename "ciste.clj")
(defonce
^{:doc "Ref containing the currently loaded site config"}
default-site-config (ref {}))
(defn get-host-address
"Returns the IP address of the host's local adapter"
[]
(.getHostAddress (InetAddress/getLocalHost)))
(defn environment*
[]
@*environment*)
(defn environment
"Returns the currently bound environment.
Throws an exception if no environment is bound"
[]
(or (environment*)
(throw
(RuntimeException.
"Environment not set. export CISTE_ENV to choose an environment"))))
(defn merge-config
"Recursively merges m1 into m2. If the value of any of the key is a map, the
elements in that map are also merged"
[m1 m2]
(->> (map (fn [[k v]]
[k (if (map? v)
(merge-config v (get m2 k))
(let [m2-val (get m2 k)]
(or m2-val v)))])
m1)
(into {})
(merge m2)))
(defn get-resource
[filename]
(some-> (or (let [f (io/file filename)]
(when (.exists f) f))
(io/resource filename))
slurp
;; TODO: Use EDN reader
read-string))
(defn load-config
"Loads the config file into the environment.
Defaults to config.clj if not specified"
([] (load-config "config.clj"))
([filename]
(when-let [options (get-resource filename)]
(dosync
(ref-set *environments* options)))))
(defn read-site-config
"Read the site config file"
([] (read-site-config default-site-config-filename))
([filename]
(or (get-resource filename)
(throw+ "Could not find service config."))))
;; TODO: This should attempt to write the config back to the same
;; place it was loaded from.
(defn write-config!
"Write the current config settings to file"
([] (write-config! "config.clj"))
([filename]
(->> @*environments*
clojure.pprint/pprint
with-out-str
(spit filename))))
(defn config*
"Like config, but does not throw an exception if the key cannot be found."
([]
(get @*environments* (environment)))
([& ks]
(let [env-val (get-in (config*) ks)
default-val (get-in (:default @*environments*) ks)
value (if (nil? env-val)
default-val
env-val)
response (if (map? env-val)
(merge-config env-val default-val)
value)]
#_(let [config-part (str "(config " (string/join " " ks) ")")
default-part (if (= response default-val) ":default" "")]
(log/debug (format "%-35s => %-20s %s" config-part (pr-str response) default-part)))
response)))
(defn config
"Returns the option matching the key sequence in the global config map for the
currently bound environment, defaulting to the :default environment.
Throws an exception if the option can not be found"
([& ks]
(let [value (apply config* ks)]
(if-not (nil? value)
value
(throw
(IllegalArgumentException.
(str "no config option matching path " ks " for " (environment))))))))
(defn set-config!
"Set the value of the config setting matching the key sequence"
[ks value]
(dosync
(alter *environments*
assoc-in (concat [(environment)] ks) value))
value)
(defn set-environment!
"Sets's the environment globally"
[env]
(log/debugf "Setting environment to %s" env)
(dosync (reset! *environment* env)))
(defmacro with-environment
"Run body with the evironment bound"
[environment & body]
`(binding [ciste.config/*environment* (atom nil)]
(set-environment! ~environment)
~@body))
(defmacro describe-config
"Macro to record config information
Example:
(describe-config [:print :request]
:boolean
\"Should the request be logged?\")"
[ks type docstring & {:as body}]
(let [m (merge {:path ks
:doc docstring
:type type}
body)]
`(dosync
(alter *doc-maps* assoc ~ks ~m))))
(defn config-doc
"Print out the documentation for the config path"
[& ks]
(when-let [m (get @*doc-maps* (vec ks))]
(println (:path m))
(println " " (:type m))
(println (:doc m))))
(defn load-site-config
"Read the site config and store it for later use"
[]
(let [site-config (read-site-config)]
(dosync
(ref-set default-site-config site-config))))