├── .gitignore ├── project.clj ├── test_projects ├── sample1.clj └── sample2.clj ├── HISTORY.md ├── test └── leiningen │ └── test │ └── oneoff.clj ├── src └── leiningen │ └── oneoff.clj └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | target 5 | .lein-deps-sum 6 | .lein-failures 7 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-oneoff "0.3.2" 2 | :description "Dependency management for one-off scripts." 3 | :url "https://github.com/mtyaka/lein-oneoff" 4 | :eval-in :leiningen 5 | :min-lein-version "2.0.0" 6 | :license {:name "Eclipse Public License" 7 | :url "http://www.eclipse.org/legal/epl-v10.html"}) 8 | -------------------------------------------------------------------------------- /test_projects/sample1.clj: -------------------------------------------------------------------------------- 1 | (defdeps 2 | [[org.clojure/clojure "1.4.0-beta1"]]) 3 | 4 | (ns sample1 5 | (:use [clojure.java.io :only [file]])) 6 | 7 | (spit (.getCanonicalFile (file *file* "../output")) 8 | (str "Running sample.clj under clojure " (clojure-version) " " 9 | "with arguments: " (prn-str *command-line-args*))) 10 | -------------------------------------------------------------------------------- /test_projects/sample2.clj: -------------------------------------------------------------------------------- 1 | 2 | #_(defdeps 3 | [[org.clojure/clojure "1.5.0-RC6"]]) 4 | 5 | (ns sample1 6 | (:require [clojure.java.io :refer [file]])) 7 | 8 | (spit (.getCanonicalFile (file *file* "../output")) 9 | (str "Running sample.clj under clojure " (clojure-version) " " 10 | "with arguments: " (prn-str *command-line-args*))) 11 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## 0.3.2 (2015-08-31) 2 | 3 | * Fix issue when invoking lein oneoff from a subfolder of a project 4 | containing a project.clj file. 5 | 6 | ## 0.3.1 (2014-06-22) 7 | 8 | * Fixes compatibility issues with Leiningen >= 2.3.x (Mathew Thomas) 9 | 10 | ## 0.3.0 (2013-05-23) 11 | 12 | * Rewrite for Leiningen 2 13 | * Drop Leiningen 1.x compatibility 14 | * Remove support for the `--swank` task 15 | 16 | ## 0.2.0 (2012-01-01) 17 | 18 | * Allow defdeps form to be prefixed with the `#_` reader macro (Tavis Rudd) 19 | -------------------------------------------------------------------------------- /test/leiningen/test/oneoff.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.test.oneoff 2 | (:require [clojure.test :refer [deftest is]] 3 | [clojure.java.shell :refer [sh]] 4 | [clojure.java.io :refer [file delete-file]])) 5 | 6 | (def output-file (file "test_projects/output")) 7 | 8 | (defn- exec [filename & args] 9 | (delete-file output-file true) 10 | (let [script (format "test_projects/%s" filename)] 11 | (apply sh "lein" "oneoff" "-e" script args))) 12 | 13 | (defn- cp [filename] 14 | (let [script (format "test_projects/%s" filename)] 15 | (sh "lein" "oneoff" "-cp" script))) 16 | 17 | (deftest test-execute-script 18 | (exec "sample1.clj" "bake" "honk!") 19 | (let [output (slurp output-file)] 20 | (is (re-find #"1\.4\.0-beta1" output)) 21 | (is (re-find #"arguments: \[\"bake\" \"honk!\"\]" output))) 22 | (exec "sample2.clj") 23 | (let [output (slurp output-file)] 24 | (is (re-find #"1\.5\.0-RC6" output)) 25 | (is (re-find #"arguments: nil" output))) 26 | (delete-file output-file)) 27 | 28 | (deftest test-classpath 29 | (let [output (:out (cp "sample1.clj"))] 30 | (is (re-find #"clojure-1\.4\.0-beta1\.jar" output))) 31 | (let [output (:out (cp "sample2.clj"))] 32 | (is (re-find #"clojure-1\.5\.0-RC6\.jar" output)))) 33 | -------------------------------------------------------------------------------- /src/leiningen/oneoff.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.oneoff 2 | (:require [clojure.string :refer [replace-first]] 3 | [clojure.java.io :refer [file]] 4 | [leiningen.core.project] 5 | [leiningen.core.eval] 6 | [leiningen.core.main] 7 | [leiningen.repl] 8 | [leiningen.classpath])) 9 | 10 | (defn parse-defdeps [script] 11 | (let [contents (slurp script) 12 | contents (replace-first contents #"^\s*#_" "") 13 | [sym deps opts] (read-string contents)] 14 | (if (= sym 'defdeps) 15 | (assoc opts :dependencies deps) 16 | {:dependencies `[[org.clojure/clojure ~(clojure-version)]]}))) 17 | 18 | (defn oneoff-project [script] 19 | (let [dir (System/getProperty "leiningen.original.pwd") 20 | tmpdir (System/getProperty "java.io.tmpdir") 21 | declarations (parse-defdeps (file dir script)) 22 | defaults {:name "oneoff" 23 | :group "oneoff" 24 | :version "0.1" 25 | :oneoff true 26 | :eval-in :subprocess 27 | :injections ['(defmacro defdeps [& args])] 28 | :prep-tasks [] 29 | :root dir 30 | :source-paths [dir] 31 | :target-path (.getAbsolutePath (file tmpdir "oneoff")) 32 | :compile-path (.getAbsolutePath (file tmpdir "oneoff" "classes")) 33 | :test-paths [] 34 | :resource-paths [] 35 | :dev-resources-path dir} 36 | ;; project-with-profiles function was added in Leiningen 2.1, 37 | ;; add a workaround for 2.0. 38 | with-profiles (resolve 'leiningen.core.project/project-with-profiles) 39 | with-profiles (or with-profiles identity)] 40 | (-> (merge defaults declarations) 41 | (leiningen.core.project/make) 42 | (with-profiles) 43 | ;; init-profiles is marked as :internal, but it looks like 44 | ;; there's no other way of doing this. 45 | (leiningen.core.project/init-profiles [:default])))) 46 | 47 | (defn execute-script [project script args] 48 | (let [args (when args (vec args)) 49 | form `(binding [*command-line-args* ~args] 50 | (clojure.main/load-script ~script))] 51 | (leiningen.core.eval/eval-in-project project form))) 52 | 53 | (defn ^:no-project-needed oneoff 54 | "Manages dependencies of one-off clojure scripts. 55 | 56 | Useful in situations when creating a proper leiningen project feels like 57 | overkill. 58 | 59 | Syntax: lein oneoff 60 | can be one of: --exec, --repl, --classpath. 61 | Short forms (-e, -r, -cp, -s) may be used instead. 62 | If is omitted, --exec is assumed. 63 | 64 | See http://github.com/mtyaka/lein-oneoff for more information." 65 | ([_ cmd script & args] 66 | (if (= (first cmd) \-) 67 | (let [project (oneoff-project script)] 68 | (case cmd 69 | ("--exec" "-e") (execute-script project script args) 70 | ("--repl" "-r") (leiningen.repl/repl project) 71 | ("--classpath" "-cp") (leiningen.classpath/classpath project) 72 | (leiningen.core.main/abort 73 | (format "Unknown command: %s\n" cmd) 74 | "Supported commands: --exec/-e, --repl/-r, --classpath/-cp"))) 75 | (apply oneoff nil "--exec" cmd script args))) 76 | ([_ script] 77 | (oneoff nil "--exec" script))) 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lein-oneoff 2 | 3 | Dealing with dependencies and the classpath can be a 4 | pain. [Leiningen](http://github.com/technomancy/leiningen) takes most 5 | of the pain away, but creating a new leiningen project for a simple 6 | one-off script may sometimes feel like overkill. This is where 7 | [lein-oneoff](http://github.com/mtyaka/lein-oneoff) comes in. 8 | 9 | With the help of lein-oneoff you can open a file, declare 10 | dependencies at the top, and write the rest of the code as 11 | usually. lein-oneoff will let you run the file or open a repl session 12 | while taking care of fetching dependencies and constructing the 13 | classpath automatically. 14 | 15 | You might find lein-oneoff useful when you want to play with a brand 16 | new alpha release of clojure, but would rather not dowload the jar 17 | manually, when you think you know the answer to a question about a 18 | particular library posted to the clojure mailing list, but would 19 | rather test your idea out in the repl before posting the answer, or when 20 | you quickly want to analyse and plot some data using 21 | [Incanter](http://incanter.org/). 22 | 23 | ## Usage 24 | 25 | lein-oneoff scripts usually consist of a single file. Dependencies 26 | should be stated at the top using the `defdeps` form. You may 27 | optionally prefix the `defdeps` form with the `#_` reader macro (ignore 28 | next form). Here's an example: 29 | 30 | #_(defdeps 31 | [[org.clojure/clojure "1.2.0"] 32 | [compojure "0.5.2"] 33 | [ring/ring-jetty-adapter "0.3.3"]]) 34 | 35 | (ns example 36 | (:use [compojure.core :only [defroutes GET]] 37 | [ring.adapter.jetty :only [run-jetty]])) 38 | 39 | (defroutes routes 40 | (GET "/" [] "Hello world!")) 41 | 42 | (def server 43 | (run-jetty routes {:port 8080 :join? false})) 44 | 45 | Save this file as `example.clj`, then run it with: 46 | 47 | $ lein oneoff example.clj 48 | 49 | This command will resolve the specified dependencies and install them 50 | into the local maven repository (`~/.m2/repository`) unless already 51 | installed, and then run `example.clj` with the classpath properly set. 52 | 53 | ### The defdeps form 54 | 55 | The `defdeps` form must be the first form in the file. It has the following 56 | signature: 57 | 58 | (defdeps dependencies additional-entries?) 59 | 60 | where dependencies should be specified as a vector using the same 61 | syntax as inside regular leiningen `defproject` form under the 62 | `:dependencies` key. The second argument is an optional map of 63 | additional standard `defproject` entries. Please note that not all of 64 | the available leinigen options make sense for a one-off script and 65 | might not work correctly. Adding a `#_` prefix will make it possible 66 | to ignore the `defdeps` form when re-compiling the file in a repl. 67 | 68 | One of the entries that can be useful is the `:repositories` entry. Here's 69 | an example: 70 | 71 | (defdeps 72 | [[org.clojure/clojure "1.3.0-alpha3"] 73 | [org.apache.pivot/pivot-web "1.5.2"]] 74 | {:repositories 75 | {"apache" "https://repository.apache.org/content/repositories/releases/"}}) 76 | 77 | The `defdeps` form may be omitted in which case the only assumed 78 | dependency is `org.clojure/clojure` of the same version as your leiningen 79 | installation is using. 80 | 81 | ### exec 82 | 83 | To execute (load) a one-off script, use the `--exec` (or `-e` for short) 84 | command. Any arguments positioned after the script name are passed to 85 | the script as `*command-line-args*`. 86 | 87 | $ lein oneoff --exec example.clj arg1 arg2 88 | $ lein oneoff -e example.clj 89 | 90 | The `--exec` command is the default, so you can omit it altogether. 91 | 92 | $ lein oneoff example.clj 93 | $ lein oneoff example.clj 8080 127.0.0.1 94 | 95 | ### repl 96 | 97 | To start a repl in the context of a one-off script, use the `--repl` 98 | command (or its shorter equivalent, `-r`): 99 | 100 | $ lein oneoff --repl example.clj 101 | $ lein oneoff -r example.clj 102 | 103 | ### classpath 104 | 105 | lein-oneoff offers an equivalent to leiningen's built-in `classpath` 106 | task which prints the project's classpath for one-off scripts: 107 | 108 | $ lein oneoff --classpath example.clj 109 | $ lein oneoff -cp example.clj 110 | 111 | ## Installation 112 | 113 | This plugin should be installed as a global user-level leiningen 114 | plugin. You can install it by adding the following line to 115 | `~/.lein/profiles.clj`: 116 | 117 | {:user {:plugins [[lein-oneoff "0.3.2"]]}} 118 | 119 | This version of `lein-oneoff` works with leiningen 2.0.0 or newer. 120 | If you are using Leinigen 1.x, please check out 121 | [release 0.2.0](https://github.com/mtyaka/lein-oneoff/tree/v0.2.0). 122 | 123 | 124 | ## License 125 | 126 | Copyright (C) 2010 Matjaz Gregoric 127 | 128 | Distributed under the Eclipse Public License, the same as Clojure. 129 | --------------------------------------------------------------------------------