Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Browse files

major update to template processor, ready for userspacing

  • Loading branch information...
commit 5b06f1872234b074718dc1566644b1145915e3ba 1 parent 0543ce7
@jeyoor authored
@@ -8,7 +8,7 @@ Here's a user template:
Here's a user script that uses the above template:
- [#name] (content bob)
+ [#name] (content `bob`)
After substitution, the HTML looks like this:
@@ -18,7 +18,7 @@ A user script consists of a series of transformation declarations. Each
such declaration begins with a css selector, within square brackets
"[]", which selects elements of the template to which a transformation
should be applied. next is a transformation function and its arguments,
-enclosed within parentheses "()".
+enclosed within parentheses "()". Text arguments should be backquoted.
The transformation function specifies what type of transformation to
apply to the selected nodes. For example the content function replaces
25 doc/
@@ -60,3 +60,28 @@ of these?
Can we provide generic support for defining pipelines of transformations
and merges?
+--- JDO 27 Feb 2012
+The skeleton for a new template processor is in place.
+This new processor should allow new pipelines such as those above to be constructed with less work.
+The template processing engine operates on a template in several steps.
+1. Replace special characters to prepare for passing to read-string
+ A. Escape literal double-quotes with backslash
+ B. Convert backquotes to double-quotes
+2. Use read-string on the template (converts text to Clojure data structures)
+3. Iterate over the data structures and apply the transformations to the HTML template
+ A. Analyze the current transformation
+ a. Place the transformation in the correct namespace
+ b. Add calls to html-snippet to convert html text into html nodes
+ B. Apply the transformation using eval
+Once more features are added to the "user-space" namespace, the templating language should be ready for basic work.
+These features include:
+1. Control structures (branch/iteration)
+2. Redis fetching
+3. Access to the user's HTTP request map
18 resources/templates/
@@ -1,17 +1,17 @@
-[title](content Testify Web App)
-[nav#topnav ul] (append
-<li><a href="/">Pages</a></li>
+~title~ (content `Testify Web App`)
+~nav#topnav ul~ (append
+`<li><a href="/">Pages</a></li>
<li><a href="/admin">Admin</a></li>
- <li><a href="/dashboard">Dashboard</li>
+ <li><a href="/dashboard">Dashboard</li>`
-[nav#sidenav] (append
+~nav#sidenav~ (append `
<li><a href="/next">Next Page</a></li>
<li><a href="/prev">Previous Page</a></li>
- <li><a href="/page/form">Add a page</a></li>
+ <li><a href="/page/form">Add a page</a></li>`
-[header#banner] (content <div style="height: px; background-color: olive; margin: auto;"><h1>Ad Banner</h1></div>)
+~header#banner~ (content `<div style="height: px; background-color: olive; margin: auto;"><h1>Ad Banner</h1></div>`)
- (content <p>Copyright &copy; 2012 the Testify Development Team</p>)
+ (content `<p>Copyright &copy; 2012 the Testify Development Team</p>`)
15 resources/templates/
@@ -0,0 +1,15 @@
+~title~ (content "testing")
+~header#banner~ (content "bobab")
+~nav#topnav ul~ (append "
+<li><a href='/'>Pages</a></li> "
+~nav#sidenav~ (append "
+ <li><a href='/next'>Next Page</a></li>
+ "
+ (content " <p>Copyright &copy; 2012 the Testify Development Team</p> ")
2  resources/templates/
@@ -0,0 +1,2 @@
+~title~ (fetch `page:PRAISES:token`)
+~nav#topnav ul~ (append `<li><a href="/">Pages</a></li>`)
5 src/testify/debug.clj
@@ -3,9 +3,14 @@
[redis.core :as redis]
[testify.appear :as appear]
[testify.persist :as persist]
+ [testify.process.transform :as transform]
[clojure.string :as string]
[net.cgrand.enlive-html :as html]))
+(defn testout
+ []
+ (transform/bulk-transform "base.html" ""))
(defn echo [params]
(str "<h1> hello </h1>" params "<br />" (get params
142 src/testify/process/transform.clj
@@ -1,39 +1,51 @@
(ns testify.process.transform
[clojure.set :as set]
+ [clojure.pprint]
[clojure.contrib.string :as string]
+ [clojure.contrib.str-utils :as str-utils]
[net.cgrand.enlive-html :as html]
+ [testify.remain :as remain]
[testify.process.fileops :as fileops]))
-;;;;Parsing functions
+;;;;Lexing functions
-(defn split-names
- "for every"
- [list]
- (loop [number 0 savelist nil worklist list]
- (if (empty? worklist)
- (partition 3 (reverse savelist))
- (if (even? number)
- (recur (+ number 1) (cons (first worklist) savelist) (rest worklist))
- (recur (+ number 1) (list* (re-find #"(?s)\s+.*" (first worklist))(re-find #"\w* " (first worklist)) savelist) (rest worklist))))))
+(defn escape-quotes
+ "Replace literal quotes with backslash-quote"
+ [str]
+ (str-utils/re-gsub #"\"" "\134\134\"" str))
+(defn replace-backquotes
+ "change backquotes to double-quotes"
+ [str]
+ (str-utils/re-gsub #"`" "\"" str))
+(defn pre-read
+ "apply transformations needed before passing the strings to the reader"
+ [str]
+ (replace-backquotes (escape-quotes str)))
;;TODO: more idiomatic here?
(defn accum-alternates
- "iterate over a list, saving second, fourth, etc items"
+ "iterate over a list, saving evens and reading odds"
- (loop [number 0 savelist nil worklist list]
+ (loop [even 0 savelist nil worklist list]
(if (empty? worklist)
(reverse savelist)
- (if (even? number)
- (recur (+ number 1) savelist (rest worklist))
- (recur (+ number 1) (cons (first worklist) savelist) (rest worklist))))))
+ (if (zero? even )
+ (recur (bit-xor even 7) (cons (first worklist) savelist) (rest worklist))
+ (recur (bit-xor even 7) (cons (read-string (pre-read (first worklist))) savelist) (rest worklist))))))
(defn lexify-transforms
- "take a string of user transforms, return a list of args to the at macro"
+ "take a string of user transforms, return a list of args to the at macro"
- ;;ignore the odd entries
- ;;split on [,],(, and )
- (split-names (accum-alternates (string/split #"[\[|\(|\]|\)]" str))))
+ ;;use rest because the first string is always empty [opening tilde]
+ (partition 2 (accum-alternates (rest (string/split #"~" str)))))
+;;;;Operator designations
+(def transforms #{'append 'prepend 'before 'after 'substitute 'content})
+(def remains #{'fetch 'store 'hfetch 'hstore})
+(def alloweds (set/union transforms remains))
;;;;Transformation primitives
@@ -43,12 +55,26 @@
[name args]
`(apply (load-string ~name) (list ~args)))
-(defn need-nodes?
- "Check enlive fun name for needed args-to-nodes conversion."
+(defn allowed-sym?
+ "Check if a symbol is allowed in the script"
;;funs that require conversion from text args to HTML nodes
- (let [conv-funs #{"append" "prepend" "before" "after" "substitute" "content"}]
- (set/subset? #{(string/trim name)} conv-funs)))
+ (set/subset? #{name} alloweds))
+(defn need-snippet?
+ "Check if item is a string contains html and thus needs to be converted to nodes"
+ [item]
+ ;;funs that require conversion from text args to HTML nodes
+ (and (string? item) (and (.contains item "<") (.contains item ">"))))
+(defn find-space
+ "Given a symbol, return a string of the namespace from whence it came"
+ [sym]
+ (cond
+ (set/subset? #{sym} transforms) "net.cgrand.enlive-html/"
+ (set/subset? #{sym} remains) "testify.remain/"
+ :else "testify.process.user-space"))
(defn process-selector
"Process a string to a proper enlive selector"
@@ -57,43 +83,50 @@
;;TODO: account for advanced tricks like text-node
(map keyword (string/split #"\s" str)))
-(defmacro process-call
- "Take a function name string and an argument string and return the proper enlive helper function"
- [funstr argstr]
- ;TODO: Code this instead of the hornet's nest below :-)
- `(let [funame# ~funstr
- args# ~argstr]))
-(defn process-fname
- "Take a function name string and return the appropriate Clojure symbol"
- [fname]
- (load-string (str "net.cgrand.enlive-html/" (.trim fname))))
+(defn prep-item
+ "Prepare one code item"
+ [item]
+ ;;Switch on type
+ (cond
+ (symbol? item) (if (allowed-sym? item)
+ ;;Prefix all symbols with correct namespace
+ (symbol (str (find-space item) item))
+ ;;Not allowed symbol, replace with identity
+ (symbol "identity"))
+ ;;Add html-snippet conversion if text contains html
+ (string? item) (if (need-snippet? item)
+ (list (symbol "net.cgrand.enlive-html/html-snippet") item)
+ item)
+ :else item))
+(defn prep-code
+ "Take a sequence of clojure code and prep it for evaluation"
+ [seq]
+ (map prep-item seq))
(defn single-transform
"take one user-script transformation statement and apply it to the given HTML nodes"
[nodes transform]
(let [selector (process-selector (first transform))
- funame (second transform)
- fusym (process-fname (second transform))
- args (nth transform 2)]
- ;;TODO: check and transform args based on function used
- (cond
- ;;check for content, append, prepend,
- (need-nodes? funame)
- (html/transform
- nodes (vec selector)
- (fusym (html/html-snippet args)))
- :else
- (html/transform
- nodes (vec selector)
- (fusym args)))))
+ code (prep-code (second transform))]
+ ;;(println (apply str code))
+ ;;(println code)
+ ;;(clojure.pprint/pprint code)
+ (html/transform
+ nodes (vec selector)
+ ;;For good examples on using eval, see
+ ;;
+ (eval code))))
;;;;Transformation operations
(defn make-transform
- "apply user transformation script to some html nodes"
+ "apply user transformation script to some html nodes"
[nodes #^java.lang.String script]
(let [transes (lexify-transforms script)]
+ ;;TODO add try/catch with "error line #" messages here
(reduce single-transform nodes transes)))
(defn bulk-transform
@@ -106,11 +139,10 @@
-(defn test-transform
- [#^java.lang.String html #^java.lang.String transform ]
- (make-transform (html/html-snippet (slurp html)) (slurp transform)))
(defn test-lex
"test the lexical analysis"
[#^java.lang.String transform]
- (lexify-transforms (slurp transform)))
+ (lexify-transforms (slurp (fileops/find-template transform))))
+(defn other-test
+ "test loading a whole file" [])
18 src/testify/process/user_space.clj
@@ -0,0 +1,18 @@
+(ns testify.process.user-space)
+(defn find-funs
+ "return a map of public namespace mappings"
+ [ns] nil )
+(defn assoc-here
+ "given one mapping, define a copycat function within this namespace"
+ [mapping] nil)
+(defmacro make-fun
+ [name value]
+ `(def ~name ~value))
+(make-fun hcontent #'net.cgrand.enlive-html/content)
+(make-fun happend #'net.cgrand.enlive-html/append)
+(make-fun hsnippet #'net.cgrand.enlive-html/html-snippet)
+(make-fun rfetch #'testify.remain/fetch)
1  src/testify/routes.clj
@@ -52,6 +52,7 @@
(GET "/on_dev" [] (str (middleware/development?)))
+ (GET "/test" [] (debug/testout))
(GET "/template" [] (template/list-template))
(GET "/" [] (page/list-page) )
(route/resources "/")
Please sign in to comment.
Something went wrong with that request. Please try again.