Steve Losh avatar Steve Losh committed cad16ca

Basic template file loading!

Comments (0)

Files changed (6)

resources/templates/dram-test/base.dram

+<html>{% block head %}<head></head>{% endblock %}</html>

resources/templates/dram-test/literals.dram

+{{ 1 }}
+{{ "cats" }}

resources/templates/dram-test/sample.dram

+this is a sample template

resources/templates/dram-test/user.dram

+Hello, {{ user.username }}!

src/dram/rendering.clj

 (ns dram.rendering
-  (:require [dram.parser :refer [parse]]
+  (:require [clojure.string :refer [join]]
+            [dram.parser :refer [parse]]
             [dram.context :refer [lookup]]))
 
 
-(defn render-block [block context])
+(defn find-template-url [path]
+  (or (clojure.java.io/resource path)
+      (clojure.java.io/resource (join java.io.File/separator
+                                      ["templates" path]))))
 
-(defn render-value [{:keys [base filters]} context]
+(defn slurp-template [path]
+  (slurp (find-template-url path)))
+
+
+(declare render-ast render-string)
+
+(defn render-value
+  "Render an AST value node into a string according to the context."
+  [{:keys [base filters]} context]
   (let [v (if (or (number? base) (string? base))
             base
             (lookup base context))]
     v))
 
-(defn render-chunk [chunk context]
+(defn render-chunk
+  "Render a chunk (a non-block) to a flat string."
+  [chunk context]
   (cond
     (string? chunk) chunk
-    (= :value (:type chunk)) (str (render-value chunk context))
-    (= :block (:type chunk)) (render-block chunk context)))
+    (= :value (:type chunk)) (str (render-value chunk context))))
 
-(defn render-base [{:keys [contents]} context]
+(defn render-contents
+  "Render the contents of a block to a flat string."
+  [contents context]
   (apply str (map #(render-chunk % context) contents)))
 
-(defn render-child [ast context])
+(defn render-block
+  "Render an AST block to a [name content] pair."
+  [{:keys [name contents]} context]
+  [name (render-contents contents context)])
 
-(defn render-ast [ast context]
+(defn render-base
+  "Render a base template to a [[name content] ...] seq."
+  [{:keys [contents]} context]
+  (for [c contents]
+    (if (= :block (get-in c [:type]))
+      (render-block c context)
+      [nil (render-chunk c context)])))
+
+(defn render-child
+  "Render a child template to a [[name content] ...] seq."
+  [{:keys [extends blocks]} context]
+  (let [parent-ast (render-string (slurp-template extends) context)
+        parent (render-ast extends context)]
+    (for [[name content] parent]
+      (if-let [override (get blocks name)]
+        [name (render-contents override context)]
+        [name content]))))
+
+(defn render-flatten
+  "Flatten a [[name content] ...] seq into a single string."
+  [blockpairs]
+  (apply str (map second blockpairs)))
+
+(defn render-ast
+  "Render the given AST and context into a [[name content] ...] seq."
+  [ast context]
   (case (:type ast)
     :base (render-base ast context)
     :child (render-child ast context)))
 
 (defn render-string [raw-template context]
-  (render-ast (parse raw-template) context))
+  (render-flatten (render-ast (parse raw-template) context)))
+
+(defn render-template [path context]
+  (render-string (slurp-template path) context))

test/dram/test/rendering.clj

 (ns dram.test.rendering
   (:require [dram.rendering :as r]
+            [dram.parser :refer [parse]]
             [dram.test.utils.ast :as ast]
             [clojure.test :refer :all]))
 
 
+(deftest slurp-test
+  (testing "Slurping should do its best to get the template."
+    (are [path content] (= (r/slurp-template path) content)
+         "dram-test/sample.dram"           "this is a sample template\n"
+         "templates/dram-test/sample.dram" "this is a sample template\n")))
+
 (deftest render-value-unfiltered-test
   (testing "Literals render to themselves."
     (are [base context result] (= result (r/render-value (ast/v base) context))
          ["a" "0"]     {:a [:goal]}
          ["a" "b" "c"] {:a {"b" {"c" :goal}}}
          ["a" "b" "c"] {:a {"b" {:c :goal}}}
-         ["0" "0"]     [[:goal]]
-         ))
+         ["0" "0"]     [[:goal]]))
   (testing "Paths can bail early if a segment isn't found."
     (are [base context] (nil? (r/render-value (ast/v base) context))
          ["a"]     {}
          ["a"] {:a :b}  ":b"
          ["a"] {:a "x"} "x")))
 
-(deftest render-base-simple-test
-  (testing "Simple, string-only base templates should be trivial."
-    (are [template context result] (= result (r/render-string template context))
-         "foo" {}     "foo"
-         ""    {}     ""
-         ""    {:a 1} ""
-         "foo" {:a 1} "foo"))
-  (testing "Base templates without blocks should be pretty simple too."
-    (are [template context result] (= result (r/render-string template context))
-         "a {{ 1 }} b"       {} "a 1 b"
-         "a {{ \"and\" }} b" {} "a and b"
-         "{{ 1}}{{2}}"       {} "12"
+(deftest render-block-test
+  (testing "AST blocks should render into an intermediate [name str] form."
+    (are [name contents context result]
+         (= result (r/render-block (ast/b name contents) context))
+         "body" ["foo" "bar"]          {} ["body" "foobar"]
+         "body" ["meow" (ast/v 10)]    {} ["body" "meow10"]
+         "body" ["meow" (ast/v ["a"])] {} ["body" "meow"]
 
-         "Hello, {{ user.name }}!" {:user {:name "Steve"}}   "Hello, Steve!"
-         "Hello, {{ user.name }}!" {:user {"name" "Steve"}}  "Hello, Steve!"
-         "Hello, {{ user.name }}!" {"user" {:name "Steve"}}  "Hello, Steve!"
-         "Hello, {{ user.name }}!" {"user" {"name" "Steve"}} "Hello, Steve!"
+         "sample"
+         ["username:" (ast/v ["user" "username"])]
+         {:user {:username "sjl"}}
+         ["sample" "username:sjl"])))
 
-         "{{ a.1 }}, {{a.2}}" {:a "xyz"}               "y, z"
-         "{{ a.0 }}, {{a.2}}" {:a ["foo" "bar" "baz"]} "foo, baz"
-         "{{ a.0 }}, {{a.2}}" {:a {0 :q "2" :r}}       ":q, :r"
-         )))
+(deftest render-base-test
+  (testing "Simple base templates render into [[name str] ...] seqs."
+    (are [template context result]
+         (= result (r/render-base (parse template) context))
+
+         "foo"
+         {}
+         [[nil "foo"]]
+
+         "foo {{ 1 }} bar"
+         {}
+         [[nil "foo "]
+          [nil "1"]
+          [nil " bar"]]
+
+         "foo {% block body %}body {{ 1 }} text{% endblock %} bar"
+         {}
+         [[nil "foo "]
+          ["body" "body 1 text"]
+          [nil " bar"]]
+
+         (apply str
+                "{% block a %}a: {{ a.val }}{% endblock %}"
+                "{% block b %}b: {{ b.val }}{% endblock %}")
+         {:a {:val 1}}
+         [["a" "a: 1"]
+          ["b" "b: "]])))
+
+
+(deftest full-template-test
+  (testing "Send templates all the way through the pipeline."
+    (are [path context result] (= (r/render-template path context) result)
+         "dram-test/sample.dram" {} "this is a sample template\n"
+         "dram-test/literals.dram" {} "1\ncats\n"
+
+         "dram-test/user.dram" {} "Hello, !\n"
+
+         "dram-test/user.dram"
+         {:user {"username" "sjl"}}
+         "Hello, sjl!\n"
+
+         "dram-test/base.dram" {} "<html><head></head></html>\n"
+         )
+    )
+
+  )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.