core: java.time + java.io shims — Selmer renders end to end (jolt-ea7)
Selmer now loads and renders templates on jolt: variables, filters (upper, date with JVM patterns), if/for tags, nested lookups, HTML escaping, and render-file with its last-modified template cache. New src/jolt/javatime.janet provides the java.time surface Selmer's date filters use (DateTimeFormatter/Instant/ZoneId/LocalDateTime/ FormatStyle/Locale, epoch-ms backed, host-local timezone) plus the java.io/java.lang/java.net shims its template reader needs (StringReader, StringBuilder, URL, File/separator, Class/forName). Everything registers through three new evaluator registries (class-statics, tagged-methods, class-ctors), so the module is data plus an install call. Fixes shaken out along the way, each load-bearing for Selmer and correct on their own: - :refer :all silently referred nothing (it iterated the :all keyword) - ns :import ignored vector specs and didn't share deftype ctor vars - dot calls on deftype/reify instances never consulted the protocol registry, so (.render-node node ctx) failed where (render-node ...) worked - instance? rejected expression type args like (Class/forName "[C") - char-array didn't accept a string - io/resource now searches the loader's source roots (the classpath analog); io/reader handles char arrays, URLs, readers, and returns an in-memory reader with :read-line-fn for file paths - String .split (regex, JVM trailing-empty semantics), file-path methods (.toURI/.toURL/.getPath/.lastModified/.exists) - System/getProperty (os.name & co), the janet/* bridge now works inside env-less fibers, and qualified class names that syntax-quote mangles (selmer.util/StringBuilder) fall back to the ctor registry Spec rows cover the shim surface; test/integration/selmer-test.janet runs the real Selmer from ~/src/selmer (skips cleanly when absent).
This commit is contained in:
parent
123c58560a
commit
d584369dda
9 changed files with 548 additions and 29 deletions
61
test/integration/selmer-test.janet
Normal file
61
test/integration/selmer-test.janet
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Selmer acceptance (jolt-ea7): load the real Selmer template engine from
|
||||
# ~/src/selmer and render through its full pipeline — the java.time shims
|
||||
# (DateTimeFormatter/Instant/ZoneId/LocalDateTime), the java.io shims
|
||||
# (StringReader/StringBuilder + char-array readers), vector :import sharing
|
||||
# deftype ctors, and :refer :all. SKIPS cleanly if the checkout is absent
|
||||
# (CI has no ~/src/selmer); the shim surface itself is covered by
|
||||
# test/spec/host-interop-spec.janet either way.
|
||||
|
||||
(import ../../src/jolt/api :as api)
|
||||
(use ../../src/jolt/reader)
|
||||
|
||||
(def selmer-src (string (os/getenv "HOME") "/src/selmer/src"))
|
||||
(def selmer-res (string (os/getenv "HOME") "/src/selmer/resources"))
|
||||
|
||||
(if (nil? (os/stat (string selmer-src "/selmer/parser.clj")))
|
||||
(print "selmer-test: ~/src/selmer not present, skipping")
|
||||
(do
|
||||
(reader-features-set! ["jolt" "clj" "default"])
|
||||
(def ctx (api/init {:paths [selmer-src selmer-res]}))
|
||||
|
||||
(print "loading selmer.parser...")
|
||||
(api/eval-string ctx "(require (quote [selmer.parser :as sp]))")
|
||||
(print " ok")
|
||||
|
||||
(defn render [tpl ctx-map-src]
|
||||
(api/eval-string ctx (string "(sp/render " (describe tpl) " " ctx-map-src ")")))
|
||||
|
||||
(print "variable + filter...")
|
||||
(assert (= "Hello WORLD!" (render "Hello {{name|upper}}!" "{:name \"world\"}")))
|
||||
(print " ok")
|
||||
|
||||
(print "date filter (java.time path)...")
|
||||
(def d (render "{{d|date:yyyy-MM-dd}}" "{:d #inst \"2020-03-05T10:00:00Z\"}"))
|
||||
(assert (peg/match '(* :d :d :d :d "-" :d :d "-" :d :d -1) d)
|
||||
(string "date filter renders a yyyy-MM-dd date, got: " d))
|
||||
(print " ok")
|
||||
|
||||
(print "if / for tags...")
|
||||
(assert (= "YES" (render "{% if ok %}YES{% else %}NO{% endif %}" "{:ok true}")))
|
||||
(assert (= "NO" (render "{% if ok %}YES{% else %}NO{% endif %}" "{:ok false}")))
|
||||
(assert (= "1,2,3," (render "{% for x in xs %}{{x}},{% endfor %}" "{:xs [1 2 3]}")))
|
||||
(print " ok")
|
||||
|
||||
(print "nested lookup + escaping...")
|
||||
(assert (= "7" (render "{{m.a.b}}" "{:m {:a {:b 7}}}")))
|
||||
(assert (= "<b>&" (render "{{x}}" "{:x \"<b>&\"}")))
|
||||
(print " ok")
|
||||
|
||||
(print "file templates (render-file + cache)...")
|
||||
(def tpl-dir (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-selmer-test"))
|
||||
(os/mkdir tpl-dir)
|
||||
(spit (string tpl-dir "/t.html") "File says {{x|upper}}")
|
||||
(api/eval-string ctx (string "(selmer.util/set-custom-resource-path! " (describe (string tpl-dir "/")) ")"))
|
||||
(assert (= "File says HI"
|
||||
(api/eval-string ctx "(sp/render-file \"t.html\" {:x \"hi\"})")))
|
||||
# second render goes through the template cache (last-modified check)
|
||||
(assert (= "File says AGAIN"
|
||||
(api/eval-string ctx "(sp/render-file \"t.html\" {:x \"again\"})")))
|
||||
(print " ok")
|
||||
|
||||
(print "selmer-test: all passed")))
|
||||
|
|
@ -61,3 +61,51 @@
|
|||
[".equalsIgnoreCase" "true" "(.equalsIgnoreCase \"AbC\" \"aBc\")"]
|
||||
["Long/MAX_VALUE" "true" "(pos? Long/MAX_VALUE)"]
|
||||
["unsupported method throws" :throws "(.frobnicate \"abc\")"])
|
||||
|
||||
# java.time shims (jolt-ea7): epoch-ms backed values + a DateTimeFormatter
|
||||
# pattern subset — the surface Selmer's date filters drive. Formatting uses
|
||||
# the HOST's local timezone, so rows assert structure, not wall-clock values.
|
||||
(defspec "interop / java.time shims"
|
||||
["ofPattern formats #inst" "true"
|
||||
"(string? (.format (DateTimeFormatter/ofPattern \"yyyy-MM-dd\") #inst \"2020-03-05T13:45:30Z\"))"]
|
||||
["pattern shape" "true"
|
||||
"(boolean (re-matches #\"\\d{4}-\\d{2}-\\d{2}\" (.format (DateTimeFormatter/ofPattern \"yyyy-MM-dd\") #inst \"2020-03-05T13:45:30Z\")))"]
|
||||
["month name + ampm" "true"
|
||||
"(boolean (re-matches #\"[A-Z][a-z]{2} \\d{1,2}, 2020 \\d{1,2}:\\d{2} [AP]M\" (.format (DateTimeFormatter/ofPattern \"MMM d, yyyy h:mm a\") #inst \"2020-03-05T13:45:30Z\")))"]
|
||||
["quoted literal" "true"
|
||||
"(boolean (re-matches #\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\" (.format DateTimeFormatter/ISO_LOCAL_DATE_TIME #inst \"2020-03-05T13:45:30Z\")))"]
|
||||
["localized style" "true"
|
||||
"(string? (.format (DateTimeFormatter/ofLocalizedDate FormatStyle/MEDIUM) #inst \"2020-03-05T13:45:30Z\"))"]
|
||||
["withLocale chain" "true"
|
||||
"(string? (.format (.withLocale (DateTimeFormatter/ofPattern \"yyyy\") (java.util.Locale. \"en\")) #inst \"2020-01-01T00:00:00Z\"))"]
|
||||
["fix-date chain" "true"
|
||||
"(instance? LocalDateTime (-> #inst \"2020-03-05T13:45:30Z\" (.toInstant) (.atZone (ZoneId/systemDefault)) (.toLocalDateTime)))"]
|
||||
["inst is java.util.Date" "true" "(instance? java.util.Date #inst \"2020-01-01T00:00:00Z\")"]
|
||||
["Instant instance" "true" "(instance? java.time.Instant (Instant/ofEpochMilli 0))"]
|
||||
["getTime epoch ms" "0" "(.getTime #inst \"1970-01-01T00:00:00Z\")"]
|
||||
["toEpochMilli round trip" "1234" "(.toEpochMilli (Instant/ofEpochMilli 1234))"]
|
||||
["Instant/now is current" "true" "(> (.toEpochMilli (Instant/now)) 1500000000000)"]
|
||||
["sql types are not" "false" "(instance? java.sql.Timestamp #inst \"2020-01-01T00:00:00Z\")"])
|
||||
|
||||
# java.io / java.lang shims that carry Selmer's char-by-char template reader.
|
||||
(defspec "interop / StringReader & StringBuilder"
|
||||
["StringReader read" "[97 98 -1]"
|
||||
"(let [r (java.io.StringReader. \"ab\")] [(.read r) (.read r) (.read r)])"]
|
||||
["mark/reset" "[97 97]"
|
||||
"(let [r (StringReader. \"ab\")] (.mark r 1) [(.read r) (do (.reset r) (.read r))])"]
|
||||
["StringBuilder append" "\"ab1\""
|
||||
"(.toString (-> (StringBuilder.) (.append \"a\") (.append \\b) (.append 1)))"]
|
||||
["capacity arg is not content" "\"x\""
|
||||
"(.toString (.append (StringBuilder. 16) \"x\"))"]
|
||||
["setLength truncates" "\"ab\""
|
||||
"(let [sb (StringBuilder.)] (.append sb \"abcd\") (.setLength sb 2) (.toString sb))"]
|
||||
["char-array of string" "true"
|
||||
"(instance? (Class/forName \"[C\") (char-array \"ab\"))"]
|
||||
["reader over char[]" "97"
|
||||
"(do (require (quote clojure.java.io)) (.read (clojure.java.io/reader (char-array \"abc\"))))"]
|
||||
["line-seq over file reader" "[\"a\" \"b\"]"
|
||||
"(do (require (quote clojure.java.io)) (janet/spit \"/tmp/jolt-lineseq-spec.txt\" \"a\\nb\\n\") (vec (line-seq (clojure.java.io/reader \"/tmp/jolt-lineseq-spec.txt\"))))"]
|
||||
["with-open closes shim" "97"
|
||||
"(with-open [r (StringReader. \"a\")] (.read r))"]
|
||||
["vector :import shares deftype ctor" "\"hi!\""
|
||||
"(do (ns spec.nodea) (defprotocol SpecP (spec-pm [this])) (deftype SpecTN [t] SpecP (spec-pm [this] (str t \"!\"))) (ns spec.nodeb (:import [spec.nodea SpecTN])) (.spec-pm (SpecTN. \"hi\")))"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue