Clojure: Template with Enlive - clojure

I copied a very basic sample from https://github.com/cgrand/enlive, but it doesn't compile:
(ns web.handler
(:require
[compojure.core :refer :all]
[compojure.handler]
[compojure.route :as route]
[net.cgrand.enlive-html :as html]))
;; Compiler throws in the next line, see the message below.
(html/deftemplate main-template "templates/index.html"
[]
[:head :title] (html/content "Enlive starter kit"))
(defroutes app-routes
(GET "/" [] "Hello")
(GET "/ping/:what" [what] (str "<h1>Ping '" what "'</h1>"))
(route/resources "/")
(route/not-found "Not Found"))
(def app
(compojure.handler/site app-routes))
Error I get:
java.lang.NullPointerException, compiling:(handler.clj:9:1)
I run with command:
lein ring server-headless
How to make it work?
EDIT
My investigation so far: error throw from enlive-html.clj:54:
(defn tagsoup-parser
"Loads and parse an HTML resource and closes the stream."
[stream]
(filter map?
(with-open [^java.io.Closeable stream stream]
(xml/parse (org.xml.sax.InputSource. stream) startparse-tagsoup)))) ; #54
Probably org.xml.sax is not referenced? How can I do this with lein?

That error normally happens when the template file is not found. With templates/index.html, it is looking in the resources/templates directory or in the src/templates directory.

Related

How do I invoke a resolved function in a Compojure view?

