Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
mtyaka committed Nov 9, 2010
0 parents commit 14e3059
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
pom.xml
*jar
lib
classes
122 changes: 122 additions & 0 deletions README.md
@@ -0,0 +1,122 @@
# lein-oneoff

Dealing with dependencies and the classpath can be a
pain. [Leiningen](http://github.com/technomancy/leiningen) takes most
of the pain away, but creating a new leiningen project for a simple
one-off script may sometimes feel like overkill. This is where
[lein-oneoff](http://github.com/mtyaka/lein-oneoff) comes in.

With the help of lein-oneoff you can open a file, declare
dependencies at the top and write the rest of the code as
usually. lein-oneoff will let you run the file, open a repl or start a swank
server while taking care of fetching dependencies and constructing the
classpath automatically.

## Usage

lein-oneoff scripts usually consist of a single file. Dependencies
should be stated at the top using the `defdeps` form. Here's an example:

(defdeps
[[org.clojure/clojure "1.2.0"]
[compojure "0.5.2"]
[ring/ring-jetty-adapter "0.3.3"]])

(ns example
(:use [compojure.core]
[ring.adapter.jetty :only [run-jetty]]))

(defroutes routes
(GET "/" [] "Hello world!"))

(def server
(run-jetty routes {:port 8080 :join? false}))

Save this file as `example.clj`, then run it with:

$ lein oneoff example.clj

This command will check the specified dependencies and install them
into the local maven repository (`~/.m2/repository`) unless already
installed, and then run `example.clj` with the necessary dependencies
in the classpath. Note that the dependencies are referenced directly
from the local maven repository.

### The defdeps form

The `defdeps` form must be the first form in the file. It has the following
signature:

(defdeps dependencies additional-entries?)

where dependencies should be specified as a vector using the same
syntax as inside regular leiningen `defproject` form under the
`:dependencies` key. The second argument is an optional map of
additional standard `defproject` entries. Please note that not
all of the available leinigen options make sense for a one-off script
and might not work correctly.

One of the entries that can be useful is the `:repositories` entry. Here's
an example:

(defdeps
[[org.clojure/clojure "1.3.0-alpha3"]
[org.apache.pivot/pivot-web "1.5.2"]]
{:repositories
{"apache" "https://repository.apache.org/content/repositories/releases/"}})

The `defdeps` form may be omitted in which case the only assumed
dependency is `org.clojure/clojure` of the same version as your leiningen
installation is using.

### repl

To start a repl in the context of a one-off script, use the `--repl`
command (or its shorter equivalent, `-r`):

$ lein oneoff --repl example.clj
$ lein oneoff -r example.clj

### swank

A swank server can be started with the `--swank` (or `-s`)
command:

$ lein oneoff --swank example.clj
$ lein oneoff -s example.clj

Please note that for the swank command to work, you'll need to have
`swank-clojure` installed as a global leiningen plugin. At the moment,
only `swank-clojure 1.3.0-SNAPSHOT` is supported.

### classpath

lein-oneoff offers an equivalent to leiningen's built-in `classpath`
task which prints the project's classpath for one-off scripts:

$ lein oneoff --classpath example.clj
$ lein oneoff -c example.clj

## Installation

This plugin should be installed as a user-level leiningen plugin. Using
leiningen 1.3.1, the easiest way to get going is to drop the
lein-oneoff jar into the `$HOME/.lein/plugins` folder. Leiningen 1.4.0
will come with a special task for installing user-level plugins.

lein-oneoff has been tested with leiningen 1.3.1 and 1.4.0-RC1.

### Windows notes

Windows lein.bat script that comes with leiningen 1.3.1 doesn't
support user-level leiningen plugins. This was fixed on the master
branch, so if you're using 1.3.1 on windows you might want to replace your
lein.bat file with [the latest
one](http://github.com/technomancy/leiningen/raw/master/bin/lein.bat)
and then change the version in the second line to 1.3.1.

## License

Copyright (C) 2010 Matjaz Gregoric

Distributed under the Eclipse Public License, the same as Clojure.
2 changes: 2 additions & 0 deletions project.clj
@@ -0,0 +1,2 @@
(defproject lein-oneoff "0.0.1"
:description "Dependency management for one-off scripts.")
149 changes: 149 additions & 0 deletions src/leiningen/oneoff.clj
@@ -0,0 +1,149 @@
(ns leiningen.oneoff
(:use [robert.hooke :only [add-hook]]
[leiningen.core :only [abort]])
(:require
[lancet]
[clojure.main]
[leiningen compile classpath repl deps])
(:import java.io.File))

(try
(require 'leiningen.swank)
(catch java.io.FileNotFoundException e))

(def lein-swank-ns (find-ns 'leiningen.swank))

(def swank-form-var
(when lein-swank-ns (ns-resolve lein-swank-ns 'swank-form)))

(def default-deps
`[[org.clojure/clojure ~(clojure-version)]])

(def defdeps-defmacro-form
`(defmacro ~'defdeps [& args#]))

(defn deps-classpath
"Resolves and installs dependencies to the local maven repository.
Returns a sequence of paths referencing jars in the repository."
[project]
(let [deps-task (leiningen.deps/make-deps-task project :dependencies)
_ (.execute deps-task)
fileset (.getReference lancet/ant-project
(.getFilesetId deps-task))
dir-scanner (.getDirectoryScanner fileset lancet/ant-project)
base-dir (.getBasedir dir-scanner)]
(for [fpath (.getIncludedFiles dir-scanner)]
(.getCanonicalPath (File. base-dir fpath)))))

(defn get-oneoff-classpath
"Returns a sequence of paths that constitute the full classpath
for a one-off project."
[project]
(concat [(:root project)]
(leiningen.classpath/user-plugins)
(deps-classpath project)))

(defn oneoff-deps-hook [deps project]
(when-not (:oneoff project) (deps project)))

(defn oneoff-get-classpath-hook [get-classpath project]
(if (:oneoff project)
(get-oneoff-classpath project)
(get-classpath project)))

(defn oneoff-eval-in-project-hook
[eval-in-project project form & [handler skip-auto-compile init]]
(let [skip-auto-compile (or (:oneoff project) skip-auto-compile)]
(eval-in-project project form handler skip-auto-compile init)))

(defn oneoff-repl-server-hook [repl-server project host port]
(let [server-form (repl-server project host port)]
(if (:oneoff project)
`(do ~defdeps-defmacro-form ~server-form)
server-form)))

(defn oneoff-swank-form-hook [swank-form project port host opts]
(let [server-form (swank-form project port host opts)]
(if (:oneoff project)
`(do ~defdeps-defmacro-form ~server-form)
server-form)))

(add-hook #'leiningen.deps/deps oneoff-deps-hook)
(add-hook #'leiningen.compile/eval-in-project oneoff-eval-in-project-hook)
(add-hook #'leiningen.classpath/get-classpath oneoff-get-classpath-hook)
(add-hook #'leiningen.repl/repl-server oneoff-repl-server-hook)

(when swank-form-var
(add-hook swank-form-var oneoff-swank-form-hook))

(defn parse-defdeps [script]
(let [contents (slurp script)
forms (load-string (str "(quote [" contents "])"))
form (first forms)]
(if (= (first form) 'defdeps)
[(nth form 1) (nth form 2 {})]
[default-deps {}])))

(defn oneoff-project [script]
(let [dir (System/getProperty "user.dir")
[deps opts] (parse-defdeps script)]
(merge
{:oneoff true
:name "A oneoff project"
:version "1.0.0"
:dependencies deps
:root dir
:compile-path (str dir "/classes")
:library-path (str dir "/lib")}
opts)))

(defn print-usage []
(abort "Usage: lein oneoff <command> <file>
<command> can be one of: --exec, --repl, --classpath, --swank.
Short forms (-e, -r, -c, -s) may be used instead.
If <command> is omitted, --exec is assumed."))

(defn execute-script [script]
(let [project (oneoff-project script)
form `(do
~defdeps-defmacro-form
(clojure.main/load-script ~script))]
(leiningen.compile/eval-in-project project form)))

(defn start-repl-server [script]
(leiningen.repl/repl (oneoff-project script)))

(defn start-swank-server [script]
(if lein-swank-ns
(if swank-form-var
(let [swank-fn (ns-resolve lein-swank-ns 'swank)]
(swank-fn (oneoff-project script)))
(abort "The oneoff swank task only works with
swank-clojure 1.3.0-SNAPSHOT or newer."))
(abort "You'll need to install swank-clojure as a user plugin
for this task to work.")))

(defn print-classpath [script]
(leiningen.classpath/classpath (oneoff-project script)))

(defn oneoff
"Handles dependencies and execution of one-off scripts when creating a
proper leiningen project feels like overkill.
Syntax: lein oneoff <command> <file>
<command> can be one of: --exec, --repl, --classpath, --swank.
Short forms (-e, -r, -c, -s) may be used instead.
If <command> is omitted, --exec is assumed.
See http://github.com/mtyaka/lein-oneoff for more information."
([cmd script]
(case cmd
("--exec" "-e") (execute-script script)
("--repl" "-r") (start-repl-server script)
("--classpath" "-c") (print-classpath script)
("--swank" "-s") (start-swank-server script)
(print-usage)))
([script]
(oneoff "--exec" script))
([]
(print-usage)))

0 comments on commit 14e3059

Please sign in to comment.