Skip to content

Commit

Permalink
Initial ideas for REPL support (#11).
Browse files Browse the repository at this point in the history
Things are actually in a vaguely working form, but I need to write a lot
of documentation and clean up the advanced example more before this is
ready for prime-time.
  • Loading branch information
emezeske committed Feb 16, 2012
1 parent ba8b27c commit 46b0361
Show file tree
Hide file tree
Showing 14 changed files with 251 additions and 88 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -5,7 +5,8 @@ classes/
lein-cljsbuild-*.*.* lein-cljsbuild-*.*.*
cljsbuild-*.*.* cljsbuild-*.*.*
example-projects/*/*.jar example-projects/*/*.jar
example-projects/*/resources example-projects/simple/resources
example-projects/advanced/resources/public/js
example-projects/*/.clojurescript-output example-projects/*/.clojurescript-output
example-projects/*/classes example-projects/*/classes
example-projects/*/lib example-projects/*/lib
Expand Down
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -53,6 +53,9 @@ exhaustive list of all options supported by lein-cljsbuild.


## Basic Configuration ## Basic Configuration


**TODO** Document the new :builds configuration, as well as :repl-listen-port
and :repl-launch-commands.

The lein-cljsbuild configuration is specified under the `:cljsbuild` section The lein-cljsbuild configuration is specified under the `:cljsbuild` section
of your `project.clj` file. A simple project might look like this: of your `project.clj` file. A simple project might look like this:


Expand Down
5 changes: 5 additions & 0 deletions example-projects/advanced/README.md
Expand Up @@ -14,6 +14,11 @@ Set up and start the server like this:
$ lein cljsbuild once $ lein cljsbuild once
$ lein ring server $ lein ring server


**TODO** Document the new REPL support.

lein trampoline cljsbuild repl-launch firefox-naked
lein trampoline cljsbuild repl-launch phantom-naked

[1]: https://github.com/mmcgrana/ring [1]: https://github.com/mmcgrana/ring
[2]: https://github.com/weavejester/compojure [2]: https://github.com/weavejester/compojure
[3]: https://github.com/emezeske/lein-cljsbuild [3]: https://github.com/emezeske/lein-cljsbuild
Expand Down
16 changes: 16 additions & 0 deletions example-projects/advanced/phantom/page-repl.js
@@ -0,0 +1,16 @@
if (phantom.args.length != 1) {
console.log('Expected a target URL parameter.');
phantom.exit(1);
}

var page = new WebPage();
var url = phantom.args[0];

page.open(url, function (status) {
if (status != "success") {
console.log('Failed to open ' + url);
phantom.exit(1);
}

// TODO: Should anything else happen here?
});
29 changes: 18 additions & 11 deletions example-projects/advanced/project.clj
Expand Up @@ -15,15 +15,22 @@
:dev-dependencies [[lein-ring "0.5.0"]] :dev-dependencies [[lein-ring "0.5.0"]]
:plugins [[lein-cljsbuild "0.1.0"]] :plugins [[lein-cljsbuild "0.1.0"]]
:hooks [leiningen.cljsbuild] :hooks [leiningen.cljsbuild]
:cljsbuild [{:source-path "src-cljs" :cljsbuild {
:jar true :repl-listen-port 9000
:crossovers [example.crossover] ; TODO: Add some way to test the firefox and phantom launch commands.
:compiler {:output-to "resources/public/js/main-debug.js" :repl-launch-commands {"firefox" ["firefox"]
:optimizations :whitespace "firefox-naked" ["firefox" "resources/public/html/naked.html"]
:pretty-print true}} "phantom" ["phantomjs" "phantom/page-repl.js"]
{:source-path "src-cljs" "phantom-naked" ["phantomjs" "phantom/page-repl.js" "resources/public/html/naked.html"]}
:crossovers [example.crossover] :builds [{:source-path "src-cljs"
:compiler {:output-to "resources/public/js/main.js" :jar true
:optimizations :advanced :crossovers [example.crossover]
:pretty-print false}}] :compiler {:output-to "resources/public/js/main-debug.js"
:optimizations :whitespace
:pretty-print true}}
{:source-path "src-cljs"
:crossovers [example.crossover]
:compiler {:output-to "resources/public/js/main.js"
:optimizations :advanced
:pretty-print false}}]}
:ring {:handler example.routes/app}) :ring {:handler example.routes/app})
10 changes: 10 additions & 0 deletions example-projects/advanced/resources/public/html/naked.html
@@ -0,0 +1,10 @@
<html>
<body>
<script src="../js/main-debug.js"></script>
<script>
example.repl.connect();
</script>
This is just a dummy HTML file to connect to the REPL.
Don't close this window until you're done with the REPL.
</body>
</html>
3 changes: 2 additions & 1 deletion example-projects/advanced/src-clj/example/views.clj
Expand Up @@ -8,6 +8,7 @@
(html5 (html5
[:head [:head
[:title (shared/make-example-text)] [:title (shared/make-example-text)]
(include-js "/js/main.js")] (include-js "/js/main-debug.js")]
(javascript-tag "example.hello.say_hello()")
[:body [:body
[:h1 (shared/make-example-text)]])) [:h1 (shared/make-example-text)]]))
3 changes: 2 additions & 1 deletion example-projects/advanced/src-cljs/example/hello.cljs
Expand Up @@ -2,4 +2,5 @@
(:require (:require
[example.crossover.shared :as shared])) [example.crossover.shared :as shared]))


(js/alert (shared/make-example-text)) (defn ^:export say-hello []
(js/alert (shared/make-example-text)))
6 changes: 6 additions & 0 deletions example-projects/advanced/src-cljs/example/repl.cljs
@@ -0,0 +1,6 @@
(ns example.repl
(:require
[clojure.browser.repl :as repl]))

(defn ^:export connect []
(repl/connect "http://localhost:9000/repl"))
5 changes: 3 additions & 2 deletions example-projects/simple/project.clj
Expand Up @@ -6,8 +6,9 @@
[hiccup "0.3.7"]] [hiccup "0.3.7"]]
:dev-dependencies [[lein-ring "0.5.0"]] :dev-dependencies [[lein-ring "0.5.0"]]
:plugins [[lein-cljsbuild "0.1.0"]] :plugins [[lein-cljsbuild "0.1.0"]]
:cljsbuild {:source-path "src-cljs" :cljsbuild {
:builds [{:source-path "src-cljs"
:compiler {:output-to "resources/public/js/main.js" :compiler {:output-to "resources/public/js/main.js"
:optimizations :whitespace :optimizations :whitespace
:pretty-print true}} :pretty-print true}}]}
:ring {:handler example.routes/app}) :ring {:handler example.routes/app})
199 changes: 128 additions & 71 deletions plugin/src/leiningen/cljsbuild.clj
Expand Up @@ -2,6 +2,7 @@
"Compile ClojureScript source into a JavaScript file." "Compile ClojureScript source into a JavaScript file."
(:require (:require
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.pprint :as pprint]
[clojure.string :as s] [clojure.string :as s]
[robert.hooke :as hooke] [robert.hooke :as hooke]
[leiningen.compile :as lcompile] [leiningen.compile :as lcompile]
Expand All @@ -11,22 +12,22 @@
(def cljsbuild-dependencies (def cljsbuild-dependencies
'[[cljsbuild "0.1.0"]]) '[[cljsbuild "0.1.0"]])


(def default-compiler (def repl-output-dir ".lein-cljsbuild-repl")

(def default-global-options
{:repl-launch-commands {}
:repl-listen-port 9000})

(def default-compiler-options
{:output-to "main.js" {:output-to "main.js"
:optimizations :whitespace :optimizations :whitespace
:pretty-print true :pretty-print true
:output-dir ".clojurescript-output"}) :output-dir ".lein-cljsbuild-compiler"})


(def default-options (def default-build-options
{:source-path "src-cljs" {:source-path "src-cljs"
:crossovers [] :crossovers []
:compiler default-compiler}) :compiler default-compiler-options})

(def relocations
{:source-dir [:source-path]
:output-file [:compiler :output-to]
:optimizations [:compiler :optimizations]
:pretty-print [:compiler :pretty-print]})


(def exit-success 0) (def exit-success 0)


Expand All @@ -40,7 +41,7 @@
(apply printerr "WARNING:" args)) (apply printerr "WARNING:" args))


(defn- usage [] (defn- usage []
(printerr "Usage: lein cljsbuild [once|auto|clean]")) (printerr "Usage: lein cljsbuild [once|auto|clean|repl-listen|repl-launch|repl-rhino]"))


(declare deep-merge-item) (declare deep-merge-item)


Expand All @@ -52,103 +53,159 @@
(deep-merge a b) (deep-merge a b)
b)) b))


(defn- backwards-compat [cljsbuild]
(apply dissoc
(apply deep-merge cljsbuild
(for [[source dest] relocations]
(when-let [value (source cljsbuild)]
(warn source "is deprecated.")
(when (nil? (get-in cljsbuild dest))
(assoc-in {} dest value)))))
(keys relocations)))

(defn- merge-dependencies [project-dependencies] (defn- merge-dependencies [project-dependencies]
(let [dependency-map #(into {} (map (juxt first rest) %)) (let [dependency-map #(into {} (map (juxt first rest) %))
project (dependency-map project-dependencies) project (dependency-map project-dependencies)
cljsbuild (dependency-map cljsbuild-dependencies)] cljsbuild (dependency-map cljsbuild-dependencies)]
(map (fn [[k v]] (vec (cons k v))) (map (fn [[k v]] (vec (cons k v)))
(merge project cljsbuild)))) (merge project cljsbuild))))


(defn- run-local-project [project option-seq form] (defn- run-local-project [project builds requires form]
(lcompile/eval-in-project (lcompile/eval-in-project
{:local-repo-classpath true {:local-repo-classpath true
:source-path (:source-path project) :source-path (:source-path project)
:extra-classpath-dirs (concat :extra-classpath-dirs (concat
(:extra-classpath-dirs project) (:extra-classpath-dirs project)
(map :source-path option-seq)) (map :source-path builds))
:dependencies (merge-dependencies (:dependencies project)) :dependencies (merge-dependencies (:dependencies project))
:dev-dependencies (:dev-dependencies project)} :dev-dependencies (:dev-dependencies project)}
form form
nil nil
nil nil
'(require 'cljsbuild.core)) requires)
exit-success) exit-success)


(defn- run-compiler [project option-seq watch?] (defn- run-compiler [project {:keys [builds]} watch?]
(run-local-project project option-seq (run-local-project project builds
`(do '(require 'cljsbuild.compiler)
(println "Compiling ClojureScript") `(do
(cljsbuild.core/in-threads (println "Compiling ClojureScript.")
(fn [opts#] (cljsbuild.core/run-compiler (cljsbuild.compiler/in-threads
(:source-path opts#) (fn [opts#]
(:crossovers opts#) (cljsbuild.compiler/run-compiler
(:compiler opts#) (:source-path opts#)
~watch?)) (:crossovers opts#)
'~option-seq) (:compiler opts#)
(shutdown-agents)))) ~watch?))

'~builds)
(defn- cleanup-files [project option-seq] (shutdown-agents))))
(run-local-project project option-seq
`(do (defn- cleanup-files [project {:keys [builds]}]
(println "Deleting generated files.") (run-local-project project builds
(cljsbuild.core/in-threads '(require 'cljsbuild.compiler)
(fn [opts#] (cljsbuild.core/cleanup-files `(do
(:source-path opts#) (println "Deleting generated files.")
(:crossovers opts#) (cljsbuild.compiler/in-threads
(:compiler opts#))) (fn [opts#]
'~option-seq) (cljsbuild.compiler/cleanup-files
(shutdown-agents)))) (:source-path opts#)
(:crossovers opts#)
(:compiler opts#)))
'~builds)
(shutdown-agents))))

(defn- run-repl-listen [project {:keys [builds repl-listen-port]}]
(run-local-project project builds
'(require 'cljsbuild.repl.listen)
`(do
(println (str "Running REPL, listening on port " ~repl-listen-port "."))
(cljsbuild.repl.listen/run-repl-listen
~repl-listen-port
~repl-output-dir)
(shutdown-agents))))

(defn- run-repl-launch [project {:keys [builds repl-listen-port repl-launch-commands]} args]
(when (< (count args) 1)
(throw (Exception. "Must supply a launch command identifier.")))
(let [launch-name (first args)
command-args (rest args)
command-base (repl-launch-commands launch-name)]
(when (nil? command-base)
(throw (Exception. (str "Unknown REPL launch command: " launch-name))))
(let [command (concat command-base command-args)]
(run-local-project project builds
'(require 'cljsbuild.repl.listen)
`(do
(println "Running REPL and launching command:" '~command)
(cljsbuild.repl.listen/run-repl-launch
~repl-listen-port
~repl-output-dir
'~command)
(shutdown-agents))))))

(defn- run-repl-rhino [project {:keys [builds]}]
(run-local-project project builds
'(require 'cljsbuild.repl.rhino)
`(do
(println "Running Rhino-based REPL.")
(cljsbuild.repl.rhino/run-repl-rhino))))

(defn- set-default-build-options [options]
(deep-merge default-build-options options))

(defn- set-default-global-options [options]
(deep-merge default-global-options
(assoc options :builds
(map set-default-build-options (:builds options)))))

(defn- warn-deprecated [options]
(warn "your deprecated :cljsbuild config was interpreted as:")
(pprint/pprint options *err*)
(printerr
"See https://github.com/emezeske/lein-cljsbuild/blob/master/README.md"
"for details on the new format.")
options)


(defn- normalize-options (defn- normalize-options
"Sets default options and accounts for backwards compatibility" "Sets default options and accounts for backwards compatibility."
[orig-options] [options]
(let [compat-options (backwards-compat orig-options)] (cond
(when (not= orig-options compat-options) (and (map? options) (nil? (:builds options)))
(warn (str (warn-deprecated
"your deprecated :cljsbuild config was interpreted as:\n" [{:builds (set-default-build-options options)}])
compat-options))) (seq? options)
(deep-merge default-options compat-options))) (warn-deprecated
[{:builds (map set-default-build-options options)}])
:else
(set-default-global-options options)))


(defn- extract-options (defn- extract-options
"Given a project, returns a seq of cljsbuild option maps." "Given a project, returns a seq of cljsbuild option maps."
[project] [project]
(when (nil? (:cljsbuild project)) (when (nil? (:cljsbuild project))
(warn "no :cljsbuild entry found in project definition.")) (warn "no :cljsbuild entry found in project definition."))
(let [raw-options (:cljsbuild project)] (let [raw-options (:cljsbuild project)]
(if (map? raw-options) (normalize-options raw-options)))
[(normalize-options raw-options)]
(map normalize-options raw-options))))


(defn cljsbuild (defn cljsbuild
"Run the cljsbuild plugin. "Run the cljsbuild plugin.
Usage: lein cljsbuild [once|auto|clean] Usage: lein cljsbuild <command>
Available commands:
once Compile the ClojureScript project once.
auto Automatically recompile when files are modified.
clean Remove automatically generated files.
once Compile the ClojureScript project once. repl-listen Run a REPL that will listen for incoming connections.
auto Automatically recompile when files are modified. repl-launch Run a REPL and launch a custom command to connect to it.
clean Remove automatically generated files." repl-rhino Run a Rhino-based REPL."
([project] ([project]
(usage) (usage)
exit-failure) exit-failure)
([project mode] ([project mode & args]
(let [option-seq (extract-options project)] (let [options (extract-options project)]
(case mode (case mode
"once" (run-compiler project option-seq false) "once" (run-compiler project options false)
"auto" (run-compiler project option-seq true) "auto" (run-compiler project options true)
"clean" (cleanup-files project option-seq) "clean" (cleanup-files project options)
(do "repl-listen" (run-repl-listen project options)
(usage) "repl-launch" (run-repl-launch project options args)
exit-failure))))) "repl-rhino" (run-repl-rhino project options)
(do
(usage)
exit-failure)))))


(defn- file-bytes (defn- file-bytes
"Reads a file into a byte array" "Reads a file into a byte array"
Expand Down

0 comments on commit 46b0361

Please sign in to comment.