Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial ideas for REPL support (#11).

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...
commit 46b03612cf0f4ef0944ac0498c59218325590981 1 parent ba8b27c
@emezeske authored
View
3  .gitignore
@@ -5,7 +5,8 @@ classes/
lein-cljsbuild-*.*.*
cljsbuild-*.*.*
example-projects/*/*.jar
-example-projects/*/resources
+example-projects/simple/resources
+example-projects/advanced/resources/public/js
example-projects/*/.clojurescript-output
example-projects/*/classes
example-projects/*/lib
View
3  README.md
@@ -53,6 +53,9 @@ exhaustive list of all options supported by lein-cljsbuild.
## 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
of your `project.clj` file. A simple project might look like this:
View
5 example-projects/advanced/README.md
@@ -14,6 +14,11 @@ Set up and start the server like this:
$ lein cljsbuild once
$ 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
[2]: https://github.com/weavejester/compojure
[3]: https://github.com/emezeske/lein-cljsbuild
View
16 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?
+});
View
29 example-projects/advanced/project.clj
@@ -15,15 +15,22 @@
:dev-dependencies [[lein-ring "0.5.0"]]
:plugins [[lein-cljsbuild "0.1.0"]]
:hooks [leiningen.cljsbuild]
- :cljsbuild [{:source-path "src-cljs"
- :jar true
- :crossovers [example.crossover]
- :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}}]
+ :cljsbuild {
+ :repl-listen-port 9000
+ ; TODO: Add some way to test the firefox and phantom launch commands.
+ :repl-launch-commands {"firefox" ["firefox"]
+ "firefox-naked" ["firefox" "resources/public/html/naked.html"]
+ "phantom" ["phantomjs" "phantom/page-repl.js"]
+ "phantom-naked" ["phantomjs" "phantom/page-repl.js" "resources/public/html/naked.html"]}
+ :builds [{:source-path "src-cljs"
+ :jar true
+ :crossovers [example.crossover]
+ :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})
View
10 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>
View
3  example-projects/advanced/src-clj/example/views.clj
@@ -8,6 +8,7 @@
(html5
[:head
[:title (shared/make-example-text)]
- (include-js "/js/main.js")]
+ (include-js "/js/main-debug.js")]
+ (javascript-tag "example.hello.say_hello()")
[:body
[:h1 (shared/make-example-text)]]))
View
3  example-projects/advanced/src-cljs/example/hello.cljs
@@ -2,4 +2,5 @@
(:require
[example.crossover.shared :as shared]))
-(js/alert (shared/make-example-text))
+(defn ^:export say-hello []
+ (js/alert (shared/make-example-text)))
View
6 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"))
View
5 example-projects/simple/project.clj
@@ -6,8 +6,9 @@
[hiccup "0.3.7"]]
:dev-dependencies [[lein-ring "0.5.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"
:optimizations :whitespace
- :pretty-print true}}
+ :pretty-print true}}]}
:ring {:handler example.routes/app})
View
199 plugin/src/leiningen/cljsbuild.clj
@@ -2,6 +2,7 @@
"Compile ClojureScript source into a JavaScript file."
(:require
[clojure.java.io :as io]
+ [clojure.pprint :as pprint]
[clojure.string :as s]
[robert.hooke :as hooke]
[leiningen.compile :as lcompile]
@@ -11,22 +12,22 @@
(def cljsbuild-dependencies
'[[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"
:optimizations :whitespace
:pretty-print true
- :output-dir ".clojurescript-output"})
+ :output-dir ".lein-cljsbuild-compiler"})
-(def default-options
+(def default-build-options
{:source-path "src-cljs"
:crossovers []
- :compiler default-compiler})
-
-(def relocations
- {:source-dir [:source-path]
- :output-file [:compiler :output-to]
- :optimizations [:compiler :optimizations]
- :pretty-print [:compiler :pretty-print]})
+ :compiler default-compiler-options})
(def exit-success 0)
@@ -40,7 +41,7 @@
(apply printerr "WARNING:" args))
(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)
@@ -52,16 +53,6 @@
(deep-merge a 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]
(let [dependency-map #(into {} (map (juxt first rest) %))
project (dependency-map project-dependencies)
@@ -69,55 +60,114 @@
(map (fn [[k v]] (vec (cons k v)))
(merge project cljsbuild))))
-(defn- run-local-project [project option-seq form]
+(defn- run-local-project [project builds requires form]
(lcompile/eval-in-project
{:local-repo-classpath true
:source-path (:source-path project)
:extra-classpath-dirs (concat
(:extra-classpath-dirs project)
- (map :source-path option-seq))
+ (map :source-path builds))
:dependencies (merge-dependencies (:dependencies project))
:dev-dependencies (:dev-dependencies project)}
form
nil
nil
- '(require 'cljsbuild.core))
+ requires)
exit-success)
-(defn- run-compiler [project option-seq watch?]
- (run-local-project project option-seq
- `(do
- (println "Compiling ClojureScript")
- (cljsbuild.core/in-threads
- (fn [opts#] (cljsbuild.core/run-compiler
- (:source-path opts#)
- (:crossovers opts#)
- (:compiler opts#)
- ~watch?))
- '~option-seq)
- (shutdown-agents))))
-
-(defn- cleanup-files [project option-seq]
- (run-local-project project option-seq
- `(do
- (println "Deleting generated files.")
- (cljsbuild.core/in-threads
- (fn [opts#] (cljsbuild.core/cleanup-files
- (:source-path opts#)
- (:crossovers opts#)
- (:compiler opts#)))
- '~option-seq)
- (shutdown-agents))))
+(defn- run-compiler [project {:keys [builds]} watch?]
+ (run-local-project project builds
+ '(require 'cljsbuild.compiler)
+ `(do
+ (println "Compiling ClojureScript.")
+ (cljsbuild.compiler/in-threads
+ (fn [opts#]
+ (cljsbuild.compiler/run-compiler
+ (:source-path opts#)
+ (:crossovers opts#)
+ (:compiler opts#)
+ ~watch?))
+ '~builds)
+ (shutdown-agents))))
+
+(defn- cleanup-files [project {:keys [builds]}]
+ (run-local-project project builds
+ '(require 'cljsbuild.compiler)
+ `(do
+ (println "Deleting generated files.")
+ (cljsbuild.compiler/in-threads
+ (fn [opts#]
+ (cljsbuild.compiler/cleanup-files
+ (: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
- "Sets default options and accounts for backwards compatibility"
- [orig-options]
- (let [compat-options (backwards-compat orig-options)]
- (when (not= orig-options compat-options)
- (warn (str
- "your deprecated :cljsbuild config was interpreted as:\n"
- compat-options)))
- (deep-merge default-options compat-options)))
+ "Sets default options and accounts for backwards compatibility."
+ [options]
+ (cond
+ (and (map? options) (nil? (:builds options)))
+ (warn-deprecated
+ [{:builds (set-default-build-options options)}])
+ (seq? options)
+ (warn-deprecated
+ [{:builds (map set-default-build-options options)}])
+ :else
+ (set-default-global-options options)))
(defn- extract-options
"Given a project, returns a seq of cljsbuild option maps."
@@ -125,30 +175,37 @@
(when (nil? (:cljsbuild project))
(warn "no :cljsbuild entry found in project definition."))
(let [raw-options (:cljsbuild project)]
- (if (map? raw-options)
- [(normalize-options raw-options)]
- (map normalize-options raw-options))))
+ (normalize-options raw-options)))
(defn cljsbuild
"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.
- auto Automatically recompile when files are modified.
- clean Remove automatically generated files."
+ repl-listen Run a REPL that will listen for incoming connections.
+ repl-launch Run a REPL and launch a custom command to connect to it.
+ repl-rhino Run a Rhino-based REPL."
([project]
(usage)
exit-failure)
- ([project mode]
- (let [option-seq (extract-options project)]
+ ([project mode & args]
+ (let [options (extract-options project)]
(case mode
- "once" (run-compiler project option-seq false)
- "auto" (run-compiler project option-seq true)
- "clean" (cleanup-files project option-seq)
- (do
- (usage)
- exit-failure)))))
+ "once" (run-compiler project options false)
+ "auto" (run-compiler project options true)
+ "clean" (cleanup-files project options)
+ "repl-listen" (run-repl-listen project options)
+ "repl-launch" (run-repl-launch project options args)
+ "repl-rhino" (run-repl-rhino project options)
+ (do
+ (usage)
+ exit-failure)))))
(defn- file-bytes
"Reads a file into a byte array"
View
2  support/src/cljsbuild/core.clj → support/src/cljsbuild/compiler.clj
@@ -1,4 +1,4 @@
-(ns cljsbuild.core
+(ns cljsbuild.compiler
(:use
[clojure.java.io :only [as-url resource]]
[clj-stacktrace.repl :only [pst+]]
View
47 support/src/cljsbuild/repl/listen.clj
@@ -0,0 +1,47 @@
+(ns cljsbuild.repl.listen
+ (:require
+ [clojure.string :as string]
+ [clojure.java.shell :as shell]
+ [cljs.repl :as repl]
+ [cljs.repl.browser :as browser])
+ (:import (java.io InputStreamReader OutputStreamWriter)))
+
+(defn run-repl-listen [port output-dir]
+ (let [env (browser/repl-env :port (Integer. port) :working-dir output-dir)]
+ (repl/repl env)))
+
+(defn- stream-seq
+ "Takes an InputStream and returns a lazy seq of integers from the stream."
+ [stream]
+ (take-while #(>= % 0) (repeatedly #(.read stream))))
+
+(defn- start-bg-command [command]
+ (Thread/sleep 1000)
+ (let [process (.exec (Runtime/getRuntime) (into-array command))]
+ (.close (.getOutputStream process))
+ (with-open [stdout (.getInputStream process)
+ stderr (.getErrorStream process)]
+ (let [[out err]
+ (for [stream [stdout stderr]]
+ (apply str (map char (stream-seq (InputStreamReader. stream "UTF-8")))))]
+ {:process process :out out :err err}))))
+
+(defn run-repl-launch [port output-dir command]
+ (println "Background command output will be shown after the REPL is exited via :cljs/quit .")
+ (let [bg (future (start-bg-command command))]
+ (run-repl-listen port output-dir)
+ (try
+ (let [{:keys [process out err]} @bg
+ delim (string/join (take 80 (repeat "-")))
+ header #(str delim "\n" % "\n" delim)]
+ (.destroy process)
+ (.waitFor process)
+ (when (not= (.length out) 0)
+ (println (header "Standard output from launched command:"))
+ (println out))
+ (when (not= (.length err) 0)
+ (println (header "Standard error from launched command:"))
+ (println err)))
+ (catch Exception e
+ (binding [*out* *err*]
+ (println "Launching command failed:" e))))))
View
8 support/src/cljsbuild/repl/rhino.clj
@@ -0,0 +1,8 @@
+(ns cljsbuild.repl.rhino
+ (:require
+ [cljs.repl :as repl]
+ [cljs.repl.rhino :as rhino]))
+
+(defn run-repl-rhino []
+ (let [env (rhino/repl-env)]
+ (repl/repl env)))
Please sign in to comment.
Something went wrong with that request. Please try again.