I am moving from [org.clojure/tools.cli "0.1.0"] to 0.2.2, but am getting
Exception in thread "main" clojure.lang.ArityException:
Wrong number of args (2) passed to: PersistentVector
at the line beginning with (cli args
(defn parse-opts
"Using the newer cli library, parses command line args."
[args]
(cli args
["--ifn1" ".csv input file" :default "benetrak_roster.csv"]
["--ifn2" ".csv input file" :default "billing_roster.csv"]
["--rpt" ".csv pipe delimited output file" :default "bene_gic_rpt.csv"]
["--dump1" "text file report for debug output" :default "dumpfile1.txt"]
["--dump2" "text file report for debug output" :default "dumpfile2.txt"]
["--debug" "Debug flag for logging." :default 0 :parse-fn #(Integer. %)]))
tools.cli is included like this (:use clojure.tools.cli).
I can't see what I'm doing wrong, and would appreciate any pointers or help. Thanks.
By the way, I've tried the following from looking at examples, and it does not work:
(defn -main
[& args]
(let [[opts args banner]
(cli args
["--ifn1" ".csv input file" :default "benetrak_roster.csv"]
["--ifn2" ".csv input file" :default "billing_roster.csv"]
["--rpt" ".csv pipe delimited output file" :default "bene_gic_rpt.csv"]
["--dump1" "text file report for debug output" :default "dumpfile1.txt"]
["--dump2" "text file report for debug output" :default "dumpfile2.txt"]
["--debug" "Debug flag for logging." :default 0 :parse-fn #(Integer. %)])
start-time (str (Date.))]
.
.
.
This seems to work:
(ns test.core
(:use clojure.tools.cli))
(defn parse-opts
"Using the newer cli library, parses command line args."
[args]
(cli args
["--ifn1" ".csv input file" :default "benetrak_roster.csv"]
["--ifn2" ".csv input file" :default "billing_roster.csv"]
["--rpt" ".csv pipe delimited output file" :default "bene_gic_rpt.csv"]
["--dump1" "text file report for debug output" :default "dumpfile1.txt"]
["--dump2" "text file report for debug output" :default "dumpfile2.txt"]
["--debug" "Debug flag for logging." :default 0 :parse-fn #(Integer. %)]))
test.core> (parse-opts [])
[{:debug 0, :dump2 "dumpfile2.txt", :dump1 "dumpfile1.txt", :rpt "bene_gic_rpt.csv", :ifn2 "billing_roster.csv", :ifn1 "benetrak_roster.csv"} [] "Usage:\n\n Switches Default Desc \n -------- ------- ---- \n --ifn1 benetrak_roster.csv .csv input file \n --ifn2 billing_roster.csv .csv input file \n --rpt bene_gic_rpt.csv .csv pipe delimited output file \n --dump1 dumpfile1.txt text file report for debug output \n --dump2 dumpfile2.txt text file report for debug output \n --debug 0 Debug flag for logging. \n"]
test.core>
Are you sure the error isn't in whatever you're passing to parse-opts?
Also: are you sure you've got the right version (and ONLY the right version) of tools.cli in your project.clj?
Related
Following this response: Org-Mode - How do I create a new file with org-capture?
I am trying to make this piece of code work, but I get the error: invalid file location: nil.
(defun capture-report-data-file (path)
(let ((name (read-string "Name: ")))
(expand-file-name (format "%s-%s.txt"
(format-time-string "%Y-%m-%d")
name) path)))
'(("t"
"todo"
entry
(file (capture-report-date-file "~/path/path/name"))
"* TODO")))
Actually, it works like this:
Replace
(file (capture-report-date-file "~/path/path/name"))
with
(file (lambda () (capture-report-date-file "~/path/path/name")))
I'm an absolute Clojure beginner and I'm trying to build a CLI app using the clojure.tools.cli library.
My problem is that I can't show any error when an option is not provided with required parameter.
What I want:
$ java -jar test.jar -m SAMPLE
Given file: SAMPLE
$ java -jar test.jar -m
ERROR: Please provide a file
What happens:
$ java -jar test.jar -m SAMPLE
Given file: SAMPLE
$ java -jar test.jar -m
$
It doesn't show anything.
Here is my code:
(ns obmed-clj.core
(:require [clojure.tools.cli :refer [parse-opts]])
(:gen-class))
(def cli-options
[["-m" "--menu FILE" "Provide menu file path"
:parse-fn #(if (nil? %)
(println "ERROR: Please provide a file")
%)
:validate-fn #(println "Given file:" %)]])
(defn -main [& args]
(parse-opts args cli-options))
You are abusing the -fn arguments here a little. Their use is to convert the "string" (in your case, since you have "--menu FILE") and then do additional validation on that (but rather use :validate [fn msg] instead). So e.g.:
user=> (def cli-opts [["-m" "--menu FILE" "menu file"
:parse-fn #(java.io.File. %)
:validate [#(.exists %) "file must exist"]]])
#'user/cli-opts
Missing argument:
user=> (parse-opts ["-m"] cli-opts)
{:arguments [],
:errors ["Missing required argument for \"-m FILE\""],
:options {},
:summary " -m, --menu FILE menu file"}
File not existing:
user=> (parse-opts ["-m" "XXX"] cli-opts)
{:arguments [],
:errors ["Failed to validate \"-m XXX\": file must exist"],
:options {},
:summary " -m, --menu FILE menu file"}
All is well:
user=> (parse-opts ["-m" "/etc/hosts"] cli-opts)
{:arguments [],
:errors nil,
:options {:menu #<java.io.File#34d63c80 /etc/hosts>},
:summary " -m, --menu FILE menu file"}
Is there a simple text encyptor for Clojure which just needs one password to decrypt? I just want something like to encrypt:
(encrypt "Some secret message" "Some secret key")
:and to decrypt:
(decrypt (encrypt "Some secret message" "Some secret key") "Some secret key")
:will return:
"Some secret message"
I decided this functionality would be helpful for a project I am working on.
Aside from the standard clojure, it also requires commons-codec.
project.clj:
(defproject <project> "VERSION"
...
:dependencies [[org.clojure/clojure "1.5.1"]
[commons-codec "1.8"]])
<project>/src/crypt.clj:
(ns <whatever>.crypt
(:import (javax.crypto KeyGenerator SecretKey Cipher SecretKeyFactory)
(javax.crypto.spec SecretKeySpec PBEKeySpec)
(org.apache.commons.codec.binary Base64)))
(def ^:dynamic *salt* "BIND SALT IN APP")
(defn cipher- [] (. Cipher getInstance "AES"))
(defn aes-keyspec [rawkey] (new SecretKeySpec rawkey "AES"))
(defn encrypt-
[rawkey plaintext]
(let [cipher (cipher-)
mode (. Cipher ENCRYPT_MODE)]
(. cipher init mode (aes-keyspec rawkey))
(. cipher doFinal (. plaintext getBytes))))
(defn decrypt-
[rawkey ciphertext]
(let [cipher (cipher-)
mode (. Cipher DECRYPT_MODE)]
(. cipher init mode (aes-keyspec rawkey))
(new String(. cipher doFinal ciphertext))))
(defn passkey
[password & [iterations size]]
(let [keymaker (SecretKeyFactory/getInstance "PBKDF2WithHmacSHA1")
pass (.toCharArray password)
salt (.getBytes *salt*)
iterations (or iterations 1000)
size (or size 128)
keyspec (PBEKeySpec. pass salt iterations size)]
(-> keymaker (.generateSecret keyspec) .getEncoded)))
(defn encrypt
[password plaintext]
(encrypt- (passkey password) plaintext))
(defn decrypt
[password cyphertext]
(decrypt- (passkey password) cyphertext))
usage:
(binding [crypt/*salt* "THE SALT WE ARE USING"]
(crypt/encrypt "password" "message")
(crypt/decrypt "password" *1)))
if you can use generated keys, that is going to be more secure:
(defn aes-keygen [] (. KeyGenerator getInstance "AES"))
(defn genkey
[keygen]
(. keygen init 128)
(. (. keygen generateKey ) getEncoded))
(def generated (keygen aes-keygen))
(encrypt- generated plaintext)
(decrypt- generated *1)
This is only using vanilla security features provided with the jvm (commons-codec is just used for base-64 encoding/decoding so that we can operate on arbitrary input, it is not part of the security setup).
Try simple-crypto:
[org.clojars.tnoda/simple-crypto "0.1.0"]
It do exactly what you want:
user=> (use 'org.clojars.tnoda.simple-crypto)
user=> (decrypt (encrypt "Some secret message" "Some secret key!") "Some secret key!")
"Some secret message"
I'm not aware of any Clojure library that is doing that mostly -I would say- because it is too easy to do using javax.crypto, javax.crypto.spec and java.security packages.
Could be done within 30 lines.
Newbie Clojure and leiningen question:
Given the code snippet in my project below, this works from the lein repl :
==> (-main "something")
produces the expected "Command: something ... running ... done"
but doesn't work from the command line:
me pallet1]lein run "something"
produces "Command: something ... error: not resolved as a command"
Why? / how do I fix it?
To reproduce:
lein new eg
Then edit the generated project file, adding :main eg.core to define the main function, and edit the generated src/eg/core.clj file, and paste this in:
core.clj
(ns eg.core)
(defn something [] (println "Something!"))
(defn run-command-if-any [^String commandname]
(printf "Command: %s ..." commandname)
(if-let [cmd (ns-resolve *ns* (symbol commandname))]
(
(println "running ...") (cmd) (println "done.")
)
(println "error: not resolved as a command.")
))
(defn -main [ commandname ] (run-command-if-any commandname))
Then
lein repl
eg.core=> (-main "something")
works (ie prints "Something!) , but
lein run something
doesn't (ie prints the "error: not resolved" message)
The problem is that when you run it from lein your default namespace is "user" namespace:
(defn -main [ commandname ] (println *ns*))
Prints #<Namespace user>. So it doesn't contain something function because it is from another namespace. You have several choices:
Pass fully qualified function name: your-namespace/something instead of something.
Use your-namespace instead of *ns*: (ns-resolve 'your-namespace (symbol commandname))
Change namespace to your-namespace in -main.
Example of method 3:
(defn -main [ commandname ]
(in-ns 'your-namespace)
(run-command-if-any commandname))
Also you if you want to call several functions one by one you should use do:
(do (println "Hello")
(println "World"))
Not just braces like ( (println "hello") (println "World"))
the lein exec plugin is very useful for scripting such things in the context of a project. I have used this extensively for writing Jenkins jobs in clojure and other scripting situations
lein exec -pe '(something ...) (something-else) (save-results)'
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.
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 "$#"
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*.
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\")
"