/
run.clj
158 lines (143 loc) · 6.23 KB
/
run.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
(ns leiningen.run
"Run a -main function with optional command-line arguments."
(:require [leiningen.core.eval :as eval]
[leiningen.core.main :as main])
(:import (java.io FileNotFoundException)
(clojure.lang Reflector)))
(defn- normalize-main [given]
(when-not (or (symbol? given)
(and (string? given) (symbol? (read-string given))))
(main/abort (str "Option -m requires a valid namespace argument, not "
given ".")))
(if (namespace (symbol given))
(symbol given)
(symbol (name given) "-main")))
(defn- prep-arg
"Prepares an argument with the given preparation method."
[prep-method arg]
(case prep-method
:stringify (if (string? arg) arg
(pr-str arg)) ;; print-dup/print-meta seems overkill
:quote (list 'quote arg)
(throw (ex-info "Unknown preparation method"
{:prep-method prep-method}))))
;; TODO: get rid of this in 3.0
(defn run-form
"Construct a form to run the given main defn or class with arguments."
[given args]
`(binding [*command-line-args* '~args]
;;
;; Some complicated error-handling logic here to support main
;; being either a namespace or a class.
;;
;; The use case that prompted this complexity is that if we
;; have a namespace such as:
;;
;; (ns foo.main
;; (:require does.not.exist))
;;
;; and we do a `lein run -m foo.main`, we will get a
;; FileNotFoundException, but NOT because foo.main doesn't
;; exist. So we want to make sure that error propogates
;; up in the event that the class doesn't exist as well.
;; But we still have to try the class first because it's
;; not easy to distinguish the above case from the case
;; when foo.main is a class and not a namespace at all.
;;
;; This would be a lot simpler if we weren't trying to
;; transparently support both namespaces and classes specified in
;; the same way.
;;
;; Try to require the namespace and run the appropriate var,
;; noting what happened.
(let [[ns-flag# data#]
(try (require '~(symbol (namespace
(normalize-main given))))
(let [v# (resolve '~(normalize-main given))]
(if (ifn? v#)
[:var v#]
[:not-found]))
(catch FileNotFoundException e#
[:threw e#]))
;; If we didn't succeed above, check if a class exists for
;; the given name
^Class class#
(when-not (= :var ns-flag#)
(try (Class/forName ~(name given))
(catch ClassNotFoundException _#)))]
(cond
(= :var ns-flag#)
(data# ~@args)
;; If the class exists, run its main method.
class#
(Reflector/invokeStaticMethod
class# "main" (into-array [(into-array String '~args)]))
;; If the symbol didn't resolve, give a reasonable message
(= :not-found ns-flag#)
(throw (Exception. ~(str "Cannot find anything to run for: " (name given))))
;; If we got an exception earlier and nothing else worked,
;; rethrow that.
(= :threw ns-flag#)
(do (binding [*out* *err*]
(println (str "Can't find '" '~given "' as .class or .clj for "
"lein run: please check the spelling.")))
(throw data#))))))
(defn- parse-args
"Parses the arguments passed in to run, and returns a map with the results. If
an argument is given more than once, it is assumed to be an argument to the
main function."
([args] (parse-args args {}))
([[f & rest-args :as args] cur-args]
(if-not (seq args)
(merge {:arg-conversion :stringify
:args args} cur-args)
(cond (= "--" f)
(merge {:arg-conversion :stringify
:args rest-args}
cur-args)
(and (or (= "-m" f)
(= ":main" f))
(not (contains? cur-args :main)))
(if (first rest-args)
(->> (assoc cur-args :main (first rest-args))
(parse-args (rest rest-args)))
(main/abort "Option -m requires a namespace argument."))
(and (= "--quote-args" f)
(not (contains? cur-args :arg-conversion)))
(->> (assoc cur-args :arg-conversion :quote)
(parse-args rest-args))
:else
(merge {:arg-conversion :stringify
:args args}
cur-args)))))
(defn- run-main
"Loads the project namespaces as well as all its dependencies and then calls
ns/f, passing it the args."
[project given prep-type args]
;; must convert lazy-seq to list(issue #2091)
;; eval can't handle well a form that contains an evaluated empty lazy-seq
(let [prepped-args (apply list (map #(prep-arg prep-type %) args))]
(try (eval/eval-in-project project (run-form given prepped-args))
(catch clojure.lang.ExceptionInfo e
(main/exit (:exit-code (ex-data e) 1))))))
(defn ^{:help-arglists '([]) :no-project-needed true} run
"Run the project's -main function.
USAGE: lein run [--] [ARGS...]
Calls the -main function in the namespace specified as :main in project.clj.
You may use -- to escape the first argument in case it begins with `-' or `:'.
If your main function is not called \"-main\", you may use a namespaced symbol
like clojure.main/main.
USAGE: lein run -m NAMESPACE[/MAIN_FUNCTION] [--] [ARGS...]
Calls the main function in the specified namespace. You may have to use -- to
escape the first argument in case it begins with `-' or `:'.
The `--quote-args' flag quotes the arguments passed in, instead of converting
them to strings. Arguments coming from the command line will always be strings,
so this is only useful when invoked from :aliases.
See also \"lein help trampoline\" for a way to save memory using this task."
[project & raw-args]
(let [arg-map (parse-args raw-args)]
(if (and (not (:main arg-map))
(not (:main project)))
(main/abort "No :main namespace specified in project.clj.")
(run-main project (or (:main arg-map) (:main project))
(:arg-conversion arg-map) (:args arg-map)))))