4

I'm attempting to write a library to do some domain-specific stuff. I'd like to add some scripts to this library that can be run straight from the commandline.

Directory layout:

+- project.clj
+- src/
|   +- my-lib.clj
|   +- my-lib/
|        +- my-sub-1.clj
+- scripts/
    +- run-command.sh

The src/my-lib.clj file loads the my-lib/my-sub-1.clj file:

(ns my-lib
  (:use [my-lib.my-sub-1])
)

The src/my-lib/my-sub-1.clj file contains the functions I want to make available. One of these functions is called "convert" and takes two arguments: an input filename and an output filename; it converts the fileformat. To use it:

(convert "input-file.txt" "output-file.txt")

The (do-something-interesting) and (convert) functions in src/my-lib/my-sub-1.clj look like this:

(defn do-something-interesting
  [input-file output-file]
  (magic-happens-here input-file output-file))

(defn convert
  [args]
  (let [infile (first args)
        outfile (second args)]
    (do-something-interesting infile outfile)))

My goal at the moment: to create a script "run-command.sh" in the "scripts" directory that takes two arguments: the input filename and output filename. It should be possible to run the script with:

./run-command.sh input-file.txt output-file.txt

I do have run-command.sh working, as long as I hard-code the filenames in that script by using the (do-something-interesting) function instead of (convert). I haven't been able yet to read from the argument list...

The run-command.sh script that works:

#!/bin/sh 
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(do-something-interesting "path-to-input-file" "path-to-output-file")
"

... but what doesn't work:

#!/bin/sh 
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(convert *command-line-args*)
"

The error I get is:

Exception in thread "main" java.lang.IllegalArgumentException: No implementation of method: :reader of protocol: #'clojure.contrib.io/Streams found for class: nil (NO_SOURCE_FILE:0)
at clojure.lang.Compiler.eval(Compiler.java:5435)
at clojure.lang.Compiler.eval(Compiler.java:5386)
at clojure.core$eval.invoke(core.clj:2382)
at clojure.main$eval_opt.invoke(main.clj:235)
at clojure.main$initialize.invoke(main.clj:254)
at clojure.main$null_opt.invoke(main.clj:279)
at clojure.main$main.doInvoke(main.clj:354)
at clojure.lang.RestFn.invoke(RestFn.java:422)
at clojure.lang.Var.invoke(Var.java:369)
at clojure.lang.AFn.applyToHelper(AFn.java:165)
at clojure.lang.Var.applyTo(Var.java:482)
at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalArgumentException: No implementation of method: :reader of protocol: #'clojure.contrib.io/Streams found for class: nil
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:471)
at clojure.contrib.io$eval32$fn__33$G__23__38.invoke(io.clj:118)
at bioclojure.vcf$header.invoke(vcf.clj:22)
at bioclojure.vcf$column_header.invoke(vcf.clj:55)
at bioclojure.vcf$column_names.invoke(vcf.clj:61)
at bioclojure.vcf$vcf2tsv.invoke(vcf.clj:169)
at bioclojure.vcf$convert.invoke(vcf.clj:185)
at user$eval474.invoke(NO_SOURCE_FILE:3)
at clojure.lang.Compiler.eval(Compiler.java:5419)
... 11 more

I've tried "use"ing clojure.contrib.io within the script file run-command.sh itself, and within the top library file my-lib.clj, but no luck so far...

If anyone could help me out that'd be great.

jan.

3 Answers 3

3

You should consider using the "gen-class" feature to handle command line arguments in a compiled Clojure/Java function, instead of on-the-fly evaluating Clojure code via Clojures main/repl function:

(ns commandline
  (:gen-class))

(defn -main [& args]
  (convert args))

Use lein jar to create the Jar file of your Application and pass command line arguments in your shell skript to the main function:

#!/bin/bash

java -cp "../lib/*":../YOURPROJECT.jar:$CLASSPATH commandline "$@"
Sign up to request clarification or add additional context in comments.

3 Comments

Hi Jurgen, I've tried to stay away from gen-class before because it adds an additional level of complexity for someone not knowing java as myself. But this looks simple enough :-) Will try it out.
If you already use Leiningen compile/jar is just another task.
This does the job marvelously. The my-lib.clj file now looks like this: (ns my-lib (:use [my-lib.my-sub-1]) (:gen-class) ) (defn -main [command & args] (case command "help" (println "Some help message") "convert" (do-something-useful (first args) (second args)))) Then a "lein compile" and "lein uberjar". This allows me to make multiple commands available based on the same jar file. I know I should rewrite the -main a bit to handle nils, or just use [& args], but thanks!
1

That's because you don't specify cli arguments. You have to call java .... clojure.main some-script.clj a b c. Then a, b and c will be contained in *command-line-args*.

Comments

0

I found the solution thanks to kotarak's suggestion. In run-command.sh I need to refer to the arguments using the bash-way instead of the clojure way. And I don't even need that separate (convert) function.

What the script looked like:

#!/bin/sh 
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(convert *command-line-args*)
"

What it should look like:

#!/bin/sh 
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(do-something-interesting \"$1\" \"$2\")
"

1 Comment

That's still not quite the right way to do it. Consider what will happen if $1 is a string consisting of a single double-quote character. kotarak's answer means that your script should pass the name of your .clj script and all its command line arguments as regular CLI args to the java command after the clojure.main argument. These will then be passed to the main method of the clojure.main class, which will in turn run the named script while arranging for the subsequent CLI args to be gathered in the *command-line-args* Var.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.