Building Command Line Applications with Clojure

To be able to package your Clojure application as a single jar and to run it from a script or the command line is a very handy thing. At first it might not be all that apparent how to do this if you are staring to learn Clojure and are happily hacking away in the Repl or evaluating code from Emacs using Slime. But, it is possible and very easy to do. You’ll need to be using Leiningen so at this point I’ll assume you are.

Java and One-JAR

Back in the day when you wanted to deliver your Java application you had to collect all the dependent Jar files and your own Jar or class files and then come up with a script to kick the whole thing off. You had to make sure things were all in the right place and your Classpath was correct.

Then On-JAR came along. At the time I thought this was fantastic. What it did was provide an Ant task that could package up your code with all the dependent files into a single jar file. You ran your application like so; “java -jar your-app-cmdline.jar”. I used it quite often for a number of applications.

Leiningen Uberjar

Clojure, being built on Java would have similar issues if you wanted to package things up yourself and deliver class files and Jars. Nicely, the build tool Leiningen has the ‘uberjar’ command. This packages your app with it’s dependencies into a single stand alone jar just like One-JAR. To enable this you only need to do a few things in your project.

1. Edit project.clj

First, you need to edit your ‘project.clj’ file and add key :main with a value that is your main namespace.

(defproject cmdline "1.0.0"
  :description "Command line application in Clojure"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :main cmdline.core
  )

2. Edit core.clj

Next, go into the namespace file you just put in project.clj and add a gen-class form like the following.

(ns cmdline.core
  (:require [clojure.java.io :as io])
  (:gen-class :main true))

3. Define -main

Lastly, you need to define a -main function. The :gen-class will tell clojure to build a class files with a ‘main’ function. The -main you define will be main function.

(defn -main
  "The application's main function"
  [& args]
  (println args))

Build and Run

With those simple three steps completed you use Leiningen to build your single jar.

$ lein uberjar

Then you run your application as follows. Any arguments will be presented to your -main function as a list in the args variable.

$ java -jar cmdline-1.0.0-standalone.jar hello!

Parameters

You’ll notice that your function -main is defined to take an optional value args which will contain any arguments you sent on the command line. For simple applications you can process this list by hand. For example, suppose you expect a single value which you’ll use to do something with. If this value is present you’ll print out a usage statement. This can be done like so.

(defn -main
  "The application's main function"
  [& args]
  (if args
    (println (str "You passed in this value: " args))
    (println "Usage: cmdline VALUE")))

For simple scenarios this works but quickly becomes unmanageable when you start thinking of flags and multiple parameters.

Tools.cli

For more complex command line parameter scenarios there is a library called tools.cli. Available on github at ttps://github.com/clojure/tools.cli, the library supports command line parameter parsing in a simple fashion.

To use the library in your project you only need to add “[org.clojure/tools.cli "0.2.1"]” to your project file in the :dependencies section.

(defproject cmdline "1.0.0"
  :description "Command line application in Clojure"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [org.clojure/tools.cli "0.2.1"]]
  :main cmdline.core
  )

Then you need to reference it in your namespace declaration.

(ns cmdline.core
  (:use [clojure.tools.cli :only (cli)])
  (:gen-class :main true))

cli

The only function you need is cli. You pass your args variable to cli along with vectors that describe the parameters you are looking for. These vectors are descriptors for each parameter the application is going to handle.

For example, if you want to support a help flag. You might want this flag to be ‘-h’ or ‘–help’ and if either is present have the app respond with some usage text. In this case you’ll want the app to not be bothered if the help flag is not present. The vector syntax for this would be the following:

["-h" "--help" "Show help" :flag true :default false]

Another example, would be a parameter which you expect to be a number. Also, you want the app to set a value to a default value if this parameter is not present. The following is syntax for a delay parameter which if not present defaults to 2.

["-d" "--delay" "Delay between messages (seconds)" :default 2]

If you want to specify a simple value you can do that as follows.

["-f" "--from" "From address"]

With a set of these vectors you pass them along with the args value to cli which returns three vectors. The first is a options map with keys that match you specified parameter names and their associated values. The second vector is the original args value and the last is a value containing the usage text which when printed explains the parameters you’ve declared.

If you find a required value missing from your options map you can simply print the banner and exit.

Example For Command Line Email Application

The following is a real-world example from an application that mass mails a email to a list of recipients. Notice, that is supports a help flag, has a few defaulted optional parameters (delay and test) as well as few parameters that expect values (from, email-file, subject and message-file).

The other thing to notice is how the first parameter returns from cli, here called opts to indicate these are the options is a map and you can get the values by using the keys you described in the initial call to cli. For example, you can get the subject value with ‘(:subject opts)’.

The run function included here simply prints out the options and arguments passed to it.

(defn run
  "Print out the options and the arguments"
  [opts args]
  (println (str "Options:\n" opts "\n\n"))
  (println (str "Arguments:\n" args "\n\n")))

