Created
April 5, 2016 00:24
-
-
Save favila/c8bd1eddb8263e53ba93ae3a60fd1bb7 to your computer and use it in GitHub Desktop.
Utilities for making it easier to read edn files. Also includes tag readers that datomic uses.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns favila.read-edn.tag-readers | |
"Common tag-reader maps for edn." | |
(:require datomic.db | |
datomic.function | |
datomic.codec) | |
(:import java.net.URI)) | |
(defmethod print-method URI [^URI x ^Writer w] | |
(doto w | |
(.write "#uri ") | |
(.write "\"") | |
;; java.net.URI does not appear to allow unescaped | |
;; double-quotes, so this should be safe. | |
(.write (.toString x)) | |
(.write "\""))) | |
;; Discovered by examining *data-readers* with datomic in the classpath. | |
(def datomic | |
"Read edn or dtm files with datomic tag literals in them. Has the same set | |
of tags (plus #uri) as understood by datomic.Util/readAll." | |
{'db/id datomic.db/id-literal | |
'db/fn datomic.function/construct | |
'base64 datomic.codec/base-64-literal | |
'uri #(URI. ^String %)}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns favila.read-edn | |
"A namespace that makes reading edn a little easier. | |
Takes care of PushbackReader coersion, supplies a `TaggedValue` record for | |
unknown tags, `edn-form-reader` for reading successive forms from edn using | |
the same handler options, and easy-to-use functions to lazily seq edn objects | |
out of anything `clojure.java.io/reader` understands. | |
Example use: | |
(def results (read-all-edn-string | |
\"#uri \"\"http://example.org\"\" | |
#unknown [:a :b]\" | |
{'uri #(URI. ^String %) | |
:default tagged-value})) | |
=> #'favila.read-edn/results | |
(first results) | |
=> #object[java.net.URI 0x1ee736ad \"http://example.org\"] | |
(second results) | |
=> #unknown[:a :b] | |
(type (second results)) | |
=> favila.read_edn.TaggedValue | |
(:tag (second results)) | |
=> unknown | |
(:value (second results)) | |
=> [:a :b]" | |
(:require clojure.edn | |
[clojure.java.io :as io]) | |
(:import [java.io Writer Reader PushbackReader StringReader])) | |
(defn pushback-reader | |
"Return x coerced to a PushbackReader. Understands anything | |
clojure.java.io/reader understands plus PushbackReader itself (which is | |
returned unchanged)." | |
^PushbackReader [x] | |
(cond (instance? PushbackReader x) x | |
(instance? Reader x) (PushbackReader. x) | |
:else (PushbackReader. (io/reader x :encoding "UTF-8")))) | |
(defn pushback-reader-string ^PushbackReader [s] | |
"Return a PushbackReader wrapping the contents of string s." | |
(PushbackReader. (StringReader. s))) | |
(let [eof (reify Object (toString [_] "edn EOF"))] | |
(defn edn-form-reader [^PushbackReader pbr tag-readers] | |
"Return a function which returns successive edn objects from pbr. | |
When EOF is reached, returns a sentinal which can be tested for with | |
`edn-eof?`." | |
(let [opts {:eof eof | |
:readers (dissoc tag-readers :default) | |
:default (get tag-readers :default)}] | |
#(clojure.edn/read opts pbr))) | |
(defn- edn-eof? | |
"Return true if x is an edn EOF marker, else false." | |
[x] | |
(identical? eof x))) | |
(defrecord TaggedValue [tag value]) | |
(defmethod print-method TaggedValue [this ^Writer w] | |
(.write w "#") | |
(print-method (:tag this) w) | |
(.write w " ") | |
(print-method (:value this) w)) | |
(defn tagged-value | |
"Given a tag symbol and a value, return a TaggedValue record which | |
holds the tag under the :tag key and the unmodified value under :value. | |
This is useful as a catch-all default tag reader because it allows unknown | |
tags to be roundtripped." | |
[tag value] | |
{:pre [(symbol? tag)]} | |
(->TaggedValue tag value)) | |
(defn read-all-edn | |
"Given something clojure.java.io/reader understands and which contains edn, | |
return a lazy sequence of the objects read from the edn. | |
`tag-readers` is an optional map of tag symbols to tag readers | |
(1-arg functions which take the object read after the tag and return an | |
object). It may also have the key `:default` for a default tag handler: a | |
2-arg function taking tag-symbol and form and returning an object. | |
Note: this function always closes the underlying reader automatically when | |
the edn is exhausted. If you need more control, use `edn-form-reader` and | |
`edn-eof?` directly." | |
([readable] (read-all-edn readable {})) | |
([readable tag-readers] | |
(let [pbr (pushback-reader readable) | |
rff (edn-form-reader pbr tag-readers)] | |
(take-while #(if (edn-eof? %) | |
(do (.close pbr) false) | |
true) | |
(repeatedly rff))))) | |
(defn read-all-edn-string | |
"Like read-all-edn except first argument must be a string containing edn. | |
Accepts the same tag-readers map as read-all-edn." | |
([s] (read-all-edn-string s {})) | |
([s tag-readers] (read-all-edn (pushback-reader-string s) tag-readers))) | |
(defn read-one-edn | |
"Like read-all-edn, but returns only the next form out of the reader. Returns | |
`nil` if there are no forms to read. Always closes the readable." | |
([readable] (read-one-edn readable {})) | |
([readable tag-readers] | |
(with-open [pbr (pushback-reader readable)] | |
(let [form ((edn-form-reader pbr tag-readers))] | |
(if (edn-eof? form) | |
nil | |
form))))) | |
(defn read-one-edn-string | |
"Like read-one-edn except first argument must be a string containing edn." | |
([s] (read-one-edn-string s {})) | |
([s tag-readers] (read-one-edn (pushback-reader-string s) tag-readers))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment