I recently made a contribution to ghijira, a small tool written in Clojure for exporting issues from GitHub in JIRA-compatible format. One of the problems to solve there was loading configuration from file.
Originally, it used have a separate config.clj
file that looked like this:
(def auth "EMAIL:PASS") (def ghuser "user-name") (def ghproject "project-name") (def user-map { "GithubUser1" "JIRAUser1" "GithubUser2" "JIRAUser2"})
Then it was imported in place with:
(load-file "config.clj")
I did not like it, because it did not feel very functional and the configuration file had too much noise (isn’t def
form manipulation too much for a configuration file?).
For a moment I thought about using standard Java .properties
files. They get the job done, but they’re also somewhat rigid and unwieldy.
It occurred to me I really could use something similar to Leiningen and its project.clj
files: Make the config a plain Clojure map. It’s very flexible with minimal syntax, and it’s pure data just like it should be.
From a quick Google search I found the answer at StackOverflow.
It turns out I can rewrite my configuration file to:
{:auth "EMAIL:PASS" :ghuser "user-name" :ghproject "project-name" :user-map { "GithubUser1" "JIRAUser1" "GithubUser2" "JIRAUser2" } }
And then load it in Clojure with read
:
(defn load-config [filename] (with-open [r (io/reader filename)] (read (java.io.PushbackReader. r))))
That’s it, just call read
.
I find this solution a lot more elegant and “pure”.
By the way, this data format is getting a life of its own, called Extensive Data Notation. The Relevance team has even published a library to use EDN in Ruby.
(def config (read-string (slurp “path/to/config.clj”)))
or
(def load-config (comp read-string slurp))
and I thought that properties are complicated enough :)
And also, by doing do, you can guarantee the consistency of your configuration if you use binding to set the per-thread value of your conf for a given request.
Laurent – exactly. Actually, that’s what I did in this project.
Just remember to (binding [*read-eval* false] (read …)), to only get data-structures. Otherwise you will execute the #=() magic, which may end badly if hostile third party edits your config file.
Or you can just use the wonderful library at https://github.com/michaelklishin/propertied