Skip to content

Commit

Permalink
Feature/load all (#22)
Browse files Browse the repository at this point in the history
A YAML 1.1 stream can contain multiple documents.
Currently, clj-yaml will only load the first document.
This change adds the ability to optionally load all documents.
Enable by specifying `:load-all?` true in `opts` for `parse-string` or `parse-stream`.
  • Loading branch information
clumsyjedi authored and borkdude committed Sep 23, 2022
1 parent fe99d1b commit 4627e70
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 18 deletions.
22 changes: 16 additions & 6 deletions src/clojure/clj_yaml/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
(org.yaml.snakeyaml.representer Representer)
(org.yaml.snakeyaml.error Mark)
(clj_yaml MarkedConstructor UnknownTagsConstructor)
(java.util LinkedHashMap)))
(java.util LinkedHashMap)
(clj_yaml MarkedConstructor)
(java.util LinkedHashMap)
(java.io StringReader)))

(def flow-styles
{:auto DumperOptions$FlowStyle/AUTO
Expand Down Expand Up @@ -163,15 +166,21 @@
(.dump ^Yaml (apply make-yaml opts)
(encode data)))

(defn- load-stream [^Yaml yaml ^java.io.Reader input load-all? keywords unknown-tag-fn]
(if load-all?
(map #(decode % keywords unknown-tag-fn) (.loadAll yaml input))
(decode (.load yaml input) keywords unknown-tag-fn)))

(defn parse-string
[^String string & {:keys [unknown-tag-fn unsafe mark keywords max-aliases-for-collections allow-recursive-keys allow-duplicate-keys] :or {keywords true}}]
[^String string & {:keys [unknown-tag-fn unsafe mark keywords max-aliases-for-collections
allow-recursive-keys allow-duplicate-keys load-all?] :or {keywords true}}]
(let [yaml (make-yaml :unsafe unsafe
:mark mark
:unknown-tag-fn unknown-tag-fn
:max-aliases-for-collections max-aliases-for-collections
:allow-recursive-keys allow-recursive-keys
:allow-duplicate-keys allow-duplicate-keys)]
(decode (.load yaml string) keywords unknown-tag-fn)))
(load-stream yaml (StringReader. string) load-all? keywords unknown-tag-fn)))

;; From https://github.com/metosin/muuntaja/pull/94/files
(defn generate-stream
Expand All @@ -180,6 +189,7 @@
(.dump ^Yaml (apply make-yaml opts) (encode data) writer))

(defn parse-stream
[^java.io.Reader reader & {:keys [keywords unknown-tag-fn] :or {keywords true} :as opts}]
(decode (.load ^Yaml (apply make-yaml (into [] cat opts))
reader) keywords unknown-tag-fn))
[^java.io.Reader reader & {:keys [keywords load-all? unknown-tag-fn] :or {keywords true} :as opts}]
(load-stream (apply make-yaml (into [] cat opts))
reader
load-all? keywords unknown-tag-fn))
67 changes: 55 additions & 12 deletions test/clj_yaml/core_test.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
(ns clj-yaml.core-test
(:require [clojure.test :refer (deftest testing is)]
[clojure.string :as string]
[clojure.java.io :as io]
[clj-yaml.core :as yaml :refer [parse-string unmark generate-string
parse-stream generate-stream]])
(:import [java.util Date]
(java.io ByteArrayOutputStream OutputStreamWriter ByteArrayInputStream)
java.nio.charset.StandardCharsets
(org.yaml.snakeyaml.error YAMLException)
(org.yaml.snakeyaml.constructor ConstructorException DuplicateKeyException)))
(:require
[clj-yaml.core :as yaml :refer [generate-stream generate-string
parse-stream parse-string unmark]]
[clojure.java.io :as io]
[clojure.string :as string]
[clojure.test :refer (deftest testing is)]
[flatland.ordered.map :refer [ordered-map]])
(:import
(java.io ByteArrayInputStream ByteArrayOutputStream OutputStreamWriter)
java.nio.charset.StandardCharsets
[java.util Date]
[org.yaml.snakeyaml.composer ComposerException]
(org.yaml.snakeyaml.constructor ConstructorException DuplicateKeyException)
(org.yaml.snakeyaml.constructor DuplicateKeyException)
(org.yaml.snakeyaml.error YAMLException)))

(def nested-hash-yaml
"root:\n childa: a\n childb: \n grandchild: \n greatgrandchild: bar\n")
Expand Down Expand Up @@ -166,8 +171,8 @@ the-bin: !!binary 0101")
;; This test ensures that generate-string uses the older behavior by default, for the sake
;; of stability, i.e. backwards compatibility.
(is
(= "{description: Big-picture diagram showing how our top-level systems and stakeholders interact}\n"
(generate-string data))))))
(= "{description: Big-picture diagram showing how our top-level systems and stakeholders interact}\n"
(generate-string data))))))

(deftest dump-opts
(let [data [{:age 33 :name "jon"} {:age 44 :name "boo"}]]
Expand Down Expand Up @@ -276,6 +281,43 @@ foo/bar: 42
(is (roundtrip list-yaml))
(is (roundtrip nested-hash-yaml))))

(defn- ->stream [string]
(io/reader (.getBytes ^String string)))

(def multi-doc-yaml "
---
foo: true
---
bar: false")

(def single-doc-yaml "
---
lol: yolo")

(deftest load-all-test
(testing "Without load-all?"
(is (= (ordered-map {:lol "yolo"})
(parse-string single-doc-yaml)))
(is (= (ordered-map {:lol "yolo"})
(parse-stream (->stream single-doc-yaml))))
(is (thrown-with-msg? ComposerException #"expected a single document in the stream\n"
(parse-stream (->stream multi-doc-yaml))))
(is (thrown-with-msg? ComposerException #"expected a single document in the stream\n"
(parse-string multi-doc-yaml))))

(testing "With load-all?=true on single docs"
(is (= [(ordered-map {:lol "yolo"})]
(parse-string single-doc-yaml :load-all? true)))
(is (= [(ordered-map {:lol "yolo"})]
(parse-stream (->stream single-doc-yaml) :load-all? true))))

(testing "With load-all?=true on multi docs"
(is (= [(ordered-map {:foo true}) (ordered-map {:bar false})]
(parse-string multi-doc-yaml :load-all? true)))
(is (= [(ordered-map {:foo true}) (ordered-map {:bar false})]
(parse-stream (->stream multi-doc-yaml) :load-all? true))))
)

(def indented-yaml "todo:
- name: Fix issue
responsible:
Expand Down Expand Up @@ -323,3 +365,4 @@ sequence: !CustomSequence
:unknown-tag-fn (fn [{:keys [tag value]}]
(if (= "!Base12" tag)
(Integer/parseInt value 12) value)))))))

0 comments on commit 4627e70

Please sign in to comment.