I have a small macro which takes a series of cmds as clojure s expressions and returns their string representations. I am having trouble passing an argument to the macro -
(defmacro cmd [& cmds]
(->> (for [v cmds]
(join " " v))
(join "; ")))
(defn install-jive [version]
(cmd
(yum remove jive-add-ons)
(yum install jive-add-ons ~version)))
(install-jive 1.2)
I want the fn to return the following -
(install-jive 1.2) => "yum remove jive-add-ons; yum install jive-add-ons 1.2"
However currently it returns -
"yum remove jive-add-ons; yum install jive-add-ons
(clojure.core/unquote version)"
You need to think about what forms you need your macro to generate. For example in your case you might want the macro to generate something like this after expansion:
(defn install-jive [version]
(clojure.string/join
";"
[(clojure.string/join " " (list "yum" "remove" "jive-add-ons"))
(clojure.string/join " " (list "yum" "install" "jive-add-ons" version))]))
The macro transformed the first 3 elements on the list in a String and didn't touch version. Here is a macro that do that for one form:
(defmacro single-cmd [cmd-form]
`(clojure.string/join " " (list ~#(map str (take 3 cmd-form))
~#(drop 3 cmd-form))))
Expanding this macro generates this:
(macroexpand '(single-cmd (yum remove jive-add-ons version)))
=> (clojure.string/join " " (clojure.core/list "yum" "remove" "jive-add-ons" version))
(macroexpand '(single-cmd (yum remove)))
=> (clojure.string/join " " (clojure.core/list "yum" "remove"))
Which is what we needed for each command to expand into. Now we need to create a macro that expands this:
(cmd
(yum remove jive-add-ons)
(yum install jive-add-ons version))
Into the form that we want. This is a simple version, just to give you the idea:
(defmacro cmd [& cmds]
`(clojure.string/join
";"
(list (single-cmd ~(first cmds))
(single-cmd ~(second cmds)))))
Expanding it generates:
(macroexpand '(cmd
(yum remove jive-add-ons)
(yum install jive-add-ons version)))
=> (clojure.string/join
";"
(clojure.core/list (user/single-cmd (yum remove jive-add-ons))
(user/single-cmd (yum install jive-add-ons version))))
And now:
(install-jive 1.2)
=> "yum remove jive-add-ons;yum install jive-add-ons 1.2"
My version makes two big assumptions: the first three elements should always be converted to String and you always call cmd with two, and only two, commands. You will probably need to improve that to solve your problem.
But I think this gives you a nudge in the right direction. There is a great book Mastering Clojure Macros that can help you develop your macros skills.
(defmacro cmd [& cmds]
`(join "; "
[~#(for [cmd cmds]
`(join " "
[~#(for [arg cmd]
(if (and (seq? arg) (= (first arg) 'clojure.core/unquote))
(second arg)
(list 'quote arg)))]))]))
If you compare to the original code, you'll see that this one defers calls to join until runtime and turns unquote forms as expressions in the resulting command template.
Related
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.
I have a command like:
(clojure.java.shell/sh "curl" "https://somesite.com" "-F" "file=#somefile.txt")
and I want to create a function that will run standard shell commands I supply... something like this:
(defn run-shell-command [command]
(clojure.java.shell/sh (clojure.string/split command #" ")))
so that I could call it like this:
(run-shell-command "curl https://somesite.com -F file=#somefile.txt")
but this throws:
Unhandled java.lang.IllegalArgumentException
No value supplied for key: ["curl" "https://somesite.com" "-F" "file=#somefile.txt"]
How do I make this work?
(of course, open to better ideas for how to do this)
You can also use the underlying command processor to process the string. E.g. using bash:
(clojure.java.shell/sh "bash" "-c" command)
...wrap it in a function and hide the command processor if you wish:
(defn run-shell-command [command]
(clojure.java.shell/sh "bash" "-c" command))
then you can use any bash shell commands. E.g.
(run-shell-command "for i in $(ls); do echo $i; done")
Your question is a common one amongst beginner lispers: If we have a function accepting arguments, how can we apply the function on a list of these arguments?
(defn f [a b c] (+ a b c))
(def args [1 2 3])
The answer is, as hinted above, the apply method. The method applies a function to a list of arguments. So in the example above:
(= (f 1 2 3)
(apply f args))
i.e
(defn run-shell-command [command]
(apply clojure.java.shell/sh (clojure.string/split command #" ")))
Using clojure.test, when running lein test, the default settings only print out the number of assertions, like "Ran 12 tests containing 19 assertions," the details for failed tests, and the namespaces tested. I would like an output of the successful tests as well, so I can see what tests were actually run. Frameworks like Mocha in JS have this behavior by default.
For instance, if the following test passed:
(deftest aTest
(testing "Simple test"
(is (= 1 1))))
I would like an output like (formatting is arbitrary):
Testing <namespace>
Passed: aTest -> Simple test
Ideally, the passed test would also be color coded.
I looked at a few libraries, including lein-test-refresh and humane-test-output, but didn't see what I needed. The other option looks like to rewrite the report function in clojure.test, but I'd like to avoid that if possible.
I think you'll have to write it yourself. If you would like an example of how to leverage the existing deftest into a custom macro, here is an example from the Tupelo library that automatically picks a "name" for the deftest group based on the line number:
(defmacro dotest [& body]
(let [test-name-sym (symbol (str "dotest-line-" (:line (meta &form))))]
`(clojure.test/deftest ~test-name-sym ~#body)))
(dotest
(is (= 5 (+ 2 3)))) ; works!
You could just add in a println and voila!
I remember, the Eftest library provides nice colored output when running tests, you may take a look.
Also, the standard clojure.test framework supports adding your own reports. Check the Test report library for the reference.
Probably, when running the tests from your editor or IDE, it could provide the colored output. Here is my Cider-powered Emacs screenshot with the failure report:
I have searched multiple times for an answer to this question, so here is a complete solution building on #ivan-grishaev's hint using Test Report:
Test Report hooks into clojure.test to provide custom reporters that are then being run with every event occuring through a test run. Those events are passed as maps called "messages". The minimal example just appliesclojure.pprint/pprint to the messages, but this only gets you so far. Here is a bit more code to give you a feel how to write custom reporters. The indenting-reporter indents test namespaces, deftests and testing vars, and lists them all independent of test outcome.
Create a file like this in your project:
(ns my.own.test-reporter
(:require [clojure.pprint :refer [pprint]]
[clojure.test :as ct]
[clojure.core.match :refer [match]]))
(defn simple-reporter [msg]
(pprint msg))
(defn indenting-reporter [msg]
(match (:type msg)
:begin-test-ns (println (str "Testing " (:ns msg) "\n"))
:begin-test-var (println (str " " (-> msg :var meta :name)))
:pass (do (println (str " " (-> msg :context first) " :pass"))
(ct/inc-report-counter :pass))
:fail (do (println (str " " (-> msg :context first) " :fail"))
(ct/inc-report-counter :fail))
:error (do (println (str " " (-> msg :context first) " :error"))
(ct/inc-report-counter :error))
:end-test-ns (println)
:end-test-var ()
:summary (pprint msg)
:else (pprint msg)))
and then use it in the :test profile of your project.clj:
:injections [(require '[my.own.test-reporter :as tr])]
:test-report {:reporters [tr/indenting-reporter]}
You could now go on an add color to the outputs.
I am trying to better understand listing 13.3 in The Joy of Clojure. It is a macro that generates other macros (much like how primitive array functions are implemented in Clojure 1.4).
I want to write a macro that, when run, simply prints the suffix of the generated macro. i.e.
user=> (nested-macro joe)
user=> (nested-macro-named-joe)
hello from joe
nil
I am having trouble making this work.
Here is what I've tried:
Attempt 1
(defmacro nested-macro [name]
`(defmacro ~(symbol (str "nested-macro-named-" name))
[]
`(println "hello from " ~name)))
Output:
hello from #<core$name clojure.core$name#502c06b2>
Attempt 2
(defmacro nested-macro [name]
(let [local-name name]
`(defmacro ~(symbol (str "my-custom-macro-named-" ~local-name))
[]
`(println "hello from " ~local-name))))
Error
IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote clojure.lang.Var$Unbound.throwArity (Var.java:43)
Attempt 3:
(defmacro nested-macro [name]
(let [local-name name]
`(defmacro ~(symbol (str "nested-macro-named-" name))
[]
`(println "hello from " ~(symbol local-name)))))
Compiler Error:
CompilerException java.lang.RuntimeException: No such var: joy.dsl/local-name
Just for the heck of it, I've also tried adding # to the local variables, with similar results as above but with "auto" names, such as local-name__1127__auto__ I don't see that being as part of the solution, however.
How can I make this work?
To know what is going wrong with macros I always use macroexpand-1.
From your first example:
(macroexpand-1 '(nested-macro joe))
Results in:
(clojure.core/defmacro nested-macro-named-joe []
(clojure.core/seq
(clojure.core/concat
(clojure.core/list (quote clojure.core/println))
(clojure.core/list "hello from ")
(clojure.core/list clojure.core/name))))
If you look at the last param, it shows that you are using clojure.core/name, which probably is not what you want, as you actually want the parameter named "name".
To fix it, just add another unquote to the name param, but as the name param is actually a symbol, what you really want is to get the name of it as in:
(defmacro nested-macro [the-name]
`(defmacro ~(symbol (str "nested-macro-named-" the-name))
[]
`(println "hello from " ~~(name the-name))))
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.