From 8d3528c0a3e0491b255be96bc416cceb3153aedb Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 8 Feb 2022 22:58:26 +0100 Subject: [PATCH 01/61] chore: start work on 0.3.32 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index e9ebcdcf..b1a2667e 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.3.31" +(defproject io.github.erdos/stencil-core "0.3.32-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index a38f8833..b7d77596 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.3.31" +(defproject io.github.erdos/stencil-service "0.3.32-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.2-alpha2"] - [io.github.erdos/stencil-core "0.3.31"] + [io.github.erdos/stencil-core "0.3.32-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha5"] [http-kit "2.5.0"] [ring/ring-json "0.4.0"]] From 3aefbdadd7e4413d63b1ee47ce806852c759e6cd Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Fri, 11 Feb 2022 00:24:02 +0100 Subject: [PATCH 02/61] feat: newlines (#111) --- src/stencil/postprocess/whitespaces.clj | 52 ++++++++++++++----- test/stencil/postprocess/whitespaces_test.clj | 22 ++++++++ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/stencil/postprocess/whitespaces.clj b/src/stencil/postprocess/whitespaces.clj index e60349fe..8b566e85 100644 --- a/src/stencil/postprocess/whitespaces.clj +++ b/src/stencil/postprocess/whitespaces.clj @@ -1,20 +1,44 @@ (ns stencil.postprocess.whitespaces - (:require [clojure.zip :as zip] + (:require [clojure.string :refer [includes? starts-with? ends-with? index-of]] + [clojure.zip :as zip] [stencil.ooxml :as ooxml] - [stencil.types :refer :all] [stencil.util :refer :all])) -(defn- should-fix? - "We only fix tags where the enclosed string starts or ends with whitespace." - [element] - (boolean - (when (and (map? element) - (= ooxml/t (:tag element)) - (seq (:content element))) - (or (.startsWith (str (first (:content element))) " ") - (.startsWith (str (last (:content element))) " "))))) +(defn- should-fix? [element] + (when (and (map? element) + (= ooxml/t (:tag element)) + (not-empty (:content element))) + (or (starts-with? (first (:content element)) " ") + (ends-with? (last (:content element)) " ") + (some #(includes? % "\n") (:content element))))) -(defn- fix-elem [element] - (assoc-in element [:attrs ooxml/space] "preserve")) +(defn- multi-replace [loc items] + (assert (zipper? loc)) + (assert (not-empty items)) + (reduce (comp zip/right zip/insert-right) (zip/replace loc (first items)) (next items))) -(defn fix-whitespaces [xml-tree] (dfs-walk-xml xml-tree should-fix? fix-elem)) +;; (defn- lines-of [s] (enumeration-seq (java.util.StringTokenizer. s "\n" true))) +;; (defn- lines-of [s] (remove #{""} (interpose "\n" (clojure.string/split s "\n" -1)))) + +(defn- lines-of [s] + (if-let [idx (index-of s "\n")] + (if (zero? idx) + (cons "\n" (lazy-seq (lines-of (subs s 1)))) + (list* (subs s 0 idx) "\n" (lazy-seq (lines-of (subs s (inc idx)))))) + (if (empty? s) [] (list s)))) + +(defn- item->elem [item] + (cond (= "\n" item) + ,,,{:tag ooxml/br} + (or (starts-with? item " ") (ends-with? item " ")) + ,,,{:tag ooxml/t :content [item] :attrs {ooxml/space "preserve"}} + :else + ,,,{:tag ooxml/t :content [item]})) + +(defn- fix-elem-node [loc] + (->> (apply str (:content (zip/node loc))) + (lines-of) + (map item->elem) + (multi-replace loc))) + +(defn fix-whitespaces [xml-tree] (dfs-walk-xml-node xml-tree should-fix? fix-elem-node)) diff --git a/test/stencil/postprocess/whitespaces_test.clj b/test/stencil/postprocess/whitespaces_test.clj index 8705444d..28581186 100644 --- a/test/stencil/postprocess/whitespaces_test.clj +++ b/test/stencil/postprocess/whitespaces_test.clj @@ -26,6 +26,11 @@ "Sum: 1 pieces" "Sum: {%=x %} pieces" {"x" 1})) + (testing "newline value splits t tags" + (test-equals + "two lines: firstsecond " + "two lines: {%=x %} " + {"x" "first\nsecond"})) (testing "existing space=preserve attributes are kept intact" (test-equals " Hello " @@ -33,3 +38,20 @@ {}))) ;; (test-eval "Sum: {%=x %} pieces" {"x" 1}) + +(let [target (the-ns 'stencil.postprocess.whitespaces)] + (doseq [[k v] (ns-map target) + :when (and (var? v) (= target (.ns v)))] + (eval `(defn ~(symbol (str "-" k)) [~'& args#] (apply (deref ~v) args#))))) + +(deftest test-lines-of + (is (= ["ab" "\n" "\n" "bc"] (-lines-of "ab\n\nbc"))) + (is (= ["\n" "xy" "\n"] (-lines-of "\nxy\n"))) + (is (= () (-lines-of "")))) + +(deftest test-multi-replace + (let [tree (stencil.util/xml-zip {:tag :a :content ["x" "y" "z"]}) + loc (clojure.zip/right (clojure.zip/down tree)) + replaced (-multi-replace loc ["1" "2" "3"])] + (is (= "3" (clojure.zip/node replaced))) + (is (= ["x" "1" "2" "3" "z"] (:content (clojure.zip/root replaced)))))) \ No newline at end of file From 987165a2ce260b8b79fd8ef99ac019fc07fbf7e9 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Sun, 13 Feb 2022 22:27:09 +0100 Subject: [PATCH 03/61] fix: html function supports uppercase tags (#113) --- src/stencil/postprocess/html.clj | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/stencil/postprocess/html.clj b/src/stencil/postprocess/html.clj index 5fa4c25f..12c5823c 100644 --- a/src/stencil/postprocess/html.clj +++ b/src/stencil/postprocess/html.clj @@ -8,6 +8,8 @@ [stencil.util :refer :all] [stencil.ooxml :as ooxml])) +(set! *warn-on-reflection* true) + (defrecord HtmlChunk [content] ControlMarker) (defmethod call-fn "html" [_ content] (->HtmlChunk content)) @@ -16,19 +18,22 @@ "Set of supported HTML tags" #{:b :em :i :u :s :sup :sub :span :br :strong}) +(defn- kw-lowercase [kw] (-> kw name .toLowerCase keyword)) + (defn- validate-tags "Throws ExceptionInfo on invalid HTML tag in tree" [xml-tree] (->> (fn [node] - (if (legal-tags (:tag node)) + (if (legal-tags (-> node :tag kw-lowercase)) node (throw (ex-info (str "Unexpected HTML tag: " (:tag node)) {:tag (:tag node)})))) (dfs-walk-xml xml-tree map?))) (defn- parse-html [xml] (-> (str xml) - (.replaceAll "
" "
") + (.replaceAll "
" "
") + (.replaceAll "
" "
") (xml/parse-str) (doto (validate-tags)) (try (catch javax.xml.stream.XMLStreamException e @@ -36,11 +41,11 @@ (defn- walk-children [xml] (if (map? xml) - (if (= :br (:tag xml)) + (if (#{:br :BR} (:tag xml)) [{:text ::br}] (for [c (:content xml) x (walk-children c)] - (update x :path conj (:tag xml)))) + (update x :path conj (kw-lowercase (:tag xml))))) [{:text xml}])) (defn- path->styles [path] From cb2029e061e81f29dec36de9d71b5e50c8080aa0 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Tue, 15 Feb 2022 20:56:10 +0100 Subject: [PATCH 04/61] feat: more transducers (#110) --- service/src/stencil/slf4j.clj | 2 +- src/stencil/cleanup.clj | 2 +- src/stencil/infix.clj | 11 ++++---- src/stencil/merger.clj | 13 ++++----- src/stencil/model.clj | 2 +- src/stencil/postprocess/table.clj | 7 +---- src/stencil/util.clj | 47 +++++++++++++++++++++++++------ 7 files changed, 54 insertions(+), 30 deletions(-) diff --git a/service/src/stencil/slf4j.clj b/service/src/stencil/slf4j.clj index ab9a5372..682fd810 100644 --- a/service/src/stencil/slf4j.clj +++ b/service/src/stencil/slf4j.clj @@ -22,7 +22,7 @@ (defn- msg+args [^String msg args] (if (empty? args) msg - (apply str (interleave (.split msg "\\{\\}" -1) (map str args))))) + (apply str (interleave (.split msg "\\{\\}" -1) args)))) (deftype StencilLoggerFactory [] org.slf4j.ILoggerFactory diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 39055c4e..4169a6b9 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -136,7 +136,7 @@ 1 (let [[then] (:blocks control-ast) else (:after then)] (-> (dissoc control-ast :blocks) - (assoc :then (vec (keep control-ast-normalize (:children then))) :else else))) + (assoc :then (vec (keep control-ast-normalize (:children then))) :else (vec else)))) ;; default (throw (parsing-exception (str open-tag "else" close-tag) "Too many {%else%} tags in one condition!")))) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index 06fd94bb..b828b8e6 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -2,7 +2,7 @@ "Parsing and evaluating infix algebraic expressions. https://en.wikipedia.org/wiki/Shunting-yard_algorithm" - (:require [stencil.util :refer [fail update-peek ->int]] + (:require [stencil.util :refer [fail update-peek ->int string]] [stencil.log :as log] [stencil.functions :refer [call-fn]])) @@ -101,8 +101,7 @@ "Reads a number literal from a sequence. Returns a tuple of read number (Double or Long) and the sequence of remaining characters." [characters] - (let [content (take-while (set "1234567890._") characters) - content ^String (apply str content) + (let [content (string (take-while (set "1234567890._")) characters) content (.replaceAll content "_" "") number (if (some #{\.} content) (Double/parseDouble content) @@ -142,7 +141,7 @@ (recur tail (conj tokens s))) :else - (let [content (apply str (take-while identifier characters))] + (let [content (string (take-while identifier) characters)] (if (seq content) (let [tail (drop-while #{\space \tab} (drop (count content) characters))] (if (= \( (first tail)) @@ -179,7 +178,7 @@ (empty? expr) (if (zero? parentheses) - (into result (remove #{:open} opstack)) + (into result (remove #{:open}) opstack) (throw (ex-info "Too many open parentheses!" {}))) (number? e0) @@ -235,7 +234,7 @@ (< (precedence e0) (precedence %))) opstack)] (recur next-expr (conj keep-ops e0) - (into result (remove #{:open :comma} popped-ops)) + (into result (remove #{:open :comma}) popped-ops) parentheses (if (= :comma e0) (if (first functions) diff --git a/src/stencil/merger.clj b/src/stencil/merger.clj index f9711eb5..aededa43 100644 --- a/src/stencil/merger.clj +++ b/src/stencil/merger.clj @@ -5,7 +5,7 @@ [stencil [types :refer [open-tag close-tag]] [tokenizer :as tokenizer] - [util :refer [prefixes suffixes subs-last]]])) + [util :refer [prefixes suffixes subs-last string]]])) (set! *warn-on-reflection* true) @@ -72,9 +72,8 @@ (defn -last-chars-count [sts-tokens] (assert (sequential? sts-tokens)) - (when (:text (last sts-tokens)) - (some #(when (.endsWith - (str (apply str (:text (last sts-tokens)))) (apply str %)) + (when-let [last-text (some-> sts-tokens last :text string)] + (some #(when (.endsWith last-text (string %)) (count %)) (prefixes open-tag)))) @@ -104,13 +103,13 @@ ] (concat (map map-action-token (:tokens sts)) - (let [ap (map-action-token {:action (apply str (map (comp :char first) this))})] + (let [ap (map-action-token {:action (string (map (comp :char first)) this)})] (if (:action ap) (concat [ap] (reverse (:stack that)) (if (seq (:text-rest that)) - (lazy-seq (cleanup-runs-1 (cons {:text (apply str (:text-rest that))} (:rest that)))) + (lazy-seq (cleanup-runs-1 (cons {:text (string (:text-rest that))} (:rest that)))) (lazy-seq (cleanup-runs (:rest that))))) (list* {:text (str open-tag (:action-part sts))} (lazy-seq (cleanup-runs rest-tokens))))))) @@ -124,7 +123,7 @@ [{:text (apply str s)}]) (let [tail (cleanup-runs-1 - (concat [{:text (str open-tag (apply str (:text-rest this)))}] + (concat [{:text (apply str open-tag (:text-rest this))}] (reverse (:stack this)) (:rest this)))] (if (:action (first tail)) diff --git a/src/stencil/model.clj b/src/stencil/model.clj index b575bf43..36c93bdb 100644 --- a/src/stencil/model.clj +++ b/src/stencil/model.clj @@ -239,7 +239,7 @@ style-ids-rename (-> fragment-model :main :style :parsed (doto assert) (style/insert-styles!)) relation-ids-rename (relations/ids-rename fragment-model frag-name) - relation-rename-map (into {} (map (juxt :old-id :new-id) relation-ids-rename)) + relation-rename-map (into {} (map (juxt :old-id :new-id)) relation-ids-rename) ;; evaluate evaled (eval-template-model fragment-model local-data-map {} {}) diff --git a/src/stencil/postprocess/table.clj b/src/stencil/postprocess/table.clj index 84535100..7fda2aa1 100644 --- a/src/stencil/postprocess/table.clj +++ b/src/stencil/postprocess/table.clj @@ -14,15 +14,10 @@ (defn- loc-row? [loc] (some-> loc zip/node :tag name #{"tr"})) (defn- loc-table? [loc] (some-> loc zip/node :tag name #{"tbl" "table"})) -(defn- find-first-in-tree [pred tree] - (assert (zipper? tree)) - (assert (fn? pred)) - (find-first (comp pred zip/node) (take-while (complement zip/end?) (iterate zip/next tree)))) - (defn- find-last-child [pred tree] (assert (zipper? tree)) (assert (fn? pred)) - (last (filter (comp pred zip/node) (iterations zip/right (zip/down tree))))) + (find-last (comp pred zip/node) (iterations zip/right (zip/down tree)))) (defn- first-right-sibling "Finds first right sibling that matches the predicate." diff --git a/src/stencil/util.clj b/src/stencil/util.clj index 9e82f211..15a9be8c 100644 --- a/src/stencil/util.clj +++ b/src/stencil/util.clj @@ -40,10 +40,10 @@ (defn fixpt [f x] (let [fx (f x)] (if (= fx x) x (recur f fx)))) (defn zipper? [loc] (-> loc meta (contains? :zip/branch?))) -(defn iterations [f xs] (take-while some? (iterate f xs))) -(defn find-first [pred xs] (first (filter pred xs))) +(defn iterations [f elem] (eduction (take-while some?) (iterate f elem))) -;; same as (last (filter pred xs)) +;; same as (first (filter pred xs)) +(defn find-first [pred xs] (reduce (fn [_ x] (if (pred x) (reduced x))) nil xs)) (defn find-last [pred xs] (reduce (fn [a x] (if (pred x) x a)) nil xs)) (def xml-zip @@ -75,17 +75,44 @@ (defn parsing-exception [expression message] (ParsingException/fromMessage (str expression) (str message))) -(defn dfs-walk-xml-node [xml-tree predicate edit-fn] - (assert (map? xml-tree)) - (assert (fn? predicate)) - (assert (fn? edit-fn)) - (loop [loc (xml-zip xml-tree)] +;; return xml zipper of location that matches predicate or nil +(defn find-first-in-tree [predicate tree-loc] + (assert (ifn? predicate)) + (assert (zipper? tree-loc)) + (letfn [(coords-of-first [node] + (loop [children (:content node) + index 0] + (when-let [[c & cs] (not-empty children)] + (if (predicate c) + [index] + (if-let [cf (coords-of-first c)] + (cons index cf) + (recur cs (inc index))))))) + (nth-child [loc i] + (loop [loc (clojure.zip/down loc), i i] + (if (zero? i) loc (recur (clojure.zip/right loc) (dec i)))))] + (if (predicate (clojure.zip/node tree-loc)) + tree-loc + (when-let [coords (coords-of-first (clojure.zip/node tree-loc))] + (reduce nth-child tree-loc coords))))) + +(defn- dfs-walk-xml-node-1 [loc predicate edit-fn] + (assert (zipper? loc)) + (loop [loc loc] (if (clojure.zip/end? loc) (clojure.zip/root loc) (if (predicate (clojure.zip/node loc)) (recur (clojure.zip/next (edit-fn loc))) (recur (clojure.zip/next loc)))))) +(defn dfs-walk-xml-node [xml-tree predicate edit-fn] + (assert (fn? predicate)) + (assert (fn? edit-fn)) + (assert (map? xml-tree)) + (if-let [loc (find-first-in-tree predicate (xml-zip xml-tree))] + (dfs-walk-xml-node-1 loc predicate edit-fn) + xml-tree)) + (defn dfs-walk-xml [xml-tree predicate edit-fn] (assert (fn? edit-fn)) (dfs-walk-xml-node xml-tree predicate #(clojure.zip/edit % edit-fn))) @@ -99,4 +126,8 @@ `(let [b# ~body] (when (~pred b#) b#))) +(defn ^String string + ([values] (apply str values)) + ([xform coll] (transduce xform (fn ([^Object s] (.toString s)) ([^StringBuilder b v] (.append b v))) (StringBuilder.) coll))) + :OK From b740e937caccdab5183cfc3058854c808556f97b Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Tue, 15 Feb 2022 21:47:11 +0100 Subject: [PATCH 05/61] feat: js engine in service (#114) --- service/README.md | 24 ++++++++++++++++++++++++ service/project.clj | 1 + service/src/stencil/service.clj | 19 ++++++++++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/service/README.md b/service/README.md index 07946072..075a5447 100644 --- a/service/README.md +++ b/service/README.md @@ -101,3 +101,27 @@ For example: ``` You can bind the `STENCIL_LOG_LEVEL` environment variable to change the default logging level from `info`. Acceptible values: `trace`, `debug`, `info`, `warn`, `error`, `fatal`. + +### Custom Functions + +It is possible to extend Stencil service with custom functions defined in Javascript. Add a `stencil.js` file in the template directory so it can be read and executed when the service loads. +The functions defined in the script will be available in the templates when rendered. + +The [Rhino](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino) scripting engine is used for evaluating the scripts. + +In current version, the script file is not reloaded automatically, therefore a service restart is necessary on changes. + +Add a `stencil.js` file in the template directory with the following contents: +``` +function daysBetweenTimestamps(ts1, ts2) { + var time1 = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochSecond(ts1), java.util.TimeZone.getDefault().toZoneId()); + var time2 = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochSecond(ts2), java.util.TimeZone.getDefault().toZoneId()); + var duration = java.time.Duration.between(time1, time2); + return duration.toDays(); +} +``` + +The function now can be called in the templates like the following: +``` + {%=daysBetweenTimestamps(1499070300, 1644956311)%} +``` diff --git a/service/project.clj b/service/project.clj index b7d77596..e62218e8 100644 --- a/service/project.clj +++ b/service/project.clj @@ -6,6 +6,7 @@ :dependencies [[org.clojure/clojure "1.10.2-alpha2"] [io.github.erdos/stencil-core "0.3.32-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha5"] + [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] [ring/ring-json "0.4.0"]] :aot :all diff --git a/service/src/stencil/service.clj b/service/src/stencil/service.clj index 255f06c6..aec55f39 100644 --- a/service/src/stencil/service.clj +++ b/service/src/stencil/service.clj @@ -3,10 +3,11 @@ (:import [java.io File]) (:require [org.httpkit.server :refer [run-server]] [stencil.api :as api] + [stencil.functions] [stencil.log :as log] [stencil.slf4j :as slf4j] [clojure.data :refer [diff]] - [clojure.java.io :refer [file]] + [clojure.java.io :as io :refer [file]] [ring.middleware.json :refer [wrap-json-body]])) (set! *warn-on-reflection* true) @@ -23,6 +24,21 @@ dir) (throw (ex-info "Missing STENCIL_TEMPLATE_DIR property!" {})))) +(defn eval-js-file [] + (let [f (file (get-template-dir) "stencil.js")] + (when (.exists f) + (log/info "Evaluating stencil.js file") + (let [manager (new javax.script.ScriptEngineManager) + engine (.getEngineByName manager "rhino") + invocable ^javax.script.Invocable engine + context (.getContext engine)] + (with-open [r (io/reader f)] + (.eval engine r context)) + (doseq [[k] (.getBindings context javax.script.ScriptContext/ENGINE_SCOPE)] + (log/info "Defining function created in stencil.js file: {}" k) + (defmethod stencil.functions/call-fn k [k & args] + (.invokeFunction invocable k (into-array Object args)))))))) + (def -prepared "Map of {file-name {timestamp prepared}}." (doto (atom {}) @@ -94,6 +110,7 @@ (defn -main [& args] (log/info "Starting Stencil Service {}" api/version) + (eval-js-file) (let [http-port (get-http-port) template-dir ^File (get-template-dir) server (run-server app {:port http-port})] From 82b6a30f4a5accb237480ba980f0991c6fab1a1c Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 15 Feb 2022 21:47:55 +0100 Subject: [PATCH 06/61] feat: version 0.4.0 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5a003ea3..38a2a6de 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.3.31` +**Latest stable** version is `0.4.0` -**Latest snapshot** version is `0.3.32-SNAPSHOT` +**Latest snapshot** version is `0.4.1-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.3.31 + 0.4.0 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.3.31"]` +file: `[io.github.erdos/stencil-core "0.4.0"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index b1a2667e..181062b7 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.3.32-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.0" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index e62218e8..6adcc2fc 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.3.32-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.0" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.2-alpha2"] - [io.github.erdos/stencil-core "0.3.32-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.0"] [org.slf4j/slf4j-api "2.0.0-alpha5"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 4abf917ba03b208579e0b00757bc03556fe38e28 Mon Sep 17 00:00:00 2001 From: erdos Date: Mon, 11 Apr 2022 22:47:36 +0200 Subject: [PATCH 07/61] chore: start work on 0.4.1 --- project.clj | 2 +- service/project.clj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project.clj b/project.clj index 181062b7..6e2fdfc6 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.0" +(defproject io.github.erdos/stencil-core "0.4.1-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 6adcc2fc..44ea551d 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,13 +1,13 @@ -(defproject io.github.erdos/stencil-service "0.4.0" +(defproject io.github.erdos/stencil-service "0.4.1-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.2-alpha2"] - [io.github.erdos/stencil-core "0.4.0"] + [io.github.erdos/stencil-core "0.4.1-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha5"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.4.0"]] + [ring/ring-json "0.4.1-SNAPSHOT"]] :aot :all :main stencil.service) From d7958689e71f4bfea3f00cb7629fd93679a996ff Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 12 Apr 2022 08:13:16 +0200 Subject: [PATCH 08/61] fix: ring-json version --- service/project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/project.clj b/service/project.clj index 44ea551d..739ebc5a 100644 --- a/service/project.clj +++ b/service/project.clj @@ -8,6 +8,6 @@ [org.slf4j/slf4j-api "2.0.0-alpha5"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.4.1-SNAPSHOT"]] + [ring/ring-json "0.5.1"]] :aot :all :main stencil.service) From 9adb1e71304393cc73bafc42cb1764a12bd5c884 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 13 Apr 2022 21:34:23 +0200 Subject: [PATCH 09/61] fix(service): update clojure and ring-json and slf4j --- service/project.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/project.clj b/service/project.clj index 739ebc5a..3f4bc60d 100644 --- a/service/project.clj +++ b/service/project.clj @@ -3,11 +3,11 @@ :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} - :dependencies [[org.clojure/clojure "1.10.2-alpha2"] + :dependencies [[org.clojure/clojure "1.11.1"] [io.github.erdos/stencil-core "0.4.1-SNAPSHOT"] - [org.slf4j/slf4j-api "2.0.0-alpha5"] + [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.5.1"]] + [ring/ring-json "0.5.0"]] :aot :all :main stencil.service) From e50196416a15e21ca996e3e117c3cbd8cde0ed97 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Wed, 13 Apr 2022 20:39:39 +0100 Subject: [PATCH 10/61] fix: service/Dockerfile to reduce vulnerabilities (#119) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-347558 - https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-347558 - https://snyk.io/vuln/SNYK-DEBIAN10-GCC8-347558 - https://snyk.io/vuln/SNYK-DEBIAN10-GMP-1920939 - https://snyk.io/vuln/SNYK-DEBIAN10-ZLIB-2433934 --- service/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/Dockerfile b/service/Dockerfile index a5cc0468..201ade69 100644 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -12,7 +12,7 @@ COPY . /usr/src/myapp RUN mv "$(lein uberjar | sed -n 's/^Created \(.*standalone\.jar\)/\1/p')" myapp-standalone.jar RUN jlink --strip-debug --add-modules "$(jdeps --print-module-deps --ignore-missing-deps myapp-standalone.jar)" --add-modules jdk.localedata --output /java -FROM debian:10.10-slim +FROM debian:10-slim ENV STENCIL_HTTP_PORT 8080 ENV STENCIL_TEMPLATE_DIR /templates From 3e9d88d575970946cfc6f15828e6d45a1b232bb1 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 13 Apr 2022 22:10:01 +0200 Subject: [PATCH 11/61] chore: simplifications --- src/stencil/cleanup.clj | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 4169a6b9..2eccc55a 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -56,9 +56,7 @@ (first result)))) (defn nested-tokens-fmap-postwalk - "Melysegi bejaras egy XML fan. - - https://en.wikipedia.org/wiki/Depth-first_search" + "Depth-first traversal of the tree." [f-cmd-block-before f-cmd-block-after f-child nested-tokens] (let [update-children #(update % :children @@ -102,8 +100,9 @@ control-ast))) (defn stack-revert-close - "Megfordítja a listát es az :open elemeket :close elemekre kicseréli." - [stack] (reduce (fn [stack item] (if (:open item) (conj stack (->CloseTag (:open item))) stack)) () stack)) + "Creates a seq of :close tags for each :open tag in the list in reverse order." + [stack] + (into () (comp (keep :open) (map ->CloseTag)) stack)) ;; egy {:cmd ...} parancs objektumot kibont: ;; a :blocks kulcs alatt levo elemeket normalizalja es specialis kulcsok alatt elhelyezi From 20e2217ee0aa77dadef1724e969a408a6983d251 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 13 Apr 2022 22:13:02 +0200 Subject: [PATCH 12/61] chore: simplifications --- src/stencil/cleanup.clj | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 2eccc55a..78c15dc7 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -58,11 +58,8 @@ (defn nested-tokens-fmap-postwalk "Depth-first traversal of the tree." [f-cmd-block-before f-cmd-block-after f-child nested-tokens] - (let [update-children - #(update % :children - (partial nested-tokens-fmap-postwalk - f-cmd-block-before f-cmd-block-after - f-child))] + (let [update-child-fn (partial nested-tokens-fmap-postwalk f-cmd-block-before f-cmd-block-after f-child) + update-children #(update % :children update-child-fn)] (vec (for [token nested-tokens] (if (:cmd token) From b66ca1a1b7711c66c9737e9d2436e3ee11e521d2 Mon Sep 17 00:00:00 2001 From: erdos Date: Sat, 16 Apr 2022 09:36:27 +0200 Subject: [PATCH 13/61] chore: update gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 337e8caf..95fb6e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ junit.xml /stencil-native /*.docx .DS_Store -.lsp/ \ No newline at end of file +.lsp/ +*.jfr \ No newline at end of file From e7beff637c4124765e7bf93080671420cbdad272 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Thu, 21 Apr 2022 09:20:54 +0200 Subject: [PATCH 14/61] feat: version 0.4.1 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- src/stencil/functions.clj | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 38a2a6de..f72f7d32 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.0` +**Latest stable** version is `0.4.1` -**Latest snapshot** version is `0.4.1-SNAPSHOT` +**Latest snapshot** version is `0.4.2-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.0 + 0.4.1 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.0"]` +file: `[io.github.erdos/stencil-core "0.4.1"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 6e2fdfc6..43173472 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.1-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.1" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 3f4bc60d..1fc415b7 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.1-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.1" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.1-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.1"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] diff --git a/src/stencil/functions.clj b/src/stencil/functions.clj index 01360417..48efdacf 100644 --- a/src/stencil/functions.clj +++ b/src/stencil/functions.clj @@ -97,7 +97,7 @@ (instance? java.util.List e)))] (fail "Wrong data, expected sequence, got: " {:data e})) (mapcat seq elems)) - (do (doseq [e elems :when (not (or (nil? e) (map? e)))] + (do (doseq [e elems :when (not (or (nil? e) (map? e) (instance? java.util.Map e)))] (fail "Wrong data, expected map, got: " {:data e})) (keep (partial lookup p) elems)))) data From bd61f0b1b5363c18a32ebbc285ef25badb6ab51d Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 21 Apr 2022 20:58:21 +0200 Subject: [PATCH 15/61] chore: start work on 0.4.2 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 43173472..15152d50 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.1" +(defproject io.github.erdos/stencil-core "0.4.2-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 1fc415b7..e6f67de8 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.1" +(defproject io.github.erdos/stencil-service "0.4.2-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.1"] + [io.github.erdos/stencil-core "0.4.2-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 0e7c0d2ad0348c40403a37f568ccf273eb65c086 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 21 Apr 2022 21:12:13 +0200 Subject: [PATCH 16/61] chore: simplifications --- src/stencil/eval.clj | 28 ++++++++++++++-------------- src/stencil/util.clj | 16 ++++++---------- test/stencil/util_test.clj | 2 +- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/stencil/eval.clj b/src/stencil/eval.clj index 02993e69..b171b4a2 100644 --- a/src/stencil/eval.clj +++ b/src/stencil/eval.clj @@ -12,12 +12,17 @@ (defmethod eval-step :default [_ _ item] [item]) +(defn normal-control-ast->evaled-seq [data function items] + (assert (map? data)) + (assert (ifn? function)) + (assert (or (nil? items) (sequential? items))) + (eduction (mapcat (partial eval-step function data)) items)) + (defmethod eval-step :if [function data item] (let [condition (eval-rpn data function (:condition item))] (log/trace "Condition {} evaluated to {}" (:condition item) condition) - (if condition - (mapcat (partial eval-step function data) (:then item)) - (mapcat (partial eval-step function data) (:else item))))) + (->> (if condition (:then item) (:else item)) + (normal-control-ast->evaled-seq data function)))) (defmethod eval-step :echo [function data item] (let [value (eval-rpn data function (:expression item))] @@ -30,17 +35,12 @@ (if (seq items) (let [datas (map #(assoc data (name (:variable item)) %) items) bodies (cons (:body-run-once item) (repeat (:body-run-next item)))] - (mapcat (fn [data body] (mapcat (partial eval-step function data) body)) datas bodies)) + (mapcat (fn [data body] (normal-control-ast->evaled-seq data function body)) datas bodies)) (:body-run-none item)))) -(defn normal-control-ast->evaled-seq [data function items] - (assert (map? data)) - (assert (ifn? function)) - (assert (or (nil? items) (sequential? items))) - (mapcat (partial eval-step function data) items)) - (defn eval-executable [part data functions] - (assert (:executable part)) - (tree-postprocess/postprocess - (tokenizer/tokens-seq->document - (normal-control-ast->evaled-seq data functions (:executable part))))) + (->> (:executable part) + (#(doto % assert)) + (normal-control-ast->evaled-seq data functions) + (tokenizer/tokens-seq->document) + (tree-postprocess/postprocess))) diff --git a/src/stencil/util.clj b/src/stencil/util.clj index 15a9be8c..86347164 100644 --- a/src/stencil/util.clj +++ b/src/stencil/util.clj @@ -14,22 +14,18 @@ [(take (- (count stack1) cnt) stack1) (take (- (count stack2) cnt) stack2)])) -(defn mod-stack-top-last - "Egy stack legfelso elemenek legutolso elemet modositja. - Ha nincs elem, IllegalStateException kivetelt dob." - [stack f & args] - (assert (list? stack) (str "Stack is not a list: " (pr-str stack))) - (assert (ifn? f)) - (conj (rest stack) - (conj (pop (first stack)) - (apply f (peek (first stack)) args)))) - (defn update-peek "Updates top element of a stack." [xs f & args] (assert (ifn? f)) (conj (pop xs) (apply f (peek xs) args))) +(defn mod-stack-top-last + "Updatest last element of top elem of stack." + [stack f & args] + (assert (list? stack) (str "Stack is not a list: " (pr-str stack))) + (apply update-peek stack update-peek f args)) + (defn mod-stack-top-conj "Conjoins an element to the top item of a stack." [stack & items] diff --git a/test/stencil/util_test.clj b/test/stencil/util_test.clj index 2231d625..a4fac1d7 100644 --- a/test/stencil/util_test.clj +++ b/test/stencil/util_test.clj @@ -19,7 +19,7 @@ (deftest mod-stack-top-last-test (testing "Invalid input" (is (thrown? IllegalStateException (mod-stack-top-last '([]) inc))) - (is (thrown? NullPointerException (mod-stack-top-last '() inc)))) + (is (thrown? IllegalStateException (mod-stack-top-last '() inc)))) (testing "simple cases" (is (= '([3]) (mod-stack-top-last '([2]) inc))) From dd440c15c7b60f9071a5b0ed7edbe5f20a32082f Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 4 May 2022 17:35:59 +0200 Subject: [PATCH 17/61] fix: non-terminating decinal expansion on division --- src/stencil/infix.clj | 2 +- test/stencil/infix_test.clj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index b828b8e6..a80403a2 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -296,7 +296,7 @@ (def-reduce-step :neg [s0] (- s0)) (def-reduce-step :times [s0 s1] (* s0 s1)) -(def-reduce-step :divide [s0 s1] (/ s1 s0)) +(def-reduce-step :divide [s0 s1] (with-precision 8 (/ s1 s0))) (def-reduce-step :plus [s0 s1] (if (or (string? s0) (string? s1)) (str s1 s0) (+ s1 s0))) (def-reduce-step :minus [s0 s1] (- s1 s0)) (def-reduce-step :eq [a b] (= a b)) diff --git a/test/stencil/infix_test.clj b/test/stencil/infix_test.clj index ed575d7a..edaf1570 100644 --- a/test/stencil/infix_test.clj +++ b/test/stencil/infix_test.clj @@ -85,6 +85,7 @@ (is (= 3 (run "coalesce(5, 1, 0) - coalesce(2, 1)"))) (is (= 6 (run "2 * 3"))) (is (= 3 (run "6 / 2"))) + (is (= 0.33333333M (run "1 / x" {"x" (bigdec 3)}))) ;; infinite expansion error (is (= 36.0 (run "6 ^ 2"))) (is (= 2 (run "12 % 5")))) From 04586c139fe4ed00b6660e25747f72cca563c76e Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 4 May 2022 17:39:00 +0200 Subject: [PATCH 18/61] feat: version 0.4.2 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f72f7d32..68e4c7f4 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.1` +**Latest stable** version is `0.4.2` -**Latest snapshot** version is `0.4.2-SNAPSHOT` +**Latest snapshot** version is `0.4.3-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.1 + 0.4.2 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.1"]` +file: `[io.github.erdos/stencil-core "0.4.2"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 15152d50..f79a3aba 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.2-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.2" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index e6f67de8..b9057ab3 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.2-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.2" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.2-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.2"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From f0eaf4ce3ea8f7718b2a8fef39b0e8dfc42317aa Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 29 May 2022 10:22:53 +0200 Subject: [PATCH 19/61] chore: start work on 0.4.3 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index f79a3aba..60cac348 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.2" +(defproject io.github.erdos/stencil-core "0.4.3-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index b9057ab3..a1985cd3 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.2" +(defproject io.github.erdos/stencil-service "0.4.3-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.2"] + [io.github.erdos/stencil-core "0.4.3-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 944d2d7d5bc0918f7b2f4e69a56c4011cfdbef90 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Tue, 31 May 2022 22:33:07 +0200 Subject: [PATCH 20/61] feat: pageBreak function (#126) --- docs/Functions.md | 5 +++++ src/stencil/functions.clj | 8 +++++++- src/stencil/ooxml.clj | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/Functions.md b/docs/Functions.md index c7acca80..1e463058 100644 --- a/docs/Functions.md +++ b/docs/Functions.md @@ -25,6 +25,7 @@ This is a short description of the functions implemented in Stencil: - [list](#list) - [lowercase](#lowercase) - [map](#map) +- [pageBreak](#pagebreak) - `percent` - `range` - [round](#round) @@ -164,6 +165,10 @@ The rendering throws an exception on invalid HTML input or unexpected HTML tags. Write the following to embed the content of `x` as HTML in the document: - {%=html(x) %}. +### PageBreak + +Inserts a page break at the place of the call. Example: {%=pageBreak()%} + ### XML You can embed custom xml fragments in the document with the `xml()` function. The parameter is a string containing the XML nodes to insert. diff --git a/src/stencil/functions.clj b/src/stencil/functions.clj index 48efdacf..6c72e0c0 100644 --- a/src/stencil/functions.clj +++ b/src/stencil/functions.clj @@ -1,7 +1,8 @@ (ns stencil.functions "Function definitions" (:require [clojure.string] - [stencil.types :refer [->HideTableColumnMarker ->HideTableRowMarker]] + [stencil.ooxml :as ooxml] + [stencil.types :refer [->HideTableColumnMarker ->HideTableRowMarker ->FragmentInvoke]] [stencil.util :refer [fail find-first]])) (set! *warn-on-reflection* true) @@ -108,3 +109,8 @@ 0 "" 1 (str (first elements)) (str (clojure.string/join separator1 (butlast elements)) separator2 (last elements)))) + +;; inserts a page break at the current run. +(let [br {:tag ooxml/br :attrs {ooxml/type "page"}} + page-break (->FragmentInvoke {:frag-evaled-parts [br]})] + (defmethod call-fn "pageBreak" [_] page-break)) diff --git a/src/stencil/ooxml.clj b/src/stencil/ooxml.clj index 414c0243..171680ef 100644 --- a/src/stencil/ooxml.clj +++ b/src/stencil/ooxml.clj @@ -1,6 +1,6 @@ (ns stencil.ooxml "Contains common xml element tags and attributes" - (:refer-clojure :exclude [val name])) + (:refer-clojure :exclude [val name type])) ;; run and properties (def r :xmlns.http%3A%2F%2Fschemas.openxmlformats.org%2Fwordprocessingml%2F2006%2Fmain/r) @@ -25,6 +25,8 @@ ;; Content Break: http://officeopenxml.com/WPtextSpecialContent-break.php (def br :xmlns.http%3A%2F%2Fschemas.openxmlformats.org%2Fwordprocessingml%2F2006%2Fmain/br) +(def type :xmlns.http%3A%2F%2Fschemas.openxmlformats.org%2Fwordprocessingml%2F2006%2Fmain/type) + (def w :xmlns.http%3A%2F%2Fschemas.openxmlformats.org%2Fwordprocessingml%2F2006%2Fmain/w) ;; XML space attribute (eg.: preserve) From ae25dd47ea40a5ae864af1d9a1ae77308f692f68 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 1 Jun 2022 22:54:47 +0200 Subject: [PATCH 21/61] feat(service): bump ring-json to 0.5.1 --- service/project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/project.clj b/service/project.clj index a1985cd3..29a6852b 100644 --- a/service/project.clj +++ b/service/project.clj @@ -8,6 +8,6 @@ [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.5.0"]] + [ring/ring-json "0.5.1"]] :aot :all :main stencil.service) From 7f4c666abd04cbd164d75ae71118825cee9f8190 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Wed, 1 Jun 2022 23:30:59 +0200 Subject: [PATCH 22/61] feat: foreach construct (#129) --- docs/Syntax.md | 7 +++++++ src/stencil/eval.clj | 11 ++++++++--- src/stencil/tokenizer.clj | 7 +++++-- test/stencil/eval_test.clj | 13 +++++++++++++ test/stencil/tokenizer_test.clj | 6 +++++- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/Syntax.md b/docs/Syntax.md index db19ca7c..bab74a64 100644 --- a/docs/Syntax.md +++ b/docs/Syntax.md @@ -104,6 +104,13 @@ Syntax: In this example we iterate over the contents of the `elements` array. The text `BODY` is inserted for every element. +### Iteration with index + +A special syntax is implemented for iterating over a collection (vector or map) with the indices bound to a new variable. Syntax: + +- {%for idx, x in elements %}BODY{%end%} + + ## Finding errors - Check that every control structure is properly closed! diff --git a/src/stencil/eval.clj b/src/stencil/eval.clj index b171b4a2..bc455252 100644 --- a/src/stencil/eval.clj +++ b/src/stencil/eval.clj @@ -30,10 +30,15 @@ [{:text (if (control? value) value (str value))}])) (defmethod eval-step :for [function data item] - (let [items (seq (eval-rpn data function (:expression item)))] + (let [items (eval-rpn data function (:expression item))] (log/trace "Loop on {} will repeat {} times" (:expression item) (count items)) - (if (seq items) - (let [datas (map #(assoc data (name (:variable item)) %) items) + (if (not-empty items) + (let [index-var-name (name (:index-var item)) + loop-var-name (name (:variable item)) + datamapper (fn [key val] (assoc data, index-var-name key, loop-var-name val)) + datas (if (or (instance? java.util.Map items) (map? items)) + (map datamapper (keys items) (vals items)) + (map-indexed datamapper items)) bodies (cons (:body-run-once item) (repeat (:body-run-next item)))] (mapcat (fn [data body] (normal-control-ast->evaled-seq data function body)) datas bodies)) (:body-run-none item)))) diff --git a/src/stencil/tokenizer.clj b/src/stencil/tokenizer.clj index 923c159a..580dfb70 100644 --- a/src/stencil/tokenizer.clj +++ b/src/stencil/tokenizer.clj @@ -1,6 +1,7 @@ (ns stencil.tokenizer "Fog egy XML dokumentumot es tokenekre bontja" (:require [clojure.data.xml :as xml] + [clojure.string :refer [includes? split trim]] [stencil.infix :as infix] [stencil.types :refer [open-tag close-tag]] [stencil.util :refer [assoc-if-val mod-stack-top-conj mod-stack-top-last parsing-exception]])) @@ -24,9 +25,11 @@ :condition (conj (vec (infix/parse (.substring text 7))) :not)} (.startsWith text "for ") - (let [[v expr] (vec (.split (.substring text 4) " in " 2))] + (let [[v expr] (split (subs text 4) #" in " 2) + [idx v] (if (includes? v ",") (split v #",") ["$" v])] {:cmd :for - :variable (symbol (.trim ^String v)) + :variable (symbol (trim v)) + :index-var (symbol (trim idx)) :expression (infix/parse expr)}) (.startsWith text "=") diff --git a/test/stencil/eval_test.clj b/test/stencil/eval_test.clj index c4b199a8..2efca1c7 100644 --- a/test/stencil/eval_test.clj +++ b/test/stencil/eval_test.clj @@ -51,6 +51,7 @@ (testing "loop without any items" (test-eval [{:cmd :for :variable "index" + :index-var "i" :expression '[list0] :body-run-once [{:text "xx"}] :body-run-none [{:text "meh"}] @@ -60,15 +61,27 @@ (testing "loop with exactly 1 item" (test-eval [{:cmd :for :variable "index" + :index-var "i" :expression '[list1] :body-run-once [{:cmd :echo :expression '[index]}] :body-run-none [{:text "meh"}] :body-run-next [{:text "x"}]}] [{:text "1"}])) + (testing "loop with exactly 1 item and index var used" + (test-eval [{:cmd :for + :variable "index" + :index-var "i" + :expression '[abc] + :body-run-once [{:cmd :echo :expression '[i]} {:text "==>"} {:cmd :echo :expression '[index]}] + :body-run-none [{:text "should-not-run"}] + :body-run-next [{:text "should-not-run"}]}] + [{:text "def"} {:text "==>"} {:text "Okay"}])) + (testing "loop with exactly 3 items" (test-eval [{:cmd :for :variable "index" + :index-var "i" :expression '[list3] :body-run-once [{:cmd :echo :expression '[index]}] :body-run-none [{:text "meh"}] diff --git a/test/stencil/tokenizer_test.clj b/test/stencil/tokenizer_test.clj index 200f66d4..9608b63d 100644 --- a/test/stencil/tokenizer_test.clj +++ b/test/stencil/tokenizer_test.clj @@ -105,8 +105,12 @@ (run "Hello {%if x%}iksz{%elif y%}ipszilon{%else%}egyebkent{%end%} Hola"))))) (deftest read-tokens-for + (testing "Indexed loop" + (is (= '[{:open :a} {:cmd :for, :expression [xs], :variable x :index-var idx} + {:text "item"} {:cmd :end} {:close :a}] + (run "{%for idx, x in xs%}item{% end %}")))) (testing "Simple loop" - (is (= '[{:open :a} {:cmd :for, :expression [xs], :variable x} + (is (= '[{:open :a} {:cmd :for, :expression [xs], :variable x :index-var $} {:text "item"} {:cmd :end} {:close :a}] (run "{%for x in xs%}item{% end %}"))))) From 7b3d20a90c330d61830cd4f6cb10942032bcc2e0 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 1 Jun 2022 23:32:16 +0200 Subject: [PATCH 23/61] Revert "feat(service): bump ring-json to 0.5.1" This reverts commit ae25dd47ea40a5ae864af1d9a1ae77308f692f68. --- service/project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/project.clj b/service/project.clj index 29a6852b..a1985cd3 100644 --- a/service/project.clj +++ b/service/project.clj @@ -8,6 +8,6 @@ [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.5.1"]] + [ring/ring-json "0.5.0"]] :aot :all :main stencil.service) From 4973536d7330fc55a0b55fa9d771c810b15f7db1 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 1 Jun 2022 23:32:33 +0200 Subject: [PATCH 24/61] feat: version 0.4.3 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 68e4c7f4..20431f0d 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.2` +**Latest stable** version is `0.4.3` -**Latest snapshot** version is `0.4.3-SNAPSHOT` +**Latest snapshot** version is `0.4.4-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.2 + 0.4.3 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.2"]` +file: `[io.github.erdos/stencil-core "0.4.3"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 60cac348..4aeaaa52 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.3-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.3" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index a1985cd3..55907409 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.3-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.3" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.3-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.3"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From c059c2d0e75efc208c870c48619c5f89a70e71b3 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 2 Jun 2022 00:06:00 +0200 Subject: [PATCH 25/61] chore: start work on 0.4.4 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 4aeaaa52..54855c8f 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.3" +(defproject io.github.erdos/stencil-core "0.4.4-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 55907409..e628a30d 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.3" +(defproject io.github.erdos/stencil-service "0.4.4-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.3"] + [io.github.erdos/stencil-core "0.4.4-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 44aa1ffec77d4b1d0512b27c42b5a7aa6b4fb53a Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 9 Jun 2022 11:25:10 +0300 Subject: [PATCH 26/61] fix: decimal precision of format fn --- src/stencil/functions.clj | 2 +- test/stencil/functions_test.clj | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stencil/functions.clj b/src/stencil/functions.clj index 6c72e0c0..4641b589 100644 --- a/src/stencil/functions.clj +++ b/src/stencil/functions.clj @@ -51,7 +51,7 @@ (string? value) (first value) :else (char (int value))) ("d" "o" "x" "X") (some-> value biginteger) - ("e" "E" "f" "g" "G" "a" "A") (some-> value bigdec) + ("e" "E" "f" "g" "G" "a" "A") (with-precision 8 (some-> value bigdec)) value))) (to-array) (String/format locale pattern-str))))) diff --git a/test/stencil/functions_test.clj b/test/stencil/functions_test.clj index e36908e3..9294341c 100644 --- a/test/stencil/functions_test.clj +++ b/test/stencil/functions_test.clj @@ -27,6 +27,8 @@ (is (= "Hello null" (call-fn "format" "Hello %c" nil))) (is (= "Hello x" (call-fn "format" "Hello %c" "x"))) (is (= "Hello X" (call-fn "format" "Hello %C" \x)))) + (testing "decimal precision" + (is (= "0.33" (call-fn "format" "%,.2f" 1/3)))) (testing "Indexed parameters" (is (= "hello 42 41.00" (call-fn "format" "hello %2$d %1$,.2f" 41.0 42.0)))) (is (= "hello john" (call-fn "format" "hello %s" "john"))) From 363f3e1c0d50d07a7010c60f84ed5c304332b282 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 9 Jun 2022 11:26:48 +0300 Subject: [PATCH 27/61] fix: precision of decimal fn --- src/stencil/functions.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stencil/functions.clj b/src/stencil/functions.clj index 4641b589..1796454c 100644 --- a/src/stencil/functions.clj +++ b/src/stencil/functions.clj @@ -19,7 +19,7 @@ ([_ x y z] (range x y z))) (defmethod call-fn "integer" [_ n] (some-> n biginteger)) -(defmethod call-fn "decimal" [_ f] (some-> f bigdec)) +(defmethod call-fn "decimal" [_ f] (with-precision 8 (some-> f bigdec))) ;; The format() function calls java.lang.String.format() ;; but it predicts the argument types from the format string and From 3a9509f5b20aa2f15fce2616ba2baafa6489482f Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 9 Jun 2022 11:27:28 +0300 Subject: [PATCH 28/61] feat: version 0.4.4 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 20431f0d..d4f89885 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.3` +**Latest stable** version is `0.4.4` -**Latest snapshot** version is `0.4.4-SNAPSHOT` +**Latest snapshot** version is `0.4.5-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.3 + 0.4.4 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.3"]` +file: `[io.github.erdos/stencil-core "0.4.4"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 54855c8f..012538ba 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.4-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.4" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index e628a30d..d4c85b93 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.4-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.4" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.4-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.4"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 8c466226e1aef44cd3d40530da7de65090117ced Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 21 Jun 2022 20:59:23 +0200 Subject: [PATCH 29/61] chore: start work on 0.4.5 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 012538ba..21179fb4 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.4" +(defproject io.github.erdos/stencil-core "0.4.5-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index d4c85b93..c62848e7 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.4" +(defproject io.github.erdos/stencil-service "0.4.5-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.4"] + [io.github.erdos/stencil-core "0.4.5-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From f2e18d940891d46ec75877fb69dd5db91cdbe7f9 Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 21 Jun 2022 21:10:33 +0200 Subject: [PATCH 30/61] simplifications --- src/stencil/merger.clj | 14 +++++++------- src/stencil/postprocess/html.clj | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/stencil/merger.clj b/src/stencil/merger.clj index aededa43..1b18232d 100644 --- a/src/stencil/merger.clj +++ b/src/stencil/merger.clj @@ -57,18 +57,16 @@ {:tokens (conj output {:text s})} {:tokens output})))) -(declare cleanup-runs) - ;; returns a map of {:char :stack :text-rest :rest} (defn -find-open-tag [last-chars-count next-token-list] (assert (integer? last-chars-count)) (assert (pos? last-chars-count)) (assert (sequential? next-token-list)) - (when (= (drop last-chars-count open-tag) - (take (- (count open-tag) last-chars-count) - (map :char (peek-next-text next-token-list)))) - (nth (peek-next-text next-token-list) - (dec (- (count open-tag) last-chars-count))))) + (let [next-text (peek-next-text next-token-list) + n (- (count open-tag) last-chars-count)] + (when (= (drop last-chars-count open-tag) + (take n (map :char next-text))) + (nth next-text (dec n))))) (defn -last-chars-count [sts-tokens] (assert (sequential? sts-tokens)) @@ -86,6 +84,8 @@ {:action parsed})) token)) +(declare cleanup-runs) + (defn cleanup-runs-1 [[first-token & rest-tokens]] (assert (:text first-token)) (let [sts (text-split-tokens (:text first-token))] diff --git a/src/stencil/postprocess/html.clj b/src/stencil/postprocess/html.clj index 12c5823c..74265c09 100644 --- a/src/stencil/postprocess/html.clj +++ b/src/stencil/postprocess/html.clj @@ -48,14 +48,15 @@ (update x :path conj (kw-lowercase (:tag xml))))) [{:text xml}])) -(defn- path->styles [path] - (cond-> [] - (some #{:b :em :strong} path) (conj {:tag ooxml/b :attrs {ooxml/val "true"}}) - (some #{:i} path) (conj {:tag ooxml/i :attrs {ooxml/val "true"}}) - (some #{:s} path) (conj {:tag ooxml/strike :attrs {ooxml/val "true"}}) - (some #{:u} path) (conj {:tag ooxml/u :attrs {ooxml/val "single"}}) - (some #{:sup} path) (conj {:tag ooxml/vertAlign :attrs {ooxml/val "superscript"}}) - (some #{:sub} path) (conj {:tag ooxml/vertAlign :attrs {ooxml/val "subscript"}}))) +(defn- path->style [p] + (case p + (:b :em :strong) {:tag ooxml/b :attrs {ooxml/val "true"}} + (:i) {:tag ooxml/i :attrs {ooxml/val "true"}} + (:s) {:tag ooxml/strike :attrs {ooxml/val "true"}} + (:u) {:tag ooxml/u :attrs {ooxml/val "single"}} + (:sup) {:tag ooxml/vertAlign :attrs {ooxml/val "superscript"}} + (:sub) {:tag ooxml/vertAlign :attrs {ooxml/val "subscript"}} + nil)) (defn html->ooxml-runs "Parses html string and returns a seq of ooxml run elements. @@ -64,7 +65,7 @@ (when (seq html) (let [ch (walk-children (parse-html (str "" html "")))] (for [parts (partition-by :path ch) - :let [prs (into (set base-style) (path->styles (:path (first parts))))]] + :let [prs (into (set base-style) (keep path->style) (:path (first parts)))]] {:tag ooxml/r :content (cons {:tag ooxml/rPr :content (vec prs)} (for [{:keys [text]} parts] From 0a6db2c6c9be585a7b1fd30242376557f15025c9 Mon Sep 17 00:00:00 2001 From: erdos Date: Tue, 21 Jun 2022 21:45:36 +0200 Subject: [PATCH 31/61] bump junit version --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 21179fb4..835ff736 100644 --- a/project.clj +++ b/project.clj @@ -32,7 +32,7 @@ :dependencies [[org.slf4j/slf4j-simple "1.7.32"]] :jvm-opts ["-Dorg.slf4j.simpleLogger.defaultLogLevel=debug"]} :test {:aot :all - :dependencies [[junit/junit "4.12"] + :dependencies [[junit/junit "4.13.2"] [org.xmlunit/xmlunit-core "2.5.1"] [hiccup "1.0.5"]] :plugins [[lein-test-out "0.3.1"]] From c3b287161c7548ebbebb98542a0933f3356e6f97 Mon Sep 17 00:00:00 2001 From: erdos Date: Wed, 20 Jul 2022 22:37:23 +0200 Subject: [PATCH 32/61] feat: test for coalesce fn --- test/stencil/functions_test.clj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/stencil/functions_test.clj b/test/stencil/functions_test.clj index 9294341c..7bf0ce46 100644 --- a/test/stencil/functions_test.clj +++ b/test/stencil/functions_test.clj @@ -106,6 +106,11 @@ (is (thrown? ExceptionInfo (call-fn "map" "x" {:x 1 :y 2}))) (is (thrown? ExceptionInfo (call-fn "map" 1 []))))) +(deftest test-coalesce + (is (= 1 (call-fn "coalesce" 1 2))) + (is (= 1 (call-fn "coalesce" nil nil 1 nil 2 nil))) + (is (= nil (call-fn "coalesce" nil nil nil)))) + (deftest test-join-and (are [expect param] (= expect (call-fn "joinAnd" param ", " " and ")) "" nil From 5a6ddd9c9d4f3e6fd15d71bb01f7fd91adffeee6 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Thu, 21 Jul 2022 17:53:34 +0200 Subject: [PATCH 33/61] feat: replace fn (#134) --- docs/Functions.md | 4 ++++ src/stencil/functions.clj | 3 +++ test/stencil/functions_test.clj | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/docs/Functions.md b/docs/Functions.md index 1e463058..60df5531 100644 --- a/docs/Functions.md +++ b/docs/Functions.md @@ -205,6 +205,10 @@ The `lowercase(x)` function turns its string argument into a lowercase string. F The `str(x)` functions convers its non-null arguments into a string. Returns an empty string when all arguments are null. +## Replace + +The `replace(text, pattern, replacement)` function replaces all occurrence of `pattern` in `text` by `replacement`. + ## Numeric functions ### Round diff --git a/src/stencil/functions.clj b/src/stencil/functions.clj index 1796454c..d30721b9 100644 --- a/src/stencil/functions.clj +++ b/src/stencil/functions.clj @@ -110,6 +110,9 @@ 1 (str (first elements)) (str (clojure.string/join separator1 (butlast elements)) separator2 (last elements)))) +(defmethod call-fn "replace" [_ text pattern replacement] + (clojure.string/replace (str text) (str pattern) (str replacement))) + ;; inserts a page break at the current run. (let [br {:tag ooxml/br :attrs {ooxml/type "page"}} page-break (->FragmentInvoke {:frag-evaled-parts [br]})] diff --git a/test/stencil/functions_test.clj b/test/stencil/functions_test.clj index 7bf0ce46..6bcfde63 100644 --- a/test/stencil/functions_test.clj +++ b/test/stencil/functions_test.clj @@ -119,6 +119,12 @@ "1 and 2" [1 2] "1, 2 and 3" [1 2 3])) +(deftest test-replace + (is (= "a1c" (call-fn "replace" "abc" "b" "1"))) + (is (= "a1a1" (call-fn "replace" "abab" "b" "1"))) + (is (= "a1a1" (call-fn "replace" "a.a." "." "1"))) + (is (= "123456" (call-fn "replace" " 12 34 56 " " " "")))) + (import '[stencil.types ReplaceImage]) (def data-uri "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==") From 24621eea4ea5805434b92e9e89a355efaaba5f3b Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 21 Jul 2022 17:54:13 +0200 Subject: [PATCH 34/61] feat: version 0.4.5 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4f89885..d2598237 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.4` +**Latest stable** version is `0.4.5` -**Latest snapshot** version is `0.4.5-SNAPSHOT` +**Latest snapshot** version is `0.4.6-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.4 + 0.4.5 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.4"]` +file: `[io.github.erdos/stencil-core "0.4.5"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 835ff736..bd91d07b 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.5-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.5" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index c62848e7..5977bd49 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.5-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.5" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.5-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.5"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 5d48f17adda3de295bfb73c89f2a2853edb7bb67 Mon Sep 17 00:00:00 2001 From: erdos Date: Sat, 23 Jul 2022 13:06:34 +0200 Subject: [PATCH 35/61] chore: start work on 0.4.6 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index bd91d07b..4a0a6cf1 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.5" +(defproject io.github.erdos/stencil-core "0.4.6-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 5977bd49..73b37332 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.5" +(defproject io.github.erdos/stencil-service "0.4.6-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.5"] + [io.github.erdos/stencil-core "0.4.6-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 20c3b29ae599e746fa5ce2a9e2506c11607a3719 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Mon, 25 Jul 2022 09:11:50 +0200 Subject: [PATCH 36/61] fix: fragments dropped environment (#136) --- src/stencil/postprocess/fragments.clj | 2 +- test/stencil/postprocess/fragments_test.clj | 25 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 test/stencil/postprocess/fragments_test.clj diff --git a/src/stencil/postprocess/fragments.clj b/src/stencil/postprocess/fragments.clj index 85ca9a82..89b2d24d 100644 --- a/src/stencil/postprocess/fragments.clj +++ b/src/stencil/postprocess/fragments.clj @@ -54,7 +54,7 @@ text (:content run) :when (map? text) :when (= ooxml/t (:tag text)) - c (:content run) + c (:content text) :when (string? c) :when (not-empty c)] c)))) diff --git a/test/stencil/postprocess/fragments_test.clj b/test/stencil/postprocess/fragments_test.clj new file mode 100644 index 00000000..5ab0a94c --- /dev/null +++ b/test/stencil/postprocess/fragments_test.clj @@ -0,0 +1,25 @@ +(ns stencil.postprocess.fragments-test + (:require [clojure.test :refer [deftest testing is are]] + [clojure.zip :as zip] + [stencil.ooxml :as ooxml] + [stencil.util :refer [find-first-in-tree xml-zip]] + [stencil.postprocess.fragments :refer :all])) + +(defn- p [& xs] {:tag ooxml/p :content (vec xs)}) +(defn- r [& xs] {:tag ooxml/r :content (vec xs)}) +(defn- t [& xs] {:tag ooxml/t :content (vec xs)}) + +(def -split-paragraphs @#'stencil.postprocess.fragments/split-paragraphs) + +(deftest test-split-paragraphs-1 + (let [data {:tag :root :content [(p (r (t "hello" :HERE) (t "vilag")))]} + loc (find-first-in-tree #{:HERE} (xml-zip data))] + (-> loc + (doto assert) + (-split-paragraphs (p (r (t "one"))) (p (r (t "two")))) + (zip/root) + (= {:tag :root + :content [(p (r (t "hello"))) ;; before + (p (r (t "one"))) (p (r (t "two"))) ;; newly inserted + (p (r (t) (t "vilag")))]}) ;; after + (is)))) \ No newline at end of file From ffe65f3c05e1719d14b2f3f24d125e099c2eb515 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Mon, 25 Jul 2022 09:15:49 +0200 Subject: [PATCH 37/61] fix: merger substring exception (#135) --- src/stencil/merger.clj | 2 +- test/stencil/merger_test.clj | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stencil/merger.clj b/src/stencil/merger.clj index 1b18232d..2891898b 100644 --- a/src/stencil/merger.clj +++ b/src/stencil/merger.clj @@ -31,7 +31,7 @@ (assert (string? s)) (let [ind (.indexOf s (str open-tag))] (when-not (neg? ind) - (let [after-idx (.indexOf s (str close-tag))] + (let [after-idx (.indexOf s (str close-tag) ind)] (if (neg? after-idx) (cond-> {:action-part (.substring s (+ ind (count open-tag)))} (not (zero? ind)) (assoc :before (.substring s 0 ind))) diff --git a/test/stencil/merger_test.clj b/test/stencil/merger_test.clj index 40f23f18..67a08721 100644 --- a/test/stencil/merger_test.clj +++ b/test/stencil/merger_test.clj @@ -126,4 +126,8 @@ [{:text "abc{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] [{:text "abc"} {:text "{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] [{:text "abc"} {:action {:cmd :echo, :expression [12]}} O1 O2 O3 O4 O5 {:text "b"}] + + [O1 {:text "{%if p"} O2 O3 {:text "%}one{%end%}"} O4] + [O1 {:text "{%if p"} O2 O3 {:text "%}one"} {:text "{%end%}"} O4] + [O1 {:action {:cmd :if, :condition '[p]}} O2 O3 {:text "one"} {:action {:cmd :end}} O4] )))) From d6ff81c747d5a02b6d086510878405a37f056725 Mon Sep 17 00:00:00 2001 From: erdos Date: Mon, 25 Jul 2022 09:29:30 +0200 Subject: [PATCH 38/61] feat: version 0.4.6 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d2598237..590e9e99 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.5` +**Latest stable** version is `0.4.6` -**Latest snapshot** version is `0.4.6-SNAPSHOT` +**Latest snapshot** version is `0.4.7-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.5 + 0.4.6 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.5"]` +file: `[io.github.erdos/stencil-core "0.4.6"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 4a0a6cf1..18ed8568 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.6-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.6" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 73b37332..8b253776 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.6-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.6" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.6-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.6"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 6df4969a49998cdda0ad87f782d468b7be6a6ba9 Mon Sep 17 00:00:00 2001 From: erdos Date: Mon, 25 Jul 2022 09:54:03 +0200 Subject: [PATCH 39/61] chore: start work on 0.4.7 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 18ed8568..51ed06c2 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.6" +(defproject io.github.erdos/stencil-core "0.4.7-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 8b253776..47e824e9 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.6" +(defproject io.github.erdos/stencil-service "0.4.7-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.6"] + [io.github.erdos/stencil-core "0.4.7-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 8e422d45f28a4fa6ce238541f5306473a88fb287 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Thu, 28 Jul 2022 21:42:59 +0200 Subject: [PATCH 40/61] feat: specs (#83) --- .../erdos/stencil/impl/NativeEvaluator.java | 9 +- project.clj | 15 ++- src/stencil/spec.clj | 105 ++++++++++++++++++ 3 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 src/stencil/spec.clj diff --git a/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java b/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java index 13c07411..3b23487f 100644 --- a/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java +++ b/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java @@ -2,6 +2,7 @@ import clojure.lang.AFunction; import clojure.lang.IFn; +import clojure.lang.PersistentHashMap; import io.github.erdos.stencil.*; import io.github.erdos.stencil.exceptions.EvalException; import io.github.erdos.stencil.functions.FunctionEvaluator; @@ -49,7 +50,7 @@ public EvaluatedDocument render(PreparedTemplate template, Map "Starting document rendering for template " + template.getTemplateFile()); final IFn fn = ClojureHelper.findFunction("eval-template"); - final Map argsMap = makeArgsMap(template.getSecretObject(), fragments, data.getData()); + final Object argsMap = makeArgsMap(template.getSecretObject(), fragments, data.getData()); final Map result; try { @@ -94,7 +95,7 @@ private static Consumer resultWriter(Map result) { } @SuppressWarnings("unchecked") - private Map makeArgsMap(Object template, Map fragments, Object data) { + private Object makeArgsMap(Object template, Map fragments, Object data) { final Map result = new HashMap(); result.put(ClojureHelper.Keywords.TEMPLATE.kw, template); result.put(ClojureHelper.Keywords.DATA.kw, data); @@ -103,9 +104,9 @@ private Map makeArgsMap(Object template, Map fragments // string to clojure map final Map kvs = fragments.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getImpl())); - result.put(ClojureHelper.Keywords.FRAGMENTS.kw, kvs); + result.put(ClojureHelper.Keywords.FRAGMENTS.kw, PersistentHashMap.create(kvs)); - return result; + return PersistentHashMap.create(result); } private final class FunctionCaller extends AFunction { diff --git a/project.clj b/project.clj index 51ed06c2..0a998453 100644 --- a/project.clj +++ b/project.clj @@ -13,10 +13,11 @@ :pom-plugins [[org.apache.maven.plugins/maven-surefire-plugin "2.20"]] :main io.github.erdos.stencil.Main :aliases {"junit" ["with-profile" "+test" "test-out" "junit" "junit.xml"] - "coverage" ["with-profile" "+ci" - "cloverage" "--codecov" + "coverage" ["with-profile" "+ci" "cloverage" "--codecov" "--exclude-call" "clojure.core/assert" - "--exclude-call" "stencil.util/fail"]} + "--exclude-call" "stencil.util/trace" + "--exclude-call" "stencil.util/fail" + "--exclude-call" "clojure.spec.alpha/def"]} :javadoc-opts {:package-names ["stencil"] :additional-args ["-overview" "java-src/overview.html" "-top" ""]} @@ -29,6 +30,8 @@ :filespecs [{:type :bytes, :path "stencil-version", :bytes ~(-> "project.clj" slurp read-string nnext first)}] :profiles {:uberjar {:aot :all} :dev {:aot :all + :injections [(require 'stencil.spec) + (require '[clojure.spec.alpha :as s])] :dependencies [[org.slf4j/slf4j-simple "1.7.32"]] :jvm-opts ["-Dorg.slf4j.simpleLogger.defaultLogLevel=debug"]} :test {:aot :all @@ -38,7 +41,9 @@ :plugins [[lein-test-out "0.3.1"]] :resource-paths ["test-resources"] :test-paths ["java-test"] - } + :injections [(require 'stencil.spec) + (require '[clojure.spec.test.alpha :as sta]) + (eval '(sta/instrument))]} :ci {:plugins [[lein-javadoc "0.3.0"] [lein-cloverage "1.2.2"]] - }}) + }}) \ No newline at end of file diff --git a/src/stencil/spec.clj b/src/stencil/spec.clj new file mode 100644 index 00000000..f54ffe22 --- /dev/null +++ b/src/stencil/spec.clj @@ -0,0 +1,105 @@ +(ns stencil.spec + (:import [java.io File]) + (:require [clojure.spec.alpha :as s] + [stencil.model :as m] + [stencil.process])) + + + +;; TODO +(s/def :stencil.model/mode #{"External"}) + +;; keys are either all keywords or all strings +(s/def ::data map?) + +;; other types are also possible +(s/def :stencil.model/type #{stencil.model/rel-type-footer + stencil.model/rel-type-header + stencil.model/rel-type-main + stencil.model/rel-type-slide}) + +(s/def :stencil.model/path (s/and string? not-empty #(not (.startsWith ^String % "/")))) + +;; relationship file +(s/def ::relations (s/keys :req [:stencil.model/path] + :req-un [::source-file] + :opt-un [:stencil.model/parsed])) + +(s/def :?/relations (s/nilable ::relations)) + +(s/def ::result (s/keys :req-un [::writer])) + +(s/def ::style (s/keys :req [:stencil.model/path] + :opt-un [::result])) + +(s/def :stencil.model/headers+footers (s/* (s/keys :req [:stencil.model/path] + :req-un [::source-file :stencil.model/executable :?/relations] + :opt-un [::result]))) + +(s/def ::source-folder (s/and (partial instance? java.io.File) + #(.isDirectory ^File %) + #(.exists ^File %))) + +(s/def ::source-file (s/and (partial instance? java.io.File) + #(.isFile ^File %) + #(.exists ^File %))) + +(s/def ::main (s/keys :req [:stencil.model/path] + :opt-un [:stencil.model/headers+footers ::result] ;; not present in fragments + :opt [::numbering] + :req-un [::source-file + ::executable + ::style + ::relations])) + + +(s/def :stencil.model/content-types + (s/keys :req [:stencil.model/path] + :req-un [::source-file])) + +(s/def :stencil.model/model + (s/keys :req [] + :req-un [::main ::source-folder :stencil.model/content-types ::relations])) + +(s/def ::parsed any?) + +(s/def :stencil.model/numbering (s/nilable (s/keys :req [:stencil.model/path] + :req-un [::source-file ::parsed]))) + +(s/fdef stencil.model/load-template-model + :args (s/cat :dir ::source-folder, :opts map?) + :ret :stencil.model/model) + +(s/fdef stencil.model/load-fragment-model + :args (s/cat :dir ::source-folder, :opts map?) + :ret :stencil.model/model) + +(s/fdef stencil.model/eval-template-model + :args (s/cat :model :stencil.model/model + :data ::data + :unused/function-arg any? + :fragments (s/map-of string? :stencil.model/model)) + :ret :stencil.model/model) + +(s/def :exec/variables (s/coll-of string? :unique true)) +(s/def :exec/dynamic? boolean?) +(s/def :exec/executable any?) ;; seq of normalized control ast. +(s/def :exec/fragments (s/coll-of string? :unique true)) ;; what was that? + +(s/def ::executable (s/keys :req-un [:exec/variables :exec/dynamic? :exec/executable :exec/fragments])) + +;; prepared template +(s/def ::template any?) + +(s/def :eval-template/data any?) ;; map of string or keyword keys +(s/def :eval-template/fragments (s/map-of string? any?)) +(s/def :eval-template/function fn?) + +(s/fdef stencil.process/eval-template + :args (s/cat :ps (s/keys :req-un [::template :eval-template/data :eval-template/function :eval-template/fragments]))) + +(s/def ::writer (s/fspec :args (s/cat :writer (partial instance? java.io.Writer)) :ret nil?)) + +(s/fdef template-model->writers-map + :args (s/cat :model :stencil.model/model, :data ::data, :functions any?, :fragments any?) + :ret (s/map-of :stencil.model/path ::writer)) \ No newline at end of file From 02a135ec2b88e6f5442fc49c8f8f0f4fc9ecc43d Mon Sep 17 00:00:00 2001 From: erdos Date: Fri, 29 Jul 2022 11:53:51 +0200 Subject: [PATCH 41/61] fix: linter warnings --- src/stencil/model/common.clj | 3 +-- src/stencil/model/relations.clj | 2 +- src/stencil/model/style.clj | 2 +- src/stencil/postprocess/fragments.clj | 2 +- src/stencil/postprocess/html.clj | 2 +- src/stencil/postprocess/list_ref.clj | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/stencil/model/common.clj b/src/stencil/model/common.clj index 9234134b..94ac3026 100644 --- a/src/stencil/model/common.clj +++ b/src/stencil/model/common.clj @@ -3,8 +3,7 @@ [java.io File] [io.github.erdos.stencil.impl FileHelper]) (:require [clojure.data.xml :as xml] - [clojure.java.io :as io] - [stencil.util :refer :all])) + [clojure.java.io :as io])) (defn ->xml-writer [tree] diff --git a/src/stencil/model/relations.clj b/src/stencil/model/relations.clj index a69f7bb8..e18d6583 100644 --- a/src/stencil/model/relations.clj +++ b/src/stencil/model/relations.clj @@ -4,7 +4,7 @@ [clojure.java.io :as io :refer [file]] [stencil.ooxml :as ooxml] [stencil.util :refer :all] - [stencil.model.common :refer :all])) + [stencil.model.common :refer [->xml-writer]])) (def tag-relationships :xmlns.http%3A%2F%2Fschemas.openxmlformats.org%2Fpackage%2F2006%2Frelationships/Relationships) diff --git a/src/stencil/model/style.clj b/src/stencil/model/style.clj index 927e9a6c..8a5b8cf8 100644 --- a/src/stencil/model/style.clj +++ b/src/stencil/model/style.clj @@ -89,7 +89,7 @@ (defn main-style-item [^File dir main-document main-document-rels] - (when-let [main-style (some #(when (= rel-type (:stencil.model/type %)) %) + (when-let [main-style (find-first #(= rel-type (:stencil.model/type %)) (vals (:parsed main-document-rels)))] (let [main-style-file (io/file (.getParentFile (io/file main-document)) (:stencil.model/target main-style)) diff --git a/src/stencil/postprocess/fragments.clj b/src/stencil/postprocess/fragments.clj index 89b2d24d..a5a80379 100644 --- a/src/stencil/postprocess/fragments.clj +++ b/src/stencil/postprocess/fragments.clj @@ -82,7 +82,7 @@ rights1 (zip/rights (zip/up chunk-loc)) ;; style of run that is split - style (some #(when (= ooxml/rPr (:tag %)) %) (:content r)) + style (find-first #(= ooxml/rPr (:tag %)) (:content r)) ->t (fn [xs] {:tag ooxml/t :content (vec xs)}) ->run (fn [cts] (assoc r :content (vec (cons style cts))))] diff --git a/src/stencil/postprocess/html.clj b/src/stencil/postprocess/html.clj index 74265c09..78469ebb 100644 --- a/src/stencil/postprocess/html.clj +++ b/src/stencil/postprocess/html.clj @@ -75,7 +75,7 @@ (defn- current-run-style [chunk-loc] (let [r (zip/node (zip/up (zip/up chunk-loc)))] - (some #(when (= ooxml/rPr (:tag %)) %) (:content r)))) + (find-first #(= ooxml/rPr (:tag %)) (:content r)))) (defn- fix-html-chunk [chunk-loc] (assert (instance? HtmlChunk (zip/node chunk-loc))) diff --git a/src/stencil/postprocess/list_ref.clj b/src/stencil/postprocess/list_ref.clj index 2f07b204..5a6107ef 100644 --- a/src/stencil/postprocess/list_ref.clj +++ b/src/stencil/postprocess/list_ref.clj @@ -28,7 +28,7 @@ (loop [buf [], n number] (if (zero? n) (apply str buf) - (let [[value romnum] (some #(if (>= n (first %)) %) roman-digits)] + (let [[value romnum] (find-first #(>= n (first %)) roman-digits)] (recur (conj buf romnum) (- n value)))))) (defmethod render-number "lowerRoman" [_ number] From 807a55c56c2b5c4c0238d9cedd23322665de939d Mon Sep 17 00:00:00 2001 From: erdos Date: Fri, 5 Aug 2022 15:14:33 +0200 Subject: [PATCH 42/61] fix: call clj function from fragment --- src/stencil/model.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stencil/model.clj b/src/stencil/model.clj index 36c93bdb..f959c906 100644 --- a/src/stencil/model.clj +++ b/src/stencil/model.clj @@ -230,7 +230,7 @@ elem))) -(defmethod eval/eval-step :cmd/include [_ local-data-map {frag-name :name}] +(defmethod eval/eval-step :cmd/include [function local-data-map {frag-name :name}] (assert (map? local-data-map)) (assert (string? frag-name)) (expect-fragment-context! @@ -242,7 +242,7 @@ relation-rename-map (into {} (map (juxt :old-id :new-id)) relation-ids-rename) ;; evaluate - evaled (eval-template-model fragment-model local-data-map {} {}) + evaled (eval-template-model fragment-model local-data-map function {}) ;; write back get-xml (fn [x] (or (:xml x) @(:xml-delay x))) From d9954183953240ba58566f2d617fac4e265245b6 Mon Sep 17 00:00:00 2001 From: erdos Date: Mon, 8 Aug 2022 07:57:44 -0400 Subject: [PATCH 43/61] feat: version 0.4.7 --- README.md | 8 ++++---- project.clj | 2 +- service/project.clj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 590e9e99..72e68fe5 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.6` +**Latest stable** version is `0.4.7` -**Latest snapshot** version is `0.4.7-SNAPSHOT` +**Latest snapshot** version is `0.4.8-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.6 + 0.4.7 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.6"]` +file: `[io.github.erdos/stencil-core "0.4.7"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 0a998453..01d658d2 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.7-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.4.7" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index 47e824e9..f7811e43 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.7-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.4.7" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.7-SNAPSHOT"] + [io.github.erdos/stencil-core "0.4.7"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From a96ae892166a54b130594fdb2e6ecf4647e2706f Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 11 Aug 2022 16:03:50 -0400 Subject: [PATCH 44/61] chore: start work on v0.4.8 --- project.clj | 2 +- service/project.clj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.clj b/project.clj index 01d658d2..067f0ea2 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.7" +(defproject io.github.erdos/stencil-core "0.4.8-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index f7811e43..c030937c 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.7" +(defproject io.github.erdos/stencil-service "0.4.8-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.7"] + [io.github.erdos/stencil-core "0.4.8-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 82c07c863187cf2cd3f933bf78be7bfaefeb9f28 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 11 Aug 2022 17:50:44 -0400 Subject: [PATCH 45/61] feat: less reflection --- src/stencil/merger.clj | 22 ++++++++++------------ test/stencil/merger_test.clj | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/stencil/merger.clj b/src/stencil/merger.clj index 2891898b..5a91a2c8 100644 --- a/src/stencil/merger.clj +++ b/src/stencil/merger.clj @@ -1,6 +1,7 @@ (ns stencil.merger "Token listaban a text tokenekbol kiszedi a parancsokat es action tokenekbe teszi." (:require [clojure.data.xml :as xml] + [clojure.string :refer [index-of ends-with?]] [stencil.postprocess.ignored-tag :as ignored-tag] [stencil [types :refer [open-tag close-tag]] @@ -29,17 +30,14 @@ (defn find-first-code [^String s] (assert (string? s)) - (let [ind (.indexOf s (str open-tag))] - (when-not (neg? ind) - (let [after-idx (.indexOf s (str close-tag) ind)] - (if (neg? after-idx) - (cond-> {:action-part (.substring s (+ ind (count open-tag)))} - (not (zero? ind)) (assoc :before (.substring s 0 ind))) - (cond-> {:action (.substring s (+ ind (count open-tag)) - after-idx)} - (not (zero? ind)) (assoc :before (.substring s 0 ind)) - (not (= (+ (count close-tag) after-idx) (count s))) - (assoc :after (.substring s (+ (count close-tag) after-idx))))))))) + (when-let [ind (index-of s (str open-tag))] + (if-let [after-idx (index-of s (str close-tag) ind)] + (cond-> {:action (subs s (+ ind (count open-tag)) after-idx)} + (pos? ind) (assoc :before (subs s 0 ind)) + (not= (+ (count close-tag) after-idx) (count s)) + (assoc :after (subs s (+ (count close-tag) after-idx)))) + (cond-> {:action-part (subs s (+ ind (count open-tag)))} + (not (zero? ind)) (assoc :before (subs s 0 ind)))))) (defn text-split-tokens [^String s] (assert (string? s)) @@ -71,7 +69,7 @@ (defn -last-chars-count [sts-tokens] (assert (sequential? sts-tokens)) (when-let [last-text (some-> sts-tokens last :text string)] - (some #(when (.endsWith last-text (string %)) + (some #(when (ends-with? last-text (string %)) (count %)) (prefixes open-tag)))) diff --git a/test/stencil/merger_test.clj b/test/stencil/merger_test.clj index 67a08721..ba5c6479 100644 --- a/test/stencil/merger_test.clj +++ b/test/stencil/merger_test.clj @@ -25,6 +25,7 @@ "asdf{%xy%}" {:action "xy" :before "asdf"} "{%xy%}" {:action "xy"} "a{%xy" {:action-part "xy" :before "a"} + "a{%x%" {:action-part "x%" :before "a"} "{%xy" {:action-part "xy"}))) (deftest text-split-tokens-test From eb8113630e711452b9d6a3daec66bf5c73101d42 Mon Sep 17 00:00:00 2001 From: erdos Date: Mon, 14 Nov 2022 21:03:39 +0100 Subject: [PATCH 46/61] feat: warning fixes, small refactor --- .../io/github/erdos/stencil/TemplateData.java | 2 +- .../erdos/stencil/TemplateVariables.java | 7 ++-- .../stencil/functions/BasicFunctions.java | 2 +- .../stencil/functions/LocaleFunctions.java | 4 +-- .../stencil/functions/StringFunctions.java | 3 +- .../impl/DirWatcherTemplateFactory.java | 2 +- .../github/erdos/stencil/impl/FileHelper.java | 8 ++--- .../stencil/impl/NativeTemplateFactory.java | 29 ++++++++-------- .../erdos/stencil/standalone/ArgsParser.java | 34 +++++++++---------- .../erdos/stencil/standalone/JsonParser.java | 4 +-- .../standalone/StandaloneApplication.java | 2 -- 11 files changed, 46 insertions(+), 51 deletions(-) diff --git a/java-src/io/github/erdos/stencil/TemplateData.java b/java-src/io/github/erdos/stencil/TemplateData.java index 83862b0d..782bde2f 100644 --- a/java-src/io/github/erdos/stencil/TemplateData.java +++ b/java-src/io/github/erdos/stencil/TemplateData.java @@ -43,7 +43,7 @@ public static TemplateData fromMap(Map data) { * * @return template data map. Not null. */ - public final Map getData() { + public Map getData() { return data; } } diff --git a/java-src/io/github/erdos/stencil/TemplateVariables.java b/java-src/io/github/erdos/stencil/TemplateVariables.java index 3c08be62..71d8f87c 100644 --- a/java-src/io/github/erdos/stencil/TemplateVariables.java +++ b/java-src/io/github/erdos/stencil/TemplateVariables.java @@ -151,14 +151,13 @@ private List validate(Object data, Node schema) { return validateImpl("", data, schema).collect(toList()); } - @SuppressWarnings("unchecked") private Stream validateImpl(String path, Object data, Node schema) { return schema.accept(new NodeVisitor>() { @Override public Stream visitArray(Node wrapped) { if (data instanceof List) { final AtomicInteger index = new AtomicInteger(); - return ((List) data).stream().flatMap(x -> { + return ((List) data).stream().flatMap(x -> { final String newPath = path + "[" + index.getAndIncrement() + "]"; return validateImpl(newPath, x, wrapped); }); @@ -170,7 +169,7 @@ public Stream visitArray(Node wrapped) { @Override public Stream visitMap(Map items) { if (data instanceof Map) { - Map dataMap = (Map) data; + Map dataMap = (Map) data; return items.entrySet().stream().flatMap(schemaEntry -> { if (dataMap.containsKey(schemaEntry.getKey())) { return validateImpl(path + "." + schemaEntry.getKey(), dataMap.get(schemaEntry.getKey()), schemaEntry.getValue()); @@ -192,7 +191,7 @@ public Stream visitLeaf() { } @SuppressWarnings("unused") - class SchemaError { + private static final class SchemaError { private final String path; private final String msg; diff --git a/java-src/io/github/erdos/stencil/functions/BasicFunctions.java b/java-src/io/github/erdos/stencil/functions/BasicFunctions.java index 638776ed..cfb8bc3f 100644 --- a/java-src/io/github/erdos/stencil/functions/BasicFunctions.java +++ b/java-src/io/github/erdos/stencil/functions/BasicFunctions.java @@ -37,7 +37,7 @@ else if (expr != null && expr.equals(value)) }, /** - * Returns the first non-null an non-empty value. + * Returns the first non-null a non-empty value. *

