How does Enlive evaluate its rules / transformations? - clojure

I like Enlive, but it got me somewhat confused when I observed the following.
Consider the following Clojure code (also available on github):
(ns enlivetest.core
(:require [net.cgrand.enlive-html :refer [deftemplate defsnippet] :as html]))
(deftemplate page "index.html"
[ctx]
[:.foobar] (html/content (do (println "GENERATING FOOBAR")
"===FOOBAR===")))
and this HTML template (resources/index.html) here:
<!DOCTYPE html>
<html>
<body>
</body>
</html>
When calling the page template, I'd expect it to ignore the right hand side of its rule (the transformation) completely, as there is no HTML tag that matches the rule's selector :.foobar.
However, as it turns out, the right hand side of the rule does in fact get evaluated:
user=> (require '[enlivetest.core :as c])
nil
user=> (c/page {})
GENERATING FOOBAR
GENERATING FOOBAR
("<!DOCTYPE html>\n" "<" "html" ">" "\n " "<" "body" ">" "\n " "</" "body" ">" "\n\n" "</" "html" ">")
(Obviously, it even gets evaluated twice - once for each root HTML element in the template as it seems).
But why is it being evaluated at all, although there is no element matching the selector? Is this correct behaviour? Am I missing something obvious here?
This example uses Enlive 1.1.6, just as its README suggests.
Clarifications are greatly appreciated.
EDIT #1:
As it turns out (thanks to #leetwinski), my assumption of how things work was incorrect:
I was assuming that the deftemplate macro would only evaluate the right hand side of a rule (the transformation part) when the selector of that rule matches an element in the given HTML.
But correct is this:
The right hand side of a rule will always get evaluated during a call to the defined template function (e.g. page) and is expected to evaluate to a function that will in turn evaluate to the desired content (e.g. "===FOOBAR===" in this example) when called. It is this function that will get called only for elements that match the selector.
This means that e.g. html/content evaluates to such a function (and not to the desired content directly).
In order to make things work as I expected originally, I could write it like this:
(deftemplate page "index.html"
[ctx]
[:.foobar] #((html/content (do (println "GENERATING FOOBAR")
"===FOOBAR===")) %))
which will result in the following output:
user=> (c/page {})
("<!DOCTYPE html>\n" "<" "html" ">" "\n " "<" "body" ">" "\n " "</" "body" ">" "\n\n" "</" "html" ">")
or when adding a <div class="foobar"></div> to the HTML template:
user=> (c/page {})
GENERATING FOOBAR
("<!DOCTYPE html>\n" "<" "html" ">" "\n " "<" "body" ">" "\n\t\t" "<" "div" " " "class" "=\"" "foobar" "\"" ">" "===FOOBAR===" "</" "div" ">" "\n " "</" "body" ">" "\n\n" "</" "html" ">")
EDIT #2:
It's been a few weeks, but I'm still struggeling with how this is implemented in Enlive. I see myself wrapping the transformation parts of rules into #((html/content ...) %) over and over again.
Does anybody have an explanation for why Enlive evaluates transformations (at all or even multiple times) even when they are not even relevant for the current rendering process?
I might be overlooking something, as I'm really surprised that this doesn't seem to bother anybody but me.