I have the following code as a test:
(defn test-fn [] (println "This is a test"))
(def my-test "test")
((resolve (symbol (str my-test "-fn"))))
Which runs as I would expect producing This is a test.
So I put it inside of a separate view like so:
(ns my-test.template-views
(:require
[hiccup.core :refer :all]
[hiccup.page :refer :all]
[my-test.home-views :refer :all]
[my-test.page1-views :refer :all]))
(defn template-body
[uri]
(html5 {:lang "en"}
[:body
(let [the-page (if (> (count uri) 1)
(clojure.string/replace uri #"/" "")
"home")]
((resolve (symbol (str the-page "-body")))))]))
Which gets called from Compojure like this:
(ns the-test.reporting-dashboard
(:gen-class)
(:require
[the-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]
))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defroutes app
(wrap-request
(GET "/" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (home-views/home-body (:uri request))}))
(wrap-request
(GET "/foo" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
When I call https//mydomain.tld/foo I get a java.lang.NullPointerException:
user=> Sat Apr 24 17:59:16 MDT 2021 [worker-2] ERROR - GET /foo
java.lang.NullPointerException
at the-test.template_views$template_body.invokeStatic(template_views.clj:14)
at the-test.template_views$template_body.invoke(template_views.clj:12)
at the-test.my_test$fn__269541.invokeStatic(my_test.clj:49)
at the-test.my_test$fn__269541.invoke(my_test.clj:46)
at compojure.core$wrap_response$fn__269102.invoke(core.clj:158)
at compojure.core$wrap_route_middleware$fn__269086.invoke(core.clj:128)
at compojure.core$wrap_route_info$fn__269091.invoke(core.clj:137)
at compojure.core$wrap_route_matches$fn__269095.invoke(core.clj:146)
at the-test.my_test$wrap_request$fn__269531.invoke(my_test.clj:22)
at compojure.core$routing$fn__269110.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2705)
at clojure.core$some.invoke(core.clj:2696)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__269114.invoke(core.clj:192)
at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
What is going on that this function is no longer able to be called when in Compojure?
Not sure about the root cause, but I was able to find a similar, minimal example and a workaround.
I created a simple project with lein new app demo, then replaced the code for core.clj with:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((resolve 'capitalize) "hello")))
Running this with lein run crashes:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Syntax error (NullPointerException) compiling at (/tmp/form-init1769060018979063159.clj:1:73).
null
Full report at:
/tmp/clojure-17609112051049758700.edn
The error report shows something about an error in Compiler.java so maybe it's some lazy initialization issue or just a bug. It seems to me the var cannot be resolved at the time you are attempting to call it as a function, hence the NullPointerException.
A workaround would be to use ns-resolve instead:
(ns demo.core
(:require [clojure.string :refer :all]))
(defn -main
[& args]
(println ((ns-resolve 'clojure.string 'capitalize) "hello")))
The above works as expected:
$ lein run
WARNING: reverse already refers to: #'clojure.core/reverse in namespace: demo.core, being replaced by: #'clojure.string/reverse
WARNING: replace already refers to: #'clojure.core/replace in namespace: demo.core, being replaced by: #'clojure.string/replace
Hello
See if you can replace your usage of resolve in the view with ns-resolve if you know the namespace where the symbol needs to be looked-up.

Clojure and Compojure: Response Map is nil

I'm using compojure for a basic web app, I have this code in core.clj:
(defroutes routes
(GET "/" [] (layout/application "Home" (contents/index)))
(route/resources "/"))
(def application (handler/site routes))
(defn -main []
(let [port (Integer/parseInt (or (System/getenv "PORT") "8090"))]
(jetty/run-jetty application {:port port :join? false})))
When I access the 0.0.0.0:8090 everything is loading ok, but I keep seeing this error:
java.lang.NullPointerException: Response map is nil
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:100)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:95)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.adapter.jetty$proxy_handler$fn__337.invoke(jetty.clj:27)
at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:503)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.base/java.lang.Thread.run(Thread.java:844)
Any idea what's going on?
#Svante is almost certainly correct. An easy way to verify is to use the spyx function from the Tupelo library:
(ns demo.core
(:use tupelo.core))
(defroutes routes
(GET "/" [] (spyx (layout/application "Home" (contents/index))))
(route/resources "/"))
which will print something like:
(layout/application "Home" (contents/index))) => nil
when run. spyx ("spy explicit") prints the expression you give it, an arrow, and the expression value. spy, spyx, spy-pretty, etc also return the value printed (unlike println which always returns nil) so you can insert a spy printout anywhere without disrupting the processing chain. Thus, you don't need to write something like:
(defroutes routes
(GET "/" [] (let [tmp-1 (layout/application "Home" (contents/index))]
(println "layout/application result => " tmp-1)
tmp-1)))
(route/resources "/"))
in order to get a debug message printed. In order to spy & friends, add this to the :dependencies in your project.clj:
[tupelo "0.9.138"]
Update
Hmmmm.... Not sure what could be the problem. I made a simple demo app from lein new compojure demo-compojure with the following:
(ns demo-compojure.handler
(:use tupelo.core)
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defn index []
(spy :index--result "Hello World"))
(defroutes app-routes
(GET "/" [] (spyx (index)))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
and results:
~/expr/demo-compojure > lein ring server
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Started server on port 3000
:index--result => "Hello World"
(index) => "Hello World"
So that is working. Also, int? is a basic Clojure function, so that is puzzling. Perhaps make a clean demo project like the above and take it from there?

Clojure require namespace: "Don't know how to create ISeq from: clojure.lang.Keyword"

I'm trying to split code in 2 files, each with it's own namespace. Following this tutorial.
But I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword
I think it's because the namespace being included is not recognised properly.
Main file:
(ns mytest2.handler
(:use compojure.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]
[mytest2.views :as foo] ;<-- line causing error
[hiccup.core :refer (html)])
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(defroutes app-routes
(GET "/" [] (main-page))
(route/resources "/")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
; (println (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))
Second file:
(ns mytest2.views
:require [hiccup.core :refer (html)]
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(note I copied the functions from mytest2.views in mytest2.handler for testing. They're not supposed to be in mytest2.handler).
Paths of files:
/mytest2/src/mytest2/handler.clj
/mytest2/src/mytest2/views.clj
(where first mytest2 is the name of the project, and the second is part of the path- automatically created by lein).
As you see in the first file I printed the class path to verify that /mytest2/src/mytest2/ is included, and yes it is.
Received the same error from trying to use :refer :all in Clojurescript, which apparently is against the rules.
You missed some brackets in your original code
;; wrong
(ns mytest2.views
:require [hiccup.core :refer [html]])
There is just one pair of brackets missing. Do it as in your Main file:
;; Done right!
(ns mytest2.views
(:require [hiccup.core :refer [html]]))
I am not familar with Compojure so I do not know what you have to require. But you need to add the bracket around :require.

Compojure app not playing well with with-redefs

I'm writing a Compojure application and am using clj-webdriver to graphically test it. I'm trying to use with-redefs to mock out the function that pulls out data from persistence to just return canned values, but it's ignoring my function overwrite. I know with-redefs works in terms of vars, but it's still not working:
project.clj relevant pieces:
(defproject run-hub "0.1.0-SNAPSHOT"
:main run-hub.handler/start-server)
handler.clj:
(ns run-hub.handler
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[ring.adapter.jetty :refer :all]
[run-hub.controllers.log-controller :as log-controller]))
(defroutes app-routes
(GET "/MikeDrogalis/log" [] (log-controller/mikes-log))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site #'app-routes))
log-controller.clj:
(ns run-hub.controllers.log-controller
(:require [run-hub.views.log :as views]
[run-hub.persistence :as persistence]))
(defn mikes-log []
(views/mikes-log (persistence/mikes-log)))
persistence.clj
(ns run-hub.persistence
(require [clj-time.core :as time]
[run-hub.models.log :as log]))
(defn mikes-log [] [])
And finally, my graphical test - which tries to override mikes-log and fails:
(fact
"It has the first date of training as August 19, 2012"
(with-redefs [persistence/mikes-log (fn [] (one-week-snippet))]
(to (local "/MikeDrogalis/log"))
(.contains (text "#training-log") "August 19, 2012"))
=> true)
Where one-week-snippet is a function that returns some sample data.
(defn start-server []
(run-jetty (var app) {:port 3000 :join? false}))
I am able to use with-redefs in a clj-webdriver test doing the following:
(defn with-server
[f]
(let [server (run-jetty #'APP {:port 0 :join? false})
port (-> server .getConnectors first .getLocalPort)]
(binding [test-port port]
(try
(println "Started jetty on port " test-port)
(f)
(finally
(.stop server))))))
(use-fixtures :once with-server)
Then the whole bunch of tests gets its own jetty and this seems to run in
such a manner that with-redefs works.

Weird lein uberwar behavior

I'm executing lein uberwar for my test webapp and I'm getting the following weird error:
Exception in thread "main" java.lang.ClassCastException: java.lang.Boolean cannot be cast to clojure.lang.Symbol (servlet.clj:1)
The servlet.clj contains the following:
(ns test.servlet
(:use ring.util.servlet)
(:require test.routes :as routes)
(:gen-class :extends javax.servlet.http.HttpServlet))
(defservice routes/app-routes)
The test.routes file contains:
(ns test.routes
(:use compojure.core)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes app-routes
(GET "/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body "<h1>Hello World</h1>"})
(route/files "/" {:root "static"})
(ANY "/:s" [s] (str "page-not-found" s)
;; For lein ring-server
;(def test-handler
; (handler/site app-routes))
The strange thing is that if I substitute the line
(:require test.routes :as routes)
With:
(:require test.routes)
And call
(defservice test.routes/app-routes)
In servlet.clj, the WAR compiles fine and it is working flawlessly in tomcat. Is there some bug in my code that I fail to see? Why do I need to call test.routes/... instead of just routes/...?
You need to put it inside a vector:
(:require [test.routes :as routes])