-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from athos/feature/api
Add API for use as `-X` program or `-T` CLI tool
- Loading branch information
Showing
6 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
(ns pogonos.api | ||
(:require [clojure.edn :as edn] | ||
[clojure.java.io :as io] | ||
[clojure.string :as str] | ||
[pogonos.core :as pg] | ||
[pogonos.error :as error] | ||
[pogonos.output :as out] | ||
[pogonos.reader :as reader]) | ||
(:import [java.io File PushbackReader] | ||
[java.util.regex Pattern])) | ||
|
||
(defn render | ||
"Renders the given Mustache template. | ||
One of the following option can be specified as a template source: | ||
- :string Renders the given template string | ||
- :file Renders the specified template file | ||
- :resource Renders the specified template resource on the classpath | ||
If none of these are specified, the template will be read from stdin. | ||
The following options can also be specified: | ||
- :output Path to the output file. If not specified, the rendering result | ||
will be emitted to stdout by default. | ||
- :data Map of the values passed to the template | ||
- :data-file If specified, reads an edn map from the file specified by that | ||
path and pass it to the template" | ||
{:added "0.2.0"} | ||
[{:keys [string file resource data data-file output] :as opts}] | ||
(let [data (or (when data-file | ||
(with-open [r (-> (io/reader (str data-file)) | ||
PushbackReader.)] | ||
(edn/read r))) | ||
data) | ||
out (if output | ||
(out/to-file (str output)) | ||
(out/to-stdout)) | ||
opts' (-> opts | ||
(assoc :output out) | ||
(dissoc :string :file :resource :data :data-file))] | ||
(cond string (pg/render-string string data opts') | ||
file (pg/render-file (str file) data opts') | ||
resource (pg/render-resource (str resource) data opts') | ||
:else (pg/render-input (reader/->reader *in*) data opts')))) | ||
|
||
(def ^:private ^:const path-separator | ||
(re-pattern (Pattern/quote (System/getProperty "path.separator")))) | ||
|
||
(defn- ->matcher [x] | ||
(let [regexes (if (coll? x) | ||
(mapv (comp re-pattern str) x) | ||
[(re-pattern (str x))])] | ||
(fn [s] | ||
(boolean (some #(re-find % s) regexes))))) | ||
|
||
(def ^:private ^:dynamic *errors*) | ||
|
||
(defn- with-error-handling [opts f] | ||
(try | ||
(f) | ||
(catch Exception e | ||
(if (::error/type (ex-data e)) | ||
(do (when (or (not (:quiet opts)) (:only-show-errors opts)) | ||
(binding [*out* *err*] | ||
(println "[ERROR]" (ex-message e)))) | ||
(set! *errors* (conj *errors* e))) | ||
(throw e))))) | ||
|
||
(defn- check-inputs [inputs {:keys [include-regex exclude-regex] :as opts}] | ||
(let [include? (if include-regex | ||
(->matcher include-regex) | ||
(constantly true)) | ||
exclude? (if exclude-regex | ||
(->matcher exclude-regex) | ||
(constantly false))] | ||
(doseq [{:keys [name input]} inputs | ||
:when (and (include? name) (not (exclude? name)))] | ||
(when (and (not (:quiet opts)) (not (:only-show-errors opts))) | ||
(binding [*out* *err*] | ||
(println "Checking template" name))) | ||
(with-open [r (reader/->reader input)] | ||
(with-error-handling opts | ||
#(pg/check-input r (assoc opts :source name))))))) | ||
|
||
(defn- check-files [files opts] | ||
(-> (for [file files | ||
:let [file (io/file file)]] | ||
{:name (.getPath file) :input file}) | ||
(check-inputs opts))) | ||
|
||
(defn- check-resources [resources opts] | ||
(-> (for [res resources] | ||
{:name res :input (io/resource res)}) | ||
(check-inputs opts))) | ||
|
||
(defn- check-dirs [dirs opts] | ||
(-> (for [dir dirs | ||
^File file (file-seq (io/file dir)) | ||
:when (.isFile file)] | ||
file) | ||
(check-files opts))) | ||
|
||
(defn- split-path [path] | ||
(if (sequential? path) | ||
(mapv str path) | ||
(str/split (str path) path-separator))) | ||
|
||
(defn check | ||
"Checks if the given Mustache template contains any syntax error. | ||
The following options cab be specified as a template source: | ||
- :string Checks the given template string | ||
- :file Checks the specified template file | ||
- :dir Checks the template files in the specified directory | ||
- :resource Checks the specified template resource on the classpath | ||
If none of these are specified, the template will be read from stdin. | ||
For the :file/:dir/:resource options, two or more files/directories/resources | ||
may be specified by delimiting them with the file path separator (i.e. ':' (colon) | ||
on Linux/macOS and ';' (semicolon) on Windows). | ||
When multiple templates are checked using the :file/:dir/:resource options, | ||
they can be filtered with the :include-regex and/or exclude-regex options. | ||
The verbosity of the syntax check results may be adjusted to some extent with | ||
the following options: | ||
- :only-show-errors Hides progress messages | ||
- :suppress-verbose-errors Suppresses verbose error messages" | ||
{:added "0.2.0"} | ||
[{:keys [string file dir resource on-failure] :or {on-failure :exit} :as opts}] | ||
(binding [*errors* []] | ||
(cond string (with-error-handling opts #(pg/check-string string opts)) | ||
file (check-files (split-path file) opts) | ||
resource (check-resources (split-path resource) opts) | ||
dir (check-dirs (split-path dir) opts) | ||
:else (with-error-handling opts | ||
#(pg/check-input (reader/->reader *in*) opts))) | ||
(when (seq *errors*) | ||
(case on-failure | ||
:exit (System/exit 1) | ||
:throw (throw (ex-info "Template checking failed" {:errors *errors*})) | ||
nil)))) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{:name "Clojurian"} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Hello, {{name}}! |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
(ns pogonos.api-test | ||
(:require [clojure.java.io :as io] | ||
[clojure.test :refer [deftest is are testing]] | ||
[pogonos.api :as api] | ||
[clojure.string :as str])) | ||
|
||
(defn test-file [path] | ||
(str (io/file (io/resource path)))) | ||
|
||
(deftest render-test | ||
(are [opts expected] (= expected (with-out-str (api/render opts))) | ||
{:string "Hello, {{name}}!" :data {:name "Clojurian"}} | ||
"Hello, Clojurian!" | ||
|
||
{:file (test-file "templates/hello.mustache") | ||
:data-file (test-file "data.edn")} | ||
"Hello, Clojurian!\n" | ||
|
||
{:resource "templates/hello.mustache" :data {:name "Clojurian"}} | ||
"Hello, Clojurian!\n") | ||
(is (= "Hello, Clojurian!" | ||
(with-out-str | ||
(with-in-str "Hello, {{name}}!" | ||
(api/render {:data {:name "Clojurian"}})))))) | ||
|
||
(defn- with-stderr-lines [f] | ||
(-> (with-out-str | ||
(binding [*err* *out*] | ||
(f))) | ||
(str/split #"\n"))) | ||
|
||
(defn- join-paths [& paths] | ||
(str/join (System/getProperty "path.separator") paths)) | ||
|
||
(deftest check-test | ||
(testing "basic use cases" | ||
(is (nil? (api/check {:string "{{foo}}" :quiet true}))) | ||
(is (thrown? Exception | ||
(api/check {:string "{{foo" :quiet true :on-failure :throw}))) | ||
(is (nil? (api/check {:file (test-file "templates/hello.mustache") | ||
:quiet true}))) | ||
(is (thrown? Exception | ||
(api/check {:file (test-file "templates/broken.mustache") | ||
:quiet true :on-failure :throw}))) | ||
(is (nil? (api/check {:resource "templates/hello.mustache" :quiet true}))) | ||
(is (thrown? Exception | ||
(api/check {:resource "templates/broken.mustache" | ||
:quiet true :on-failure :throw}))) | ||
(is (nil? (with-in-str "{{foo}}" (api/check {:quiet true})))) | ||
(is (thrown? Exception | ||
(with-in-str "{{foo" | ||
(api/check {:quiet true :on-failure :throw}))))) | ||
(testing "bulk check" | ||
(testing "files" | ||
(is (= [(str "Checking template " (test-file "templates/hello.mustache")) | ||
(str "Checking template " (test-file "templates/main.mustache"))] | ||
(with-stderr-lines | ||
#(api/check {:file [(test-file "templates/hello.mustache") | ||
(test-file "templates/main.mustache")]})))) | ||
(let [lines (with-stderr-lines | ||
#(api/check {:file (join-paths | ||
(test-file "templates/hello.mustache") | ||
(test-file "templates/broken.mustache")) | ||
:on-failure nil | ||
:suppress-verbose-errors true}))] | ||
(is (= (count lines) 3)) | ||
(is (= (str "Checking template " (test-file "templates/hello.mustache")) | ||
(nth lines 0))) | ||
(is (= (str "Checking template " (test-file "templates/broken.mustache")) | ||
(nth lines 1))) | ||
(is (str/starts-with? (nth lines 2) "[ERROR]")))) | ||
(testing "resources" | ||
(is (= ["Checking template templates/main.mustache" | ||
"Checking template templates/node.mustache"] | ||
(with-stderr-lines | ||
#(api/check {:resource ["templates/main.mustache" | ||
"templates/node.mustache"]})))) | ||
(let [lines (with-stderr-lines | ||
#(api/check {:resource (join-paths "templates/broken.mustache" | ||
"templates/hello.mustache") | ||
:on-failure nil | ||
:suppress-verbose-errors true}))] | ||
(is (= (count lines) 3)) | ||
(is (= "Checking template templates/broken.mustache" (nth lines 0))) | ||
(is (str/starts-with? (nth lines 1) "[ERROR]")) | ||
(is (= "Checking template templates/hello.mustache" (nth lines 2))))) | ||
(testing "dirs" | ||
(let [lines (sort | ||
(with-stderr-lines | ||
#(api/check {:dir (test-file "templates") | ||
:on-failure nil | ||
:suppress-verbose-errors true})))] | ||
(is (= [(str "Checking template " (test-file "templates/broken.mustache")) | ||
(str "Checking template " (test-file "templates/demo.mustache")) | ||
(str "Checking template " (test-file "templates/hello.mustache")) | ||
(str "Checking template " (test-file "templates/main.mustache")) | ||
(str "Checking template " (test-file "templates/node.mustache"))] | ||
(butlast lines))) | ||
(is (str/starts-with? (last lines) "[ERROR]"))) | ||
(let [lines (with-stderr-lines | ||
#(api/check {:dir (test-file "templates") | ||
:only-show-errors true | ||
:on-failure nil | ||
:suppress-verbose-errors true}))] | ||
(is (= (count lines) 1)) | ||
(is (str/starts-with? (first lines) "[ERROR]"))) | ||
(let [lines (sort | ||
(with-stderr-lines | ||
#(api/check {:dir [(test-file "templates")] | ||
:include-regex ["broken.mustache$" "hello.mustache$"] | ||
:on-failure nil | ||
:suppress-verbose-errors true})))] | ||
(is (= [(str "Checking template " (test-file "templates/broken.mustache")) | ||
(str "Checking template " (test-file "templates/hello.mustache"))] | ||
(butlast lines))) | ||
(is (str/starts-with? (last lines) "[ERROR]"))) | ||
(is (= [(str "Checking template " (test-file "templates/hello.mustache")) | ||
(str "Checking template " (test-file "templates/main.mustache"))] | ||
(sort | ||
(with-stderr-lines | ||
#(api/check {:dir (test-file "templates") | ||
:exclude-regex "(broken|demo|node).mustache$"})))))))) |
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