* Accepts any arguments. Skips null values, empty strings and empty collections. */ diff --git a/java-src/io/github/erdos/stencil/functions/LocaleFunctions.java b/java-src/io/github/erdos/stencil/functions/LocaleFunctions.java index 4c08c731..fbf14e5a 100644 --- a/java-src/io/github/erdos/stencil/functions/LocaleFunctions.java +++ b/java-src/io/github/erdos/stencil/functions/LocaleFunctions.java @@ -10,7 +10,7 @@ public enum LocaleFunctions implements Function { /** - * Formats number as localized currency. An optional second argument can be used to specify locale code. + * Formats number as a localized monetary amount with currency. An optional second argument can be used to specify locale code. * Returns a string. *

* Usage: currency(34) or currency(34, "HU") @@ -23,7 +23,7 @@ public Object call(Object... arguments) throws IllegalArgumentException { }, /** - * Formats number as localized percent. An optional second argument can be used to specify locale code. + * Formats number as a localized percentage value. An optional second argument can be used to specify locale code. * Returns a string. *

* Usage: percent(34) or percent(34, "HU") diff --git a/java-src/io/github/erdos/stencil/functions/StringFunctions.java b/java-src/io/github/erdos/stencil/functions/StringFunctions.java index b4b9d695..ca2bb6d8 100644 --- a/java-src/io/github/erdos/stencil/functions/StringFunctions.java +++ b/java-src/io/github/erdos/stencil/functions/StringFunctions.java @@ -2,7 +2,6 @@ import java.util.Arrays; import java.util.Collection; -import java.util.IllegalFormatException; import java.util.Objects; import java.util.stream.Collectors; @@ -58,7 +57,7 @@ public Object call(Object... arguments) throws IllegalArgumentException { StringBuilder builder = new StringBuilder(); for (Object argument : arguments) { if (argument != null) - builder.append(argument.toString()); + builder.append(argument); } return builder.toString(); } diff --git a/java-src/io/github/erdos/stencil/impl/DirWatcherTemplateFactory.java b/java-src/io/github/erdos/stencil/impl/DirWatcherTemplateFactory.java index 41516acf..3370d870 100644 --- a/java-src/io/github/erdos/stencil/impl/DirWatcherTemplateFactory.java +++ b/java-src/io/github/erdos/stencil/impl/DirWatcherTemplateFactory.java @@ -180,7 +180,7 @@ public PreparedTemplate prepareTemplateFile(File inputTemplateFile, PrepareOptio .orElseThrow(() -> new IllegalArgumentException("Can not build template file: " + inputTemplateFile)); } - private final class DelayedContainer implements Delayed { + private static final class DelayedContainer implements Delayed { private final long expiration; private final X contents; diff --git a/java-src/io/github/erdos/stencil/impl/FileHelper.java b/java-src/io/github/erdos/stencil/impl/FileHelper.java index 75d929d5..1a429fd0 100644 --- a/java-src/io/github/erdos/stencil/impl/FileHelper.java +++ b/java-src/io/github/erdos/stencil/impl/FileHelper.java @@ -51,7 +51,7 @@ public static String removeExtension(File f) { * @return a new file object pointing to a non-existing file in temp directory. */ public static File createNonexistentTempFile(String prefix, String suffix) { - return new File(TEMP_DIRECTORY, prefix + UUID.randomUUID().toString() + suffix); + return new File(TEMP_DIRECTORY, prefix + UUID.randomUUID() + suffix); } /** @@ -101,7 +101,7 @@ public static void forceDelete(final File file) { * @param file to delete, not null * @throws NullPointerException on null or invalid file */ - @SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"}) + @SuppressWarnings({"ConstantConditions"}) public static void forceDeleteOnExit(final File file) { file.deleteOnExit(); if (file.isDirectory()) { @@ -113,7 +113,7 @@ public static void forceDeleteOnExit(final File file) { /** * Returns a string representation of path with unix separators ("/") instead of the - * system-dependent separators (which is backslash on windows.) + * system-dependent separators (which is backslash on Windows). * * @param path not null path object * @return string of path with slash separators @@ -128,7 +128,7 @@ public static String toUnixSeparatedString(Path path) { // on unix systems return path.toString(); } else { - // on windows systems we replace backslash with slashes + // on Windows systems we replace backslash with slashes return path.toString().replaceAll(Pattern.quote(separator), "/"); } } diff --git a/java-src/io/github/erdos/stencil/impl/NativeTemplateFactory.java b/java-src/io/github/erdos/stencil/impl/NativeTemplateFactory.java index 90fa6dae..a2d88981 100644 --- a/java-src/io/github/erdos/stencil/impl/NativeTemplateFactory.java +++ b/java-src/io/github/erdos/stencil/impl/NativeTemplateFactory.java @@ -48,18 +48,11 @@ public PreparedFragment prepareFragmentFile(final File fragmentFile, PrepareOpti final Map prepared; try { - //noinspection unchecked - prepared = (Map) prepareFunction.invoke(fragmentFile, options); - } catch (ParsingException e) { + prepared = invokePrepareFunction(fragmentFile, options); + } catch (ParsingException | IOException e) { throw e; } catch (Exception e) { - //noinspection ConstantConditions - if (e instanceof IOException) { - // possible because of Clojure magic :-( - throw (IOException) e; - } else { - throw ParsingException.wrapping("Could not parse template file!", e); - } + throw ParsingException.wrapping("Could not parse template file!", e); } final File zipDirResource = (File) prepared.get(ClojureHelper.Keywords.SOURCE_FOLDER.kw); @@ -70,25 +63,31 @@ public PreparedFragment prepareFragmentFile(final File fragmentFile, PrepareOpti return new PreparedFragment(prepared, zipDirResource); } + @SuppressWarnings({"unchecked", "RedundantThrows"}) + private static Map invokePrepareFunction(File fragmentFile, PrepareOptions options) throws Exception { + final IFn prepareFunction = ClojureHelper.findFunction("prepare-fragment"); + return (Map) prepareFunction.invoke(fragmentFile, options); + } + /** * Retrieves content of :variables keyword from map as a set. */ @SuppressWarnings("unchecked") - private Set variableNames(Map prepared) { + private static Set variableNames(Map prepared) { return prepared.containsKey(ClojureHelper.Keywords.VARIABLES.kw) - ? unmodifiableSet(new HashSet((Collection) prepared.get(ClojureHelper.Keywords.VARIABLES.kw))) + ? unmodifiableSet(new HashSet<>((Collection) prepared.get(ClojureHelper.Keywords.VARIABLES.kw))) : emptySet(); } @SuppressWarnings("unchecked") - private Set fragmentNames(Map prepared) { + private static Set fragmentNames(Map prepared) { return prepared.containsKey(ClojureHelper.Keywords.FRAGMENTS.kw) - ? unmodifiableSet(new HashSet((Collection) prepared.get(ClojureHelper.Keywords.FRAGMENTS.kw))) + ? unmodifiableSet(new HashSet<>((Collection) prepared.get(ClojureHelper.Keywords.FRAGMENTS.kw))) : emptySet(); } @SuppressWarnings("unchecked") - private PreparedTemplate prepareTemplateImpl(TemplateDocumentFormats templateDocFormat, InputStream input, File originalFile, PrepareOptions options) { + private static PreparedTemplate prepareTemplateImpl(TemplateDocumentFormats templateDocFormat, InputStream input, File originalFile, PrepareOptions options) { final IFn prepareFunction = ClojureHelper.findFunction("prepare-template"); final String format = templateDocFormat.name(); diff --git a/java-src/io/github/erdos/stencil/standalone/ArgsParser.java b/java-src/io/github/erdos/stencil/standalone/ArgsParser.java index fdb57b4c..8fae6464 100644 --- a/java-src/io/github/erdos/stencil/standalone/ArgsParser.java +++ b/java-src/io/github/erdos/stencil/standalone/ArgsParser.java @@ -10,7 +10,7 @@ @SuppressWarnings("WeakerAccess") public class ArgsParser { - private final Set markers = new HashSet<>(); + private final Set> markers = new HashSet<>(); public ParamMarker addParam(char shortForm, String longForm, String description, Function parser) { final ParamMarker added = new ParamMarker<>(parser, shortForm, longForm, description, false); @@ -20,7 +20,7 @@ public ParamMarker addParam(char shortForm, String longForm, String descr public ParseResult parse(String... args) { - final Map result = new HashMap<>(); + final Map, String> result = new HashMap<>(); final List restArgs = new ArrayList<>(args.length); @@ -39,7 +39,7 @@ public ParseResult parse(String... args) { final String argName = parts[0]; final String argValue = parts[1]; - final Map.Entry flag = parsePair(argName, argValue); + final Map.Entry, String> flag = parsePair(argName, argValue); result.put(flag.getKey(), flag.getValue()); } else { @@ -47,21 +47,21 @@ public ParseResult parse(String... args) { if (item.startsWith("--no-")) { // long form if (markerForLong(item.substring(5)).orElseThrow(() -> new IllegalArgumentException("Unexpected option: " + item)).isFlag()) { - Map.Entry pair = parsePair(item, null); + Map.Entry, String> pair = parsePair(item, null); result.put(pair.getKey(), pair.getValue()); continue; } } else if (item.startsWith("--")) { // long form if (markerForLong(item.substring(2)).orElseThrow(() -> new IllegalArgumentException("Unexpected option: " + item)).isFlag()) { - Map.Entry pair = parsePair(item, null); + Map.Entry, String> pair = parsePair(item, null); result.put(pair.getKey(), pair.getValue()); continue; } } else { // short form if (markerForShort(item.charAt(1)).orElseThrow(() -> new IllegalArgumentException("Unexpected option: " + item)).isFlag()) { - Map.Entry pair = parsePair(item, null); + Map.Entry, String> pair = parsePair(item, null); result.put(pair.getKey(), pair.getValue()); continue; } @@ -70,10 +70,10 @@ public ParseResult parse(String... args) { // maybe the next item is a value final String nextItem = i + 1 < args.length ? args[i + 1] : null; if (nextItem == null || nextItem.startsWith("-")) { - final Map.Entry flag = parsePair(item, null); + final Map.Entry, String> flag = parsePair(item, null); result.put(flag.getKey(), flag.getValue()); } else { - final Map.Entry flag = parsePair(item, nextItem); + final Map.Entry, String> flag = parsePair(item, nextItem); result.put(flag.getKey(), flag.getValue()); } } @@ -88,32 +88,32 @@ public ParseResult parse(String... args) { return new ParseResult(result, restArgs); } - private Map.Entry parsePair(String k, String v) { + private Map.Entry, String> parsePair(String k, String v) { if (k.startsWith("--no-") && v == null) { final String longName = k.substring(5); - final ParamMarker marker = markerForLong(longName).get(); + final ParamMarker marker = markerForLong(longName).get(); return new AbstractMap.SimpleImmutableEntry<>(marker, "false"); } else if (k.startsWith("--")) { final String longName = k.substring(2); - final ParamMarker marker = markerForLong(longName).get(); + final ParamMarker marker = markerForLong(longName).get(); if (v == null) { return new AbstractMap.SimpleImmutableEntry<>(marker, "true"); } else { return new AbstractMap.SimpleImmutableEntry<>(marker, v); } } else if (k.startsWith("-") && k.length() == 2 && v == null) { - final ParamMarker marker = markerForShort(k.charAt(1)).get(); + final ParamMarker marker = markerForShort(k.charAt(1)).get(); return new AbstractMap.SimpleImmutableEntry<>(marker, "true"); } else { throw new IllegalArgumentException("Unexpected key, not a parameter: " + k); } } - private Optional markerForLong(String longName) { + private Optional> markerForLong(String longName) { return markers.stream().filter(x -> x.getLongName().equals(longName)).findAny(); } - private Optional markerForShort(char shortName) { + private Optional> markerForShort(char shortName) { return markers.stream().filter(x -> x.getShortForm() == shortName).findAny(); } @@ -138,17 +138,17 @@ public ParamMarker addFlagOption(char shortName, String longName, Strin } public static final class ParseResult { - private final Map args; + private final Map, String> args; private final List varargs; - private ParseResult(Map args, List varargs) { + private ParseResult(Map, String> args, List varargs) { this.args = unmodifiableMap(args); this.varargs = unmodifiableList(varargs); } public List getRestArgs() { - return Collections.unmodifiableList(varargs); + return varargs; } diff --git a/java-src/io/github/erdos/stencil/standalone/JsonParser.java b/java-src/io/github/erdos/stencil/standalone/JsonParser.java index e280f206..1f7b318d 100644 --- a/java-src/io/github/erdos/stencil/standalone/JsonParser.java +++ b/java-src/io/github/erdos/stencil/standalone/JsonParser.java @@ -15,7 +15,7 @@ public final class JsonParser { /** * Parses string and returns read object if any. */ - @SuppressWarnings({"unchecked", "unused", "WeakerAccess"}) + @SuppressWarnings({"unused", "WeakerAccess"}) public static Object parse(String contents) throws IOException { return read(new StringReader(contents)); } @@ -89,7 +89,7 @@ static Number readNumber(PushbackReader pb) throws IOException { static String readStr(PushbackReader pb) throws IOException { expectWord("\"", pb); - final StringBuffer buf = new StringBuffer(); + final StringBuilder buf = new StringBuilder(); while (true) { final int read = pb.read(); diff --git a/java-src/io/github/erdos/stencil/standalone/StandaloneApplication.java b/java-src/io/github/erdos/stencil/standalone/StandaloneApplication.java index 6937a1ea..edb8d713 100644 --- a/java-src/io/github/erdos/stencil/standalone/StandaloneApplication.java +++ b/java-src/io/github/erdos/stencil/standalone/StandaloneApplication.java @@ -1,13 +1,11 @@ package io.github.erdos.stencil.standalone; -import clojure.lang.IFn; import clojure.lang.RT; import clojure.lang.Symbol; import io.github.erdos.stencil.EvaluatedDocument; import io.github.erdos.stencil.PrepareOptions; import io.github.erdos.stencil.PreparedTemplate; import io.github.erdos.stencil.TemplateData; -import io.github.erdos.stencil.impl.ClojureHelper; import io.github.erdos.stencil.impl.FileHelper; import java.io.BufferedReader; From 8acbb1448f4ee9ac007d426d77834c60da18959a Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 00:18:56 +0100 Subject: [PATCH 47/61] chore: rm unused flatten from cleanup.clj --- src/stencil/cleanup.clj | 16 ++++++++-------- test/stencil/cleanup_test.clj | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 78c15dc7..b1882f3e 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -71,9 +71,9 @@ (f-child token)))))) (defn annotate-environments - "Vegigmegy minden tokenen es a parancs blokkok :before es :after kulcsaiba - beleteszi az adott token kornyezetet." + "Puts the context of each element into its :before and :after keys." [control-ast] + (assert (sequential? control-ast)) (let [stack (volatile! ())] (nested-tokens-fmap-postwalk (fn before-cmd-block [_ block] @@ -120,19 +120,19 @@ (defmethod control-ast-normalize-step :if [control-ast] (case (count (:blocks control-ast)) 2 (let [[then else] (:blocks control-ast) - then2 (concat (keep control-ast-normalize (:children then)) + then2 (concat (map control-ast-normalize (:children then)) (stack-revert-close (:before else)) (:after else)) else2 (concat (stack-revert-close (:before then)) (:after then) - (keep control-ast-normalize (:children else)))] + (map control-ast-normalize (:children else)))] (-> (dissoc control-ast :blocks) (assoc :then (vec then2) :else (vec else2)))) 1 (let [[then] (:blocks control-ast) else (:after then)] (-> (dissoc control-ast :blocks) - (assoc :then (vec (keep control-ast-normalize (:children then))) :else (vec else)))) + (assoc :then (mapv control-ast-normalize (:children then)) :else (vec else)))) ;; default (throw (parsing-exception (str open-tag "else" close-tag) "Too many {%else%} tags in one condition!")))) @@ -147,7 +147,7 @@ (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} in a loop!"))) (let [[{:keys [children before after]}] (:blocks control-ast) - children (keep control-ast-normalize children)] + children (mapv control-ast-normalize children)] (-> control-ast (dissoc :blocks) (assoc :body-run-none (vec (concat (stack-revert-close before) after)) @@ -157,8 +157,8 @@ (defn control-ast-normalize "Mélységi bejárással rekurzívan normalizálja az XML fát." [control-ast] + (assert (map? control-ast)) (cond - (vector? control-ast) (vec (flatten (keep control-ast-normalize control-ast))) (:text control-ast) control-ast (:open control-ast) control-ast (:close control-ast) control-ast @@ -214,7 +214,7 @@ (defn process [raw-token-seq] (let [ast (tokens->ast raw-token-seq) - executable (control-ast-normalize (annotate-environments ast))] + executable (mapv control-ast-normalize (annotate-environments ast))] {:variables (find-variables ast) :fragments (find-fragments ast) :dynamic? (boolean (some :cmd executable)) diff --git a/test/stencil/cleanup_test.clj b/test/stencil/cleanup_test.clj index 68b43974..a094a3af 100644 --- a/test/stencil/cleanup_test.clj +++ b/test/stencil/cleanup_test.clj @@ -42,7 +42,7 @@ :blocks [{:children [{:text "Then"}]} {:children [{:text "Else"}]}]}]))) (deftest normal-ast-test-1 - (is (= (control-ast-normalize + (is (= (map control-ast-normalize (annotate-environments [(->OpenTag "html") (->OpenTag "a") @@ -91,7 +91,7 @@ (deftest normal-ast-test-0 (testing "Amikor a formazas a THEN blokk kozepeig tart, akkor az ELSE blokk-ba is be kell tenni a lezaro taget." - (is (= (control-ast-normalize + (is (= (map control-ast-normalize (annotate-environments [{:cmd :if :blocks [{:children [(->text "bela") (->text "Hello")]} @@ -105,7 +105,7 @@ (deftest normal-ast-test-0-deep (testing "Amikor a formazas a THEN blokk kozepeig tart, akkor az ELSE blokk-ba is be kell tenni a lezaro taget." - (is (= (control-ast-normalize + (is (= (map control-ast-normalize (annotate-environments [{:cmd :if :blocks [{:children [(->text "bela") (->text "Hello") </i>]} @@ -119,7 +119,7 @@ (deftest normal-ast-test-condition-only-then (testing "Az elagazasban eredeetileg csak THEN ag volt de beszurjuk az else agat is." - (is (= (control-ast-normalize + (is (= (map control-ast-normalize (annotate-environments [ {:cmd :if @@ -137,7 +137,7 @@ (deftest test-normal-ast-for-loop-1 (testing "ismetleses ciklusban" - (is (= (control-ast-normalize + (is (= (map control-ast-normalize (annotate-environments [ (->text "before") From 6acff766c70ad7a91493b0908497eec873bf1e21 Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 01:24:10 +0100 Subject: [PATCH 48/61] feat: simplify cleanup.clj --- src/stencil/cleanup.clj | 119 ++++++++++++++++------------------ test/stencil/cleanup_test.clj | 22 ++++--- 2 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index b1882f3e..6b33bb47 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -12,8 +12,6 @@ (set! *warn-on-reflection* true) -(declare control-ast-normalize) - (defn- tokens->ast-step [[queue & ss0 :as stack] token] (case (:cmd token) (:if :for) (conj (mod-stack-top-conj stack token) []) @@ -22,14 +20,14 @@ (if (empty? ss0) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} tag, it must come right after a condition!")) - (conj (mod-stack-top-last ss0 update :blocks (fnil conj []) {:children queue}) [])) + (conj (mod-stack-top-last ss0 update :blocks (fnil conj []) {::children queue}) [])) :else-if (if (empty? ss0) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} tag, it must come right after a condition!")) (-> ss0 - (mod-stack-top-last update :blocks (fnil conj []) {:children queue}) + (mod-stack-top-last update :blocks (fnil conj []) {::children queue}) (conj [(assoc token :cmd :if :r true)]) (conj []))) @@ -38,7 +36,7 @@ (throw (parsing-exception (str open-tag "end" close-tag) "Too many {%end%} tags!")) (loop [[queue & ss0] stack] - (let [new-stack (mod-stack-top-last ss0 update :blocks conj {:children queue})] + (let [new-stack (mod-stack-top-last ss0 update :blocks conj {::children queue})] (if (:r (peek (first new-stack))) (recur (mod-stack-top-last new-stack dissoc :r)) new-stack)))) @@ -55,46 +53,49 @@ "Missing {%end%} tag from document!")) (first result)))) -(defn nested-tokens-fmap-postwalk +(defn- nested-tokens-fmap-postwalk "Depth-first traversal of the tree." - [f-cmd-block-before f-cmd-block-after f-child nested-tokens] - (let [update-child-fn (partial nested-tokens-fmap-postwalk f-cmd-block-before f-cmd-block-after f-child) - update-children #(update % :children update-child-fn)] - (vec - (for [token nested-tokens] - (if (:cmd token) - (update token :blocks - (partial mapv - (comp (partial f-cmd-block-after token) - update-children - (partial f-cmd-block-before token)))) - (f-child token)))))) + [f-cmd-block-before f-cmd-block-after f-child node] + (assert (map? node)) + (letfn [(children-mapper [children] + (mapv update-blocks children)) + (update-children [node] + (update node ::children children-mapper)) + (visit-block [block] + (-> block f-cmd-block-before update-children f-cmd-block-after)) + (blocks-mapper [blocks] + (mapv visit-block blocks)) + (update-blocks [node] + (if (:cmd node) + (update node :blocks blocks-mapper) + (f-child node)))] + (update-blocks node))) (defn annotate-environments "Puts the context of each element into its :before and :after keys." [control-ast] (assert (sequential? control-ast)) (let [stack (volatile! ())] - (nested-tokens-fmap-postwalk - (fn before-cmd-block [_ block] - (assoc block :before @stack)) - - (fn after-cmd-block [_ block] - (let [stack-before (:before block) - [a b] (stacks-difference-key :open stack-before @stack)] - (assoc block :before a :after b))) - - (fn child [item] - (cond - (:open item) - (vswap! stack conj item) - - (:close item) - (if (= (:close item) (:open (first @stack))) - (vswap! stack next) - (throw (ex-info "Unexpected stack state" {:stack @stack, :item item})))) - item) - control-ast))) + (mapv (partial nested-tokens-fmap-postwalk + (fn before-cmd-block [block] + (assoc block ::before @stack)) + + (fn after-cmd-block [block] + (let [stack-before (::before block) + [a b] (stacks-difference-key :open stack-before @stack)] + (assoc block ::before a ::after b))) + + (fn child [item] + (cond + (:open item) + (vswap! stack conj item) + + (:close item) + (if (= (:close item) (:open (first @stack))) + (vswap! stack next) + (throw (ex-info "Unexpected stack state" {:stack @stack, :item item})))) + item)) + control-ast))) (defn stack-revert-close "Creates a seq of :close tags for each :open tag in the list in reverse order." @@ -105,34 +106,34 @@ ;; a :blocks kulcs alatt levo elemeket normalizalja es specialis kulcsok alatt elhelyezi ;; igy amikor vegrehajtjuk a parancs objektumot, akkor az eredmeny is ;; valid fa lesz, tehat a nyito-bezaro tagek helyesen fognak elhelyezkedni. -(defmulti control-ast-normalize-step :cmd) +(defmulti control-ast-normalize :cmd) ;; Itt nincsen blokk, amit normalizálni kellene -(defmethod control-ast-normalize-step :echo [echo-command] echo-command) +(defmethod control-ast-normalize :echo [echo-command] echo-command) -(defmethod control-ast-normalize-step :cmd/include [include-command] +(defmethod control-ast-normalize :cmd/include [include-command] (if-not (string? (:name include-command)) (throw (parsing-exception (pr-str (:name include-command)) "Parameter of include call must be a single string literal!")) include-command)) ;; A feltételes elágazásoknál mindig generálunk egy javított THEN ágat -(defmethod control-ast-normalize-step :if [control-ast] +(defmethod control-ast-normalize :if [control-ast] (case (count (:blocks control-ast)) 2 (let [[then else] (:blocks control-ast) - then2 (concat (map control-ast-normalize (:children then)) - (stack-revert-close (:before else)) - (:after else)) - else2 (concat (stack-revert-close (:before then)) - (:after then) - (map control-ast-normalize (:children else)))] + then2 (concat (map control-ast-normalize (::children then)) + (stack-revert-close (::before else)) + (::after else)) + else2 (concat (stack-revert-close (::before then)) + (::after then) + (map control-ast-normalize (::children else)))] (-> (dissoc control-ast :blocks) (assoc :then (vec then2) :else (vec else2)))) 1 (let [[then] (:blocks control-ast) - else (:after then)] + else (::after then)] (-> (dissoc control-ast :blocks) - (assoc :then (mapv control-ast-normalize (:children then)) :else (vec else)))) + (assoc :then (mapv control-ast-normalize (::children then)) :else (vec else)))) ;; default (throw (parsing-exception (str open-tag "else" close-tag) "Too many {%else%} tags in one condition!")))) @@ -142,11 +143,11 @@ ;; - body-run-once: a body resz eloszor fut le, ha a lista legalabb egy elemu ;; - body-run-next: a body resz masodik, harmadik, stb. beillesztese, haa lista legalabb 2 elemu. ;; Ezekbol az esetekbol kell futtataskor a megfelelo(ket) kivalasztani es behelyettesiteni. -(defmethod control-ast-normalize-step :for [control-ast] +(defmethod control-ast-normalize :for [control-ast] (when-not (= 1 (count (:blocks control-ast))) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} in a loop!"))) - (let [[{:keys [children before after]}] (:blocks control-ast) + (let [[{::keys [children before after]}] (:blocks control-ast) children (mapv control-ast-normalize children)] (-> control-ast (dissoc :blocks) @@ -154,17 +155,9 @@ :body-run-once (vec children) :body-run-next (vec (concat (stack-revert-close after) before children)))))) -(defn control-ast-normalize - "Mélységi bejárással rekurzívan normalizálja az XML fát." - [control-ast] - (assert (map? control-ast)) - (cond - (:text control-ast) control-ast - (:open control-ast) control-ast - (:close control-ast) control-ast - (:cmd control-ast) (control-ast-normalize-step control-ast) - (:open+close control-ast) control-ast - :else (throw (ex-info (str "Unexpected object: " (type control-ast)) {:ast control-ast})))) +(defmethod control-ast-normalize :default [control-ast] + (assert (not (:blocks control-ast))) + control-ast) (defn find-variables [control-ast] ;; meg a normalizalas lepes elott diff --git a/test/stencil/cleanup_test.clj b/test/stencil/cleanup_test.clj index a094a3af..bbfdba57 100644 --- a/test/stencil/cleanup_test.clj +++ b/test/stencil/cleanup_test.clj @@ -30,7 +30,7 @@ {:text "Then"} {:cmd :end}] [{:cmd :if :condition 1 - :blocks [{:children [{:text "Then"}]}]}] + :blocks [{:stencil.cleanup/children [{:text "Then"}]}]}] ;; if-then-else-fi [{:cmd :if :condition 1} @@ -39,7 +39,7 @@ {:text "Else"} {:cmd :end}] [{:cmd :if, :condition 1 - :blocks [{:children [{:text "Then"}]} {:children [{:text "Else"}]}]}]))) + :blocks [{:stencil.cleanup/children [{:text "Then"}]} {:stencil.cleanup/children [{:text "Else"}]}]}]))) (deftest normal-ast-test-1 (is (= (map control-ast-normalize @@ -48,12 +48,14 @@ (->OpenTag "a") (->TextTag "Inka") {:cmd :if - :blocks [{:children [(->TextTag "ikarusz") + :blocks [{:stencil.cleanup/children [ + (->TextTag "ikarusz") (->CloseTag "a") (->TextTag "bela") (->OpenTag "b") (->TextTag "Hello")]} - {:children [(->TextTag "Virag") + {:stencil.cleanup/children [ + (->TextTag "Virag") (->CloseTag "b") (->TextTag "Hajdiho!") (->OpenTag "c") @@ -94,8 +96,8 @@ (is (= (map control-ast-normalize (annotate-environments [{:cmd :if - :blocks [{:children [(->text "bela") (->text "Hello")]} - {:children [(->text "Virag")]}]} + :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello")]} + {:stencil.cleanup/children [(->text "Virag")]}]} (->close "b")])) [{:cmd :if @@ -108,8 +110,8 @@ (is (= (map control-ast-normalize (annotate-environments [{:cmd :if - :blocks [{:children [(->text "bela") (->text "Hello") </i>]} - {:children [ (->text "Virag") </j>]}]} + :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]} + {:stencil.cleanup/children [ (->text "Virag") </j>]}]} </b>])) [{:cmd :if @@ -123,7 +125,7 @@ (annotate-environments [ {:cmd :if - :blocks [{:children [(->text "bela") (->text "Hello") </i>]}]} + :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]}]} </b> </a>])) @@ -133,7 +135,7 @@ </b> </a>])))) -(defn >>for-loop [& children] {:cmd :for :blocks [{:children (vec children)}]}) +(defn >>for-loop [& children] {:cmd :for :blocks [{:stencil.cleanup/children (vec children)}]}) (deftest test-normal-ast-for-loop-1 (testing "ismetleses ciklusban" From 1e20f58497f8c1b90f9823f06f4aff764faf388e Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 10:58:31 +0100 Subject: [PATCH 49/61] feat: use qualified keys in cleanup.clj --- src/stencil/cleanup.clj | 32 ++++++++++++++++---------------- test/stencil/cleanup_test.clj | 28 ++++++++++++++-------------- test/stencil/process_test.clj | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 6b33bb47..cb14ffc8 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -20,14 +20,14 @@ (if (empty? ss0) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} tag, it must come right after a condition!")) - (conj (mod-stack-top-last ss0 update :blocks (fnil conj []) {::children queue}) [])) + (conj (mod-stack-top-last ss0 update ::blocks (fnil conj []) {::children queue}) [])) :else-if (if (empty? ss0) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} tag, it must come right after a condition!")) (-> ss0 - (mod-stack-top-last update :blocks (fnil conj []) {::children queue}) + (mod-stack-top-last update ::blocks (fnil conj []) {::children queue}) (conj [(assoc token :cmd :if :r true)]) (conj []))) @@ -36,7 +36,7 @@ (throw (parsing-exception (str open-tag "end" close-tag) "Too many {%end%} tags!")) (loop [[queue & ss0] stack] - (let [new-stack (mod-stack-top-last ss0 update :blocks conj {::children queue})] + (let [new-stack (mod-stack-top-last ss0 update ::blocks conj {::children queue})] (if (:r (peek (first new-stack))) (recur (mod-stack-top-last new-stack dissoc :r)) new-stack)))) @@ -67,7 +67,7 @@ (mapv visit-block blocks)) (update-blocks [node] (if (:cmd node) - (update node :blocks blocks-mapper) + (update node ::blocks blocks-mapper) (f-child node)))] (update-blocks node))) @@ -119,20 +119,20 @@ ;; A feltételes elágazásoknál mindig generálunk egy javított THEN ágat (defmethod control-ast-normalize :if [control-ast] - (case (count (:blocks control-ast)) - 2 (let [[then else] (:blocks control-ast) + (case (count (::blocks control-ast)) + 2 (let [[then else] (::blocks control-ast) then2 (concat (map control-ast-normalize (::children then)) (stack-revert-close (::before else)) (::after else)) else2 (concat (stack-revert-close (::before then)) (::after then) (map control-ast-normalize (::children else)))] - (-> (dissoc control-ast :blocks) + (-> (dissoc control-ast ::blocks) (assoc :then (vec then2) :else (vec else2)))) - 1 (let [[then] (:blocks control-ast) + 1 (let [[then] (::blocks control-ast) else (::after then)] - (-> (dissoc control-ast :blocks) + (-> (dissoc control-ast ::blocks) (assoc :then (mapv control-ast-normalize (::children then)) :else (vec else)))) ;; default (throw (parsing-exception (str open-tag "else" close-tag) @@ -144,19 +144,19 @@ ;; - body-run-next: a body resz masodik, harmadik, stb. beillesztese, haa lista legalabb 2 elemu. ;; Ezekbol az esetekbol kell futtataskor a megfelelo(ket) kivalasztani es behelyettesiteni. (defmethod control-ast-normalize :for [control-ast] - (when-not (= 1 (count (:blocks control-ast))) + (when-not (= 1 (count (::blocks control-ast))) (throw (parsing-exception (str open-tag "else" close-tag) "Unexpected {%else%} in a loop!"))) - (let [[{::keys [children before after]}] (:blocks control-ast) + (let [[{::keys [children before after]}] (::blocks control-ast) children (mapv control-ast-normalize children)] (-> control-ast - (dissoc :blocks) + (dissoc ::blocks) (assoc :body-run-none (vec (concat (stack-revert-close before) after)) :body-run-once (vec children) :body-run-next (vec (concat (stack-revert-close after) before children)))))) (defmethod control-ast-normalize :default [control-ast] - (assert (not (:blocks control-ast))) + (assert (not (::blocks control-ast))) control-ast) (defn find-variables [control-ast] @@ -187,20 +187,20 @@ :echo (expr mapping (:expression x)) :if (concat (expr mapping (:condition x)) - (collect mapping (apply concat (:blocks x)))) + (collect mapping (apply concat (::blocks x)))) :for (let [variable (maybe-variable mapping (:expression x)) exprs (expr mapping (:expression x)) mapping (if variable (assoc mapping (:variable x) (str variable "[]")) mapping)] - (concat exprs (collect mapping (apply concat (:blocks x))))) + (concat exprs (collect mapping (apply concat (::blocks x))))) []))] (distinct (collect {} control-ast)))) (defn- find-fragments [control-ast] ;; returns a set of fragment names use in this document - (set (for [item (tree-seq map? (comp flatten :blocks) {:blocks [control-ast]}) + (set (for [item (tree-seq map? (comp flatten ::blocks) {::blocks [control-ast]}) :when (map? item) :when (= :cmd/include (:cmd item))] (:name item)))) diff --git a/test/stencil/cleanup_test.clj b/test/stencil/cleanup_test.clj index bbfdba57..a698b0c9 100644 --- a/test/stencil/cleanup_test.clj +++ b/test/stencil/cleanup_test.clj @@ -30,7 +30,7 @@ {:text "Then"} {:cmd :end}] [{:cmd :if :condition 1 - :blocks [{:stencil.cleanup/children [{:text "Then"}]}]}] + :stencil.cleanup/blocks [{:stencil.cleanup/children [{:text "Then"}]}]}] ;; if-then-else-fi [{:cmd :if :condition 1} @@ -39,7 +39,7 @@ {:text "Else"} {:cmd :end}] [{:cmd :if, :condition 1 - :blocks [{:stencil.cleanup/children [{:text "Then"}]} {:stencil.cleanup/children [{:text "Else"}]}]}]))) + :stencil.cleanup/blocks [{:stencil.cleanup/children [{:text "Then"}]} {:stencil.cleanup/children [{:text "Else"}]}]}]))) (deftest normal-ast-test-1 (is (= (map control-ast-normalize @@ -48,7 +48,7 @@ (->OpenTag "a") (->TextTag "Inka") {:cmd :if - :blocks [{:stencil.cleanup/children [ + :stencil.cleanup/blocks [{:stencil.cleanup/children [ (->TextTag "ikarusz") (->CloseTag "a") (->TextTag "bela") @@ -96,7 +96,7 @@ (is (= (map control-ast-normalize (annotate-environments [{:cmd :if - :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello")]} + :stencil.cleanup/blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello")]} {:stencil.cleanup/children [(->text "Virag")]}]} (->close "b")])) @@ -110,7 +110,7 @@ (is (= (map control-ast-normalize (annotate-environments [{:cmd :if - :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]} + :stencil.cleanup/blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]} {:stencil.cleanup/children [ (->text "Virag") </j>]}]} </b>])) @@ -125,7 +125,7 @@ (annotate-environments [ {:cmd :if - :blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]}]} + :stencil.cleanup/blocks [{:stencil.cleanup/children [(->text "bela") (->text "Hello") </i>]}]} </b> </a>])) @@ -135,7 +135,7 @@ </b> </a>])))) -(defn >>for-loop [& children] {:cmd :for :blocks [{:stencil.cleanup/children (vec children)}]}) +(defn >>for-loop [& children] {:cmd :for :stencil.cleanup/blocks [{:stencil.cleanup/children (vec children)}]}) (deftest test-normal-ast-for-loop-1 (testing "ismetleses ciklusban" @@ -169,20 +169,20 @@ (testing "Variables from if branches" (is (= ["x"] (find-variables [{:cmd :if :condition [] - :blocks [[] [{:cmd :echo :expression '[x]}]]}])))) + :stencil.cleanup/blocks [[] [{:cmd :echo :expression '[x]}]]}])))) (testing "Variables from loop expressions" (is (= ["xs" "xs[]"] (find-variables '[{:cmd :for, :variable y, :expression [xs], - :blocks [[{:cmd :echo, :expression [y 1 :plus]}]]}]))) + :stencil.cleanup/blocks [[{:cmd :echo, :expression [y 1 :plus]}]]}]))) (is (= ["xs" "xs[]" "xs[][]"] (find-variables '[{:cmd :for, :variable y, :expression [xs] - :blocks [[{:cmd :for :variable w :expression [y] - :blocks [[{:cmd :echo :expression [1 w :plus]}]]}]]}]))) + :stencil.cleanup/blocks [[{:cmd :for :variable w :expression [y] + :stencil.cleanup/blocks [[{:cmd :echo :expression [1 w :plus]}]]}]]}]))) (is (= ["xs" "xs[].z.k"] (find-variables '[{:cmd :for :variable y :expression [xs] - :blocks [[{:cmd :echo :expression [y.z.k 1 :plus]}]]}])))) + :stencil.cleanup/blocks [[{:cmd :echo :expression [y.z.k 1 :plus]}]]}])))) (testing "Variables from loop bindings and bodies" ;; TODO: impls this test @@ -195,8 +195,8 @@ (is (= ["xs" "xs[].t" "xs[].t[].n"] (find-variables '[{:cmd :for :variable a :expression [xs] - :blocks [[{:cmd :for :variable b :expression [a.t] - :blocks [[{:cmd :echo :expression [b.n 1 :plus]}]]}]]}]))) + :stencil.cleanup/blocks [[{:cmd :for :variable b :expression [a.t] + :stencil.cleanup/blocks [[{:cmd :echo :expression [b.n 1 :plus]}]]}]]}]))) )) (deftest test-process-if-then-else diff --git a/test/stencil/process_test.clj b/test/stencil/process_test.clj index 2e30dbce..e1b6435b 100644 --- a/test/stencil/process_test.clj +++ b/test/stencil/process_test.clj @@ -28,7 +28,7 @@ (is (= {:dynamic? true, :executable [{:open :a} {:open :b} - {:blocks [], :cmd :cmd/include, :name "elefant"} + {:stencil.cleanup/blocks [], :cmd :cmd/include, :name "elefant"} {:close :b} {:close :a}], :fragments #{"elefant"} From 858b1e221a4a53c14e0b121054841f3e50bef2fd Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 12:04:00 +0100 Subject: [PATCH 50/61] feat: add extract-wordxml script --- scripts/extract-wordxml.clj | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 scripts/extract-wordxml.clj diff --git a/scripts/extract-wordxml.clj b/scripts/extract-wordxml.clj new file mode 100755 index 00000000..e26339a4 --- /dev/null +++ b/scripts/extract-wordxml.clj @@ -0,0 +1,17 @@ +#!/usr/bin/env bb + +(def input (first *command-line-args*)) +(assert input) + +(def input-file (clojure.java.io/file input)) +(assert (.exists input-file)) + +(with-open [fistream (new java.io.FileInputStream input-file) + zipstream (new java.util.zip.ZipInputStream fistream)] + (doseq [entry (repeatedly #(.getNextEntry zipstream)) + :while entry + :when (= "word/document.xml" (.getName entry))] + (-> zipstream + (clojure.data.xml/parse) + (clojure.data.xml/indent-str) + (println)))) From 3e9adb4052d89da02890d1782875f23b51b17cc3 Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 18:07:07 +0100 Subject: [PATCH 51/61] chore: simplification --- src/stencil/tokenizer.clj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stencil/tokenizer.clj b/src/stencil/tokenizer.clj index 580dfb70..997e60c1 100644 --- a/src/stencil/tokenizer.clj +++ b/src/stencil/tokenizer.clj @@ -66,8 +66,7 @@ [{:close (:tag parsed)}]) :else - [(cond-> {:open+close (:tag parsed)} - (seq (:attrs parsed)) (assoc :attrs (:attrs parsed)))])) + [(assoc-if-val {:open+close (:tag parsed)} :attrs (not-empty (:attrs parsed)))])) (defn- tokens-seq-reducer [stack token] (cond From 0b751ac2d22a3e16b6b61c0bebf13c1aaa0900d7 Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 20:42:09 +0100 Subject: [PATCH 52/61] dead code removal --- src/stencil/api.clj | 6 --- src/stencil/cleanup.clj | 4 +- src/stencil/types.clj | 8 ---- test/stencil/cleanup_test.clj | 80 ++++++++++++++++++----------------- 4 files changed, 44 insertions(+), 54 deletions(-) diff --git a/src/stencil/api.clj b/src/stencil/api.clj index 734a02bd..8ef589f0 100644 --- a/src/stencil/api.clj +++ b/src/stencil/api.clj @@ -2,20 +2,14 @@ "A simple public API for document generation from templates." (:require [clojure.walk :refer [stringify-keys]] [clojure.java.io :as io] - [clojure.pprint :refer [simple-dispatch]] [stencil.types]) (:import [io.github.erdos.stencil API PreparedFragment PreparedTemplate TemplateData] - [stencil.types OpenTag CloseTag TextTag] [java.util Map])) (set! *warn-on-reflection* true) (set! *unchecked-math* :warn-on-boxed) -(defmethod simple-dispatch OpenTag [t] (print (str "<" (:open t) ">"))) -(defmethod simple-dispatch CloseTag [t] (print (str ""))) -(defmethod simple-dispatch TextTag [t] (print (str "'" (:text t) "'"))) - (defn ^PreparedTemplate prepare "Creates a prepared template instance from an input document." [input] diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index cb14ffc8..d299d120 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -8,7 +8,7 @@ valid XML String -> tokens -> Annotated Control AST -> Normalized Control AST -> Evaled AST -> Hiccup or valid XML String " (:require [stencil.util :refer [mod-stack-top-conj mod-stack-top-last parsing-exception stacks-difference-key]] - [stencil.types :refer [open-tag close-tag ->CloseTag]])) + [stencil.types :refer [open-tag close-tag]])) (set! *warn-on-reflection* true) @@ -100,7 +100,7 @@ (defn stack-revert-close "Creates a seq of :close tags for each :open tag in the list in reverse order." [stack] - (into () (comp (keep :open) (map ->CloseTag)) stack)) + (into () (comp (keep :open) (map #(do {:close %}))) stack)) ;; egy {:cmd ...} parancs objektumot kibont: ;; a :blocks kulcs alatt levo elemeket normalizalja es specialis kulcsok alatt elhelyezi diff --git a/src/stencil/types.clj b/src/stencil/types.clj index 95386dc5..b31804a0 100644 --- a/src/stencil/types.clj +++ b/src/stencil/types.clj @@ -5,14 +5,6 @@ (def open-tag "{%") (def close-tag "%}") -(defrecord OpenTag [open]) -(defrecord CloseTag [close]) -(defrecord TextTag [text]) - -(defn ->text [t] (->TextTag t)) -(defn ->close [t] (->CloseTag t)) -(def ->open ->OpenTag) - (defprotocol ControlMarker) ;; Invocation of a fragment by name diff --git a/test/stencil/cleanup_test.clj b/test/stencil/cleanup_test.clj index a698b0c9..a6d5073f 100644 --- a/test/stencil/cleanup_test.clj +++ b/test/stencil/cleanup_test.clj @@ -4,13 +4,17 @@ [cleanup :refer :all] [types :refer :all]])) +(defn- ->text [t] {:text t}) +(defn- ->close [t] {:close t}) +(defn- ->open [t] {:open t}) + (deftest stack-revert-close-test (testing "Egyszeru es ures esetek" (is (= [] (stack-revert-close []))) (is (= [] (stack-revert-close nil))) (is (= [] (stack-revert-close [{:whatever 1} {:else 2}])))) (testing "A kinyito elemeket be kell csukni" - (is (= [(->CloseTag "a") (->CloseTag "b")] + (is (= [(->close "a") (->close "b")] (stack-revert-close [{:open "b"} {:open "a"}]))))) (deftest tokens->ast-test @@ -44,47 +48,47 @@ (deftest normal-ast-test-1 (is (= (map control-ast-normalize (annotate-environments - [(->OpenTag "html") - (->OpenTag "a") - (->TextTag "Inka") + [(->open "html") + (->open "a") + (->text "Inka") {:cmd :if :stencil.cleanup/blocks [{:stencil.cleanup/children [ - (->TextTag "ikarusz") - (->CloseTag "a") - (->TextTag "bela") - (->OpenTag "b") - (->TextTag "Hello")]} + (->text "ikarusz") + (->close "a") + (->text "bela") + (->open "b") + (->text "Hello")]} {:stencil.cleanup/children [ - (->TextTag "Virag") - (->CloseTag "b") - (->TextTag "Hajdiho!") - (->OpenTag "c") - (->TextTag "Bogar")]}]} - (->TextTag "Kaktusz") - (->CloseTag "c") - (->CloseTag "html")])) - - [(->OpenTag "html") - (->OpenTag "a") - (->TextTag "Inka") + (->text "Virag") + (->close "b") + (->text "Hajdiho!") + (->open "c") + (->text "Bogar")]}]} + (->text "Kaktusz") + (->close "c") + (->close "html")])) + + [(->open "html") + (->open "a") + (->text "Inka") {:cmd :if - :then [(->TextTag "ikarusz") - (->CloseTag "a") - (->TextTag "bela") - (->OpenTag "b") - (->TextTag "Hello") - (->CloseTag "b") - (->OpenTag "c")] - :else [(->CloseTag "a") - (->OpenTag "b") - (->TextTag "Virag") - (->CloseTag "b") - (->TextTag "Hajdiho!") - (->OpenTag "c") - (->TextTag "Bogar")]} - (->TextTag "Kaktusz") - (->CloseTag "c") - (->CloseTag "html")]))) + :then [(->text "ikarusz") + (->close "a") + (->text "bela") + (->open "b") + (->text "Hello") + (->close "b") + (->open "c")] + :else [(->close "a") + (->open "b") + (->text "Virag") + (->close "b") + (->text "Hajdiho!") + (->open "c") + (->text "Bogar")]} + (->text "Kaktusz") + (->close "c") + (->close "html")]))) (def (->open "i")) (def </i> (->close "i")) (def (->open "b")) (def </b> (->close "b")) From 35f00dbf51bb931966e351b758040d0bba6f0b6e Mon Sep 17 00:00:00 2001 From: erdos Date: Sun, 27 Nov 2022 21:41:09 +0100 Subject: [PATCH 53/61] fix: subtraction following field access --- src/stencil/infix.clj | 2 +- test/stencil/infix_test.clj | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index a80403a2..ee1e7fbd 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -126,7 +126,7 @@ (contains? ops2 [first-char (first next-chars)]) (recur (next next-chars) (conj tokens (ops2 [first-char (first next-chars)]))) - (and (= \- first-char) (or (nil? (peek tokens)) (and (not= (peek tokens) :close) (keyword? (peek tokens))))) + (and (= \- first-char) (or (nil? (peek tokens)) (and (not= (peek tokens) :close) (not= (peek tokens) :close-bracket) (keyword? (peek tokens))))) (recur next-chars (conj tokens :neg)) (contains? ops first-char) diff --git a/test/stencil/infix_test.clj b/test/stencil/infix_test.clj index edaf1570..10ce3e18 100644 --- a/test/stencil/infix_test.clj +++ b/test/stencil/infix_test.clj @@ -132,6 +132,9 @@ (is (= 7 (run "a[a[1]+4]" {"a" {"1" 2 "6" 7}}))) (is (= 8 (run "a[1][2]" {"a" {"1" {"2" 8}}})))) + (testing "negation" + (is (= 6 (run "a[1]-2" {"a" {"1" 8}})))) + (testing "syntax error" (is (thrown? ExceptionInfo (run "[3]" {}))) (is (thrown? ExceptionInfo (run "a[[3]]" {}))) From a7dc048dbe57f1d7ffe2ef8b34e44ba9422258f8 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Mon, 28 Nov 2022 08:33:39 +0100 Subject: [PATCH 54/61] fix: nonbreaking space in control (#141) --- src/stencil/infix.clj | 4 ++-- src/stencil/postprocess/list_ref.clj | 3 ++- src/stencil/tokenizer.clj | 6 +++--- src/stencil/util.clj | 18 ++++++++++++++++++ test/stencil/tokenizer_test.clj | 2 +- test/stencil/util_test.clj | 15 ++++++++++++++- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index ee1e7fbd..3b893188 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -2,7 +2,7 @@ "Parsing and evaluating infix algebraic expressions. https://en.wikipedia.org/wiki/Shunting-yard_algorithm" - (:require [stencil.util :refer [fail update-peek ->int string]] + (:require [stencil.util :refer [fail update-peek ->int string whitespace?]] [stencil.log :as log] [stencil.functions :refer [call-fn]])) @@ -117,7 +117,7 @@ (empty? characters) tokens - (contains? #{\space \tab \newline} first-char) + (whitespace? first-char) (recur next-chars tokens) (contains? #{\, \;} first-char) diff --git a/src/stencil/postprocess/list_ref.clj b/src/stencil/postprocess/list_ref.clj index 5a6107ef..cc2ff9b3 100644 --- a/src/stencil/postprocess/list_ref.clj +++ b/src/stencil/postprocess/list_ref.clj @@ -3,6 +3,7 @@ [stencil.ooxml :as ooxml] [stencil.model.numbering :as numbering] [stencil.log :as log] + [clojure.string] [clojure.zip :as zip])) (set! *warn-on-reflection* true) @@ -175,7 +176,7 @@ (defn parse-instr-text [^String s] (assert (string? s)) - (let [[type id & flags] (vec (.split (.trim s) "\\s\\\\?+"))] + (let [[type id & flags] (vec (.split (trim s) "\\s\\\\?+"))] (when (= "REF" type) {:id id :flags (set (map keyword flags))}))) diff --git a/src/stencil/tokenizer.clj b/src/stencil/tokenizer.clj index 997e60c1..6ba40148 100644 --- a/src/stencil/tokenizer.clj +++ b/src/stencil/tokenizer.clj @@ -1,16 +1,16 @@ (ns stencil.tokenizer "Fog egy XML dokumentumot es tokenekre bontja" (:require [clojure.data.xml :as xml] - [clojure.string :refer [includes? split trim]] + [clojure.string :refer [includes? split]] [stencil.infix :as infix] [stencil.types :refer [open-tag close-tag]] - [stencil.util :refer [assoc-if-val mod-stack-top-conj mod-stack-top-last parsing-exception]])) + [stencil.util :refer [assoc-if-val mod-stack-top-conj mod-stack-top-last parsing-exception trim]])) (set! *warn-on-reflection* true) (defn- text->cmd-impl [^String text] (assert (string? text)) - (let [text (.trim text) + (let [text (trim text) pattern-elseif #"^(else\s*if|elif|elsif)(\(|\s+)"] (cond (#{"end" "endfor" "endif"} text) {:cmd :end} diff --git a/src/stencil/util.clj b/src/stencil/util.clj index 86347164..594dbfc3 100644 --- a/src/stencil/util.clj +++ b/src/stencil/util.clj @@ -126,4 +126,22 @@ ([values] (apply str values)) ([xform coll] (transduce xform (fn ([^Object s] (.toString s)) ([^StringBuilder b v] (.append b v))) (StringBuilder.) coll))) +(defn ^{:inline (fn [c] `(case ~c (\tab \space \newline + \u00A0 \u2007 \u202F ;; non-breaking spaces + \u000B \u000C \u000D \u001C \u001D \u001E \u001F) + true false))} + whitespace? [c] (whitespace? c)) + +;; like clojure.string/trim but supports a wider range of whitespace characters +(defn ^String trim [^CharSequence s] + (loop [right-idx (.length s)] + (if (zero? right-idx) + "" + (if (whitespace? (.charAt s (dec right-idx))) + (recur (dec right-idx)) + (loop [left-idx 0] + (if (whitespace? (.charAt s left-idx)) + (recur (inc left-idx)) + (.toString (.subSequence s left-idx right-idx)))))))) + :OK diff --git a/test/stencil/tokenizer_test.clj b/test/stencil/tokenizer_test.clj index 9608b63d..a71a6bab 100644 --- a/test/stencil/tokenizer_test.clj +++ b/test/stencil/tokenizer_test.clj @@ -34,7 +34,7 @@ (deftest read-tokens-if-then (testing "Simple conditional with THEN branch only" - (is (= (run "elotte {% if x%} akkor {% end %} utana") + (is (= (run "elotte {% if x%} akkor {% end %} utana") [{:open :a} {:text "elotte "} {:cmd :if :condition '(x)} diff --git a/test/stencil/util_test.clj b/test/stencil/util_test.clj index a4fac1d7..853b59af 100644 --- a/test/stencil/util_test.clj +++ b/test/stencil/util_test.clj @@ -1,5 +1,5 @@ (ns stencil.util-test - (:require [clojure.test :refer [deftest testing is]] + (:require [clojure.test :refer [deftest testing is are]] [clojure.zip :as zip] [stencil.util :refer :all])) @@ -104,3 +104,16 @@ (deftest suffixes-test (is (= [] (suffixes []) (suffixes nil))) (is (= [[1 2 3] [2 3] [3]] (suffixes [1 2 3])))) + +(deftest whitespace?-test + (is (= true (whitespace? \space))) + (is (= true (whitespace? \tab))) + (is (= false (whitespace? " "))) + (is (= false (whitespace? \A)))) + +(deftest trim-test + (are [input] (= "" (trim input)) + "", " ", "\t\t\n") + (are [input] (= "abc" (trim input)) + "abc", " abc", "abc ", " \t \n abc \t") + (is (= "a b c" (trim " a b c \t")))) From 92a7dab98affd26ac7c32ab7c4ba5477f5ad1633 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Fri, 2 Dec 2022 14:22:50 +0100 Subject: [PATCH 55/61] feat: first steps for better error reporting (#140) --- .../stencil/exceptions/EvalException.java | 8 +- .../erdos/stencil/impl/NativeEvaluator.java | 4 +- .../github/erdos/stencil/IntegrationTest.java | 9 --- service/src/stencil/service.clj | 8 +- src/stencil/cleanup.clj | 4 +- src/stencil/eval.clj | 12 ++- src/stencil/infix.clj | 10 +-- src/stencil/merger.clj | 9 ++- src/stencil/model.clj | 6 +- src/stencil/tokenizer.clj | 6 +- src/stencil/util.clj | 6 +- .../failures/test-eval-division.docx | Bin 0 -> 4357 bytes test-resources/failures/test-no-such-fn.docx | Bin 0 -> 4361 bytes .../failures/test-syntax-arity.docx | Bin 0 -> 4343 bytes .../failures/test-syntax-closed.docx | Bin 0 -> 4329 bytes .../failures/test-syntax-fails.docx | Bin 0 -> 4333 bytes .../failures/test-syntax-incomplete.docx | Bin 0 -> 4321 bytes .../failures/test-syntax-nonclosed.docx | Bin 0 -> 4355 bytes .../test-syntax-unexpected-command.docx | Bin 0 -> 4334 bytes .../failures/test-syntax-unexpected-elif.docx | Bin 0 -> 4340 bytes .../failures/test-syntax-unexpected-else.docx | Bin 0 -> 4337 bytes test-resources/test-function-date.docx | Bin 0 -> 4354 bytes test/stencil/errors.clj | 70 +++++++++++++++--- test/stencil/integration.clj | 23 ++++++ test/stencil/merger_test.clj | 14 ++-- test/stencil/process_test.clj | 2 +- test/stencil/tokenizer_test.clj | 4 +- 27 files changed, 141 insertions(+), 54 deletions(-) delete mode 100644 java-test/io/github/erdos/stencil/IntegrationTest.java create mode 100644 test-resources/failures/test-eval-division.docx create mode 100644 test-resources/failures/test-no-such-fn.docx create mode 100644 test-resources/failures/test-syntax-arity.docx create mode 100644 test-resources/failures/test-syntax-closed.docx create mode 100644 test-resources/failures/test-syntax-fails.docx create mode 100644 test-resources/failures/test-syntax-incomplete.docx create mode 100644 test-resources/failures/test-syntax-nonclosed.docx create mode 100644 test-resources/failures/test-syntax-unexpected-command.docx create mode 100644 test-resources/failures/test-syntax-unexpected-elif.docx create mode 100644 test-resources/failures/test-syntax-unexpected-else.docx create mode 100644 test-resources/test-function-date.docx diff --git a/java-src/io/github/erdos/stencil/exceptions/EvalException.java b/java-src/io/github/erdos/stencil/exceptions/EvalException.java index 46860669..f896e277 100644 --- a/java-src/io/github/erdos/stencil/exceptions/EvalException.java +++ b/java-src/io/github/erdos/stencil/exceptions/EvalException.java @@ -5,6 +5,10 @@ */ public final class EvalException extends RuntimeException { + public EvalException(String message, Exception cause) { + super(message, cause); + } + private EvalException(Exception cause) { super(cause); } @@ -12,8 +16,4 @@ private EvalException(Exception cause) { private EvalException(String message) { super(message); } - - public static EvalException wrapping(Exception cause) { - return new EvalException(cause); - } } diff --git a/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java b/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java index 3b23487f..9cae6606 100644 --- a/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java +++ b/java-src/io/github/erdos/stencil/impl/NativeEvaluator.java @@ -55,8 +55,10 @@ public EvaluatedDocument render(PreparedTemplate template, Map stream = resultWriter(result); diff --git a/java-test/io/github/erdos/stencil/IntegrationTest.java b/java-test/io/github/erdos/stencil/IntegrationTest.java deleted file mode 100644 index 89b6da0b..00000000 --- a/java-test/io/github/erdos/stencil/IntegrationTest.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.erdos.stencil; - - -/** - * Annotates a test that needs a LibreOffice process in the background. - */ -public interface IntegrationTest { - /* category marker */ -} diff --git a/service/src/stencil/service.clj b/service/src/stencil/service.clj index aec55f39..7b644923 100644 --- a/service/src/stencil/service.clj +++ b/service/src/stencil/service.clj @@ -63,6 +63,9 @@ (prepared template) (throw (ex-info "Template file does not exist!" {:status 404}))))) +(defn- exception->str [e] + (clojure.string/join "\n" (for [e (iterate #(.getCause %) e) :while e] (.getMessage e)))) + (defn wrap-err [handler] (fn [request] (try (handler request) @@ -72,7 +75,10 @@ {:status status :body (str "ERROR: " (.getMessage e))}) (do (log/error "Error" e) - (throw e))))))) + (throw e)))) + (catch Exception e + {:status 400 + :body (exception->str e)})))) (defn- wrap-log [handler] (fn [req] diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index d299d120..16bc8b31 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -24,8 +24,8 @@ :else-if (if (empty? ss0) - (throw (parsing-exception (str open-tag "else" close-tag) - "Unexpected {%else%} tag, it must come right after a condition!")) + (throw (parsing-exception (str open-tag "else if" close-tag) + "Unexpected {%else if%} tag, it must come right after a condition!")) (-> ss0 (mod-stack-top-last update ::blocks (fnil conj []) {::children queue}) (conj [(assoc token :cmd :if :r true)]) diff --git a/src/stencil/eval.clj b/src/stencil/eval.clj index bc455252..7f239590 100644 --- a/src/stencil/eval.clj +++ b/src/stencil/eval.clj @@ -4,6 +4,7 @@ [stencil.infix :refer [eval-rpn]] [stencil.types :refer [control?]] [stencil.tokenizer :as tokenizer] + [stencil.util :refer [eval-exception]] [stencil.tree-postprocess :as tree-postprocess])) (set! *warn-on-reflection* true) @@ -18,19 +19,24 @@ (assert (or (nil? items) (sequential? items))) (eduction (mapcat (partial eval-step function data)) items)) +(defn- eval-rpn* [data function expr raw-expr] + (try (eval-rpn data function expr) + (catch Exception e + (throw (eval-exception (str "Error evaluating expression: " raw-expr) e))))) + (defmethod eval-step :if [function data item] - (let [condition (eval-rpn data function (:condition item))] + (let [condition (eval-rpn* data function (:condition item) (:raw item))] (log/trace "Condition {} evaluated to {}" (:condition item) condition) (->> (if condition (:then item) (:else item)) (normal-control-ast->evaled-seq data function)))) (defmethod eval-step :echo [function data item] - (let [value (eval-rpn data function (:expression item))] + (let [value (eval-rpn* data function (:expression item) (:raw item))] (log/trace "Echoing {} as {}" (:expression item) value) [{:text (if (control? value) value (str value))}])) (defmethod eval-step :for [function data item] - (let [items (eval-rpn data function (:expression item))] + (let [items (eval-rpn* data function (:expression item) (:raw item))] (log/trace "Loop on {} will repeat {} times" (:expression item) (count items)) (if (not-empty items) (let [index-var-name (name (:index-var item)) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index 3b893188..268b0038 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -153,10 +153,10 @@ ;; throws ExceptionInfo when token sequence has invalid elems (defn- validate-tokens [tokens] (cond - (some true? (map #(and (or (symbol? %1) (number? %1) (#{:close} %1)) - (or (symbol? %2) (number? %2) (#{:open} %2))) + (some true? (map #(and (or (symbol? %1) (number? %1) (= :close %1)) + (or (symbol? %2) (number? %2) (= :open %2))) tokens (next tokens))) - (throw (ex-info "Could not parse!" {})) + (throw (ex-info "Invalid stencil expression!" {})) :else tokens)) @@ -280,8 +280,8 @@ (log/trace "Result was {}" result) (conj new-stack result)) (catch clojure.lang.ArityException e - (throw (ex-info (str "Wrong arity: " (.getMessage e)) - {:fn fn :expected args :got (count ops) :ops (vec ops)}))))) + (throw (ex-info (format "Function '%s' was called with a wrong number of arguments (%d)" fn args) + {:fn fn :got args}))))) (defmacro def-reduce-step [cmd args body] (assert (keyword? cmd)) diff --git a/src/stencil/merger.clj b/src/stencil/merger.clj index 5a91a2c8..ff002af0 100644 --- a/src/stencil/merger.clj +++ b/src/stencil/merger.clj @@ -6,7 +6,7 @@ [stencil [types :refer [open-tag close-tag]] [tokenizer :as tokenizer] - [util :refer [prefixes suffixes subs-last string]]])) + [util :refer [prefixes suffixes subs-last string parsing-exception]]])) (set! *warn-on-reflection* true) @@ -75,7 +75,8 @@ (defn map-action-token [token] (if-let [action (:action token)] - (let [parsed (tokenizer/text->cmd action)] + (let [parsed (tokenizer/text->cmd action) + parsed (assoc parsed :raw (str open-tag action close-tag))] (if (and *only-includes* (not= :cmd/include (:cmd parsed))) {:text (str open-tag action close-tag)} @@ -95,7 +96,9 @@ (take (count close-tag) (map :char %))) (suffixes (peek-next-text next-token-list))) that (if (empty? that) - (throw (ex-info "Tag is not closed? " {:read (first this)})) + (throw (parsing-exception "" (str "Stencil tag is not closed. Reading " open-tag + (string (comp (take 20) (map first) (map :char)) this)))) + ;; (throw (ex-info "Tag is not closed? " {:read (first this)})) (first (nth that (dec (count close-tag))))) ; action-content (apply str (map (comp :char first) this)) ] diff --git a/src/stencil/model.clj b/src/stencil/model.clj index f959c906..66e0a6bd 100644 --- a/src/stencil/model.clj +++ b/src/stencil/model.clj @@ -10,7 +10,7 @@ [stencil.model.numbering :as numbering] [stencil.types :refer [->FragmentInvoke ->ReplaceImage]] [stencil.postprocess.images :refer [img-data->extrafile]] - [stencil.util :refer [unlazy-tree assoc-if-val]] + [stencil.util :refer [unlazy-tree assoc-if-val eval-exception]] [stencil.model.relations :as relations] [stencil.model.common :refer [unix-path ->xml-writer resource-copier]] [stencil.functions :refer [call-fn]] @@ -254,9 +254,7 @@ (swap! *inserted-fragments* conj frag-name) (run! add-extra-file! relation-ids-rename) [{:text (->FragmentInvoke {:frag-evaled-parts evaled-parts})}]) - (throw (ex-info "Did not find fragment for name!" - {:fragment-name frag-name - :all-fragment-names (set (keys *all-fragments*))}))))) + (throw (eval-exception (str "No fragment for name: " frag-name) nil))))) ;; replaces the nearest image with the content diff --git a/src/stencil/tokenizer.clj b/src/stencil/tokenizer.clj index 6ba40148..d758a8f3 100644 --- a/src/stencil/tokenizer.clj +++ b/src/stencil/tokenizer.clj @@ -47,12 +47,12 @@ {:cmd :else-if :condition (infix/parse (.substring text prefix-len))}) - :else (throw (ex-info "Unexpected command" {:command text}))))) + :else (throw (ex-info (str "Unexpected command: " text) {}))))) (defn text->cmd [text] (try (text->cmd-impl text) - (catch clojure.lang.ExceptionInfo e - (throw (parsing-exception (str open-tag text close-tag) (.getMessage e)))))) + (catch clojure.lang.ExceptionInfo e + (throw (parsing-exception text (.getMessage e)))))) (defn structure->seq [parsed] (cond diff --git a/src/stencil/util.clj b/src/stencil/util.clj index 594dbfc3..26549bc7 100644 --- a/src/stencil/util.clj +++ b/src/stencil/util.clj @@ -1,6 +1,6 @@ (ns stencil.util (:require [clojure.zip]) - (:import [io.github.erdos.stencil.exceptions ParsingException])) + (:import [io.github.erdos.stencil.exceptions ParsingException EvalException])) (set! *warn-on-reflection* true) @@ -71,6 +71,10 @@ (defn parsing-exception [expression message] (ParsingException/fromMessage (str expression) (str message))) +(defn eval-exception [message expression] + (assert (string? message)) + (EvalException. message expression)) + ;; return xml zipper of location that matches predicate or nil (defn find-first-in-tree [predicate tree-loc] (assert (ifn? predicate)) diff --git a/test-resources/failures/test-eval-division.docx b/test-resources/failures/test-eval-division.docx new file mode 100644 index 0000000000000000000000000000000000000000..40a5d6700a0ae2e032617ee3ce284273bb39f9d9 GIT binary patch literal 4357 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 bf}84p_NT5EF$wMvIrb%otx`EP?%O{AE{Gi} literal 0 HcmV?d00001 diff --git a/test-resources/failures/test-syntax-arity.docx b/test-resources/failures/test-syntax-arity.docx new file mode 100644 index 0000000000000000000000000000000000000000..d9d6a6a6adfe635ca8411d159afe944575f6b852 GIT binary patch literal 4343 zcmaJ^2{_dI8aDRb*o9=x8j>~p(i~$Qno*KunGnV@BTGrv?2fTiYA6iZYKXB@7@F*4 zZ?R^L?1Z{AopWza_ujsFo_S`T=l{<9|L^yH@9+H}rc~5i6!i4;6wjReU=&A+iS*gQ z5AKOnkSD*FqTicP=`%;I?sFNg)Q#EzQ!t+s=^Ho;*?`x|>kKt@T|lMYtHN>eY3E*b zh)d4hibU1D&@JT6Sa#~gxnF_^npMar@Mp2^ED4Nk6|baNLW{&L<>9aZgCWIqG=lxb zgOT$Hrzwb}A(tgKL&-@LnU~@_jvtoN)qb2ERMapie6tSVA82|Yu&U17jZ6GAij?9N z?L~^1M)ev^`lGLl5`3Q>6BpxU@!x-f-?*Do2)J{v1|K?GYd0~4oV+?JFJ@(8YINZ{ zSoy-OmU>XxuK$!}ChYDD@A;1ah$$WY{Zazsd(tx-QBqJq{x2)+q$^y!ogseSK1c;; zZ$G$vpqFQ&b%#ZZBCvjgJ7u-d`GOuCTS)y>Qbt6DJLX|oRiTKl+<3^Lwncv)P4EQ% z#hI)S4b1T(rLi#oqM`FfAci=-_)9YOW_>c*?VMa1J(V-?QpdQYiC(!naFk;X{)>|b z)sU=@m12Jq!*o%C(fvuBMpn(kBC3=}=`K*jQ@B*NL~n?^btm53AFo zzcJjl(^43>a>hP{G3eO_Uyo#9dKZxp4p@4#)w#60CY_y|a5AkevUNtMAgwPlap7iM zXoEL%T{YgeB}+d>LftWaZ8G}!o*26i(Z7s^sYkE97%qFvx=Y%0(0q*PBwFrq#E z@<%KGH;1lyV2ic{4v-|T*cPo9oJn)=pkWz*>g>yWkE+96>dCqkTA)woyZ4}ba_5oNq874HiAhWa#)4X*>S#W)R2+8ti8?o;!3 z<|1AAo&hSTH7coqTdiFZ2fQ+g^DA>~gf5U!E=>h~62M0Yb#|omW*nrvDJ;$(k(WHA zYVrjb(0pUDy8XCECE#)-pKH7dK}@4ON#^ZpMm+sRw6B{> z>&bT6cCX`L`}dD?_!`3xNh3fQ9D_pogeaHz?6K1k&o;a#c2%wl_~>KD)=T<+$E{0=GlXg)zbwoQcjUlfPpt6$`RqPYRRa^-6cio3P>`BNss5XYm9(;D?T zzC2BR4bYL--7mQ1#ge{0GCBULc&n@R;JL0|&i&ar-YiT(6u6sik|%&n$DV@XO9JU( zIe$wB*&%+=ag>NISO;R96L|brj_;#6zpwd&@yAEffjk?dy@;&S0|@bCu3#gs&k)>}h85wm)3+ zeaCH{ILlwweOAeXX@CN#0L`t@|LtJ-pW79p1NgZP+#FH~UE#VeI&GX{1$)h>I5G{AZY}*qr zIhh(+420v_E5)LDFQ)Dh8Yt$(q?OqmKZbja>e_677FAH^9GUS7)~Krw4p=)3O|Aaa zIX^W#fPAzkzjKw&HdA+xyEJgT*JkN?%!EXG_cdgF!o!lb@55>vd6v6IfL!!Fp|H*C zLVkMCEh2s_s7K@?)7B%NmNo&+{Pea2cf08ZtK%b;!6Sv{vXxNdkJ@*5SlR<=+IDnI zWb9p=bFo;8iuik|WYZB(D#7H4nEe19p{A4ZC&S_HSV!Q9+(? zBze9FP%lf8tiClmu;i^o`Y)>ET{SCahgTo*@{FPM)M6D9 z4==ACyxVxRa^PeiGr^uXYXVx{b$d;j7Hd*-8MORuCD^Bpa!D^=IIT6ttG7I!Sg6cn*!FpTD>WvfOb>`=yY0fES z$zzppz_+K?961+NU_|a~5JuO-!2`vv${JX*j$6hzfqzt`6yqXm4nqaVp zt+;7GKV*N2mHnY~LAZvoK&wL&&Bn+doTj!ibMn>AfBcD~Zn2%u4$%`^U`rMjRV@

xn$wQ%iuF-RkkM^L(tqr{3Nxf(;*0em==_ z<};8EO;FYrU(mL~Z7q*#uNXS26Cs0W&=XxWqx_od!mztaHj9CoZgzRnl)|RY!oYrS zKBC57lLdCwK|!JY>5JH}w?5AGKY=>;-Ui(m!S8xZ?U;qs`FevlZEbt`#wA=uMV&uSXVF zCP~)$48M~#$G>HL%^QJ&Iy!m&h*YKM9z?SubKC08IH>?*{V`=2m$Lhq`;_7c)%}Tn zLSeOY3G?D?1f#rW$F%`Ha#~hthk{x4}LVP@i|HZrc+xJDVB|4bOM^ z4qqP-&SsBjif?v5@-KE`E!!4urnlS zl$328pg}%xq&?Y!8`6Gc|8qNsaj)&ts&RVs5)0w&9JTZwF3aHSF(=C3_kYu#RxW~Y zXHrJNwUDb1YAnz_Lq+B`6ZOiq!gHf(;*V?Ct<`9PidF_>m1HG@D$6D~mj@e#)J-YdDgCwx8>@}wGa+TLojAGf;Kp4E>Ts>pN5#56+6M9b2zoEWMRsYoD6n_{KN0M01)=pUd zGYPuQi!y00zz81UB$p-u$*(>EJR{#u=m*UA^g4V#{@w@9`eC{tR$y5s4y=9lF55$% zgN1^TuSruLMmEdX!&gjds;|0(wW#O;DxoO`tM8H(iS9d1a=JgVC(|H%JCxLrD>US$ z04TXAey*uUS2gmQ`p-JDwEhY_x<8Qh|Fd{VfAwE`-LLSYd6>Lp{H$7%&HV@d_sa1r z`e=M4XXc+JP5t`>{p&9s%?tm040RG){}n!eg&*~0a;W%ODRh6se@2X7!AH%7Y~4T0 zjqz{rKaBiW{Lx|i=etj>JpZf G{Ow<<1R)Cm literal 0 HcmV?d00001 diff --git a/test-resources/failures/test-syntax-closed.docx b/test-resources/failures/test-syntax-closed.docx new file mode 100644 index 0000000000000000000000000000000000000000..bc28d854e3040a70cfdafe20aeeb6ea724a6f22f GIT binary patch literal 4329 zcmaJ^2UJtrwx#z#kRpOg?-(K?#n5{-q4y#Xkrqlony7&Ej)W#%id2Ji5Ron*803bk z(xi$+ka~mqll$&_a((}~&KPGTW1N|sJ=ffG&jr&br(h+ap`jr$tMWA?IaTz8&klZY zPoyM>c&|-=uSc%R5QW3Ds&9A9m;=-DKPS<2vQ#nwZ$9f#hbX&f)q3N2;z1dj-6I0R zYj+;_cQhzhp3U5H!l2!(Vdo7RL5Z9NjEA4NruVD2)1anR0#Fd#EKqF{j7mo^H{?v4 zAe@$9!s@KhvP>x_K4fW{?_Begh_Yf)VQ^I^mM64>G$2U-gbOFfFosTAok5D6<-;I( z^`kHvivj8GeEq(4=LGoA0s`>G&0k|mDoO8WwKd;cX}4KeLM}SafcTB{^ffGg=t^7M z>3yzMe-y9;%{7Z@@Lr!Mh3Qk%WY_l7y(g?$gN%d(_CHx+COqNd?F{qt_CZQId;7sb zL0+Co#&*yiFl*SMg+$nVJ0s-;I_=4#?im&y&UyFHbJ$vs*`9^@IWNzm-Gw>D!@L4K zc4G_1-hD9(+G>33E z)dwA`;ehz&F}L{o6&sP9q~~hEekM8T@=S)wSC<_msPp=EIIw+_BCRb&C*A4COIzhHz6z-(w3T!cCMlaeFIUHft@Y$=dE!{&5*kMQl*~Q$UOp zDGABkZyN+W-JlyjKE(dyr;Q?dd4LaaVRZB>oX(J17R~G@%A2Cb{8+<~1`$tBF)m=L zNHBiMG9V08k)J=_f=(SR^LTv4Qn*PB#wjd9*58y`1;S__Uvnsa6u*If+g zZUul2EoYs z-X*F?1E*YnI6T2T?<$M+98pUQ1W7{%LP45!Rb;Dm=qe zShf*?hf=YU3(c94ozxBAN6E%?p}p)pqPVKZ6gfH57Qzu1qIEJ}sGj-qOf#2gqHO^{!>N^-NnlqeamA4n_yA}0qLPa>_U>)Ccg$I3V1MQF_As?e=Pp_o0s z_=Y=Q-&mG<<1V5S8PDf|uL*A`@G?)mW>vG0KYrFx_&)Vja235+(yF|?pZ2yn4l!2) zt}(L3)6mYsg@gg?uj_NgH*e~J!DAVB2NH)3tmp>tQG4f7AI^q8>h~>_md*6oLs7Br zw?>I=8&8JS2BisbJ1wM#{PD^C#(NdWN$q#lA>O*qQg@Fcv?E6#BXzNhg@sYEwn45f z#%Ce%+(G&!BTj8HAIaD5`pRhFJbZ5gVw9RC1Pz+YwN@Ihni^M%aNANb*-A&S zMR_LySQ?HJwxd|T2f5!lg915JY;L@^AAsQRzWsEMTte%<(@cdPS5au3FfB@>xRWN% zC5qbJJKovy^G1F6#It?vp~o^1SkA*3@HO2flSYv~N9eT)3mJ|teG#}spvq3;fn(6B z$Ca-3vkP|wqE@@NAg);l1H*T1eeG<&oy@y<$zN$d`r3Ei^+k%}tWoA_%3XEw4{VLt z14zghC%v-?g7z=H%$M=|qf+O{luT5rKbC20|9SVElZxs7Otbs*HJ~;9aUT2f02d?* zHDGWfbh*>0vq5qn^TbZL$uw3{j%Asqcl`bR9d&L&RGoK^v%UxEG|2vSq(cva``wOo zY|zoz@&>odK0Q%Qf>8Wxn@^Le$KaLj zOnRW>mccrbA^LJ0E76ORs+18^^{BkV+cWMH#L50r&|~C$;TEe{kW+?P4n)tCxk_T0 z*1SyQTaaZw-8j2QdJ9nWr9wuC%D%MRi-m{|L(OPPO{oGHPr##1ZDxfYg82Y*XEf~^bH@t3w5k1&V{)`>qS|p;W2hR13Jv+S$c@*+(T2T?%?u&x~4ncRJvdL(wg=hYoHy8bhr|I?nNut#KWMnzN3>v3wU7>xb-&Ut@7pOiJjkFXJlR>3|;@PXuA1&>y* zSZZ?aFl@S3v2r>cXvcM9%I#_)K+Qn8@LBSbaogZdPuEKDzdmCm&iZesrL|J1z3RrUqvM#6novhFNTh=$d5&ot( zoIL*uQMKuB5Itaq0bFRjNT81Dth5=cwA-xP5_pS`-r>+Y9W$4Q!H>)HIEVmXosAFoGU?CcVEOpr~2_+5JboCt36a6-iqap zUUkX-L9^>F!_gjdMx3F1JNxAT++0#=eqImY3$1HAtRK6#Kkx`o$Iz$#{F~no!{h3v z0w#qY6hz*~B$A@!2xoN{`R{oD&*6lG`}?~gT>ml@c&Z|z7tBC7pnEX(Ba9ej{@cwZ z`W2ZGT6ezy8C9t2(q?y&)EDI$D=GgRVZQT5{_x-3U z{xvYfhZSu`e=}hH;sNqunw4o=Du7oOS*^7?eJg)7_M)FIb0j)EojTLCeLcsRo8{bV zE3M9nj;fH!i=MFjniJmL{&yM%ROBP_0cCrDJ-L@3k0{poO<0H6Y^&|7TG)E~_UikX z$IHJPa|7D0le*-<=c~mi7_l&yDWpJkc-P zF5jORHTEtD!B=gcCGz#wO5)=VL51D~&i;4SXCXXcqfXE$A<#IO2K&H~c0>#A%ow@e z3kGT)ev5x)lhKM+p%L7O7(CNXmJMf?ETyCip$bfFt%K>D7XR=m(zAV{mo>R zq4~md>2{vAnGAuVHfCd4%HXQ)32`ZL;ozqF1(q%B3!Vhv*445CNtMfm=jb`bn|$Lb z@Aw0n61(cD*8BZKblb*B{dLs$c;I*Gui7;8w%IK2WG48fzw^ZNRq?!@HTPpj8#tpF zH_tbIzwcAl|H0o)*(!RtZcwsx-_?Po25~@Q+&{W>j9$X_pi9MzR+*P?8>>jUf)v0} zfIWGHS_2F2ipH7fH~IA!pZ2TXmOCPU{^a^4&T#rO$^0ezhvDUQg4BDKVi_*LNOqoN zmo6^hKYaq(r@wQn2Cl!sIDEeF-UrV3VYwoXYfCI%SMgd5(LbUFmWli@t_#a08EB^Fm w`{%pACA9LdcRy`Nzrs(;g194p78&6G-JdXhN2&hPr4nZKaNRduN zL?DWErG(z4qk#U&efK@NzW+IAt+SG~&LrpCvuDp9m>x9^h=QJ;p2EV}*OcN|Fp)mn z`ocX?5HR^(n)F$hT9r9s{Q#uC)-YlQOw3q~r*GsaW&=7`HmEBoJ86}Auk*)(lQmnr zg+=FY-$OT4DHn66t~d~|ZWgdp`qkk3yqTQ(+Mskw5`w+z z>98@vfd~^-2N~q2UT_dZVj2hdgBvLQ0`v~~*5#RdvGH>wC^2q9 z0!lzHf}kbuk7$r)8dc@JFhL2^1JFMy#WQ{;omqp5f&%tGQDG-N;pFWI^YuoeAdcR? zaBzT^XS|WM!Ch%k$li^IP3q$EG7MOao=hu;%5%IE?!lcHSQ?IRtodb&-Sxf6F{Qoq z%!2{6f03>?+i0|fb2N>>rb=ZRlev>?W7>i7%t<|wLijlWb!}eJ6!O>cRr!&t#dQlC|wYU+?WZ)eHLg250FL_dq{_ zCU|#!O1Dy#sJT(n`b_Z8EWw+CQ%ESh0>#v)`fV95deV4RT+e-_?T-+lJIbdrI#WIo z4E^371?NEnE`P8im=0pOS0}omH_-RIhMe~{7~bs)#kb(ihEIIk27dpV@Kugn9!#Lh zh8-mZ#n^Al13X?HdnA%vos7gDM4L45-g-#v8=~$hwP!{%JUiM-0-VL-+mTi-P%9A} z96~?d#A?`|zTDs2yD(PMV>XYAxjy;o!WGdfc>#7;H{JY_^xL5Fe%csCM(fzuu5Y?% z70oyJa6YfY)oHIX(yXG4ghoa3Z3YR9X8@Lfc{?jfX`LH}D0wjkh}LOKf=<71msmPG znql@MtI7wC$D)UjYT3%MKJU0V7R6phS1%!;d~PV*9wr;%9@eK83|x6Xss#<3(%iQQ zxg5~&;Q8K$VlNS0Y4$B(JQQ3R;?EaPb%2cUeyB6wHM3Zh=9_lmN~GV6*N>04{kcV_ ztf-rnaO|A>h-Xc93e&oMXLwebcCDC1g(Kw+WVEz>omN4E>t=5#o)k_K-q1fvFG|Py z(fdu)adv!RKLn8Bc4X6QKTm@D^l#xlMS}a5x33fAUqV4@3_mY80!>E!U!swCjgbU> z#9pT0yYT{3U}6D611Y&faXtgrdWoS+EGU>d)xBLxl1 zHpG1>U`L9*>=$_v2W{%SZe+z!>K}&Z^c``fCotaS>0qq4$aor-r@f9N7f+ro8>sIk5Tdnh7oW>?x|bC_6K`#iHzwXcnewy}bbn9pql?Pvf#^$d(^uUPQbXf6 zcwwL;E=;YS`CbiCGwD@Am|m<+c}avH_3D>~Zvc^~+TA2fX;@ph{in;y^db%Qv5ZO% zA7;WMsa@V-ADe*h;9Nwqn9OhW#w^$fXrDwual#S-acwMBCO1wRXDfk9w?7k9oh#9k zg;bXuAE+AGeYUsXEp9E9`uzl=YP5Oc6Znz9BnPc`$}5d^o$|*5bQ-2vUL|yi>I+gc zD2w}ivi^*c@lV@!>E0KOk30_Y{8;wLLLvbo&oNEYzVZ1^Yj#4C?vPwqQ+^G_L|Jn> zp!weCtF_Z^z>P1Zo&C*kmPE;%%9(0*;Uw`XmhN{0hwa~-LZJga;V5!z;Z3{@NR0a4 zWXt|F7+MTe=M;BPjYbqbGux*en)z^39_OM^Jv+3X&doK7R*{Q=#2;C$AAZ_OUpsWL zj-F(XpVie`*>`~5G$Uy>FJyxAFJOTm&u!Rq zEL}A1;&g^Fx*QE0O83;%nPz#2WU!byVj1Sy8)lhgO>DaC9z?p_SNob@9jrz_Xl=pD z{!F|u{IU*Thix%x84ecx)(K z2{l}FE-zt=4jP2I0v_zU>I4kzQ@I&FA97rXQJW(WY)&;!pnZ`E^Veoz-3nTnJ3N8A zkUteYYQ3TX)Mr8llC-jwlNiApE{j8M7qeK@^p&$J9}pqiR~P#a2nC2bKLr+3J6i~( zv#2WO+wF<@57{P;gg>?XhjIHJ#6A6x1|M(ewz)ZhXH-dMz3}i4ndVTnz$3mfIY_4Z zPTE`0kb?WbLTLXIkfAJ za2Z}Za3*rj>B$fJP4`PY^-(7znG4pQw7iC!L9R|r=(6}2l-2E3^oH)des;jf+@b#J z$oI#!uO&5^Y>Gc<$hh}P#Yf1KCU!IR?{NRmh(f{9Xg7q*UxEUEsDx;jW+sj4HiAtz zD?wSvAD5$7mOV#_a5)Ad~O{ryuamLE*QuNO7UG z$X4D%z5KO;o&Fvp@64bB%o;ZtuMg%@GaDopdXq5w-+rHi^n|54iK8T2V`~zKgrls< z65N>FeZ5^8sJV9(`^GZ47OO%pvKZcZqMqKvX$9&YeWvpBhwn<$vKSa=8dU^b5w-rb z?s`(+Am*CcN6h`cU25$OQlzG*ycn9Ow*DzWy31{^SxT8y1OPqL0DGqisUSAA)x8z7rYiS5#j3==bqkcl+RJVg7gOjahG z?b(BgD{#kIsE7{8r1+kkqd-7WhTjx+TvX%ylj}|Gi6Yy(E$%ZnR%cp_0wd4 zD3`!c0KZZEknT)SriTE|ui_6OK`xe{{e0|JV!>0&nEQ>`=!GVm>Dho8he>I{%Z5g} zmPZEimXVz;deyr@vnDy_iO+nNK2k!B0QA}AIL7y+JL^(WP(c4DDr}?^&OT01e;?lf zh?9>$OghxtE7i2`Ql|{CWs5U|kUVFm=3M-g=81?ne;s{S|HRZNQ1#xr;`+xqA!F;u z0Jy5^+~obONjvj5tYvrWas@JrM5+2c`GZt0KuW_R69pC@fXgrn7vE6w=5Si7C?Ute zx!&F{!WpC)_3)SBXt%l6{FAC}lsH><(4Ti!@#3GTYlRh4qN~|`@1d2h#=6FZCD?RM zq@*pmiS4?%W3emDG(FH*s`XQ68QMk40j2M7fKvfzv*9z$wn*0bCm&Q*7$2eyatuUR z4ml36q1M5L-KO=WvoVN+3Hk+aHh_(m8h@WBd%U-E&La&W*aEB9yfE-D{yelVFc|mBCTp2@2#O8yMLpI?bd{my;k1o4vnMgT{?1o zS|~GCuTxS`O#QY#z~l9~?(0h~4=SS{-XR0LMTkPYoHgXt<^lSr>=owTL5zaJbt;|- zRj^Ohu*B695}vpZY`eR<63bg%R*j}~U48BTK;5-^bceoHzIfRDk~(61Ia61i{u-j& z?PV{gn$?ynHmEy}i81mP&3b@oqJt$e+)76xG6k!*=w>THWoUUhKuw$hqL*tu6pJ>< zyxc%DB#gYGN?~APi|UNdFV1*>cu>(`J@03s>B++ub8gQPd^QOUmHL}z>ZU{bTSY@e z49^VD_QLeKVx07B*dyGsb0Klrf7jL}aZR0@mUZ`*&z;zTyS*a@3JdbEK<3q#%{Rs( zcYq?H@Ys*1rx1*4d9rveFCW|GuW@hM90u!rwdqo}Kvx7+YIej&2^RV^MU%?%a=w`y zSsd7Is1h71nvg!NK}Nf^BYsAPg!bv*qRmS}JH*G|8S*coAT>siHw+#~2K`^6kzve8 zLO%XWuE6ULXmel&8m{Xru}vY1!nP|i^ooW@P$R5GXFC=Sq6o4Od;9&gYeN9<)EG&F z*!;`bVKkesX#I$A*=>Wio=c{-4CTSGI35m~T3t9U3@WuFC-gQy`7VCX&Qvn06J;hc z$(L8W9Jg;m2ckYZWlnWa@hT=uDe-xg_chOWp3;Hy+}s({vG7v~`p7md_P?LpeMzY< zt+8Jc>CKY$d17X&y>z?3>!4CYvmkq6kt-Kf5^pd7m;r~7;rI%Xd4VHcmgBc@kj3FU z9LIscA&t^UWPm+{NJa^Ekx^sQCO1y+ru!aj0E;YZ^_W%VE^JkYquC9 zrrCS8cKf5xRuj%#GRzbNh@=b@63-^nkG}?KTZR!~M-2_PlXNgl;;Em)M*X4n|A`8B*#Bk zazjCfU`&I_By-&?EHg4G)(Bx&SsEWCxV~Gm_m^-_!%hySJhmh5`a2a3dg1*h1mpSZ z!}Ev)YS&j)<7Uz^ST5mc&YES^xi_tvm*n6rDc8eY5sSsW=RQ=R?+w7kCbJAf4jgml@?!9+*cF;IW%)3Bjy4KOyxN}pc_WVa$JGH>-Cy$t z%OvTs4CIeqDcortH4Tewe~_(ra^NMHIV}ol)|Agcl=7z84L)R1-VU`? z@!ZOlZ4)w&AkmN}1`>w|w7(lNC;rW&fWR;>SOB@5aAw{nQ!>DBZT4(mBMhDbbvVTw zwUXdZ9$M^Dj?WKYRmQri)-H?_9&v&3ftpImkkmt4!oj<(M;ix@*OI2$QWp&M)^^=q zQX!KK8*TO0zHNm2c2litqWO?rN!~*>w|h&r#wMClHF=_69dCR?xxp`lJ^Og`)FXg#_9Qa=FsQa#g@lsm<#gD+=bloGwsC*Tb;` znf|&)^E^*s6pMvZ6~iK1^8%B+nO(0#T7qke_SfRt2yObe*Q{CD9*UL3sp#``**~Y< zn)r*u$Vz-sx}oE*J5@BDR!ezNnu5zG()k1wG5q^P=0~;qUiGq+{*#WPfG=D3C38P( zscj9#mFTK0-duhyl3?69gJlli|D++2i!s00aZ=dhZ1LL!y6Kw06_>&QhJj_i`wC8I>H@6b7S&WecS8YjwTV0RZ=c#$dhd-Y zqtAuwCP*k6MklnAXmUaL&}O|+>+e#Ro;+|R4sd%pWO5PJRp+^>`e*dzWtYm*6G`E3 z4cq`}yKefSW4l!DmmiNiEhTGz8XB_WZT%SN@0-48JP++t)yv&J8MXttq2}4(odlph z6E&8pm#>k@D81>rJnpWL$D(~vBd;cH7D7~C9@!s4!yAKCSh{L&#=VL&$vKl&xKYxx;a&YS0^Jwj`L!K!mNUqLy&MVNks>j$; z^vDX3p(%ps-ueMwpa3O6qnFJepP%gYA=7%uGZ&OYqBWY~ceG~zH?3WK;DKh>9lgGr zr}E4}(sX9-CPW}aL-e)q^5!6Ucf9+o%mj7z^ss*N6v~ zUYj#z$_@^5Pc!fO@zZAR*yuDHwpH5erJMTF;N!M$WvL1D;SS*UUg57eXY?ianf0FS z#+o65ZnB21*%XzHsVgUa1RjwpPHd0l%3M==L|gv()KKXOK(!sQ1jdtG1wNh)GsZZ3RTtj-?B4Nfi;YsN%-WP zeMJA+L-Ax&BD(}LdL#QqH_QT}{_&$Bi=RnFvB)1 z9np|+ACOFqS0+vA=hVN${Xb(V02UbN4tM=7D6l){;hi$fq|w|lWY@GV#(-3%+U#yc$hN&%&MpC;T;{Yj_&4cKm0#byHTxi&35Fg?TZ$7?IS z){$mR_}kN7P*m9g|7YAg-HUY8eJVl4L>8j*i}_ueMZqheUQXLmJ4`)iXBV-$i;cQ! zz9HZeM+KFRI+f3uU+cA7lr2Tp%{yMGg@wD}W^rieBiQG+La*d~#RPK_+PM#miZ{^P zBmJg6x#9bm4K6ZX!&Z{>nAYs(k#wf!{qVQ*bMhD{XAV_7Of7)$pz%`X;9HEb#dM2& z6W`(_Qs{9bo2e3Q7-nNcLQX;?tgd>RV{NRB?>2Bvp*R7n@eO65z7CNOc7sX52Fu@QGq-u~wtwb3uYCaw-)p>uKe+0mQx@wAZ|%1j z-(uW{Kz9wRgjW?kkfLo@dyX>r4uvVMfA(wD>{v%tk;LOqY;(b;nsTnvYBKRGL>0L9 zGY-NzUG{`QRQ2gfoK~>%E_KV3OH$miOzdfb1>0X@(G|h~;#F~^Gw>#uFU|QmkI0^H z2zcV#8Lg0|!6EzgQ}2CYtRLn|l6ls|5eDb~Og!-re6U)fBmK7 ze&Oe9oFk$2UtaUq^y9irHWfc20`O1z4~y|@@^Nt?OZSg3W&9`kCn5he|9G?g{Oqqt prTqTv#|7!v^y9Q3cjS-QWBz~p6KX_DM?Sp7H)AxQ~-v0luGuJiO%ym6y=J(v^KKHqgu>ma|n2L#siE5zG4Mue+SjnHQ z-Qmt2GSZayQp|)ttp;1@>MmGorGC^5kbwOh!_){W;sn?~uh&vhv(qVcT@{LwPQ2XO zB_cNG{{UV8Lam55dHD_r=VW0lU|1;~&6m!xy~IDVRkD%*ffkEEq~Wl8nnSWl7$oq; zlaXu4JJZHuT3|?CvfLeEkK6>eas05j+QrNapW?_>J)FBI^4owfMlKaGjr|Ha1{ndR$6inIPX;n}&+Y_@(~`PWW$&% zu)J-R{#ki+PpHI(vop$KHbVO>i+V*WE9}!eyphR492YBLRAQkK8*9_oP(yrdWbQIG z`j#&mWJBwk>--uWi|<(g+jilFieefxecZ3{>tP{RBNgD*m2_zx>tF*^;5sCQBG=hlm&NEM&+(zFlTj@m8nw+qb{vd%tMoFF91M(*47IK>c zgC~M?E0k$f}@J9!&&K;>^`0$bS&;mvGIfr%Z>@=D^aLN25<0b5q%-p{Zo zqD3GhKdz+aKN=kDMkMG52_6pG~vD}=s}Uy_O} z>A)}3w~h}xy_IhqD!^jw)())I;Y$;g-QKj{gk6VT^TP8xO~8MfU3Uu z1WE7bpOl=JqUpm~{J~6P4!N3@lKPF$GJ%&k@EkpTH99u&Wx1ccpn-Z#}wqm^cQq}2zYY5f7}tE{6Nw_So?y34yPE5cjk1C)(wOAo{(;_ z4Q1vzm9+s|OOs&N>ID|OQW=wPtK;>Gm-@;l&VKy17uy#kaKkTju!LI<(65#i1k^ci za^bdQFYo6DEBd}4bU$b)9Rc7~yDLY=Tkwx~3y|@?=jv`J^KY>rH;1PS9EqmD{%_$( zxXMDtK6E=>_|5x#7$6}Zsg07{qB@Jkw<9_lr{G%~%(T;0MuFY9k3g#%I3Di40{$J{tROWhxcW zj)jVi3T5Oi1n)u^z_h2vVKjSXukR-*g*W3|ZXrVXOL{Ky@g+d16?8mcnyy@72P`w_;4?OoMaD0=IZ6S~^3;H7* z6oL2&$6+A4&AMX8WWf{LlEMZr>@ddpe3wD?A%%7GOw8Y@JKead|62GS7H_t{ec>bF zO(FMZsn-lMyUN>Xw6Pm&-cUJZ6sm1Lv7;KuTw{Lq`d6gl_I^rr0;u+qR#UxE~AD|-EHccP6=8quvYTHi38wR1Og^H@BFtMAn95v2*f zuBzTy2Jq$PP~l|@v;AkN?M- zLpY;6LE5B5+`q(m>G4+cyp?WHx89DngjGY#ph52e`|9!!(B%HH7JoWpsc%q6TC~i* z-WS7QGpi{ev0t#Tk(laLRS?IXmV8DL!|3UrDJKdk^~5|VxSJ4p;<#s4xiixah;@WM zi=L&RiZSeZ?SUc|D`YUg&{KoP3S3`%O{F{8=na$ZO(mey(xc;($k zY(+0Ee`m9N%7WW#w2eKtjNn)4@865yw^&1Fp21HBbWYx1_itKL3cw~F>`v?p^RZmI zwq`=~*o@rS&n~4lNpu*`*8E7)N9>;O?qugvNt*36g!_d!LNk`HT!h^nHt9|Z?^Oon zgnm9&K^+njo2EbfP+i7Jw54myl}gL14LOeMch#S@_g!hkE#9{|#llgG%M> z@M;<_&lp-=DN-h8|K{r6yN$G!y*sxerhqZC`Z~)yj<0AEBlT-<>MS3u_@X*#meliw z5<4PXNY5V<3pNHv>SEOSjhiZGU>)vro62l17+dPEx=xAUuEf(VeS=c%NpeMs5*mmi z{}XL}C~rQV7Ewf(K*lmy!uw7l?1_p#6j&@Z!)%r(zVCel%hJaqj;RKmZc$D2Ro{|V zZkh_Nzj`^0{<2)UbexIp{-T*$&3v9PP{AMk9x zyPwn~(~Dt-ZAV1!p2`~vW0-o5UbN5gNbK!MJ6++Up(KMwgsCV(LAs;J9HJyULox$; zMell+(;`7+{4zZI0nK2-&NVJC$Qc1=UI zjOVe_GMkqc26jpL$XZVoc9@N|jLgfz7m?rnC+GUJpthvHbi79JJBaCR!+?4>SG`Sh zbJCHqi|1Ag_Kuxv4%GD7=O0&+N!Qv+eGMK~xjQ%?IIsv{uJhY;YJ5MItzf)r9Jm0> zyf{j9rO=v$NC&5qX`RpfCt7p;ht~G4NHp~J9p|6+sua^pp5APotA0`9_jEPJ_w=HLljx+i%44*6 z3=F5cRWhYDlmJH{=NWNHQeUP50O$@}+YO?f;}R(D)WB{L6f3E08<&zwdg7^x$VN)9 z)6&uMKNQp362Gonk~8kDqU*k41vw9|859irWcT<7)0ex7N9w}4B-!#;9=CMD&15c3 zPU^F}LCR{kD|!OAI-l;cuytrP?7RQC`mLlYom2G(9R>FushCh@@(gdL{S)s09a-31<@$YAZ4o)GJNovAE-!fHYs zWVgNg9)bHB9&7uu~lM2H=YV<6x}zp7Y1QdOS)nht|fkpHT8B z(fKsui0zIz?yd(6!3AL$lO5{jpO^}Qj=nM1X&k68_8mIzY>a)jcj61-owgwZZP$6v zyiN8^<(7{-baN-Kfr;EVORS2kx!XH7D>{I*xQ{g+MxZhptxGhngKmwt9#A*rTGW4a z#?H<@@`VQH+x>ul84`Idk2-xaHLR6y&meCle`}!I)HU6Aw|M2J{PvqmeOxEA(3OnY z|K0*XJL}F7h4#xV*g|^~y4_2B*#>x)A)5tBwg_x2$(J zLh$)S|G~u>`C?=4RGLt@s>kY+S|d#FQ1Ml>sRsEvp}EmSk<414sS>?U@ydXtoTQjf zRmBu&d9X?7Az)b{FI`6cY{pSmKFKP#D0+W1dsTFE1;ac6?Wr8_K?U9;x{Pw0cgPE36k=alD+M;ibGo+fZn9 zhA)VfEB0je)^<>SnJDAt4T(fMKnRaetX(s|*mu-Do{_!3Gkh!s7hDAY7ys$VPX;kAvjvi>s;EwH~Q9^NJ>{Qng^Hch|fB&V!e&L_jP$HxCf1K#=>4$ZhVk&+G zmhtcOUl!x{4#}S>BwIp!2bXC Wr?CM&1LY7i`K3y(QUwR)+rI!>p)>dZ literal 0 HcmV?d00001 diff --git a/test-resources/failures/test-syntax-unexpected-command.docx b/test-resources/failures/test-syntax-unexpected-command.docx new file mode 100644 index 0000000000000000000000000000000000000000..e24be2e8ee483ddcdf599ce73d2e97ddf189f481 GIT binary patch literal 4334 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 literal 0 HcmV?d00001 diff --git a/test-resources/failures/test-syntax-unexpected-else.docx b/test-resources/failures/test-syntax-unexpected-else.docx new file mode 100644 index 0000000000000000000000000000000000000000..db4e40e634ea4fde65f1b9419816722d5711bb0c GIT binary patch literal 4337 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 E4+*0tp8x;= literal 0 HcmV?d00001 diff --git a/test-resources/test-function-date.docx b/test-resources/test-function-date.docx new file mode 100644 index 0000000000000000000000000000000000000000..36367418a9584759deb16ef181ff1d259f3a9e02 GIT binary patch literal 4354 zcmaJ^2{hDy_h#(b8N13FAtY;*waE}O$&$S=c4L^aCn1U~8AB2ygiws^Yqm)eMzUA7 zWX+Oo%J!e>{k?DM{lDEgXU@zypJ(R#dG2$cdoR?GiW*2kPft&AsUp~n;!rS>KHK}k zJdtv;K{ zz5;icf=cKDA$h3^4kE}WNj_s$Lz0^39%ls=H4X@c)KmHe8tw|Ls5157;=ha_C3!>$ zNMXY$g5IPb+FbVqto%H||6wv?43Onh9leZ((*BgP9bM*Fw z$p(6P#+%qeTIGSE+ZHmR<8|=cY0>d|%n)=MMIX_;zUyg;Z9g~d!6&#QRSMnsa_#KK zhti%e7cFV%59c(ridMig2JMe7cT@R0v6Mxu9``|CNRjP&jy?+Ia$iwkY*L> z1Mr-P$K=_#xXxze^IA7ib$Wj0dXqdhs{chpQG75itc(M}(Q!qlr7S`%+PV#zm@wyQ zx!Py4Vl==96x5~Rz?7%yQQKFr#8s#qN4-rkITG*=apmm&+#jxZNze2X?(d8jb|i>i z-y0S{%u((Zc*>b_q$oz<8cS@m0HNtab3=Y2WRd1gvMN|dr`4&>*d+ZGO6QXcacE}{ zm*|mPy+lK-q3DVy^o-*q!o)O6(xkR;PO3dclrM-m7M+9PpIQIN^`-%`O04*Zs-VPr zYY!3?vpYY#KWTYf>^UJc`tm_?$sGMy$=Cg9&YH!G{Nah22(FEJ;obG)>)z!2NULPl zT&JX<82f8}*be9CIs!pXPi9g#yj7m<_DX2%>nQ_4dLwqJ{^JzQi|O($}-2HJSts`FX~K$ zY_{OoG5xbsQ@0PIQbBgx>tj<6REgyPt1)`&o;iCoF;-j{) zB5yzCo3SP3!}So-n-r%rt6G#9IwgZcsA8=or&?!rLsw4A+1u|V*b?Zx;%~`-!?Q0} zy~}4uNLKfYKf4ER>M%C3VR-Hzju+shuF!+ygP^Cj6vPIzW0FUI*qO+Lwq=@1j0k1r z%}4A&Xn|BG#>@b_C2ww}sN8vp^Ro4b5-9FD$IqWM5e`2Zt&eHa=2(52{F?H-tmaO^ z4KL=DZ^M&gEybJN?YpI#S~+Pmvpm?$f+%ng-6VGanU3#r^38bCWjX&!2iY5b(s7uG zJdCw>zdZ2P_;;?PJmLGU$ru)+>+^O}Y^;x@Dt9S{d^jSHK5{Iu)ZX1bIy>qG;7Yxt zdj7$R&D8YbqY&{(DmrOY3Oevu(k+WiE;&*r-~6`S{4W3&T~jU>N@LNY)t{X=7wW(Y z-R^j49--_PF~(qKGY88~uF5mwyspJa6-O)av%NIadpg0a`r!Eta4Rk#-J?zV{R}_Q zuO>=l$!0M08G`-7ah(bD*|$Tft;6B|MC;v1bfhiP_&rG1S?dh^l(J5zY)MWI|7jl- zs0Di_I1MS{qV*{Gej=x3z?&G?Ea48tFKM9h#?*)g)!^2#)c30~<_&RMd?+v4bcb2f zV#yn&@hfR9nsq!THICLqjA_FSOLIubR#-#wI;Z`1(ZzUQ z-&_?Ml6iE`eRPO+6t6=iyaLyMoz>SKgeh`dI> z5?K9Bj1uO0Fm25YV#291zr-B8x+v29blThQ;A!PfV7rTdh1qoL7p%qx!~}oUXU?)w*4l1_U|F!LN;BllMdQ<;ZGl#6Dfy2zq_hh<)U6OGqjSy z!##@9Qi+j^-?LfS{ji>~yz5|lcY-~B#sIWLbbSrL#2D1tfR^@`gAuO)i(2_YnD)C~ zgctWZ3)TmQ>*BQppiLFiX6?SS8>&oRXh+H|eK!oS3v;r$Z$PFkMWIkxO53CGhHz^i zB9NcES=^&bFlz}Y73hF5d#G+;%3dTh&0wA@OlY@Py+?ft2>>zoTpW}RHl&?7E;1H1Cwm9=JB9^#qI=8iaqS@!xFMnzM* z&T9$LE^#{F^D07g=m%}BSlAz(EQnCk7ihPCNwYqDk<-vpYF4(U_2PXTb(`f}R;ZTf zJZqwmh;ote&IVIPg}!I?X-eM_2T8i^^#?N8Z`vB`gopw?wb{t|w-V7tZIe|@!8>0x zrLje3ms)wn-B08VM$=BbKrJ}uA~C(~87Iqvv{mF#9?|N`rkwgC@Z3&mX4+I*)H2v2 z+id|aHg*A)a~m*L&0mwVu~^3+zv^AlNbj|bGyInO6@r20!)V18?m#Yx?^%DX)m|-j z?#SXU#nauJ4;Y!vY_IfKQ$HJadEU9SnDuV(AlQ{If#|9qI6wrrU4A^|I2WVyg+Q5(hYp}Y*_!AhvTH8$LvBi0%sQ7evtA@j z$!(mU@82Qh!)yK2na!@*%gJ>-EsyzeV|?~qwy7iGCdhxdis&)5eJQlw#~Zw1X-VK6 zJ$GiMVE5>mmti_VdjexBa#-EX^f$mEb@zd}u>J)$hPsdqx5iJSIZDtKXxO~j<8vdO zKbtqf0}D(i$vU6mZ?fk2x2&DL;V9GV4xT@!tLNxmQu}6lwGt958KAE{s%QpObQ^V> zk{_l@n|P-ma|ItBS8eaAoepB%X*;#%61G3m>A5!fO!eBe=|Xg}k1!2-%UZYIpsCaz zlxTvW9*x+%ouVPqh2Mpzz8SsZl++c|W!ib-Rh4P1UnEQ5lJ%2k1Lsxme&_d>`i}F8 z9D1GcG1II+ClJcQ)LCvsjEOJx$Xy#*Y}Acy1Zy##hBU{!S@!gzL{g|_bxY%QxeYH( zb*n#?)mCBSg{aZtQe?hOgtDR9ajjQ7om>|=6HoN-6q;sAt2!n=NGCk>)A7iG%dSyT zQwiLY0B=fK=ojaX1*+@&uG>M>U^N5BqrW(%9ngPsSLUs|!y(O-zns?m3T7^Metg`3 z*#}ZmyIs~3w)yJO&Jm_|-G)8i1EcT7l~`7d18TCJ^vJ|VsgnBmORB%|{-4H$grQJw zaF?Hk0=s_>-X_mPYTm5`yDk=jrl@~aj$y$Qx@%5nxrJgP^g8??_W7WRYytdAQX*uh ztvUeDb)^buW%eLNN}hMYtEOUMaburY6#JGf7y-nYF*^Ir9os@CCs~@--e(q8K^B9) z47+A_-#z9FW{<>$qv=vz>gFDr9OvYFYYA%XuP+K7Jmv|_e6}n64gW#!5-nAinqS@q z^M-2kXCn2i=oMfmmrb!3P?;qC=nCm6je?9sU0b}_zT@WL% zBozxjclHkJBktY#g5e(tQyzNeOL==Oj4H}4J;54O^pr|rNjfVZ66HJHwqMF??3+E@>7ZRCyh_c*k_LJfZh9$qKN`u`Q&q`&&F74J{@;Q^Vv zbNq@blFj`G{?7*TC;IR_Ngkqqg(&skN9sR+>F~Vp`!y6uZ2i|X`X~IbFO!FgUy(ug z5B%4N@hAAOxsa{> xml-str (str) (.getBytes) (new java.io.ByteArrayInputStream) (model/->exec))) -(defmacro ^:private throw-ex-info? [expr] - `(is (~'thrown? clojure.lang.ExceptionInfo (test-prepare ~expr)))) - - (defmacro ^:private throw-ex-parsing? [expr] `(is (~'thrown? ParsingException (test-prepare ~expr)))) @@ -54,12 +51,67 @@ (deftest test-not-closed (testing "Expressions are not closed properly" - (throw-ex-info? "{%=") - (throw-ex-info? "{%=x") - (throw-ex-info? "{%=x%") - (throw-ex-info? "{%=x}")) + (throw-ex-parsing? "{%=") + (throw-ex-parsing? "{%=x") + (throw-ex-parsing? "{%=x%") + (throw-ex-parsing? "{%=x}")) (testing "Middle expr is not closed" (throw-ex-parsing? "{%=1%}{%=3{%=4%}"))) + (deftest test-unexpected-cmd (throw-ex-parsing? "{% echo 3 %}")) + +;; integration tests + +(deftest test-parsing-errors + (testing "Closing tag is missing" + (test-fails "test-resources/failures/test-syntax-nonclosed.docx" nil + ParsingException "Missing {%end%} tag from document!")) + (testing "Extra closing tag is present" + (test-fails "test-resources/failures/test-syntax-closed.docx" nil + ParsingException "Too many {%end%} tags!")) + (testing "A tag not closed until the end of document" + (test-fails "test-resources/failures/test-syntax-incomplete.docx" nil + ParsingException "Stencil tag is not closed. Reading {% if x + y")) + (testing "Unexpected {%else%} tag" + (test-fails "test-resources/failures/test-syntax-unexpected-else.docx" nil + ParsingException "Unexpected {%else%} tag, it must come right after a condition!")) + (testing "Unexpected {%else if%} tag" + (test-fails "test-resources/failures/test-syntax-unexpected-elif.docx" nil + ParsingException "Unexpected {%else if%} tag, it must come right after a condition!")) + (testing "Cannot parse infix expression" + (test-fails "test-resources/failures/test-syntax-fails.docx" nil + ParsingException "Invalid stencil expression!")) + (testing "Test unexpected command" + (test-fails "test-resources/failures/test-syntax-unexpected-command.docx" nil + ParsingException "Unexpected command: unexpected"))) + +(deftest test-evaluation-errors + (testing "Division by zero" + (test-fails "test-resources/failures/test-eval-division.docx" {:x 1 :y 0} + EvalException "Error evaluating expression: {%=x/y%}" + java.lang.ArithmeticException "Divide by zero")) + (testing "NPE" + (test-fails "test-resources/failures/test-eval-division.docx" {:x nil :y nil} + EvalException "Error evaluating expression: {%=x/y%}" + java.lang.NullPointerException nil #_"Cannot invoke \"Object.getClass()\" because \"x\" is null")) + (testing "function does not exist" + (test-fails "test-resources/failures/test-no-such-fn.docx" {} + EvalException "Error evaluating expression: {%=nofun()%}" + java.lang.IllegalArgumentException "Did not find function for name nofun")) + (testing "function invoked with wrong arity" + (test-fails "test-resources/failures/test-syntax-arity.docx" {} + EvalException "Error evaluating expression: {%=decimal(1,2,3)%}" + clojure.lang.ExceptionInfo "Function 'decimal' was called with a wrong number of arguments (3)")) + (testing "Missing fragment" + (test-fails "test-resources/multipart/main.docx" {} + EvalException "No fragment for name: body")) + (testing "date() function has custom message" + (test-fails "test-resources/test-function-date.docx" {"date" "2022-01-04XXX11:22:33"} + EvalException "Error evaluating expression: {%=date(\"yyyy-MM-dd\", date)%}" + IllegalArgumentException "Could not parse date object 2022-01-04XXX11:22:33")) + + (testing "function invocation error" + ;; TODO: invoke fn with wrong types + )) \ No newline at end of file diff --git a/test/stencil/integration.clj b/test/stencil/integration.clj index 115b30b4..6703dd38 100644 --- a/test/stencil/integration.clj +++ b/test/stencil/integration.clj @@ -1,6 +1,7 @@ (ns stencil.integration "Integration test helpers" (:require [clojure.zip :as zip] + [clojure.test :refer [do-report is]] [stencil.api :as api])) @@ -23,3 +24,25 @@ :when (= stencil.ooxml/t (:tag (zip/node node))) c (:content (zip/node node))] c))))) + + +(defn test-fails + "Tests that rendering the template with the payload results in the given exception chain." + [template payload & bodies] + (assert (string? template)) + (assert (not-empty bodies)) + (try (with-open [template (api/prepare template)] + (api/render! template payload + :overwrite? true + :output (java.io.File/createTempFile "stencil" ".docx"))) + (do-report {:type :error + :message "Should have thrown exception" + :expected (first bodies) + :actual nil}) + (catch RuntimeException e + (let [e (reduce (fn [e [t reason]] + (is (instance? t e)) + (or (= t NullPointerException) (is (= reason (.getMessage e)))) + (.getCause e)) + e (partition 2 bodies))] + (is (= nil e) "Cause must be null."))))) \ No newline at end of file diff --git a/test/stencil/merger_test.clj b/test/stencil/merger_test.clj index ba5c6479..7cca5d18 100644 --- a/test/stencil/merger_test.clj +++ b/test/stencil/merger_test.clj @@ -106,29 +106,29 @@ [{:text "{%=1%}"}] [{:text "{%=1%}"}] - [{:action {:cmd :echo, :expression [1]}}] + [{:action {:cmd :echo, :expression [1] :raw "{%=1%}"}}] [{:text "abc{%=1%}b"}] [{:text "abc"} {:text "{%=1%}"} {:text "b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1]}} {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} {:text "b"}] [{:text "abc{%="} O1 O2 {:text "1"} O3 O4 {:text "%}b"}] [{:text "abc"} {:text "{%="} O1 O2 {:text "1"} O3 O4 {:text "%}b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1]}} O1 O2 O3 O4 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] [{:text "abc{%="} O1 O2 {:text "1%"} O3 O4 {:text "}b"}] [{:text "abc"} {:text "{%="} O1 O2 {:text "1%"} O3 O4 {:text "}b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1]}} O1 O2 O3 O4 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] [{:text "abcd{%="} O1 {:text "1"} O2 {:text "%"} O3 {:text "}"} O4 {:text "b"}] [{:text "abcd"} {:text "{%="} O1 {:text "1"} O2 {:text "%"} O3 {:text "}"} O4 {:text "b"}] - [{:text "abcd"} {:action {:cmd :echo, :expression [1]}} O1 O2 O3 O4{:text "b"}] + [{:text "abcd"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4{:text "b"}] [{:text "abc{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] [{:text "abc"} {:text "{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [12]}} O1 O2 O3 O4 O5 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression [12] :raw "{%=12%}"}} O1 O2 O3 O4 O5 {:text "b"}] [O1 {:text "{%if p"} O2 O3 {:text "%}one{%end%}"} O4] [O1 {:text "{%if p"} O2 O3 {:text "%}one"} {:text "{%end%}"} O4] - [O1 {:action {:cmd :if, :condition '[p]}} O2 O3 {:text "one"} {:action {:cmd :end}} O4] + [O1 {:action {:cmd :if, :condition '[p] :raw "{%if p%}"}} O2 O3 {:text "one"} {:action {:cmd :end :raw "{%end%}"}} O4] )))) diff --git a/test/stencil/process_test.clj b/test/stencil/process_test.clj index e1b6435b..1c738414 100644 --- a/test/stencil/process_test.clj +++ b/test/stencil/process_test.clj @@ -28,7 +28,7 @@ (is (= {:dynamic? true, :executable [{:open :a} {:open :b} - {:stencil.cleanup/blocks [], :cmd :cmd/include, :name "elefant"} + {:stencil.cleanup/blocks [], :cmd :cmd/include, :name "elefant" :raw "{%include \"elefant\"%}"} {:close :b} {:close :a}], :fragments #{"elefant"} diff --git a/test/stencil/tokenizer_test.clj b/test/stencil/tokenizer_test.clj index a71a6bab..1df3cfa8 100644 --- a/test/stencil/tokenizer_test.clj +++ b/test/stencil/tokenizer_test.clj @@ -3,7 +3,9 @@ [clojure.test :refer [deftest testing is]])) (defn- run [s] - (m/parse-to-tokens-seq (java.io.ByteArrayInputStream. (.getBytes (str s))))) + (->> (java.io.ByteArrayInputStream. (.getBytes (str s))) + (m/parse-to-tokens-seq) + (map #(dissoc % :raw)))) (deftest read-tokens-nested (testing "Read a list of nested tokens" From f6fc4012f881a5fd004e07cb724eb280d8c54fa6 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Fri, 2 Dec 2022 14:26:55 +0100 Subject: [PATCH 56/61] feat: version 0.5.0 (#143) --- README.md | 8 ++++---- project.clj | 4 ++-- service/project.clj | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 72e68fe5..8ed88240 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ The project has a simple [service implementation](https://github.com/erdos/stenc ## Version -**Latest stable** version is `0.4.7` +**Latest stable** version is `0.5.0` -**Latest snapshot** version is `0.4.8-SNAPSHOT` +**Latest snapshot** version is `0.5.1-SNAPSHOT` If you are using Maven, add the followings to your `pom.xml`: @@ -57,7 +57,7 @@ The dependency: io.github.erdos stencil-core - 0.4.7 + 0.5.0 ``` @@ -72,7 +72,7 @@ And the [Clojars](https://clojars.org) repository: Alternatively, if you are using Leiningen, add the following to the `:dependencies` section of your `project.clj` -file: `[io.github.erdos/stencil-core "0.4.7"]` +file: `[io.github.erdos/stencil-core "0.5.0"]` Previous versions are available on the [Stencil Clojars](https://clojars.org/io.github.erdos/stencil-core) page. diff --git a/project.clj b/project.clj index 067f0ea2..8f53dd84 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.4.8-SNAPSHOT" +(defproject io.github.erdos/stencil-core "0.5.0" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" @@ -46,4 +46,4 @@ (eval '(sta/instrument))]} :ci {:plugins [[lein-javadoc "0.3.0"] [lein-cloverage "1.2.2"]] - }}) \ No newline at end of file + }}) diff --git a/service/project.clj b/service/project.clj index c030937c..c0e5954d 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,10 +1,10 @@ -(defproject io.github.erdos/stencil-service "0.4.8-SNAPSHOT" +(defproject io.github.erdos/stencil-service "0.5.0" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.4.8-SNAPSHOT"] + [io.github.erdos/stencil-core "0.5.0"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] From 1ff454f702af80c6939452c981a95e30369ca956 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 8 Dec 2022 22:22:26 +0100 Subject: [PATCH 57/61] chore: start work on v0.5.1 --- project.clj | 2 +- service/project.clj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project.clj b/project.clj index 8f53dd84..addf9493 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject io.github.erdos/stencil-core "0.5.0" +(defproject io.github.erdos/stencil-core "0.5.1-SNAPSHOT" :url "https://github.com/erdos/stencil" :description "Templating engine for office documents." :license {:name "Eclipse Public License - v 2.0" diff --git a/service/project.clj b/service/project.clj index c0e5954d..16b55359 100644 --- a/service/project.clj +++ b/service/project.clj @@ -1,13 +1,13 @@ -(defproject io.github.erdos/stencil-service "0.5.0" +(defproject io.github.erdos/stencil-service "0.5.1-SNAPSHOT" :description "Web service for the Stencil templating engine" :url "https://github.com/erdos/stencil" :license {:name "Eclipse Public License - v 2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [io.github.erdos/stencil-core "0.5.0"] + [io.github.erdos/stencil-core "0.5.1-SNAPSHOT"] [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.5.0"]] + [ring/ring-json "0.5.1-SNAPSHOT"]] :aot :all :main stencil.service) From 80628667f0e5fece740a6582c57397a0c2813e48 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Thu, 8 Dec 2022 22:28:58 +0100 Subject: [PATCH 58/61] feat: refactor infix tokenizer (#142) --- src/stencil/infix.clj | 109 ++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index 268b0038..3aac8bc7 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -28,7 +28,9 @@ \< :lt \> :gt \& :and - \| :or}) + \| :or + \, :comma \; :comma + }) (def ops2 {[\> \=] :gte [\< \=] :lte @@ -88,67 +90,62 @@ - First elem is read string literal. - Second elem is seq of remaining characters." [characters] - (let [until (quotation-marks (first characters)) - sb (new StringBuilder)] - (loop [[c & cs] (next characters)] - (cond (nil? c) (throw (ex-info "String parse error" - {:reason "Unexpected end of stream"})) - (= c until) [(.toString sb) cs] - (= c (first "\\")) (do (.append sb (first cs)) (recur (next cs))) - :else (do (.append sb c) (recur cs)))))) + (when-let [until (quotation-marks (first characters))] + (let [sb (new StringBuilder)] + (loop [[c & cs] (next characters)] + (cond (nil? c) (throw (ex-info "String parse error" + {:reason "Unexpected end of stream"})) + (= c until) [(.toString sb) cs] + (= c (first "\\")) (do (.append sb (first cs)) (recur (next cs))) + :else (do (.append sb c) (recur cs))))))) (defn read-number "Reads a number literal from a sequence. Returns a tuple of read number (Double or Long) and the sequence of remaining characters." [characters] - (let [content (string (take-while (set "1234567890._")) characters) - content (.replaceAll content "_" "") - number (if (some #{\.} content) - (Double/parseDouble content) - (Long/parseLong content))] - [number (drop (count content) characters)])) - -(defn tokenize + (when (contains? digits (first characters)) + (let [content (string (take-while (set "1234567890._")) characters) + content (.replaceAll content "_" "") + number (if (some #{\.} content) + (Double/parseDouble content) + (Long/parseLong content))] + [number (drop (count content) characters)]))) + +(defn- read-ops2 [chars] + (when-let [op (get ops2 [(first chars) (second chars)] )] + [op (nnext chars)])) + +(defn- read-ops1 [chars] + (when-let [op (get ops (first chars))] + [op (next chars)])) + +(defn- read-iden [characters] + (when-let [content (not-empty (string (take-while identifier) characters))] + (let [tail (drop-while #{\space \tab} (drop (count content) characters))] + (if (= \( (first tail)) + [(->FnCall content) (next tail)] + [(symbol content) tail])))) + +(def token-readers + (some-fn read-number + read-string-literal + read-iden + read-ops2 + read-ops1)) + +(defn- tokenize' "Returns a sequence of tokens for an input string" - [original-string] - (loop [[first-char & next-chars :as characters] (str original-string) - tokens []] - (cond - (empty? characters) - tokens - - (whitespace? first-char) - (recur next-chars tokens) - - (contains? #{\, \;} first-char) - (recur next-chars (conj tokens :comma)) - - (contains? ops2 [first-char (first next-chars)]) - (recur (next next-chars) (conj tokens (ops2 [first-char (first next-chars)]))) - - (and (= \- first-char) (or (nil? (peek tokens)) (and (not= (peek tokens) :close) (not= (peek tokens) :close-bracket) (keyword? (peek tokens))))) - (recur next-chars (conj tokens :neg)) - - (contains? ops first-char) - (recur next-chars (conj tokens (ops first-char))) - - (contains? digits first-char) - (let [[n tail] (read-number characters)] - (recur tail (conj tokens n))) - - (quotation-marks first-char) - (let [[s tail] (read-string-literal characters)] - (recur tail (conj tokens s))) - - :else - (let [content (string (take-while identifier) characters)] - (if (seq content) - (let [tail (drop-while #{\space \tab} (drop (count content) characters))] - (if (= \( (first tail)) - (recur (next tail) (conj tokens (->FnCall content))) - (recur tail (conj tokens (symbol content))))) - (throw (ex-info (str "Unexpected character: " first-char) - {:character first-char}))))))) + [text] + (when-let [text (seq (drop-while (comp whitespace? char) text))] + (if-let [[token tail] (token-readers text)] + (cons token (lazy-seq (tokenize' tail))) + (throw (ex-info "Unexpected endof string" {:text text}))))) + +(defn tokenize [text] + ;; replace :minus by :neg where it is negation instead of subtraction based on context + (->> (tokenize' text) + (reductions (fn [previous current] (if (and (= :minus current) (not= previous :close) (not= previous :close-bracket) (keyword? previous)) :neg current)) ::SENTINEL) + (next))) ;; throws ExceptionInfo when token sequence has invalid elems (defn- validate-tokens [tokens] From a708b60c8182761e9cac69bd9c8e1aee591b59e8 Mon Sep 17 00:00:00 2001 From: erdos Date: Thu, 8 Dec 2022 22:45:52 +0100 Subject: [PATCH 59/61] fix: service ring-json version --- service/project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/project.clj b/service/project.clj index 16b55359..7f31cdbc 100644 --- a/service/project.clj +++ b/service/project.clj @@ -8,6 +8,6 @@ [org.slf4j/slf4j-api "2.0.0-alpha7"] [org.mozilla/rhino-engine "1.7.14"] [http-kit "2.5.0"] - [ring/ring-json "0.5.1-SNAPSHOT"]] + [ring/ring-json "0.5.0"]] :aot :all :main stencil.service) From 83a8dd28648c520ee8d4694683d8ae9f65c928ca Mon Sep 17 00:00:00 2001 From: erdos Date: Fri, 9 Dec 2022 22:24:30 +0100 Subject: [PATCH 60/61] feat: tests --- test/stencil/infix_test.clj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/stencil/infix_test.clj b/test/stencil/infix_test.clj index 10ce3e18..36f26a59 100644 --- a/test/stencil/infix_test.clj +++ b/test/stencil/infix_test.clj @@ -135,9 +135,16 @@ (testing "negation" (is (= 6 (run "a[1]-2" {"a" {"1" 8}})))) + #_ + (testing "mixed lookup" + (is (= 7 (run "a['x'].y" {"a" {"x" {"y" 7}}}))) + (is (= 8 (run "a.x['y']" {"a" {"x" {"y" 8}}})))) + (testing "syntax error" (is (thrown? ExceptionInfo (run "[3]" {}))) (is (thrown? ExceptionInfo (run "a[[3]]" {}))) + #_(is (thrown? ExceptionInfo (run "a.[1]b" {}))) + #_(is (thrown? ExceptionInfo (run "a..b" {}))) (is (thrown? ExceptionInfo (run "a[1,2]" {})))) (testing "key is missing from input" From 27335c33477c40329d6637e2eb198f37268e6287 Mon Sep 17 00:00:00 2001 From: Janos Erdos Date: Sat, 10 Dec 2022 17:46:52 +0100 Subject: [PATCH 61/61] feat: refactor parser (#144) --- src/stencil/cleanup.clj | 38 +++-- src/stencil/grammar.clj | 89 ++++++++++ src/stencil/infix.clj | 282 +++++++------------------------- src/stencil/tokenizer.clj | 5 +- test/stencil/cleanup_test.clj | 40 ++--- test/stencil/eval_test.clj | 24 +-- test/stencil/infix_test.clj | 48 +++--- test/stencil/merger_test.clj | 14 +- test/stencil/tokenizer_test.clj | 26 +-- 9 files changed, 252 insertions(+), 314 deletions(-) create mode 100644 src/stencil/grammar.clj diff --git a/src/stencil/cleanup.clj b/src/stencil/cleanup.clj index 16bc8b31..78336a79 100644 --- a/src/stencil/cleanup.clj +++ b/src/stencil/cleanup.clj @@ -56,7 +56,7 @@ (defn- nested-tokens-fmap-postwalk "Depth-first traversal of the tree." [f-cmd-block-before f-cmd-block-after f-child node] - (assert (map? node)) + (assert (map? node)) (letfn [(children-mapper [children] (mapv update-blocks children)) (update-children [node] @@ -164,23 +164,25 @@ ;; amikor van benne blocks ;; mapping: {Sym -> Str} (letfn [(resolve-sym [mapping s] - (assert (map? mapping)) - (assert (symbol? s)) - ;; megprobal egy adott szimbolumot a mapping alapjan rezolvalni. - ;; visszaad egy stringet - (if (.contains (name s) ".") - (let [[p1 p2] (vec (.split (name s) "\\." 2))] - (if-let [pt (mapping (symbol p1))] - (str pt "." p2) - (name s))) - (mapping s (name s)))) - (expr [mapping rpn] - (assert (sequential? rpn)) ;; RPN kifejezes kell legyen - (keep (partial resolve-sym mapping) (filter symbol? rpn))) - ;; iff rpn expr consists of 1 variable only -> resolves that one variable. - (maybe-variable [mapping rpn] - (when (and (= 1 (count rpn)) (symbol? (first rpn))) - (resolve-sym mapping (first rpn)))) + (assert (map? mapping)) + (assert (symbol? s)) + (mapping s (name s))) + (expr [mapping e] + (cond (symbol? e) [(resolve-sym mapping e)] + (not (sequential? e)) nil + (= :fncall (first e)) (mapcat (partial expr mapping) (nnext e)) + (= :get (first e)) (let [[ss rest] (split-with string? (nnext e))] + (cons + (reduce (fn [root item] (str root "." item)) + (resolve-sym mapping (second e)) + ss) + (mapcat (partial expr mapping) rest))) + :else (mapcat (partial expr mapping) (next e)))) + (maybe-variable [mapping e] + (cond (symbol? e) + (resolve-sym mapping e) + (and (sequential? e) (= :get (first e)) (symbol? (second e)) (every? string? (nnext e))) + (reduce (fn [a b] (str a "." b)) (resolve-sym mapping (second e)) (nnext e)))) (collect [m xs] (mapcat (partial collect-1 m) xs)) (collect-1 [mapping x] (case (:cmd x) diff --git a/src/stencil/grammar.clj b/src/stencil/grammar.clj new file mode 100644 index 00000000..cf8be23b --- /dev/null +++ b/src/stencil/grammar.clj @@ -0,0 +1,89 @@ +(ns stencil.grammar) + +(defn- guarded [pred] + (fn [t] + (when (pred (first t)) + [(first t) (next t)]))) + +;; left-associative chained infix expression +(defn- chained [reader reader* reducer] + (fn [tokens] chained + (when-let [[result tokens] (reader tokens)] + (loop [tokens tokens + result result] + (if (empty? tokens) + [result nil] + (if-let [[fs tokens] (reader* tokens)] + (recur tokens (reducer result fs)) + [result tokens])))))) + +(defn- read-or-throw [reader tokens] + (or (reader tokens) + (throw (ex-info (str "Invalid stencil expression!") {:reader reader :prefix tokens})))) + +(defn- all [condition & readers] + (fn [tokens] + (when-let [[result tokens] (condition tokens)] + (reduce (fn [[result tokens] reader] + (let [[r tokens] (read-or-throw reader tokens)] + [(conj result r) tokens])) + [[result] tokens] readers)))) + +(defmacro ^:private grammar [bindings body] + `(letfn* [~@(for [[k v] (partition 2 bindings), x [k (list 'fn '[%] (list v '%))]] x)] ~body)) + +(defn- mapping [reader mapper] + (fn [tokens] + (when-let [[result tokens] (reader tokens)] + [(mapper result) tokens]))) + +(defn- parenthesed [reader] + (mapping (all (guarded #{:open}) reader (guarded #{:close})) second)) + +(defn- op-chain [operand operator] + (chained operand (all operator operand) (fn [a [op b]] (list op a b)))) + +(defn- op-chain-r [operand operator] + (mapping (chained (all operand) (all operator operand) (fn [a [op b]] (list* b op a))) + (fn [a] (reduce (fn [a [op c]] [op c a]) (first a) (partition 2 (next a)))))) + +(defn- at-least-one [reader] + (fn [tokens] + (when-let [[result tokens] (reader tokens)] + (loop [tokens tokens, result [result]] + (if-let [[res tokens] (reader tokens)] + (recur tokens (conj result res)) + [result tokens]))))) + +(defn- optional [reader] ;; always matches + (fn [t] (or (reader t) [nil t]))) + +(def testlang + (grammar [val (some-fn iden-or-fncall + (parenthesed expression) + (guarded number?) + (guarded string?)) + iden (guarded symbol?) + dotted (mapping (all (guarded #{:dot}) iden) (comp name second)) + bracketed (mapping (all (guarded #{:open-bracket}) expression (guarded #{:close-bracket})) second) + args (mapping (optional (chained (all expression) (all (guarded #{:comma}) expression) into)) + (fn [x] (take-nth 2 x))) + args-suffix (parenthesed args) + iden-or-fncall (mapping (all iden (optional args-suffix)) + (fn [[id xs]] (if xs (list* :fncall id xs) id))) + accesses (mapping (all val (optional (at-least-one (some-fn bracketed dotted)))) + (fn [[id chain]] (if chain (list* :get id chain) id))) + neg (some-fn (all (guarded #{:minus}) neg) accesses) + not (some-fn (all (guarded #{:not}) not) neg) + pow (op-chain-r not (guarded #{:power})) + mul (op-chain pow (guarded #{:times :divide :mod})) + add (op-chain mul (guarded #{:plus :minus})) + cmp (op-chain add (guarded #{:lt :gt :lte :gte})) + cmpe (op-chain cmp (guarded #{:eq :neq})) + and (op-chain cmpe (guarded #{:and})) + or (op-chain and (guarded #{:or})) + expression or] + expression)) + +(defn runlang [grammar input] + (ffirst (read-or-throw (all grammar {nil []}) input))) \ No newline at end of file diff --git a/src/stencil/infix.clj b/src/stencil/infix.clj index 3aac8bc7..5767ab6a 100644 --- a/src/stencil/infix.clj +++ b/src/stencil/infix.clj @@ -2,16 +2,14 @@ "Parsing and evaluating infix algebraic expressions. https://en.wikipedia.org/wiki/Shunting-yard_algorithm" - (:require [stencil.util :refer [fail update-peek ->int string whitespace?]] - [stencil.log :as log] - [stencil.functions :refer [call-fn]])) + (:require [stencil.util :refer [->int string whitespace?]] + [stencil.functions :refer [call-fn]] + [stencil.grammar :as grammar])) (set! *warn-on-reflection* true) (def ^:dynamic ^:private *calc-vars* {}) -(defrecord FnCall [fn-name]) - (def ops {\+ :plus \- :minus @@ -30,6 +28,7 @@ \& :and \| :or \, :comma \; :comma + \. :dot }) (def ops2 {[\> \=] :gte @@ -45,36 +44,7 @@ (def identifier "Characters found in an identifier" - (set "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_.1234567890")) - -(def operation-tokens - "Operator precedences. - - source: http://en.cppreference.com/w/cpp/language/operator_precedence - " - {:open -999 - ;;;:close -998 - :comma -998 - :open-bracket -999 - - :or -21 - :and -20 - - :eq -10 :neq -10, - - :lt -9 :gt -9 :lte -9 :gte -9 - - :plus 2 :minus 2 - :times 3 :divide 4 - :power 5 - :not 6 - :neg 7}) - -(defn- precedence [token] - (get operation-tokens token)) - -(defn- associativity [token] - (if (#{:power :not :neg} token) :right :left)) + (set "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_1234567890")) (def ^:private quotation-marks {\" \" ;; programmer quotes @@ -112,7 +82,7 @@ [number (drop (count content) characters)]))) (defn- read-ops2 [chars] - (when-let [op (get ops2 [(first chars) (second chars)] )] + (when-let [op (get ops2 [(first chars) (second chars)])] [op (nnext chars)])) (defn- read-ops1 [chars] @@ -121,141 +91,56 @@ (defn- read-iden [characters] (when-let [content (not-empty (string (take-while identifier) characters))] - (let [tail (drop-while #{\space \tab} (drop (count content) characters))] - (if (= \( (first tail)) - [(->FnCall content) (next tail)] - [(symbol content) tail])))) + [(symbol content) (drop (count content) characters)])) -(def token-readers - (some-fn read-number - read-string-literal - read-iden - read-ops2 - read-ops1)) +(def token-readers (some-fn read-number read-string-literal read-iden read-ops2 read-ops1)) -(defn- tokenize' +(defn tokenize "Returns a sequence of tokens for an input string" [text] (when-let [text (seq (drop-while (comp whitespace? char) text))] (if-let [[token tail] (token-readers text)] - (cons token (lazy-seq (tokenize' tail))) - (throw (ex-info "Unexpected endof string" {:text text}))))) - -(defn tokenize [text] - ;; replace :minus by :neg where it is negation instead of subtraction based on context - (->> (tokenize' text) - (reductions (fn [previous current] (if (and (= :minus current) (not= previous :close) (not= previous :close-bracket) (keyword? previous)) :neg current)) ::SENTINEL) - (next))) - -;; throws ExceptionInfo when token sequence has invalid elems -(defn- validate-tokens [tokens] - (cond - (some true? (map #(and (or (symbol? %1) (number? %1) (= :close %1)) - (or (symbol? %2) (number? %2) (= :open %2))) - tokens (next tokens))) - (throw (ex-info "Invalid stencil expression!" {})) - - :else - tokens)) - -(defn tokens->rpn - "Classic Shunting-Yard Algorithm extension to handle vararg fn calls." - [tokens] - (loop [[e0 & next-expr :as expr] tokens ;; bemeneti token lista - opstack () ;; stack of Shunting-Yard Algorithm - result [] ;; Vector of output tokens - - - parentheses 0 ;; count of open parentheses - ;; on a function call we save function name here - functions ()] - (cond - (neg? parentheses) - (throw (ex-info "Parentheses are not balanced!" {})) - - (empty? expr) - (if (zero? parentheses) - (into result (remove #{:open}) opstack) - (throw (ex-info "Too many open parentheses!" {}))) - - (number? e0) - (recur next-expr opstack (conj result e0) parentheses functions) - - (symbol? e0) - (recur next-expr opstack (conj result e0) parentheses functions) - - (string? e0) - (recur next-expr opstack (conj result e0) parentheses functions) - - (= :open e0) - (recur next-expr (conj opstack :open) result (inc parentheses) (conj functions nil)) - - (= :open-bracket e0) - (recur next-expr (conj opstack :open-bracket) result (inc parentheses) functions) - - (instance? FnCall e0) - (recur next-expr (conj opstack :open) result - (inc parentheses) - (conj functions {:fn (:fn-name e0) - :args (if (= :close (first next-expr)) 0 1)})) - ;; (recur next-expr (conj opstack :fncall) result (conj functions {:fn e0})) - - (= :close-bracket e0) - (let [[popped-ops [_ & keep-ops]] - (split-with (partial not= :open-bracket) opstack)] - (recur next-expr - keep-ops - (into result (concat popped-ops [:get])) - (dec parentheses) - functions)) - - (= :close e0) - (let [[popped-ops [_ & keep-ops]] - (split-with (partial not= :open) opstack)] - (recur next-expr - keep-ops - (into result - (concat - (remove #{:comma} popped-ops) - (some-> functions first vector))) - (dec parentheses) - (next functions))) - - (empty? next-expr) ;; current is operator but without an operand - (throw (ex-info "Missing operand!" {})) - - :else ;; operator - (let [[popped-ops keep-ops] - (split-with #(if (= :left (associativity e0)) - (<= (precedence e0) (precedence %)) - (< (precedence e0) (precedence %))) opstack)] - (recur next-expr - (conj keep-ops e0) - (into result (remove #{:open :comma}) popped-ops) - parentheses - (if (= :comma e0) - (if (first functions) - (update-peek functions update :args inc) - (throw (ex-info "Unexpected ',' character!" {}))) - functions)))))) - -(defn- reduce-step-dispatch [_ cmd] - (cond (string? cmd) :string - (number? cmd) :number - (symbol? cmd) :symbol - (keyword? cmd) cmd - (map? cmd) FnCall - :else (fail "Unexpected opcode!" {:opcode cmd}))) - -(defmulti ^:private reduce-step reduce-step-dispatch) -(defmulti ^:private action-arity (partial reduce-step-dispatch [])) - -;; throws exception if there are operators out of place, returns input otherwise -(defn- validate-rpn [rpn] - (let [steps (map #(- 1 (action-arity %)) rpn)] - (if (or (not-every? pos? (reductions + steps)) (not (= 1 (reduce + steps)))) - (throw (ex-info (str "Wrong tokens, unsupported arities: " rpn) {:rpn rpn})) - rpn))) + (cons token (lazy-seq (tokenize tail))) + (throw (ex-info "Unexpected end of string" {:index (.index ^clojure.lang.IndexedSeq text)}))))) + +(defmulti eval-tree (fn [tree] (if (sequential? tree) (first tree) (type tree)))) + +(defmethod eval-tree java.lang.Number [tree] tree) +(defmethod eval-tree String [s] s) +(defmethod eval-tree clojure.lang.Symbol [s] (get-in *calc-vars* (vec (.split (name s) "\\.")))) + +(defmethod eval-tree :eq [[_ a b]] (= (eval-tree a) (eval-tree b))) +(defmethod eval-tree :neq [[_ a b]] (not= (eval-tree a) (eval-tree b))) +(defmethod eval-tree :plus [[_ a b]] + (let [a (eval-tree a) b (eval-tree b)] + (if (or (string? a) (string? b)) + (str a b) + (+ a b)))) +(defmethod eval-tree :minus [[_ a b :as expr]] + (if (= 2 (count expr)) + (- (eval-tree a)) + (- (eval-tree a) (eval-tree b)))) +(defmethod eval-tree :times [[_ a b]] (* (eval-tree a) (eval-tree b))) +(defmethod eval-tree :divide [[_ a b]] (with-precision 8 (/ (eval-tree a) (eval-tree b)))) + +(defmethod eval-tree :or [[_ a b]] (or (eval-tree a) (eval-tree b))) +(defmethod eval-tree :and [[_ a b]] (and (eval-tree a) (eval-tree b))) +(defmethod eval-tree :mod [[_ a b]] (mod (eval-tree a) (eval-tree b))) +(defmethod eval-tree :power [[_ a b]] (Math/pow (eval-tree a) (eval-tree b))) +(defmethod eval-tree :not [[_ a]] (not (eval-tree a))) + +(defmethod eval-tree :gte [[_ a b]] (>= (eval-tree a) (eval-tree b))) +(defmethod eval-tree :lte [[_ a b]] (<= (eval-tree a) (eval-tree b))) +(defmethod eval-tree :gt [[_ a b]] (> (eval-tree a) (eval-tree b))) +(defmethod eval-tree :lt [[_ a b]] (< (eval-tree a) (eval-tree b))) + +(defmethod eval-tree :get [[_ m & path]] + (reduce (fn [b a] + (cond (sequential? b) (when (number? a) (get b (->int a))) + (string? b) (when (number? a) (get b (->int a))) + (instance? java.util.List b) (when (number? a) (.get ^java.util.List b (->int a))) + :else (get b (str a)))) + (eval-tree m) (map eval-tree path))) (defmethod call-fn :default [fn-name & args-seq] (if-let [default-fn (::functions *calc-vars*)] @@ -266,65 +151,22 @@ ;; Example: you can write data()['key1']['key2'] instead of key1.key2. (defmethod call-fn "data" [_] *calc-vars*) -(defmethod action-arity FnCall [{:keys [args]}] args) - -(defmethod reduce-step FnCall [stack {:keys [fn args]}] - (try - (log/trace "Calling function {} with arguments {}" fn args) - (let [[ops new-stack] (split-at args stack) - ops (reverse ops) - result (apply call-fn fn ops)] - (log/trace "Result was {}" result) - (conj new-stack result)) - (catch clojure.lang.ArityException e - (throw (ex-info (format "Function '%s' was called with a wrong number of arguments (%d)" fn args) - {:fn fn :got args}))))) - -(defmacro def-reduce-step [cmd args body] - (assert (keyword? cmd)) - (assert (every? symbol? args)) - `(do (defmethod action-arity ~cmd [_#] ~(count args)) - (defmethod reduce-step ~cmd [[~@args ~'& stack#] action#] - (let [~'+action+ action#] (conj stack# ~body))))) - -(def-reduce-step :string [] +action+) -(def-reduce-step :number [] +action+) -(def-reduce-step :symbol [] (get-in *calc-vars* (vec (.split (name +action+) "\\.")))) - -(def-reduce-step :neg [s0] (- s0)) -(def-reduce-step :times [s0 s1] (* s0 s1)) -(def-reduce-step :divide [s0 s1] (with-precision 8 (/ s1 s0))) -(def-reduce-step :plus [s0 s1] (if (or (string? s0) (string? s1)) (str s1 s0) (+ s1 s0))) -(def-reduce-step :minus [s0 s1] (- s1 s0)) -(def-reduce-step :eq [a b] (= a b)) -(def-reduce-step :or [a b] (or b a)) -(def-reduce-step :not [b] (not b)) -(def-reduce-step :and [a b] (and b a)) -(def-reduce-step :neq [a b] (not= a b)) -(def-reduce-step :mod [s0 s1] (mod s1 s0)) -(def-reduce-step :lt [s0 s1] (< s1 s0)) -(def-reduce-step :lte [s0 s1] (<= s1 s0)) -(def-reduce-step :gt [s0 s1] (> s1 s0)) -(def-reduce-step :gte [s0 s1] (>= s1 s0)) -(def-reduce-step :power [s0 s1] (Math/pow s1 s0)) -(def-reduce-step :get [a b] - (cond (sequential? b) (when (number? a) (get b (->int a))) - (string? b) (when (number? a) (get b (->int a))) - (instance? java.util.List b) (when (number? a) (.get ^java.util.List b (->int a))) - :else (get b (str a)))) +(defmethod eval-tree :fncall [[_ f & args]] + (let [args (mapv eval-tree args)] + (try (apply call-fn (name f) args) + (catch clojure.lang.ArityException e + (throw (ex-info (format "Function '%s' was called with a wrong number of arguments (%d)" f (count args)) + {:fn f :args args})))))) (defn eval-rpn - ([bindings default-function tokens] + ([bindings default-function tree] (assert (ifn? default-function)) - (eval-rpn (assoc bindings ::functions default-function) tokens)) - ([bindings tokens] + (eval-rpn (assoc bindings ::functions default-function) tree)) + ([bindings tree] (assert (map? bindings)) - (assert (seq tokens)) (binding [*calc-vars* bindings] - (let [result (reduce reduce-step () tokens)] - (assert (= 1 (count result))) - (first result))))) + (eval-tree tree)))) -(def parse (comp validate-rpn tokens->rpn validate-tokens tokenize)) +(def parse (comp (partial grammar/runlang grammar/testlang) tokenize)) -:OK +:OK \ No newline at end of file diff --git a/src/stencil/tokenizer.clj b/src/stencil/tokenizer.clj index d758a8f3..f453af6f 100644 --- a/src/stencil/tokenizer.clj +++ b/src/stencil/tokenizer.clj @@ -3,7 +3,6 @@ (:require [clojure.data.xml :as xml] [clojure.string :refer [includes? split]] [stencil.infix :as infix] - [stencil.types :refer [open-tag close-tag]] [stencil.util :refer [assoc-if-val mod-stack-top-conj mod-stack-top-last parsing-exception trim]])) (set! *warn-on-reflection* true) @@ -22,7 +21,7 @@ (.startsWith text "unless ") {:cmd :if - :condition (conj (vec (infix/parse (.substring text 7))) :not)} + :condition (list :not (infix/parse (.substring text 7)))} (.startsWith text "for ") (let [[v expr] (split (subs text 4) #" in " 2) @@ -39,7 +38,7 @@ ;; fragment inclusion (.startsWith text "include ") {:cmd :cmd/include - :name (first (infix/parse (.substring text 8)))} + :name (infix/parse (.substring text 8))} ;; `else if` expression (seq (re-seq pattern-elseif text)) diff --git a/test/stencil/cleanup_test.clj b/test/stencil/cleanup_test.clj index a6d5073f..07c16f94 100644 --- a/test/stencil/cleanup_test.clj +++ b/test/stencil/cleanup_test.clj @@ -166,27 +166,27 @@ (is (= () (find-variables [{:open "a"} {:close "a"}])))) (testing "Variables from simple subsitutions" - (is (= ["a"] (find-variables [{:cmd :echo :expression '[a 1 :plus]}])))) + (is (= ["a"] (find-variables [{:cmd :echo :expression '[:plus a 1]}])))) (testing "Variables from if conditions" - (is (= ["a"] (find-variables [{:cmd :if :condition '[a 1 :eq]}])))) + (is (= ["a"] (find-variables [{:cmd :if :condition '[:eq a 1]}])))) (testing "Variables from if branches" - (is (= ["x"] (find-variables [{:cmd :if :condition [] - :stencil.cleanup/blocks [[] [{:cmd :echo :expression '[x]}]]}])))) + (is (= ["x"] (find-variables [{:cmd :if :condition '3 + :stencil.cleanup/blocks [[] [{:cmd :echo :expression 'x}]]}])))) (testing "Variables from loop expressions" (is (= ["xs" "xs[]"] - (find-variables '[{:cmd :for, :variable y, :expression [xs], - :stencil.cleanup/blocks [[{:cmd :echo, :expression [y 1 :plus]}]]}]))) + (find-variables '[{:cmd :for, :variable y, :expression xs, + :stencil.cleanup/blocks [[{:cmd :echo, :expression [:plus y 1]}]]}]))) (is (= ["xs" "xs[]" "xs[][]"] - (find-variables '[{:cmd :for, :variable y, :expression [xs] - :stencil.cleanup/blocks [[{:cmd :for :variable w :expression [y] - :stencil.cleanup/blocks [[{:cmd :echo :expression [1 w :plus]}]]}]]}]))) + (find-variables '[{:cmd :for, :variable y, :expression xs + :stencil.cleanup/blocks [[{:cmd :for :variable w :expression y + :stencil.cleanup/blocks [[{:cmd :echo :expression [:plus 1 w]}]]}]]}]))) (is (= ["xs" "xs[].z.k"] (find-variables - '[{:cmd :for :variable y :expression [xs] - :stencil.cleanup/blocks [[{:cmd :echo :expression [y.z.k 1 :plus]}]]}])))) + '[{:cmd :for :variable y :expression xs + :stencil.cleanup/blocks [[{:cmd :echo :expression [:plus [:get y "z" "k"] 1]}]]}])))) (testing "Variables from loop bindings and bodies" ;; TODO: impls this test @@ -198,16 +198,16 @@ (testing "Nested loop bindings" (is (= ["xs" "xs[].t" "xs[].t[].n"] (find-variables - '[{:cmd :for :variable a :expression [xs] - :stencil.cleanup/blocks [[{:cmd :for :variable b :expression [a.t] - :stencil.cleanup/blocks [[{:cmd :echo :expression [b.n 1 :plus]}]]}]]}]))) + '[{:cmd :for :variable a :expression xs + :stencil.cleanup/blocks [[{:cmd :for :variable b :expression [:get a "t"] + :stencil.cleanup/blocks [[{:cmd :echo :expression [:plus [:get b "n"] 1]}]]}]]}]))) )) (deftest test-process-if-then-else (is (= '[{:open :body} {:open :a} - {:cmd :if, :condition [a], + {:cmd :if, :condition a, :then [{:close :a} {:open :a} {:text "THEN"} @@ -219,7 +219,7 @@ (:executable (process '({:open :body} {:open :a} - {:cmd :if, :condition [a]} + {:cmd :if, :condition a} {:close :a} {:open :a} {:text "THEN"} @@ -232,9 +232,9 @@ (deftest test-process-if-nested (is (= [ - {:cmd :if, :condition '[x.a], + {:cmd :if, :condition '[:get x "a"], :then [</a> - {:cmd :if, :condition '[x.b], + {:cmd :if, :condition '[:get x "b"], :then [ {:text "THEN"}] :else []} </a>] @@ -242,9 +242,9 @@ (:executable (process [ - ,,{:cmd :if, :condition '[x.a]} + ,,{:cmd :if, :condition '[:get x "a"]} </a> - {:cmd :if, :condition '[x.b]} + {:cmd :if, :condition '[:get x "b"]} ,,{:text "THEN"} ,,{:cmd :end} diff --git a/test/stencil/eval_test.clj b/test/stencil/eval_test.clj index 2efca1c7..8eeea0a4 100644 --- a/test/stencil/eval_test.clj +++ b/test/stencil/eval_test.clj @@ -23,7 +23,7 @@ (deftest test-if (testing "THEN branch" (test-eval [-text1- - {:cmd :if :condition '[truthy] + {:cmd :if :condition 'truthy :then [{:text "ok"}] :else [{:text "err"}]} -text1-] @@ -33,7 +33,7 @@ (testing "ELSE branch" (test-eval [-text1- - {:cmd :if :condition '[falsey] + {:cmd :if :condition 'falsey :then [{:text "ok"}] :else [{:text "err"}]}] [-text1- @@ -41,10 +41,10 @@ (deftest test-echo (testing "Simple math expression" - (test-eval [{:cmd :echo :expression '[1 2 :plus]}] + (test-eval [{:cmd :echo :expression [:plus 1 2]}] [{:text "3"}])) (testing "Nested data access with path" - (test-eval [{:cmd :echo :expression '[abc.def]}] + (test-eval [{:cmd :echo :expression 'abc.def}] [{:text "Okay"}]))) (deftest test-for @@ -52,7 +52,7 @@ (test-eval [{:cmd :for :variable "index" :index-var "i" - :expression '[list0] + :expression 'list0 :body-run-once [{:text "xx"}] :body-run-none [{:text "meh"}] :body-run-next [{:text "x"}]}] @@ -62,8 +62,8 @@ (test-eval [{:cmd :for :variable "index" :index-var "i" - :expression '[list1] - :body-run-once [{:cmd :echo :expression '[index]}] + :expression 'list1 + :body-run-once [{:cmd :echo :expression 'index}] :body-run-none [{:text "meh"}] :body-run-next [{:text "x"}]}] [{:text "1"}])) @@ -72,8 +72,8 @@ (test-eval [{:cmd :for :variable "index" :index-var "i" - :expression '[abc] - :body-run-once [{:cmd :echo :expression '[i]} {:text "==>"} {:cmd :echo :expression '[index]}] + :expression 'abc + :body-run-once [{:cmd :echo :expression 'i} {:text "==>"} {:cmd :echo :expression 'index}] :body-run-none [{:text "should-not-run"}] :body-run-next [{:text "should-not-run"}]}] [{:text "def"} {:text "==>"} {:text "Okay"}])) @@ -82,8 +82,8 @@ (test-eval [{:cmd :for :variable "index" :index-var "i" - :expression '[list3] - :body-run-once [{:cmd :echo :expression '[index]}] + :expression 'list3 + :body-run-once [{:cmd :echo :expression 'index}] :body-run-none [{:text "meh"}] - :body-run-next [{:text "x"} {:cmd :echo :expression '[index]}]}] + :body-run-next [{:text "x"} {:cmd :echo :expression 'index}]}] [{:text "1"} {:text "x"} {:text "2"} {:text "x"} {:text "3"}]))) diff --git a/test/stencil/infix_test.clj b/test/stencil/infix_test.clj index 36f26a59..f259dd0c 100644 --- a/test/stencil/infix_test.clj +++ b/test/stencil/infix_test.clj @@ -1,6 +1,6 @@ (ns stencil.infix-test (:import [clojure.lang ExceptionInfo]) - (:require [stencil.infix :as infix :refer :all] + (:require [stencil.infix :as infix] [stencil.types :refer [hide-table-column-marker?]] [clojure.test :refer [deftest testing is are]])) @@ -8,16 +8,24 @@ ([xs] (run xs {})) ([xs args] (infix/eval-rpn args (infix/parse xs)))) + (deftest tokenize-test (testing "simple fn call" - (is (= [(->FnCall "sin") 1 :plus 2 :close] (infix/tokenize " sin(1+2)")))) + (is (= ['sin :open 1 :plus 2 :close] (infix/tokenize " sin(1+2)")))) (testing "comma" - (is (= [:open 1 :comma 2 :comma 3 :close] (infix/tokenize " (1,2 ,3) "))))) + (is (= [:open 1 :comma 2 :comma 3 :close] (infix/tokenize " (1,2 ,3) ")))) + + (testing "Unexpected end of string" + (try (dorun (infix/tokenize "1 + 2 ##")) + (assert false "should have thrown") + (catch ExceptionInfo e + (is (= "Unexpected end of string" (.getMessage e))) + (is (= {:index 6} (ex-data e))))))) (deftest test-read-string-literal (testing "Incomplete string" - (is (thrown? ExceptionInfo (read-string-literal "'alaba"))))) + (is (thrown? ExceptionInfo (infix/read-string-literal "'alaba"))))) (deftest tokenize-string-literal (testing "spaces are kept" @@ -34,9 +42,9 @@ (deftest tokenize-string-fun-eq (testing "tricky" - (is (= ["1" :eq #stencil.infix.FnCall{:fn-name "str"} 1 :close] + (is (= ["1" :eq 'str :open 1 :close] (infix/tokenize "\"1\" = str(1)"))) - (is (= ["1" 1 {:fn "str" :args 1} :eq] + (is (= [:eq "1" [:fncall 'str 1]] (infix/parse "\"1\" = str(1)"))))) (deftest parse-simple @@ -45,20 +53,20 @@ (is (thrown? ExceptionInfo (infix/parse "")))) (testing "Simple values" - (is (= [12] (infix/parse " 12 ") (infix/parse "12"))) - (is (= '[ax.y] (infix/parse " ax.y ")))) + (is (= 12 (infix/parse " 12 ") (infix/parse "12"))) + (is (= '[:get ax "y"] (infix/parse " ax.y ")))) (testing "Simple operations" - (is (= [1 2 :plus] + (is (= [:plus 1 2] (infix/parse "1 + 2") (infix/parse "1+2 ") (infix/parse "1+2"))) - (is (= [3 2 :times] (infix/parse "3*2")))) + (is (= [:times 3 2] (infix/parse "3*2")))) (testing "Parentheses" - (is (= [3 2 :plus 4 :times] + (is (= [:times [:plus 3 2] 4] (infix/parse "(3+2)*4"))) - (is (= [3 2 :plus 4 1 :minus :times] + (is (= [:times [:plus 3 2] [:minus 4 1]] (infix/parse "(3+2)*(4 - 1)"))))) (deftest all-ops-supported @@ -66,9 +74,8 @@ (let [ops (-> #{} (into (vals infix/ops)) (into (vals infix/ops2)) - (into (keys infix/operation-tokens)) - (disj :open :close :comma :open-bracket :close-bracket)) - known-ops (set (filter keyword? (keys (methods @#'infix/reduce-step))))] + (disj :open :close :comma :open-bracket :close-bracket :dot)) + known-ops (set (filter keyword? (keys (methods @#'infix/eval-tree))))] (is (every? known-ops ops))))) (deftest basic-arithmetic @@ -135,7 +142,6 @@ (testing "negation" (is (= 6 (run "a[1]-2" {"a" {"1" 8}})))) - #_ (testing "mixed lookup" (is (= 7 (run "a['x'].y" {"a" {"x" {"y" 7}}}))) (is (= 8 (run "a.x['y']" {"a" {"x" {"y" 8}}})))) @@ -143,8 +149,8 @@ (testing "syntax error" (is (thrown? ExceptionInfo (run "[3]" {}))) (is (thrown? ExceptionInfo (run "a[[3]]" {}))) - #_(is (thrown? ExceptionInfo (run "a.[1]b" {}))) - #_(is (thrown? ExceptionInfo (run "a..b" {}))) + (is (thrown? ExceptionInfo (run "a.[1]b" {}))) + (is (thrown? ExceptionInfo (run "a..b" {}))) (is (thrown? ExceptionInfo (run "a[1,2]" {})))) (testing "key is missing from input" @@ -196,6 +202,8 @@ (is (= 2 (run "a || b" {"b" 2}))) (is (nil? (run "a || b" {"a" false})))))) + + (deftest operator-precedeces (testing "Operator precedencia" (is (= 36 (run "2*3+5*6")))) @@ -287,13 +295,11 @@ (run "sum(map(\"x\", vals))" {"vals" [{"x" 1} {"x" 2} {"x" 3}]}))))) - - (deftest test-colhide-expr (is (hide-table-column-marker? (run "hideColumn()")))) (deftest test-unexpected - (is (thrown? ExceptionInfo (parse "aaaa:bbbb")))) + (is (thrown? ExceptionInfo (infix/parse "aaaa:bbbb")))) (deftest tokenize-wrong-tokens (testing "Misplaced operators and operands" diff --git a/test/stencil/merger_test.clj b/test/stencil/merger_test.clj index 7cca5d18..2c5c286c 100644 --- a/test/stencil/merger_test.clj +++ b/test/stencil/merger_test.clj @@ -106,29 +106,29 @@ [{:text "{%=1%}"}] [{:text "{%=1%}"}] - [{:action {:cmd :echo, :expression [1] :raw "{%=1%}"}}] + [{:action {:cmd :echo, :expression 1 :raw "{%=1%}"}}] [{:text "abc{%=1%}b"}] [{:text "abc"} {:text "{%=1%}"} {:text "b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression 1 :raw "{%=1%}"}} {:text "b"}] [{:text "abc{%="} O1 O2 {:text "1"} O3 O4 {:text "%}b"}] [{:text "abc"} {:text "{%="} O1 O2 {:text "1"} O3 O4 {:text "%}b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression 1 :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] [{:text "abc{%="} O1 O2 {:text "1%"} O3 O4 {:text "}b"}] [{:text "abc"} {:text "{%="} O1 O2 {:text "1%"} O3 O4 {:text "}b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression 1 :raw "{%=1%}"}} O1 O2 O3 O4 {:text "b"}] [{:text "abcd{%="} O1 {:text "1"} O2 {:text "%"} O3 {:text "}"} O4 {:text "b"}] [{:text "abcd"} {:text "{%="} O1 {:text "1"} O2 {:text "%"} O3 {:text "}"} O4 {:text "b"}] - [{:text "abcd"} {:action {:cmd :echo, :expression [1] :raw "{%=1%}"}} O1 O2 O3 O4{:text "b"}] + [{:text "abcd"} {:action {:cmd :echo, :expression 1 :raw "{%=1%}"}} O1 O2 O3 O4{:text "b"}] [{:text "abc{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] [{:text "abc"} {:text "{"} O1 {:text "%"} O2 {:text "=1"} O3 {:text "2"} O4 {:text "%"} O5 {:text "}"} {:text "b"}] - [{:text "abc"} {:action {:cmd :echo, :expression [12] :raw "{%=12%}"}} O1 O2 O3 O4 O5 {:text "b"}] + [{:text "abc"} {:action {:cmd :echo, :expression 12 :raw "{%=12%}"}} O1 O2 O3 O4 O5 {:text "b"}] [O1 {:text "{%if p"} O2 O3 {:text "%}one{%end%}"} O4] [O1 {:text "{%if p"} O2 O3 {:text "%}one"} {:text "{%end%}"} O4] - [O1 {:action {:cmd :if, :condition '[p] :raw "{%if p%}"}} O2 O3 {:text "one"} {:action {:cmd :end :raw "{%end%}"}} O4] + [O1 {:action {:cmd :if, :condition 'p :raw "{%if p%}"}} O2 O3 {:text "one"} {:action {:cmd :end :raw "{%end%}"}} O4] )))) diff --git a/test/stencil/tokenizer_test.clj b/test/stencil/tokenizer_test.clj index 1df3cfa8..6376d3e0 100644 --- a/test/stencil/tokenizer_test.clj +++ b/test/stencil/tokenizer_test.clj @@ -30,7 +30,7 @@ (is (= (run "elotte {%=a%} utana") [{:open :a} {:text "elotte "} - {:cmd :echo :expression '(a)} + {:cmd :echo :expression 'a} {:text " utana"} {:close :a}])))) @@ -39,7 +39,7 @@ (is (= (run "elotte {% if x%} akkor {% end %} utana") [{:open :a} {:text "elotte "} - {:cmd :if :condition '(x)} + {:cmd :if :condition 'x} {:text " akkor "} {:cmd :end} {:text " utana"} @@ -50,7 +50,7 @@ (is (= (run "elotte {% if x%} akkor {% else %} egyebkent {% end %} utana") [{:open :a} {:text "elotte "} - {:cmd :if :condition '(x)} + {:cmd :if :condition 'x} {:text " akkor "} {:cmd :else} {:text " egyebkent "} @@ -63,9 +63,9 @@ (is (= (run "elotte{%if x%}akkor{%else if y%}de{%end%}utana") '[{:open :a} {:text "elotte"} - {:cmd :if :condition [x]} + {:cmd :if :condition x} {:text "akkor"} - {:cmd :else-if :condition [y]} + {:cmd :else-if :condition y} {:text "de"} {:cmd :end} {:text "utana"} @@ -76,9 +76,9 @@ (is (= (run "elotte{%if x%}akkor{%else if y%}de{%else%}egyebkent{%end%}utana") '[{:open :a} {:text "elotte"} - {:cmd :if :condition [x]} + {:cmd :if :condition x} {:text "akkor"} - {:cmd :else-if :condition [y]} + {:cmd :else-if :condition y} {:text "de"} {:cmd :else} {:text "egyebkent"} @@ -89,17 +89,17 @@ (deftest read-tokens-unless-then (testing "Simple conditional with THEN branch only" (is (= (run "{%unless x%} akkor {% end %}") - [{:open :a} {:cmd :if :condition '(x :not)} {:text " akkor "} {:cmd :end} {:close :a}])))) + [{:open :a} {:cmd :if :condition '(:not x)} {:text " akkor "} {:cmd :end} {:close :a}])))) (deftest read-tokens-unless-then-else (testing "Simple conditional with THEN branch only" (is (= (run "{%unless x%} akkor {%else%} egyebkent {%end %}") - [{:open :a} {:cmd :if :condition '(x :not)} {:text " akkor "} {:cmd :else} {:text " egyebkent "} {:cmd :end} {:close :a}])))) + [{:open :a} {:cmd :if :condition '(:not x)} {:text " akkor "} {:cmd :else} {:text " egyebkent "} {:cmd :end} {:close :a}])))) (deftest read-tokens-if-elif-then-else (testing "If-else if-then-else branching" - (is (= '({:open :a} {:text "Hello "} {:cmd :if, :condition [x]} {:text "iksz"} - {:cmd :else-if, :condition [y]} {:text "ipszilon"} {:cmd :else} + (is (= '({:open :a} {:text "Hello "} {:cmd :if, :condition x} {:text "iksz"} + {:cmd :else-if, :condition y} {:text "ipszilon"} {:cmd :else} {:text "egyebkent"} {:cmd :end} {:text " Hola"} {:close :a}) (run "Hello {%if x%}iksz{%else if y%}ipszilon{%else%}egyebkent{%end%} Hola") (run "Hello {%if x%}iksz{%elseif y%}ipszilon{%else%}egyebkent{%end%} Hola") @@ -108,11 +108,11 @@ (deftest read-tokens-for (testing "Indexed loop" - (is (= '[{:open :a} {:cmd :for, :expression [xs], :variable x :index-var idx} + (is (= '[{:open :a} {:cmd :for, :expression xs, :variable x :index-var idx} {:text "item"} {:cmd :end} {:close :a}] (run "{%for idx, x in xs%}item{% end %}")))) (testing "Simple loop" - (is (= '[{:open :a} {:cmd :for, :expression [xs], :variable x :index-var $} + (is (= '[{:open :a} {:cmd :for, :expression xs, :variable x :index-var $} {:text "item"} {:cmd :end} {:close :a}] (run "{%for x in xs%}item{% end %}")))))