├── .travis.yml ├── src └── enliven │ ├── text │ ├── model.clj │ └── emit │ │ └── static.clj │ ├── core │ ├── grounder.clj │ ├── actions.clj │ ├── locs.clj │ ├── selectors.clj │ ├── transformations.clj │ ├── plans.clj │ └── lenses.clj │ ├── text.clj │ ├── html │ ├── jsoup.clj │ ├── model.clj │ └── emit │ │ └── static.clj │ ├── commons │ └── emit │ │ └── static.clj │ └── html.clj ├── project.clj ├── test └── enliven │ ├── core │ └── paths_test.clj │ └── html_test.clj └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein2 do test 4 | jdk: 5 | - openjdk6 6 | - openjdk7 7 | - oraclejdk7 -------------------------------------------------------------------------------- /src/enliven/text/model.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.text.model 2 | (:refer-clojure :exclude [chars]) 3 | (:require [enliven.core.lenses :as lens])) 4 | 5 | (lens/deflens chars [s cs] 6 | :fetch (vec s) 7 | :putback (apply str cs)) 8 | 9 | (lens/deftransitions 10 | {::chars {`chars ::chars 11 | `lens/slice ::chars 12 | Number ::char}}) 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject enliven "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :repositories [["phloc.com" "http://repo.phloc.com/maven2"]] 7 | :dependencies [[org.clojure/clojure "1.5.1"] 8 | [com.phloc/phloc-css "3.7.0"] 9 | [org.jsoup/jsoup "1.7.2"]]) 10 | -------------------------------------------------------------------------------- /src/enliven/core/grounder.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.grounder 2 | (:require [enliven.core.actions :as action] 3 | [enliven.core.locs :as loc] 4 | [enliven.core.lenses :as lens])) 5 | 6 | ;; a transformation is a function from loc to seq of [loc action] 7 | ;; rules (as returned by ground-loc) are seq of [path action] 8 | (defn ground-loc [transformation loc] 9 | (let [path (lens/canonical (loc/path loc))] 10 | (for [[sloc action] (transformation loc)] 11 | [(lens/relativize (lens/canonical (loc/path sloc)) path) 12 | (action/update action :arg lens/canonical)]))) 13 | 14 | (defn ground 15 | "Returns a seq of [canonical-path action]." 16 | [transformation node] 17 | (ground-loc transformation (loc/loc node))) 18 | -------------------------------------------------------------------------------- /src/enliven/text/emit/static.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.text.emit.static 2 | (:require [enliven.text.model :as text] 3 | [enliven.core.actions :as action] 4 | [enliven.core.plans :as plan] 5 | [enliven.commons.emit.static :as static])) 6 | 7 | (defmethod static/prerenderer-fn ::text/char [node-type] 8 | (fn [text plan enc emit acc] 9 | (if (char? text) 10 | (if (nil? plan) 11 | (emit acc (enc (str text))) 12 | (static/prerender-unknown plan ::text/char (comp enc str) emit acc)) 13 | (static/prerender ::text/chars text plan enc emit acc)))) 14 | 15 | (defmethod static/prerenderer-fn ::text/chars [node-type] 16 | (fn [text plan enc emit acc] 17 | (static/prerender-nodes text plan ::text/char enc emit acc))) 18 | -------------------------------------------------------------------------------- /src/enliven/core/actions.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.actions 2 | (:refer-clojure :exclude [replace])) 3 | 4 | ;; An action is made of an op, a scope-idx, an arg and 5 | 6 | (defn replace [path] {:op ::replace :scope-idx 0 :arg path}) 7 | (defn dup [path sub] {:op ::dup :scope-idx 0 :arg path :subs [[list sub]]}) 8 | 9 | (defmulti update (fn [action key f & args] key)) 10 | 11 | (defmethod update :default [action key f & args] 12 | (assoc action key (mapv #(apply f % args) (get action key)))) 13 | 14 | #_(defmethod update :subs [action key f & args] 15 | (assoc action :subs (mapv (fn [[p sub]] [p (apply f sub args)]) (:subs action)))) 16 | 17 | (defmethod update :arg [action key f & args] 18 | (if-let [path (:arg action)] 19 | (assoc action :arg (apply f path args)) 20 | action)) 21 | -------------------------------------------------------------------------------- /src/enliven/core/locs.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.locs 2 | (:require [enliven.core.lenses :as lens])) 3 | 4 | (defrecord ^:private Loc [value seg ploc]) 5 | 6 | (defn loc [x] 7 | (->Loc x nil nil)) 8 | 9 | (defn down [loc seg] 10 | (->Loc (lens/fetch (:value loc) seg) seg loc)) 11 | 12 | (defn up [loc] 13 | (:ploc loc)) 14 | 15 | (defn node [loc] 16 | (:value loc)) 17 | 18 | (defn segment [loc] 19 | (:seg loc)) 20 | 21 | (defn path [loc] 22 | (loop [path () loc loc] 23 | (if-let [ploc (up loc)] 24 | (recur (conj path (:seg loc)) ploc) 25 | path))) 26 | 27 | (defn root [loc] 28 | (if-let [ploc (up loc)] 29 | (recur ploc) 30 | (:value loc))) 31 | 32 | (defn canonicalize-path [loc'] 33 | (reduce down (-> loc' root loc) (lens/canonical (path loc')))) 34 | 35 | (defn spliceable 36 | "Returns the immediate splicing location, if none returns nil." 37 | [loc] 38 | (let [seg (segment loc)] 39 | (cond 40 | (number? seg) 41 | (-> loc up (down (lens/slice seg (inc seg)))) 42 | (lens/slice? seg) loc))) 43 | -------------------------------------------------------------------------------- /src/enliven/text.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.text 2 | (:refer-clojure :exclude [replace]) 3 | (:require [enliven.text.model :as text] 4 | [enliven.core.lenses :as lens] 5 | [enliven.core.locs :as loc] 6 | [enliven.core.actions :as action] 7 | [enliven.core.transformations :as transform])) 8 | 9 | (defn sel [selector] 10 | (if (instance? java.util.regex.Pattern selector) 11 | (fn [loc] 12 | (let [s (loc/node loc)] 13 | (when (string? s) 14 | (let [loc (loc/down loc chars) 15 | m (re-matcher selector s)] 16 | (loop [locs []] 17 | (if (.find m) 18 | (recur (conj locs (loc/down loc (lens/slice (.start m) (.end m))))) 19 | locs)))))) 20 | selector)) 21 | 22 | (defn replace [selector path] 23 | (transform/replace (sel selector) path)) 24 | 25 | #_(defn static-template [text & transformations] 26 | (let [plan (plan/plan (grounder/ground (apply at transformations) text)) 27 | emitted (common/tight-fn-emit! (static/prerender node plan common/tight-fn-emit! (common/tight-fn-emit!)))] 28 | (fn 29 | ([data] (common/render emitted data)) 30 | ([data emit acc] (common/render emitted data emit acc))))) 31 | -------------------------------------------------------------------------------- /test/enliven/core/paths_test.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.paths-test 2 | (:use clojure.test) 3 | (:require 4 | [enliven.html.model :as html] 5 | [enliven.text.model :as text] 6 | [enliven.core.lenses :as lens])) 7 | 8 | (deftest canonicalization 9 | (testing "Hoisting segments" 10 | (is (= (lens/canonical :foo) :foo)) 11 | (is (= (lens/canonical [:foo]) :foo))) 12 | (testing "empty path is the identity" 13 | (is (= (lens/canonical []) lens/identity))) 14 | (testing "Merge nested slices" 15 | (is (= (lens/canonical [(lens/slice 1 3) (lens/slice 1 2)]) 16 | (lens/slice 2 3)))) 17 | (testing "Reparent indexed segment as a 0-index in a singleton slice." 18 | (is (= (lens/decompose (lens/canonical [12])) 19 | [(lens/slice 12 13) 0])) 20 | (is (= (lens/decompose (lens/canonical [(lens/slice 2 20) 12])) 21 | [(lens/slice 14 15) 0]))) 22 | (testing "Simplify constant paths" 23 | (is (= (lens/canonical [:foo (lens/const 42)]) 24 | (lens/const 42))) 25 | (is (= (lens/canonical [:foo (lens/const 42) (lens/const 63)]) 26 | (lens/const 63))) 27 | (is (= (lens/canonical [:foo (lens/const {:pi 3.14}) :pi]) 28 | (lens/const 3.14))) 29 | (is (= (lens/canonical [:foo (lens/const {:pi 3.14}) :pi (lens/const 42)]) 30 | (lens/const 42))))) 31 | 32 | (deftest abstract-fetch 33 | (are [from path to] (= (lens/fetch-type from path) to) 34 | ::html/node [:content (lens/slice 1 2) 0 :attrs :class html/classes "important"] ::lens/boolsy 35 | ::html/node [:content (lens/slice 1 2) 0 :attrs :style text/chars] ::text/chars)) 36 | -------------------------------------------------------------------------------- /src/enliven/core/selectors.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.selectors 2 | (:require [enliven.core.locs :as locs] 3 | [enliven.core.lenses :as lens])) 4 | 5 | ;; A selector is a function from one loc to a coll of locs; loc -> locs 6 | (defn locs [root sel] 7 | (sel (locs/loc root))) 8 | 9 | (defn paths [root sel] 10 | (map locs/path (locs root sel))) 11 | 12 | (defn nodes [root sel] 13 | (map locs/node (locs root sel))) 14 | 15 | (defn chain 16 | "Composes several selectors, from left to right." 17 | ([] list) 18 | ([sel] sel) 19 | ([sela selb] 20 | (fn [loc] 21 | (for [loc (sela loc), loc (selb loc)] 22 | loc))) 23 | ([sela selb & sels] 24 | (reduce chain (chain sela selb) sels))) 25 | 26 | (defn union 27 | "Returns the union selector of the specified selectors." 28 | [& sels] 29 | (fn [loc] 30 | (distinct (mapcat #(% loc) sels)))) 31 | 32 | (defn rights 33 | "A selector that returns all the rights siblings of a node 34 | (ordered from closest to farthest)." 35 | [loc] 36 | (let [loc (locs/canonicalize-path loc)] 37 | (when-let [loc (and (zero? (locs/segment loc)) (locs/up loc))] 38 | (let [[idx] (lens/bounds (locs/segment loc)) 39 | loc (locs/up loc) 40 | n (-> loc locs/node count)] 41 | (map #(locs/down loc %) (range (inc idx) n)))))) 42 | 43 | (defn loc-pred 44 | "Returns a filtering selector which keeps only locs for which pred is true." 45 | [pred] 46 | (fn [loc] 47 | (when (pred loc) (list loc)))) 48 | 49 | (defn node-pred [pred] 50 | (loc-pred (comp pred locs/node))) 51 | 52 | (defn by-path [path] 53 | (fn [loc] 54 | (list (reduce locs/down loc path)))) 55 | -------------------------------------------------------------------------------- /src/enliven/html/jsoup.clj: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Christophe Grand, 2009. All rights reserved. 2 | ; Copyright (c) Baishampayan Ghose, 2013. All rights reserved. 3 | 4 | ; The use and distribution terms for this software are covered by the 5 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 6 | ; which can be found in the file epl-v10.html at the root of this 7 | ; distribution. 8 | ; By using this software in any fashion, you are agreeing to be bound by 9 | ; the terms of this license. 10 | ; You must not remove this notice, or any other, from this software. 11 | 12 | (ns enliven.html.jsoup 13 | "JSoup based parser backend." 14 | (:import [org.jsoup Jsoup] 15 | [org.jsoup.nodes Attribute Attributes Comment DataNode Document 16 | DocumentType Element Node TextNode XmlDeclaration] 17 | [org.jsoup.parser Parser Tag])) 18 | 19 | (def ^:private ->key (comp keyword #(-> % .toString .toLowerCase))) 20 | 21 | (defprotocol IEnlive 22 | (->nodes [d] "Convert object into Enlive node(s).")) 23 | 24 | (extend-protocol IEnlive 25 | Attribute 26 | (->nodes [a] [(->key (.getKey a)) (.getValue a)]) 27 | 28 | Attributes 29 | (->nodes [as] (not-empty (into {} (map ->nodes as)))) 30 | 31 | Comment 32 | (->nodes [c] {:type :comment :data (.getData c)}) 33 | 34 | DataNode 35 | (->nodes [dn] (str dn)) 36 | 37 | Document 38 | (->nodes [d] (not-empty (map ->nodes (.childNodes d)))) 39 | 40 | DocumentType 41 | (->nodes [dtd] {:type :dtd :data ((juxt :name :publicid :systemid) (->nodes (.attributes dtd)))}) 42 | 43 | Element 44 | (->nodes [e] {:tag (->key (.tagName e)) 45 | :attrs (->nodes (.attributes e)) 46 | :content (vec (map ->nodes (.childNodes e)))}) 47 | 48 | TextNode 49 | (->nodes [tn] (.getWholeText tn)) 50 | 51 | nil 52 | (->nodes [_] nil)) 53 | 54 | (defn parser 55 | "Parse a HTML document stream into Enlive nodes using JSoup." 56 | [stream] 57 | (with-open [^java.io.Closeable stream stream] 58 | (->nodes (Jsoup/parse stream "UTF-8" "")))) 59 | 60 | (defn parse [^String s] 61 | (first (->nodes (org.jsoup.Jsoup/parse s)))) 62 | -------------------------------------------------------------------------------- /src/enliven/core/transformations.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.core.transformations 2 | (:refer-clojure :exclude [replace]) 3 | (:require [enliven.core.actions :as action] 4 | [enliven.core.grounder :as grounder] 5 | [enliven.core.selectors :as sel] 6 | [enliven.core.locs :as loc] 7 | [enliven.core.lenses :as lens])) 8 | 9 | (defn composite [transformations] 10 | (fn [loc] (mapcat #(% loc) transformations))) 11 | 12 | (defn mash [& transformations] 13 | (composite transformations)) 14 | 15 | (defn at* 16 | ([selector+transformations] (at* selector+transformations identity)) 17 | ([selector+transformations sel] 18 | (if (next selector+transformations) 19 | (composite 20 | (for [[selector t] (partition 2 selector+transformations) 21 | :let [selector (sel selector)]] 22 | (fn [loc] (mapcat t (selector loc))))) 23 | (first selector+transformations)))) 24 | 25 | (defn at [& selector+transformations] 26 | (at* selector+transformations)) 27 | 28 | (defn replace 29 | ([path] 30 | (let [action (action/replace path)] 31 | (fn [loc] 32 | [[(or (loc/spliceable loc) loc) action]]))) 33 | ([selector path] 34 | (at selector (replace path)))) 35 | 36 | (defn dup [data-path sub] 37 | (let [action (action/dup data-path sub)] 38 | (fn [loc] 39 | (let [sloc (loc/spliceable loc) 40 | _ (when-not sloc 41 | (throw (ex-info "Unexpected location for a dup" 42 | {:loc loc :action action}))) 43 | path (lens/canonical (loc/path sloc))] 44 | [[sloc (action/update action :subs 45 | (fn [[subsel sub]] 46 | (let [subloc (first (subsel loc))] 47 | [(lens/relativize (lens/canonical (loc/path subloc)) path) 48 | (grounder/ground-loc sub subloc)])))]])))) 49 | 50 | #_(defn if' [path then-sub else-sub] 51 | (let [action (action/if' path then-sub else-sub)] 52 | (fn [loc] 53 | (let [sloc (or (loc/spliceable loc) loc) 54 | nloc (-> sloc loc/node loc/loc) 55 | nloc (if (= loc sloc) 56 | nloc 57 | (loc/down nloc 0))] 58 | [[sloc (action/update action :subs grounder/ground-loc nloc)]])))) 59 | -------------------------------------------------------------------------------- /src/enliven/html/model.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.html.model 2 | (:require 3 | [clojure.string :as str] 4 | [enliven.text.model :as text] 5 | [enliven.core.lenses :as lens])) 6 | 7 | ;; html-specific segments 8 | (lens/deflens classes [class-attr classes] 9 | :fetch 10 | (zipmap (re-seq #"\S+" (or class-attr "")) (repeat true)) 11 | :putback 12 | (some->> classes (keep (fn [[k v]] (when v k))) seq (str/join " "))) 13 | 14 | 15 | (defn read-css-attributes 16 | "parses an style attribute string into a sequence of key value pairs" 17 | [attr-str] 18 | (let [el (com.phloc.css.reader.CSSReaderDeclarationList/readFromString 19 | (or attr-str "") com.phloc.css.ECSSVersion/CSS30)] 20 | (if el 21 | (for [idx (range (.getDeclarationCount el))] 22 | [(-> (.getDeclarationAtIndex el idx) 23 | (.getProperty)) 24 | (-> (.getDeclarationAtIndex el idx) 25 | (.getExpression) 26 | (.getAsCSSString (com.phloc.css.writer.CSSWriterSettings. 27 | com.phloc.css.ECSSVersion/CSS30) 28 | 0))]) 29 | '()))) 30 | 31 | (lens/deflens styles 32 | "allows you to operate on seqence of key value pairs vs the 33 | style attribute string directly" 34 | [style-attr styles] 35 | :fetch 36 | (read-css-attributes style-attr) 37 | :putback 38 | (reduce (fn [s [k v]] (str s k ":" v ";")) "" styles)) 39 | 40 | 41 | 42 | (lens/deftransitions 43 | {::node {:content {:type ::nodes 44 | :js/fetcher (fn [node seg] 45 | `(.-childNodes ~node))} 46 | :attrs {:type ::attrs 47 | :js/fetcher (fn [node seg] 48 | `(.-attributes ~node))} 49 | :tag ::tag 50 | `text/chars ::text/chars} 51 | ::nodes {`lens/slice {:type ::nodes 52 | :js/fetcher (fn [node seg] 53 | `(nodes-slice ~node ~(:from seg) ~(:to seg)))} 54 | Number {:type ::node 55 | :js/fetcher (fn [node seg] 56 | `(aget ~node ~seg))} 57 | #_#_:js/replace (fn [node data] ; not the right place 58 | `(set! (.-nodeValue ~node) (as-nodes ~data)))} 59 | ::attrs {clojure.lang.Keyword {:type ::attr-value 60 | :js/fetcher (fn [node seg] 61 | `(aget ~node ~(name seg)))}} 62 | ::attr-value {`classes ::classes 63 | `text/chars ::text/chars 64 | `styles ::style-decls 65 | #_#_:js/replace (fn [node data] ; not the right place 66 | `(set! (.-nodeValue ~node) ~data))} 67 | ::style-decls {`lens/append-on-assoc ::style-maps} 68 | ::classes {String ::lens/boolsy}}) 69 | 70 | -------------------------------------------------------------------------------- /src/enliven/html/emit/static.clj: -------------------------------------------------------------------------------- 1 | (ns enliven.html.emit.static 2 | (:require [enliven.html.model :as html] 3 | [enliven.text.model :as text] 4 | [enliven.core.lenses :as lens] 5 | [enliven.commons.emit.static :as static] 6 | [enliven.core.plans :as plan] 7 | [enliven.core.actions :as action])) 8 | 9 | (defn known-segs-only? [plan known-segs] 10 | (every? known-segs (keys plan))) 11 | 12 | (defn escape-text-node [text-node] 13 | (-> text-node str (.replace "&" "&") (.replace "<" "<"))) 14 | 15 | (defn escape-attr-value 16 | "Escape string for use in an attribute. Avoid escaping ampersands as much as possible. 17 | (see HTML5 parsing algorithm)" 18 | [^String attr-value] 19 | (-> (re-matcher #"&([a-zA-Z0-9]+(?![=a-zA-Z0-9])|#)" attr-value) 20 | (.replaceAll "&$1") 21 | (.replace "'" """))) 22 | 23 | (defn prerender-text-node [node plan enc emit acc] 24 | (let [enc (comp enc escape-text-node)] 25 | (if plan 26 | (if-let [char-plan (some-> plan :misc (get text/chars))] 27 | (static/prerender ::text/chars 28 | (lens/fetch node text/chars) 29 | char-plan 30 | enc emit acc) 31 | (static/prerender-unknown node plan ::html/node enc emit acc)) 32 | (emit acc (enc node))))) 33 | 34 | (defmethod static/prerenderer-fn ::html/tag [node-type] 35 | (fn [tag plan enc emit acc] 36 | (if plan 37 | (static/prerender-unknown tag plan node-type enc emit acc) 38 | (emit acc (enc (name tag)))))) 39 | 40 | (defmacro ^:private inline-emit 41 | "Threads the emit fn and its accumulator through each items of coll. 42 | When the item is unquoted, emit and acc are apssed as the last two arguments." 43 | [enc emit acc & coll] 44 | (let [encsym (gensym 'enc) 45 | emitsym (gensym 'emit)] 46 | `(let [~encsym ~enc 47 | ~emitsym ~emit] 48 | ~(reduce (fn [acc x] 49 | (if (and (seq? x) (= `clojure.core/unquote (first x))) 50 | (let [expr (second x)] 51 | (concat (if (seq? expr) expr (list expr)) [encsym emitsym acc])) 52 | (list emitsym acc (list encsym x)))) 53 | acc coll)))) 54 | 55 | (defn- render-attrs [attrs enc emit acc] 56 | (reduce-kv (fn [acc attr v] 57 | (cond 58 | (true? v) (emit acc (enc (name attr))) 59 | v (inline-emit enc emit acc " " (name attr) 60 | "='" (escape-attr-value v) "'") 61 | :else acc)) 62 | acc attrs)) 63 | 64 | (defmethod static/prerenderer-fn ::html/attrs [node-type] 65 | (fn [attrs plan enc emit acc] 66 | (cond 67 | (nil? plan) (render-attrs attrs enc emit acc) 68 | (or (:action plan) (not-every? keyword? (keys (:misc plan)))) 69 | (static/prerender-unknown attrs plan node-type enc emit acc) 70 | :else 71 | (let [untoucheds (reduce dissoc attrs (keys (:misc plan))) 72 | toucheds (reduce dissoc attrs (keys untoucheds))] 73 | (inline-emit enc emit acc 74 | ~(render-attrs untoucheds) 75 | ~(static/prerender-unknown toucheds plan node-type)))))) 76 | 77 | (defn prerender-element [node plan enc emit acc] 78 | (if (and (not (:action plan)) (known-segs-only? (:misc plan) #{:tag :attrs :content})) 79 | (let [plan-by-seg (:misc plan) 80 | prerender (fn [seg enc emit acc] 81 | (static/prerender (lens/fetch-type ::html/node seg) (lens/fetch node seg) (get plan-by-seg seg) 82 | enc emit acc))] 83 | (inline-emit enc emit acc 84 | "<" ~(prerender :tag) ~(prerender :attrs) ">" ~(prerender :content) "" ~(prerender :tag) ">")) 85 | (static/prerender-unknown node plan ::html/node enc emit acc))) 86 | 87 | (defmethod static/prerenderer-fn ::html/node [node-type] 88 | (fn [node plan enc emit acc] 89 | (cond 90 | (string? node) (prerender-text-node node plan enc emit acc) 91 | (:tag node) (prerender-element node plan enc emit acc) 92 | (or (nil? node) (sequential? node)) (static/prerender ::html/nodes node plan enc emit acc) 93 | :else (throw (ex-info "Unexpected node" {:node node}))))) 94 | 95 | (defmethod static/prerenderer-fn ::html/nodes [node-type] 96 | (fn [nodes plan enc emit acc] 97 | (static/prerender-nodes nodes plan ::html/node enc emit acc))) 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enliven [](https://travis-ci.org/cgrand/enliven) 2 | 3 | Enliven: a (not yet) continuous templating system. 4 | 5 | *WARNING* very wet paint 6 | 7 | ## Enliven is the successor to Enlive 8 | 9 | Even dictionaries say so: 10 | 11 | > Enlive: To Enliven (Obs.) 12 | 13 | Enliven also stands for Enlive N(ext). 14 | 15 | ### Not tied to HTML 16 | 17 | Currently Enliven can template plain text (`enliven.text`) and html (`enliven.html`). 18 | The HTML "domain" is even composite since it uses the text "domain" to template text nodes. 19 | 20 | ### Simpler selectors 21 | 22 | Selectors are now functions from loc to locs. Most domains should expose a `sel` function to coerce 23 | a domain specific selector (eg a CSS-selector in string or a regex) to a selector fn. 24 | 25 | ### Parallel execution of transformations 26 | 27 | All transformations occur at once for maximum declarativeness (and it enables good performance). It follows that two transformations can't work on the same node 28 | or on a node and one of its ancestors. 29 | 30 | This constraint is heavily mitigated by infinite-resolution selectors and transformation-refined selectors. 31 | 32 | ### Infinite-resolution selectors 33 | 34 | Selectors don't stop at nodes as specified in the DOM. Any node can be subdivided at will! 35 | 36 | For example classes in a `class` attribute can be targeted independently. Each character of a text node can be transformed independently. 37 | You can append/content/prepend on an element without conflicts! 38 | 39 | However this happens under the hood, see: 40 | 41 | ```clj 42 | (at 43 | "div" (class "important" :important) 44 | "div" (class "footnote" :foot-note)) 45 | ``` 46 | 47 | Those two transformations won't conflict because they refine their selector. 48 | 49 | ### Point-free 50 | 51 | Templates take a single argument which is the data model to render. 52 | 53 | Transformations don't take as arguments the actual values but keys or paths into the model. 54 | 55 | ```clj 56 | => ((static-template 57 | (enliven.html.jsoup/parse "