I have a main function declaration which accepts command line arguments
(defn -main [& args]
(let [options (cli/parse-opts args [["-k", "--key KEY","Secret key"]
["-u", "--user USER", "User name"]])]
(println user key)))
I am trying to load this in lein repl for debugging, but unable to figure out how to pass arguments over REPL.
I have tried multiple methods to pass the args over repl including
(-main "key-val" "user-val") (prints nil nil)
(-main ("key-val" "user-val")) (prints nil nil)
I tried multiple failing attempts as well trying to pass a list or vector which throw cast errors.
you have defined params with keys "-k" and "-u", but haven't passed the keys to the function, so the parser won't be able to guess what do you expect it to get. The working version is like this:
user> (defn -main [& args]
(let [options (cli/parse-opts args [["-k", "--key KEY","Secret key"]
["-u", "--user USER", "User name"]])
{:keys [key user]} (:options options)]
[key user]))
#'user/-main
user> (-main "-k" "key-val" "-u" "user-val")
;;=> ["key-val" "user-val"]
user> (-main "--key" "key-val" "--user" "user-val")
;;=> ["key-val" "user-val"]
First, note that user and key are not bound to result of parse-opts.
parse-opts returns a map where these options are under the key :options.
Let's bind the result as follows:
(defn -main [& args]
(let [options (parse-opts args [["-k", "--key KEY","Secret key"]
["-u", "--user USER", "User name"]])
user (get-in options [:options :user])
key (get-in options [:options :key])]
(println user key)))
Your parse-opts call specifies that you expect named arguments, i.e., options to parse. A call like the following works from REPL:
(-main "-k a" "-u b") ; prints "b a"
Note that if you specify positional arguments, they are under the key :arguments. After changing the example to print the value bound to options ((println options)), your example call would work like so:
(-main "key-val" "user-val") ; {:arguments [key-val user-val] :options {} ...}
So the call was made without options, that is, without specifying named arguments.
Related
I'm using multimethods to parse command line commands and their arguments.
(defmulti run (fn [command args] command))
(defmethod run :default
[& _]
...)
^{:args "[command]"}
(defmethod run "help"
[_ & [args]]
"Display command list or help for a given command"
...)
^{:args ""}
(defmethod run "version"
[_ & [args]]
"Print program's version"
...)
(defn -main
[& args]
(run (first args)
(next args)))
When I try to access the metadata, for a specific method, clojure returns nil:
(meta ((methods run) "help"))
There's no such possibility. The first reason (straightforward one) is that defmethod doesn't provide an ability to set metadata for a particular method (only defmulti allows that, but only for the whole multimethod). Second reason is that multimethod is essentially a single function, just with multiple "variants" of execution, each of which fires depending on passed parameters. Rougly speaking, from caller point of view, there's no particular difference between functions f1 and f2 defined below:
(defmulti f1 (fn [x] x))
(defmethod f1 :foo [x]
...)
(defmethod f1 :bar [x]
...)
(defmethod f1 :baz [x]
...)
(defn f2 [x]
(case x
:foo ...
:bar ...
:baz ...))
Personally, I'd consider depending on whether particular function is multimethod or ordinary function as relying on implementation details. Also if you need to explicitly document each method of multimehod, you should consider replacing each method with ordinary function and don't use multimethods at all.
I am trying to separate my cli options into a stand-alone namespace for starting up an HTTP server, and I am getting this error-
clojure.lang.ArraySeq cannot be cast to java.lang.CharSequence
In main.clj, this code works fine-
(ns served.main
(:require [org.httpkit.server :refer [run-server]]
[served.app.core :refer [handler]]
[served.server.cli-options :refer [set-options]]
[clojure.tools.cli :refer [parse-opts]])
(:gen-class))
(def cli-options
[
["-p" "--port PORT" "Port number"
:default 5000
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
])
(defn -main [& args]
(println "Server starting")
(let [options (get (parse-opts args cli-options) :options)]
;;(let [options (set-options args)]
(println (str options))
(run-server handler options)))
It will work with the default options in (def cli-options) and it compiles correctly if I pass in arguments, such as -p 7000.
When I call the main function with the external namespace served.server.cli-options instead of clojure.tools.cli directly (i.e. switch the comment in main), I get the error only when passing in args.
That is, starting the server without arguments, e.g. lein run compiles fine and will print out the defaults. The error comes with lein run -p 7000.
After deleting (def cli-options) in main to avoid any global conflict, here is served.server.cli-options
(ns served.server.cli-options
(:require [clojure.tools.cli :refer [parse-opts]]))
(def cli-options
[
["-p" "--port PORT" "Port number"
:default 5000
:parse-fn #(Integer/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
])
(defn set-options [& args]
(let [options (get (parse-opts args cli-options) :options)]
(println (str options))
options))
So far as I can tell, I copied the contents to the new namespace correctly. Here are the docs for parse-opts, here is the example that I am drawing from, and a similar but different SO issue here.
My question - how are the CLI args being transformed to throw casting error, and how do I fix it?
Any help would be greatly appreciated.
Delete the & in:
(defn set-options [& args]
& wraps up any additional arguments in a seq. Since you’ve already wrapped the program arguments once in main, you mustn’t do it again in the call to set-options.
I'm having a hard time getting the multimethods in Clojure to work as I would expect. A distillation of my code is as follows.
(defn commandType [_ command] (:command-type command))
(defmulti testMulti commandType)
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
(testMulti "something" {:command-type :one})
(commandType "something" {:command-type :one})
Now I would expect here to have the method commandType called on the arguments which would of course return :one which should send it to the first defmethod but instead I get a null pointer exception. Even the simplest invocation of a multimethod I could come up with gives me a null pointer:
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
(simpleMulti {:key "basic"})
And yet the example in the clojure docs located here works fine. Is there something fundamental I'm doing wrong?
So far as I can see, it works.
Given
(defmulti testMulti (fn [_ command] (:command-type command)))
(defmethod testMulti :one [game command] (str "blah"))
(defmethod testMulti :default [& args] "Cannot understand")
then
(testMulti "something" {:command-type :one})
; "blah"
(testMulti "something" {:command-type :two})
; "Cannot understand"
(testMulti "something" 5)
; "Cannot understand"
as expected.
I reset the REPL before running the above afresh.
And the simple example works too. Given
(defmulti simpleMulti :key)
(defmethod simpleMulti "basic" [params] "basic value")
then
(simpleMulti {:key "basic"})
; "basic value"
hopefully this is something simple for the more experienced out there. I am using clj-http and trying to pass the command line arg int it (to take a URL). I am an absolute Clojure beginer but I have managed to pass the args through to a ptintln which works.
(ns foo.core
(:require [clj-http.client :as client]))
(defn -main
[& args]
(def url (str args))
(println url)
(def resp (client/get url))
(def headers (:headers resp))
(def server (headers "server"))
(println server))
Error message
Ants-MacBook-Pro:target ant$ lein run "http://www.bbc.com"
("http://www.bbc.com")
Exception in thread "main" java.net.MalformedURLException: no protocol: ("http://www.bbc.com")
This works
(def resp (client/get "http://www.bbc.com"))
thanks in advance.
args is a list, which means that calling str on it returns the representation of the list, complete with parentheses and inner quotes, as you can see in your error trace:
(println (str '("http://www.bbc.com")))
;; prints ("http://www.bbc.com")
Of course, URLs don't start with parentheses and quotes, which is why the JVM tells you your URL is malformed.
What you really want to pass to get is not the string representation of your argument list, but your first argument:
(let [url (first args)]
(client/get url)) ;; Should work!
In addition, you should never use def calls within functions -- they create or rebind vars at the toplevel of your namespace, which don't want.
What you should be using instead is let forms, which create local variables (like url in my example). For more information on let, look at http://clojure.org/special_forms.
I'd probably structure your code like so:
(defn -main
[& args]
(let [url (first args)
resp (client/get url)
server (get-in resp [:headers "server"])]
(println url)
(println server)))
I get this Compilation Error saying : Invalid token: function-name
(defn execute-task-from-message
"Parses the message dictionary, gets the function-name and arguments-list
and applys the function on the arguments"
[{function-name: "function-name"
arguments-list: "arguments-list"} msg]
(when-let [task (ns-resolve
*ns*
(symbol
(str task-namespace function-name)))]
(apply task arguments-list)))
What's going wrong with my code ?
some extra :s in your argument destructuring pehaps? If you include a bit of the stack trace and a bit more context I may be able to be more specific:
user> (def task-namespace "where-does-this-come-from")
#'user/task-namespace
user> (defn execute-task-from-message
"Parses the message dictionary, gets the function-name and arguments-list
and applys the function on the arguments"
[{function-name "function-name"
arguments-list "arguments-list"} msg]
(when-let [task (ns-resolve *ns*
(symbol
(str task-namespace function-name)))]
(apply task arguments-list)))
#'user/execute-task-from-message