The reason is the nature of enlive's deftemplate macro:
it takes pairs of selector-to-function. In yours, the function is generated dynamically here:
(html/content (do (println "GENERATING FOOBAR") "===FOOBAR==="))
content just creates the function, that would be called in case of the match.
user> ((html/content "this" "is" "fine") {:content []})
{:content ("this" "is" "fine")}
content is not a macro, so it should evaluate it's argument.
so, what you see, is not the false matching function call, rather it is the call to a generation of the function that would be called in case of the match.
you could easily see it with macroexpansion of your deftemplate form:
(def page
(let*
[opts__8226__auto__
(merge (html/ns-options (find-ns 'user)) {})
source__8227__auto__
"index.html"]
(html/register-resource! source__8227__auto__)
(comp
html/emit*
(let*
[nodes29797
(map
html/annotate
(html/html-resource
source__8227__auto__
opts__8226__auto__))]
(fn*
([ctx]
(doall
(html/flatmap
(fn*
([node__8199__auto__]
(html/transform
(html/as-nodes node__8199__auto__)
[:.foobar]
(html/content
(do
(println "GENERATING FOOBAR")
"===FOOBAR===")))))
nodes29797))))))))
so the correct string in println would be:
(deftemplate page "index.html"
[ctx]
[:.foobar] (html/content (do (println "GENERATING FUNCTION SETTING FOOBAR AS THE NODE CONTENT")
"===FOOBAR===")))
The behaviour you expect could be achieved this way:
user>
(deftemplate page "index.html"
[ctx]
[:.foobar] (fn [node] (assoc node :content
(do (println "GENERATING FOOBAR" node)
"===FOOBAR==="))))
#'ttask.core/page
user> (page {})
("<!DOCTYPE html>\n" "<" "html" ">" "\n " "<" "body" ">" "\n " "</" "body" ">" "\n\n" "</" "html" ">")
and if you add class "foobar" to body in index.html it would do this (don't forget to re-run deftemplate after changing html):
user> (page {})
GENERATING FOOBAR {:tag :body, :attrs {:class foobar}, :content []}
("<!DOCTYPE html>\n" "<" "html" ">" "\n " "<" "body" " " "class" "=\"" "foobar" "\"" ">" "=" "=" "=" "F" "O" "O" "B" "A" "R" "=" "=" "=" "</" "body" ">" "\n\n" "</" "html" ">")

Related

Concatenate all fields within map

In clojure, I want to be able to concatenate all the fields(with a separator) in a map for each map in a list.
For the following result I want to be able to get:
(def h '({:key1 "one" :key2 "descone"} {:key1 "two" :key2 "destwo"}))
The result I want:
({one,descone} {two,destwo})
I did the following but I could not get the correct result
(def h '({:key1 "one" :key2"descone"} {:key1 "two" :key2"destwo"}))
(~#(apply interpose "," (map (juxt :key1 :key2) h))))
And I am getting the following instead:
one,desconetwo,destwo
Edit
The scenario is as follows: We use jdbc to make get all the records from postgres. The records are returned like this: ({:col1 "one" :col2 "descone"} {:col1 "two" :col2 "destwo"}). Now, we need to concatenate all the columns with a separator and have this as primary key and insert it back into a new table in postgres.
I'm assuming you want to return a string since you talk about a comma as a separator. I further assume that when you say "all the fields" you mean "all the values of each key-value pair", and that each value is a string. In that case, the following gives you what you wanted.
user> (str "(" (clojure.string/join " " (map #(str "{" (clojure.string/join "," (vals %)) "}") h)) ")")
"({one,descone} {two,destwo})"
user>
What the code is doing is first taking the values of each map and concatenating them with a comma separator and then enclosing each map with an open and close brace. Then it is taking each such string and concatenating them with a space character and then enclosing the whole result with an open and close paren.
EDIT: To match the edit to the question.
Since you want to generate a primary key value that consists of all the value of a database row, what I'm assuming you want to return is a sequence of strings. Where each string is a concatenation, with a separator, of all the values of each map in a specific order. Assuming that is correct, here is a modified answer.
user> (map #(str "{" (clojure.string/join "," ((juxt :key1 :key2) %)) "}") h)
("{one,descone}" "{two,destwo}")
user>

How to chain function calls in Clojure?

Imagine I have a string which I want to transform as follows:
Remove all spaces.
Remove all dots.
Make the string lower-case.
One way to do it is this:
(defn my-function
[s]
(let
[
clean-string1 (clojure.string/replace s " " "")
clean-string2 (clojure.string/replace clean-string1 "." "")
clean-string3 (clojure.string/lower-case clean-string2)
]
;; ...
)
)
How can I "chain" the functions clojure.string/replace and clojure.string/lower-case so that
the output of (clojure.string/replace s " " "") is fed to the input of
(clojure.string/replace clean-string1 "." "") and its output is fed to the input of
(clojure.string/lower-case clean-string2)
so that I don't need intermediate variables clean-string1 and clean-string2?
You just do it the same way you would in any language. You're asking for function composition, which in math or non-lisp languages looks like f(g(x)). In lisp of course that's (f (g x)), but the principle is the same.
(require '[clojure.string :as s])
(s/lower-case (s/replace (s/replace s " " "") "." ""))
is the most straightforward answer. But it's rather unpleasant to have this level of nesting where the function names are so far removed from their extra arguments, and so most people would instead write
(-> s
(s/replace " " "")
(s/replace "." "")
(s/lower-case))
which is the same thing but just using -> to shuffle the forms around a bit for clarity.

How to expand HugSQL parameter into multiple like statements

Does anyone know how this can be accomplished?
(get-lists ["free" "food"]) ->
Select name
From Lists
Where name like '%free%' and name like '%food%'
I have tried:
-- :name get-lists :? :*
Select id, name
from Lists
where
--~ (clojure.string/join "" (interpose " AND " (map #(str "name LIKE '%" % "%'") :sKeyWords)))
But of course that does not work. Can someone point me in the right direction please?
To form a query you can also use clojure.pprint/cl-format from clojure's standard lib, which is quite powerful and concise:
user> (require '[clojure.pprint :refer [cl-format]])
user> (cl-format nil "WHERE~{ name LIKE '%~a%' ~^AND~}"
["me" "you" "somebody"])
;;=> "WHERE name LIKE '%me%' AND name LIKE '%you%' AND name LIKE '%somebody%' "
Figured out answer. Putting here for anyone else who might need it.
-- :name get-lists :? :*
Select id, name
from Lists
--~ (str "WHERE "
(clojure.string/join " AND " (map #(str "name LIKE '%" % "%'")
(:key-words params))))

In clojure enlive how to create template to add values and keys from a map

I want to create a template with Clojure's Enlive for a "table" like html page, that should have a header and rows. I want to populate this "table" with data that comes from this map. Basicaly I want to populate header with keys, and cells with vals from each of the maps that come from :event-data key.
(def dummy-content
{:title "Events Mashup"
:event-data [{ :event-name "event name 1"
:performer "performer 1"
:date "date 1"
:start-time "start time 1"
:end-time "end time 1"}
{:event-name "event name 2"
:performer "performer 2"
:date "date 2"
:start-time "start time 2"
:end-time "end time 2"}]})
My snippets and template look like this:
(defsnippet header-cell "index.html" [:.psdg-top-cell] [value] [:.psdg-top-cell]
(defsnippet value-cell "index.html" [:.psdg-right] [value] [:.psdg-right]
(deftemplate mshp "index.html" [content]
[:.psdg-top] (append (for [c (keys content)] (header-cell c)))
[:.psdg-right] (append (for [v (vals content)] (value-cell v))))
And index.html has these tags, that are rellevant for this template.
<div id="psdg-top">
<div class="psdg-top-cell" style="width:129px; text-align:left; padding- left:24px;">Summary</div>
<div class="psdg-top-cell">Website.com</div>
</div>
<div class="psdg-right">10 000</div>
When I call the template I get this error:
=> (mshp (:event-data dummy-content))
ClassCastException clojure.lang.PersistentHashMap cannot be cast to java.util.Map$Entry clojure.lang.APersistentMap$ValSeq.first (APersistentMap.java:183)
What am I doing wrong?
The error is occurring when you call (keys content), because (:event-data dummy-content) returns a vector, and keys won't work on a vector. You've got two options - you can either define your header columns elsewhere, or just take them from the first element in the vector, like this: (keys (first content)).
Edit
I've had a go at replicating what you set out in the question, but fixing the errors, and you can find my efforts here: https://www.refheap.com/17659.
If you want something that has a more typical table structure, with the value cells contained in rows, you can try the following. It uses clone-for rather than a combination of append and for, and uses nth-of-type to just return the first "psdg-top-cell" (otherwise there would be twice as many)
(def template
(html-snippet
"<div id=\"psdg-top\">
<div class=\"psdg-top-cell\">Summary</div>
<div class=\"psdg-top-cell\">Website.com</div>
</div>
<div class=\"psdg-right\">10 000</div>"))
(defsnippet header-row template [[:.psdg-top-cell (nth-of-type 1)] ] [headers]
(clone-for [h headers]
(content h)))
(defsnippet value-row template [:.psdg-right] [values]
(clone-for [v values]
(content v)))
(deftemplate mshp template [events]
[:#psdg-top] (content (header-row (map name (keys (first events)))))
[:.psdg-right] (clone-for [e events]
(do->
(wrap :div {:class "psdg-row"})
(content (value-row (vals e))))))

Binding a local var in deftemplate for enlive

I'm brand new to clojure and the web development stack. I'm trying to use enlive to set values in an HTML template:
(en/deftemplate project-main-page
(en/xml-resource "project-main.html")
[id]
[:#project-name] (en/content (str "Name: " ((get-project id) :name)))
[:#project-desc] (en/content (str "Desc: " ((get-project id) :desc))))
This works fine to set my two HTML elements, but it involves a repeated call to my function get-project. At the moment this just reads from a local map, but eventually it will involve some external storage access, so I'd prefer to just perform it once in this function.
I was thinking of using let:
(en/deftemplate project-main-page
(en/xml-resource "project-main.html")
[id]
(let [project (get-project id)]
[:#project-name] (en/content (str "Name: " (project :name)))
[:#project-desc] (en/content (str "Desc: " (project :desc)))))
But this only affects the description element and ignores the name forms.
What is the best way to bind a local var within deftemplate?
If I have understood what you are trying to achieve; you could also try using the transformation macro provided by enlive.
(defn main-page [{:keys [name desc] :as project}]
(en/transformation
[:#project-name] (en/content (str "Name: " name)
[:#project-desc] (en/content (str "Desc: " desc))))
(en/deftemplate project-main-page
(en/xml-resource "project-main.html")
[id]
(main-page (get-project id)))
The code is untested, but I hope it conveys a different way to do what you need
Enlive's deftemplate macro expects a series of tag/content pairs after the args vector (the args vector is [id] in your example). You can't just stick a let in there because the macro isn't expecting a let form, so when it does its splicing everything gets messed up and results in the behavior you described above.
One way you could fix this would be to write your own deftemplate macro that allows binding definitions using the identifiers in the args vector. Example:
(alt/deftemplate project-main-page
(en/xml-resource "project-main.html")
[id]
[project (get-project id)]
[:#project-name] (en/content (str "Name: " (project :name)))
[:#project-desc] (en/content (str "Desc: " (project :desc))))
The deftemplate macro is a simple wrapper around template, which uses snippet* and this is probably where you'd need to insert your changes:
(defmacro snippet* [nodes args & forms]
`(let [nodes# (map annotate ~nodes)]
(fn ~args
; You could add let bindings here since args are in scope
(doall (flatmap (transformation ~#forms) nodes#)))))
The other option—which might be simpler since you don't have to muck around in the library code—would be to add a level of indirection to your get-project function to cache results. You might try the core.cache library.