tags:

views:

80

answers:

3

Hi all,

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.

+1  A: 

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*.

kotarak
A: 

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\")
"
jandot
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.
Michał Marczyk
+2  A: 

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 "$@"
Jürgen Hötzel
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.
jandot
If you already use Leiningen compile/jar is just another task.
Jürgen Hötzel
jandot