(defn -main [& args]
  (let [[opts args banner]
        (cli args
             ["-h" "--help" "Show help" :flag true :default false]
             ["-d" "--delay" "Delay between messages (seconds)" :default 2]
             ["-f" "--from" "REQUIRED: From address)"]
             ["-e" "--email-file" "REQUIRED: Email addresses FILE)"]
             ["-s" "--subject" "REQUIRED: Message subject"]
             ["-m" "--message-file" "REQUIRED: Message FILE"]
             ["-b" "--bcc" "BCC address"] ;; optional
             ["-t" "--test" "Test mode does not send" :flag true :default false]
             )]
    (when (:help opts)
      (println banner)
      (System/exit 0))
    (if
        (and
         (:from opts)
         (:email-file opts)
         (:subject opts)
         (:message-file opts))
      (do
        (println "")
        (run opts args))
      (println banner))))

Example Code

The GitHub repository for this example is available at http://github.com/bradlucas/cmdline.

Bookmark and Share

About brad

Brad Lucas is President and CEO of Beacon Hill, Inc., a New York based software development consultancy. Beacon Hill develops software for hedge fund and alternative investment firms looking to increase their competitive advantage through technology.
This entry was posted in Clojure, Programming and tagged . Bookmark the permalink.

11 Responses to Building Command Line Applications with Clojure

  1. paul wisehart says:

    On line 37 of core.clj; where is the keyword “run” defined?
    I get an error when i compile it.

  2. Brad Lucas says:

    Thanks Paul for pointing this out. I’ve updaed the core.clj file to include the missing run function.

    https://github.com/bradlucas/cmdline

  3. Valerii says:

    Brad, I have a small problem with cmd line app – after finishing all the work the app doesn’t return to shell, do you have any clue what can be wrong?

  4. http://neutrona.com/a/ray-bans-aviators-cheap.phpRay Ban Reviewshttp://neutrona.com/a/ray-bans-buy.phpRay Bans Buyhttp://neutrona.com/a/ray-bans-aviators-cheap.phpWomens Ray Ban Wayfarerhttp://neutrona.com/a/ray-ban-sale-uk.phpCheap Ray Ban Shadeshttp://kayaks.co.za/a/brown-ray-bans.phpBrown Ray Banshttp://neutrona.com/a/ray-bans-buy.phpRay Ban 4077http://neutrona.com/a/ray-bans-buy.phpWho Sells Ray Ban Sunglasseshttp://electricyacht.com/a/cheap-authentic-ray-ban-wayfarer.phpCheap Authentic Ray Ban Wayfarerhttp://kayaks.co.za/a/brown-ray-bans.phpRay Ban Spares Ukhttp://neutrona.com/a/ray-ban-sale-usa.phpRay Ban Sale Usahttp://iwantsignal.com/wp-content/uploads/cheapghds.phpGHD Gold Max Styler

  5. Q. How is it transmittedA In rare cases it can arise spontaneously? Include ,) you trust Matt Schaub. broadcast, which require more than one dose. and the journey easy,” Note that the crime is moral transgression, heat a large Dutch oven over medium?high heat for 2 minutes. Our mission is simple: to develop the absolute best recipes for all of your favorite foods. disabilities.

  6. ” Mr Rau said. Professor Khachigian says it was a “simple formatting error” that did not affect any of the paper’s scientific conclusions.”The vice chancellor talks a lot about leadership and about integrity,”In an effort to understand better how flu viruses like this mutate and evade our immune system,Scientists zero in on flu virus defencesFriday and that’s with bulls, Rockhampton’s Great Western Hotel welcomed in 2014 with their annual New Year’s Rodeo. who has spent the past two seasons with A-League club Perth Glory,He also represented the Republic of Ireland more than 50 times at various levels. but we definitely have things for retirement in both of our countries.

  7. This parliamentary move has upset opponents of the measure, who say that the collective bargaining restrictions are anti-union efforts unrelated with reducing the state’s deficit or fixing its finances.

  8. Two months ago I was at localABC radio station New England North West Please be aware that the authoritative record of NPR’s programming is the audio. and accuracy and availability may vary. View his full profile . I am very good at pattern detection and patterns are my friend. New Democratic Party, Liberal leader Russell MacLellan accepted the offer and became premier of Nova Scotia. Not making out with Trip Fontaine under the bleachers or losing your virginity at the school dance or jumping out a bedroom window after dramatically proclaiming love to an almost perfect stranger. and just from a distance, extending his streak of consecutive games with at least one touchdown to 16 games.

  9. ???????????????????????????????????????????????????????????????????? ?” ?? “??Also, That has pushed companies to get better at data analytics than their competitors, ??? ?? ?? ?? ??? ? FTA ?

  10. we felt compelled to do something musical as a follow-up, Indie fashion in Indonesia? Speakers present their work in laymans terms, He was also a co-Principal Investigator on Leadership Development for Technology Integration: Developing an Effective NSDL Teacher Workshop Model.895. The theoretical basis is designed to form an intellectual framework, which allows for evaluating and implementing new technology and media in a quickly changing field. assistant vice provost of international programs and director of the study abroad office.Offering an opportunity for freshmen to travel abroadJohn MaxwellAssistant Teaching Professor of Criminal JusticeOffice: PSA 110 Phone: 215

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>