pax_global_header00006660000000000000000000000064124715323050014514gustar00rootroot0000000000000052 comment=55598b378f204342b8dba6a6280d8aab9bb51a54 cljx-0.6.0/000077500000000000000000000000001247153230500124575ustar00rootroot00000000000000cljx-0.6.0/.gitignore000066400000000000000000000000731247153230500144470ustar00rootroot00000000000000lib/ .lein* test/generated pom.xml target/ bin .nrepl-port cljx-0.6.0/CHANGES.md000066400000000000000000000071121247153230500140520ustar00rootroot00000000000000# changelog ## [0.6.0](https://github.com/lynaghk/cljx/issues?q=milestone%3A0.6.0) * Fixed issue with `0.5.0` where either cljx or cljsbuild would be invoked multiple times in the course of a build when run under Leiningen >= `2.4.2`. (gh-61) * Fixed issue where cljx would not be invoked despite inclusion of it in `:prep-tasks`. (gh-60) * Polling rate of `cljx auto` changed to 250ms (gh-46) The first of the above fixes necessitated a change in how cljx folds into the Leiningen "lifecycle" (now it is a "pure" plugin, and does not use `eval-in` at all). This is _technically_ a breaking change, in that any use of custom `:rules` in `:cljx` configuration resolved from project dependencies will break under `0.6.0`. The fix is to either change project configuration and/or refactor the definition of such `:rules` vars such that they are added to the project as `:plugin` dependencies (rather than "regular" `:dependencies` dependencies). [Terminology fail :-/] ## [0.5.0](https://github.com/lynaghk/cljx/issues?q=milestone%3A0.5.0) * cljx hooks have been eliminated (their use only emits a warning now), due to [upstream changes in Leiningen](https://github.com/lynaghk/cljx/issues/51) (gh-51, gh-49, gh-22) * cljx now depends upon a canonical release of sjacket, which eliminates various dependency resolution warnings * `cljx auto` will now only reprocess files that have changed (gh-53) ## 0.4.0 * The cljx nREPL middleware has been enhanced to make running `lein cljx once` unnecessary when running a _ClojureScript_ REPL. This means that e.g. `(load-namespace 'ns.written.in.cljx)` or `(ns foo (:require ns.written.in.cljx))` will work, even if no corresponding `.cljs` file has been generated yet. (gh-24) * Updated the piggieback dependency to align with its range of compatibility with the ClojureScript compiler API (gh-40) ## 0.3.2 * The cljx nREPL middleware has been enhanced to make running `lein cljx once` unnecessary when running a _Clojure_ REPL. This means that e.g. `(require 'ns.written.in.cljx)` will work, even if no corresponding `.clj` file has been generated yet. (Analogous support for on-the-fly cljx transformations for _ClojureScript_ is coming.) (gh-24) ## 0.3.1 * cljx now includes a small bit of Leiningen middleware that will automatically configure your project to use cljx's nREPL middleware and Piggieback, eliminating the need to duplicate cljx as a dependency in your `project.clj`. (gh-26) * Clojure test files generated by cljx are now properly recognized as "eligible" for testing by Leiningen's `lein test` task (gh-19) ## 0.3.0 Mostly a rewrite to use [sjacket](https://github.com/cgrand/sjacket), removing all dependency on the Clojure reader and thereby eliminating all of the issues that go along with it (e.g. lossy representation, line number changes when emitting Clojure[Script], [issues like this](https://github.com/jonase/kibit/issues/14), etc). The syntax for annotations has changed, as described in the readme. tl;dr, all you need to do is `s/\^:(cljs?)/#+$1/g`. The only gotcha is that putting annotations on var symbols no longer supported (which carried a bunch of problems before anyway, but nevermind that); so, you have to change e.g. this: ```clojure (defn ^:cljs foo [x] (whatever x)) ``` into: ```clojure #+cljs (defn foo [x] (whatever x)) ``` Again, tl;dr: cljx annotations apply to _expressions_, and nothing else. Otherwise, everything works as it did; even your existing `project.clj` configurations can stay as they are (though anything other than the three `:build` map keys described in the README are now superfluous). cljx-0.6.0/LICENSE000066400000000000000000000026071247153230500134710ustar00rootroot00000000000000Copyright (c) 2012, Kevin Lynagh All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name Kevin Lynagh may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEVIN LYNAGH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cljx-0.6.0/README.markdown000066400000000000000000000313301247153230500151600ustar00rootroot00000000000000 /$$ | $$ /$$$$$$$ | $$ /$$ /$$ /$$ /$$_____/ | $$ |__/ | $$ /$$/ | $$ | $$ /$$ \ $$$$/ | $$ | $$ | $$ >$$ $$ | $$$$$$$ | $$ | $$ /$$/\ $$ \_______/ |__/ | $$ |__/ \__/ /$$ | $$ | $$$$$$/ \______/ cljx is a [Leiningen](https://github.com/technomancy/leiningen) plugin and nREPL middleware that produces Clojure and ClojureScript code from a single annotated codebase. Effectively, it is an s-expression preprocessor that yields either Clojure and ClojureScript sources on disk (e.g. for inclusion in jars or for input to compilation tools like Clojure AOT-compilation and lein-cljsbuild): ``` +---------+ | .cljx | | sources | +-----+---+ | | | +-----v-----+ +----------------+ | cljx <-------+ configuration | | Leiningen | | + | | plugin | | rules | +--+--+-----+ +----------------+ | | | | +------------+ | | +------------+ | .clj <-+ +-> .cljs | | sources | | sources | +------------+ +------------+ ``` …or, when used in a REPL, cljx automatically applies the same transformation to any namespaces to be loaded (e.g. as a result of `:require` declarations) from `.cljx` files before they are consumed by the Clojure or ClojureScript compiler: ``` +------------+ +---------------+ +---------+ | cljx | | configuration | | .cljx | | nREPL | | + | | sources +----------> middleware <-------+ rules | +---------+ +-----+------+ +---------------+ | | | +-------v---------+ | `require` | | `:require` | +---------+ | `load-namespace`| +---------+ | .cljs +-------> ...etc... <--------+ .cljs | | sources | +------+--+-------+ | sources | +---------+ | | +---------+ | | +------------+ | | +---------------+ | Clojure <-+ +-> ClojureScript | | compiler | | compiler | +------------+ +---------------+ ``` When using cljx, you put APIs and implementations that are meant to be fundamentally portable between Clojure and ClojureScript into one annotated `.cljx` codebase, and leave things that are necessarily tied to a single compilation target in their "native" language (e.g. macros should always be in Clojure sources, DOM manipulation stuffs always in ClojureScript sources, etc). Does this seem crazy? Crazy awesome, maybe. ### Projects that use cljx Here's some real-world examples of projects that use cljx (when you feel in trouble, refer to these for usage and configuration examples): * [schema](https://github.com/Prismatic/schema) * [formative](https://github.com/jkk/formative) * [pprng](https://github.com/cemerick/pprng/) * [sablano](https://github.com/r0man/sablono) * [double-check](https://github.com/cemerick/double-check) * [garden](https://github.com/noprompt/garden) * [hickory](https://github.com/davidsantiago/hickory) * [validateur](https://github.com/michaelklishin/validateur) * [frak](https://github.com/noprompt/frak) * [cellular](https://github.com/nodename/cellular) * [enoki](https://github.com/harto/enoki) * [inflections-clj](https://github.com/r0man/inflections-clj) * [pathetic](https://github.com/davidsantiago/pathetic) * [cats](https://github.com/niwibe/cats) ## "Installation" To use it, add to the `:dev` profile in your `project.clj`: ```clojure :profiles {:dev {:plugins [[com.keminglabs/cljx "0.6.0"]]}} :cljx {:builds [{:source-paths ["src/cljx"] :output-path "target/classes" :rules :clj} {:source-paths ["src/cljx"] :output-path "target/classes" :rules :cljs}]} ``` A more comprehensive configuration example can be found [here](https://github.com/lynaghk/cljx/blob/master/sample.project.clj). If you want cljx to be invoked automatically as part of the Leiningen compilation process (e.g. before cutting a jar, performing a release), add cljx to your `:prep-tasks` vector in `project.clj`. The :prep-tasks project value will replace the default leiningen setting (below) which is required for AOT compilation: ```clojure :prep-tasks ["javac" "compile"] ``` With cljx once: ```clojure :prep-tasks [["cljx" "once"] "javac" "compile"] ``` Note that any Clojure tests generated by cljx will not be picked up by Leiningen's test runner (see [this](https://github.com/technomancy/leiningen/issues/1782) for some background). In general, it is probably advisable to set up aliases so you can completely control what Leiningen will run: ```clojure :aliases {"cleantest" ["do" "clean," "cljx" "once," "test," "cljsbuild" "test"]} ``` ## Changelog See `CHANGES.md` at the root of this repo. (You'll especially want to look at the entry for `0.3.0` if you've been using older versions of cljx, as things have changed [of course, we think significantly for the better :-P].) ## Usage There are two ways in which the cljx transformation can be made: via a Leiningen task (necessary when you need the transformation result on disk for e.g. packaging into a jar for distribution), and/or via an nREPL middleware that makes using the Leiningen task unnecessary in REPL sessions. cljx's Leiningen task can be run `once` or `auto`; if the latter (e.g. `lein cljx auto`), it will watch all `source-paths` for changes to `.cljx` files. `once` is the default. Each build (i.e. maps in the `:builds` vector in the `:cljx` configuration) can be configured with the following options: * `:source-paths`, a sequence of the source roots that contain your `.cljx` files. Note that putting your `.cljx` files in your "regular" Leiningen project's `:source-paths` (by default, `"src"`) is not recommended; doing so will likely lead to them being included in e.g. jar files created by Leiningen. Better to keep them separate, and use cljx to direct Clojure and ClojureScript sources whereever they will be picked up by other tooling. * `:output-path`, the root directory where cljx's output will land. Common options are `"target/classes"` for both Clojure and ClojureScript files you plan on distributing as a library; or, in an application project using [lein-cljsbuild](https://github.com/emezeske/lein-cljsbuild) to produce deployable JavaScript, sending cljx-produced Clojure output to `"target/classes"` (so it's on the classpath and available to be added to a jar/war) and ClojureScript output to a dummy directory (e.g. `"target/generated/cljs"`) that can be a source path in your lein-cljsbuild configuration(s). * `:rules` can be one of: * `:clj` or `:cljs` to use cljx's default Clojure or ClojureScript ruleset (`cljx.rules/clj-rules` and `cljx.rules/cljs-rules`, respectively) * a map that specifies the three slots that make up a cljx ruleset: * `:filetype`, a string that defines what the extension of output filenames will be, e.g. `"cljs"` * `:features`, a _set_ of strings, each naming an enabled "feature"; code in `.cljx` files that is annotated with a feature that is not included in this set will be pruned in the output * `:transforms`, a sequence of functions that are applied to each expression in each input file, and can modify that expression without constraint * a fully-qualified symbol that names a var containing a map as described above In general, you'll never need to go beyond the named cljx-provided rules. E.g., the `.cljx` source containing ```clojure (ns example (#+clj :use #+cljs :use-macros [c2.macros :only (combine-with)])) (defn x-to-string [x] (let [buf #+clj (StringBuilder.) #+cljs (gstring/StringBuffer.)] (.append buf "x is: ") (.append buf (str x)))) (reify #+clj clojure.lang.IFn #+cljs cljs.core.IFn (invoke [_ x] (inc x))) ``` …will, when transformed using the `:cljs` ruleset, yield: ```clojure (ns example ( :use-macros [c2.macros :only (combine-with)])) (defn x-to-string [x] (let [buf (gstring/StringBuffer.)] (.append buf "x is: ") (.append buf (str x)))) (reify cljs.core.IFn (invoke [_ x] (inc x))) ``` Notice that only the `#+cljs`-annotated expressions remain, and that everything is still in the same position as it was in the `.cljx` file; this last fact means that line and column numbers produced by the resulting Clojure/ClojureScript code (e.g. in error messages, stack traces/frames, debuggers, source maps, etc) will remain true to the original sources. The `#+feature-name` "annotation" syntax is shamelessly stolen from [Common Lisp](http://www.lispworks.com/documentation/lw50/CLHS/Body/02_dhq.htm) (and is perhaps being considered for inclusion in Clojure[Script] itself?...see [feature expressions](http://dev.clojure.org/display/design/Feature+Expressions)). cljx only supports the simplest form of the syntax; other forms can be considered valid TODOs: * Exclusionary annotations, e.g. `#-cljs` * "Union" annotations, e.g. `#+(or clj clr)` ### Clojure is a hosted language, in all flavours cljx does *not* try to hide implementation differences between host platforms. Clojure has ints, floats, longs, &c., ClojureScript has number; Clojure regular expressions act differently than ClojureScript regular expressions, because *they are different*, and so on. cljx only tries to unify Clojure/ClojureScript abstractions when it makes sense. E.g., converting `clojure.lang.IFn` into `IFn` when generating ClojureScript. The rest is up to you, in annotating your code to include or exclude what's needed by each runtime. Also, note that *cljx has no effect on code produced by macros*. Macroexpansion occurs long after cljx touches your code. ### REPL Integration cljx provides an nREPL middleware that allows you to work with `.cljx` files in the same way you work with regular `.clj` files from any toolchain with good nREPL support, like [cider](https://github.com/clojure-emacs/cider), [Counterclockwise](http://code.google.com/p/counterclockwise/), etc. When you add cljx as a `:plugin` to your Leiningen project: 1. The cljx and [Piggieback](https://github.com/cemerick/piggieback) nREPL middlewares will automatically be added to your `:repl-options` 2. cljx itself will be added as a project dependency (this will only affect REPL processes, and won't leak out into your project's `pom.xml` (_as long as you add cljx to your `:dev` profile_), influencing downstream users of your library, if you're writing one) (Note that this does not conflict with using the [Austin](http://github.com/cemerick/austin) plugin to automate the configuration of your project to use Piggieback. In fact, the pairing is highly recommended for making the ClojureScript REPL side of your cljx project easy-peasy.) With cljx installed as a plugin, all nREPL evaluations and `load-file` operations will be processed by cljx appropriately before they reach the Clojure or ClojureScript compiler. Whether cljx code is processed for Clojure or ClojureScript is determined by the existence [or not] of a Piggieback ClojureScript environment in your current nREPL session's environment; this is entirely automatic. Currently, only cljx's default rulesets are used in this case (though you can work around this by making your own higher-order cljx nREPL middleware that uses whatever rulesets you want). ### Misc #### Syntax highlighting Get the same syntax highlighting of `.cljx` files as you currently do for `.clj` files! ##### Emacs `clojure-mode` supports `.cljx` source files out-of-the-box. It will even font-lock features properly. ##### Vim `autocmd BufNewFile,BufReadPost *.cljx setfiletype clojure` ##### Eclipse + CounterClockwise 1. In Preferences, go to General > Editors > File Associations. 2. Add a `*.cljx` file type in the upper list. 3. Add an editor association for that `*.cljx` file type to Counterclockwise's `Clojure Editor`. ## Thanks * @jonase and @ohpauleez for enabling the first [kibit](https://github.com/jonase/kibit)-based cljx * @cemerick for design chats, maintaining and extending cljx, and rewriting the core to use sjacket * @cgrand and @trptcolin for [sjacket](https://github.com/cgrand/sjacket) * @swannodette for [core.match](https://github.com/clojure/core.match) cljx-0.6.0/project.clj000066400000000000000000000023001247153230500146120ustar00rootroot00000000000000(defproject com.keminglabs/cljx "0.6.0" :description "Static Clojure code rewriting" :url "http://github.com/lynaghk/cljx" :license {:name "BSD" :url "http://www.opensource.org/licenses/BSD-3-Clause"} :dependencies [[org.clojure/clojure "1.5.1"] [org.clojure/core.match "0.2.0"] [net.cgrand/sjacket "0.1.1" :exclusions [org.clojure/clojure]] [com.cemerick/piggieback "0.1.5"] [watchtower "0.1.1"]] :cljx {:builds [{:source-paths ["test"] :output-path "target/test-output" :rules :clj} {:source-paths ["test"] :output-path "target/test-output" :rules :cljs}]} :profiles { ; self-reference and chained `lein install; lein cleantest` invocation ; needed to use the project as its own plugin. Leiningen :-( :self-plugin [:default {:plugins [[com.keminglabs/cljx "0.6.0-SNAPSHOT"] [com.cemerick/clojurescript.test "0.3.1"]]}]} :aliases {"cleantest" ["with-profile" "self-plugin" "do" "clean," "cljx" "once," "test"]} :eval-in :leiningen) cljx-0.6.0/sample.project.clj000066400000000000000000000053521247153230500161040ustar00rootroot00000000000000; This sample project.clj file is suitable as a starting point for building ; libraries using cljx. Some things it does: ; ; * Clojure and ClojureScript resources generated by cljx are put under ; `target/*`; this means they'll be removed by `lein clean`. ; * Non-test resources are put into `target/classes`; by default, that ; directory's contents will be included in the .jar file produced by Leiningen ; * Generated Clojure testing resources are dropped into `target/test-classes`, ; where `lein test` will pick them up. ; * `.cljx` files are excluded from any generated jar files ; * ClojureScript is a dependency in the `:dev` profile, so it will not be ; transitively passed on to this project's dependents. ; ; Using cljx in an "application" setting (where you're producing artifacts for ; deployment, as opposed to for consumption by other Clojure/ClojureScript ; projects) would use largely the same cljx configuration, though likely a ; different lein-cljsbuild configuration (so that the generated JavaScript is ; placed appropriately for inclusion in .war, ubarjar, or other resource bundles ; destined for deployment). (defproject org.you/yourproject "1.0.0" :jar-exclusions [#"\.cljx|\.swp|\.swo|\.DS_Store"] :source-paths ["src/cljx"] :test-paths ["target/test-classes"] :dependencies [[org.clojure/clojure "1.6.0-alpha1"]] ;; needs disabling for Cljx. See ;; https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L389-392. :auto-clean false :cljx {:builds [{:source-paths ["src/cljx"] :output-path "target/classes" :rules :clj} {:source-paths ["src/cljx"] :output-path "target/classes" :rules :cljs} {:source-paths ["test/cljx"] :output-path "target/test-classes" :rules :clj} {:source-paths ["test/cljx"] :output-path "target/test-classes" :rules :cljs}]} :cljsbuild {:test-commands {"node" ["node" :node-runner "target/testable.js"]} :builds [{:source-paths ["target/classes" "target/test-classes"] :compiler {:output-to "target/testable.js" :optimizations :advanced :pretty-print true}}]} :profiles {:dev {:plugins [[org.clojure/clojurescript "0.0-2156"] [com.keminglabs/cljx "CLJX-VERSION-HERE"] [lein-cljsbuild "1.0.1"]] :aliases {"cleantest" ["do" "clean," "cljx" "once," "test," "cljsbuild" "test"] "deploy" ["do" "clean," "cljx" "once," "deploy" "clojars"]}}}) cljx-0.6.0/src/000077500000000000000000000000001247153230500132465ustar00rootroot00000000000000cljx-0.6.0/src/cljx/000077500000000000000000000000001247153230500142065ustar00rootroot00000000000000cljx-0.6.0/src/cljx/core.clj000066400000000000000000000066711247153230500156420ustar00rootroot00000000000000(ns cljx.core (:require [cljx.rules :as rules] [net.cgrand.sjacket :as sj] [net.cgrand.sjacket.parser :as p] [clojure.string :as str] [clojure.java.io :as io] [clojure.zip :as z]) (:import java.io.File)) (def ^:private warning-str ";;;;;;;;;;;; This file autogenerated from ") ;;Taken from clojure.tools.namespace (defn- cljx-source-file? "Returns true if file is a normal file with a .cljx extension." [^File file] (and (.isFile file) (.endsWith (.getName file) ".cljx"))) (defn- find-cljx-sources-in-dir "Searches recursively under dir for CLJX files. Returns a sequence of File objects, in breadth-first sort order." [^File dir] ;; Use sort by absolute path to get breadth-first search. (sort-by #(.getAbsolutePath ^File %) (filter cljx-source-file? (file-seq (io/file dir))))) (defn- walk [zloc {:keys [features transforms] :as rules}] (let [zloc (reduce #(%2 %) (rules/apply-features zloc features) transforms)] (if-not (z/branch? zloc) zloc (->> (z/down zloc) ; I feel like I've done this kind of zipper walk in a simpler way ; before... (iterate #(let [loc (walk % rules)] (or (z/right loc) {::last loc}))) (some ::last) z/up)))) (defn transform [code rules] (if (empty? (str/trim code)) code (-> (p/parser code) z/xml-zip (walk rules) z/root sj/str-pt))) (defn- relativize [f root] (-> root io/file .toURI (.relativize (-> f io/file .toURI)))) (defn- destination-path [source source-path output-dir] (.getAbsolutePath (io/file output-dir (str (relativize source source-path))))) (defn generate ([options] (generate options (find-cljx-sources-in-dir (:source-path options)))) ([{:keys [source-path output-path rules] :as options} files] (println "Rewriting" source-path "to" output-path (str "(" (:filetype rules) ")") "with features" (:features rules) "and" (count (:transforms rules)) "transformations.") (doseq [f files :let [result (transform (slurp f) rules) destination (str/replace (destination-path f source-path output-path) #"\.[^\.]+$" (str "." (:filetype rules)))]] (doto destination io/make-parents (spit (with-out-str (println result) (print warning-str) (println (.getPath f)))))))) (defn cljx-compile ([builds & {:keys [files]}] "The actual static transform, separated out so it can be called repeatedly." (doseq [{:keys [source-paths output-path rules] :as build} builds] (let [rules (cond (= :clj rules) rules/clj-rules (= :cljs rules) rules/cljs-rules (symbol? rules) (do (require (symbol (namespace rules))) @(resolve rules)) :default (eval rules))] (doseq [p source-paths :let [abs-path (.getAbsolutePath (io/file p))] ] (if files (when-let [files (->> files (filter #(.startsWith (.getAbsolutePath (io/file %)) abs-path)) seq)] (generate (assoc build :rules rules :source-path p) files)) (generate (assoc build :rules rules :source-path p)))))))) cljx-0.6.0/src/cljx/hooks.clj000066400000000000000000000004221247153230500160210ustar00rootroot00000000000000(ns cljx.hooks (:require [leiningen.core.main :as lmain])) (defn activate [] (lmain/warn "cljx no longer provides Leiningen hooks; please use :prep-tasks in your project.clj instead, e.g.:") (lmain/warn " :prep-tasks [[\"cljx\" \"once\"] \"javac\" \"compile\"]")) cljx-0.6.0/src/cljx/plugin.clj000066400000000000000000000017341247153230500162030ustar00rootroot00000000000000(ns cljx.plugin (:require [robert.hooke :as hooke] leiningen.repl [clojure.java.io :as io])) (def cljx-coordinates (-> (or (io/resource "META-INF/leiningen/com.keminglabs/cljx/project.clj") (io/resource "META-INF/leiningen/org.clojars.cemerick/cljx/project.clj")) slurp read-string ((fn [[_ artifact version]] [artifact version])))) (assert (and (symbol? (first cljx-coordinates)) (string? (second cljx-coordinates))) (str "Something went wrong, cljx coordinates are invalid: " cljx-coordinates)) (defn middleware [project] (if (or (-> project :cljx :disable-repl-integration) (not (-> project meta :included-profiles set (contains? :repl)))) project (-> project (update-in [:repl-options :nrepl-middleware] (fnil into []) '[cljx.repl-middleware/wrap-cljx cemerick.piggieback/wrap-cljs-repl]) (update-in [:dependencies] (fnil conj []) cljx-coordinates)))) cljx-0.6.0/src/cljx/repl_middleware.clj000066400000000000000000000104761247153230500200470ustar00rootroot00000000000000(ns cljx.repl-middleware (:require [cljx.core :as cljx] [cljx.rules :as rules] [cemerick.piggieback :as piggieback] cljs.closure [clojure.java.io :as io] [clojure.tools.nrepl.middleware :refer (set-descriptor!)])) (defn- find-resource [name] (if-let [cl (clojure.lang.RT/baseLoader)] (.getResource cl name) (ClassLoader/getSystemResourceAsStream name))) ; maybe eventually allow different rules to be used by the cljx-load ; monkey-patch on a per-nREPL-session basis? Crazy. (def ^:private cljx-load-rules (atom rules/clj-rules)) ; clojure.core/load from ~Clojure 1.6.0 ; clojure.core/load hasn't really changed since ~2009, so monkey patching here ; seems entirely reasonable/safe. (defn- cljx-load "Loads Clojure code from resources in classpath. A path is interpreted as classpath-relative if it begins with a slash or relative to the root directory for the current namespace otherwise." {:added "1.0"} [& paths] (doseq [^String path paths] (let [^String path (if (.startsWith path "/") path (str (#'clojure.core/root-directory (ns-name *ns*)) \/ path))] (when @#'clojure.core/*loading-verbosely* (printf "(clojure.core/load \"%s\")\n" path) (flush)) (#'clojure.core/check-cyclic-dependency path) (when-not (= path (first @#'clojure.core/*pending-paths*)) (with-bindings {#'clojure.core/*pending-paths* (conj @#'clojure.core/*pending-paths* path)} (let [base-resource-path (.substring path 1) cljx-path (str base-resource-path ".cljx")] (if-let [cljx (find-resource cljx-path)] (do (when @#'clojure.core/*loading-verbosely* (printf "Transforming cljx => clj from %s.cljx\n" base-resource-path)) (-> (slurp cljx) (cljx/transform (:clj @cljx-load-rules)) java.io.StringReader. (clojure.lang.Compiler/load base-resource-path (last (re-find #"([^/]+$)" cljx-path))))) (clojure.lang.RT/load base-resource-path)))))))) (def ^:private clojure-load load) (def ^:private clojure-resource io/resource) (defn cljx-cljs-resource [& [^String resource-name :as resource-args]] (or (apply clojure-resource resource-args) (when-let [cljx (and (.endsWith resource-name ".cljs") (apply clojure-resource (cons (.replaceAll resource-name ".cljs$" ".cljx") (rest resource-args))))] (let [tmp-cljs (java.io.File/createTempFile "cljxtransform" ".cljs")] (.deleteOnExit tmp-cljs) (as-> (slurp cljx) % (cljx/transform % (:cljs @cljx-load-rules)) (spit tmp-cljs %)) (.toURL tmp-cljs))))) (def ^:private install-cljx-load (delay (alter-var-root #'load (constantly cljx-load)) ; I originally thought that this was a hack, and that replacing ; `cljs.closure/cljs-source-for-namespace` was going to be a much cleaner ; solution, but that puts the reference to a temp file within view of the ; CLJS compiler, as *cljs-file*; this makes compiler warnings _useless_. (alter-var-root #'cljs.closure/cljs-dependencies (fn [cljs-dependencies] (fn [& args] (with-redefs [io/resource cljx-cljs-resource] (apply cljs-dependencies args))))))) (defn wrap-cljx ([h] (wrap-cljx h {:clj rules/clj-rules :cljs rules/cljs-rules})) ([h {:keys [clj cljs] :as rules}] ; essentially changing the rules each time a new nREPL endpoint is set up... ; generally will only ever happen once per JVM process (reset! cljx-load-rules rules) @install-cljx-load (fn [{:keys [op code file file-name session] :as msg}] (let [rules (if (@session #'piggieback/*cljs-repl-env*) cljs clj)] (cond (and (= op "eval") code) (h (assoc msg :code (cljx/transform code rules))) (and (= op "load-file") file (re-matches #".+\.cljx$" file-name)) (h (assoc msg :file (cljx/transform file rules))) :else (h msg)))))) (set-descriptor! #'wrap-cljx {:requires #{"clone"} :expects #{#'piggieback/wrap-cljs-repl} :handles {}}) cljx-0.6.0/src/cljx/rules.clj000066400000000000000000000055001247153230500160320ustar00rootroot00000000000000(ns cljx.rules (:require [net.cgrand.sjacket :as sj] [clojure.core.match :refer (match)] [clojure.zip :as z] [clojure.string :as str]) (:import net.cgrand.parsley.Node)) (defn- whitespace-for [string] (str/replace string #"[^\n\r]" " ")) (defn- whitespace-node-for [node] (Node. :whitespace [(whitespace-for (sj/str-pt node))])) (defn apply-features [zip-loc features] (match [(z/node zip-loc)] [{:tag :reader-literal :content ["#" {:tag :symbol :content [{:tag :name :content [(feature-string :guard #(re-find #"^[\-\+]" %))]}]} & annotated-exprs]}] (let [inclusive? (= \+ (first feature-string)) ;; TODO exclusive expressions, sets in any case feature (subs feature-string 1)] (if-not (and inclusive? (features feature)) (z/edit zip-loc whitespace-node-for) (z/edit zip-loc assoc :content (vec (cons (Node. :whitespace [(whitespace-for (str "#" feature-string))]) annotated-exprs))))) :else zip-loc)) (defn elide-form [form-name zip-loc] (match [(z/node zip-loc)] [{:tag :list :content ["(" {:tag :symbol :content [{:tag :name :content [(sym-name :guard #(= % form-name))]}]} & _]}] (z/edit zip-loc whitespace-node-for) :else zip-loc)) (defn- pad1 [replacement original] (apply str replacement (repeat (- (count original) (count replacement)) \space))) (defn replace-symbols [symbols-map zip-loc] (match [(z/node zip-loc)] [{:tag :symbol :content [{:tag :name :content [(symbol-name :guard #(and (string? %) (-> % symbol symbols-map)))]}]}] (z/edit zip-loc update-in [:content 0 :content 0] #(-> % symbol symbols-map name (pad1 %))) :else zip-loc)) ; disabled until a full solution can be proffered ; see gh-11 #_(def ^:private clj->cljs-symbols (->> '[IFn] (map name) (map #(vector (str "clojure.lang." %) (str "cljs.core." %))) (map (partial mapv symbol)) (into {}))) (def cljs-rules {:filetype "cljs" :features #{"cljs"} :transforms [(partial elide-form "defmacro") ; disabled until a full solution can be proffered, ; see gh-11 #_(partial replace-symbols clj->cljs-symbols)]}) (def clj-rules {:filetype "clj" :features #{"clj"} :transforms []}) cljx-0.6.0/src/leiningen/000077500000000000000000000000001247153230500152165ustar00rootroot00000000000000cljx-0.6.0/src/leiningen/cljx.clj000066400000000000000000000021431247153230500166500ustar00rootroot00000000000000(ns leiningen.cljx (:require cljx.plugin cljx.core [watchtower.core :as wt])) (def no-opts-warning "You need a :cljx entry in your project.clj! See the cljx docs for more info.") (defn- once "Transform .cljx files once and then exit." [project builds] (cljx.core/cljx-compile builds)) (defn- auto "Watch .cljx files and transform them after any changes." [project builds] (let [dirs (set (flatten (map :source-paths builds)))] (println "Watching" (vec dirs) "for changes.") (-> (wt/watcher* dirs) (wt/file-filter (wt/extensions :cljx)) (wt/rate 250) (wt/on-change (fn [files] (cljx.core/cljx-compile builds :files files))) (wt/watch)))) (defn cljx "Statically transform .cljx files into Clojure and ClojureScript sources." {:subtasks [#'once #'auto]} ([project] (cljx project "once")) ([project subtask] (if-let [opts (:cljx project)] (if-let [{builds :builds} opts] (case subtask "once" (once project builds) "auto" (auto project builds))) (println no-opts-warning)))) cljx-0.6.0/test-output/000077500000000000000000000000001247153230500147745ustar00rootroot00000000000000cljx-0.6.0/test-output/cljx/000077500000000000000000000000001247153230500157345ustar00rootroot00000000000000cljx-0.6.0/test-output/cljx/a.clj000066400000000000000000000007701247153230500166520ustar00rootroot00000000000000 (ns cljx.a (:use [clojure.pprint :only [pp]])) (defn p [x] (println x)) (defn both [x] (+ x x)) (reify clojure.lang.IFn (invoke [_ x] (inc x))) (defmacro increment [x] `(inc ~x)) (comment foo) ; making sure other tagged literals are left untouched #whatever [1 2 3] ;;;;;;;;;;;; This file autogenerated from test/cljx/a.cljx cljx-0.6.0/test-output/cljx/a.cljs000066400000000000000000000007701247153230500170350ustar00rootroot00000000000000 (ns cljx.a (:use-macros [clojure.pprint :only [pp]])) (defn p [x] (.log js/console x)) (defn both [x] (+ x x)) (reify cljs.core.IFn (invoke [_ x] (inc x))) (comment foo) ; making sure other tagged literals are left untouched #whatever [1 2 3] ;;;;;;;;;;;; This file autogenerated from test/cljx/a.cljx cljx-0.6.0/test-output/cljx/b.clj000066400000000000000000000003771247153230500166560ustar00rootroot00000000000000(ns cljx.b ( :require [foo.bar])) (defn clj-only [x] "kthx") (defn x [y] ( + y y)) ;;;;;;;;;;;; This file autogenerated from test/cljx/b.cljx cljx-0.6.0/test-output/cljx/b.cljs000066400000000000000000000003771247153230500170410ustar00rootroot00000000000000(ns cljx.b ( :require-macros [foo.bar])) (defn cljs-only [x] "Internets time") (defn x [y] ( - y y)) ;;;;;;;;;;;; This file autogenerated from test/cljx/b.cljx cljx-0.6.0/test/000077500000000000000000000000001247153230500134365ustar00rootroot00000000000000cljx-0.6.0/test/cljx/000077500000000000000000000000001247153230500143765ustar00rootroot00000000000000cljx-0.6.0/test/cljx/a.cljx000066400000000000000000000006741247153230500155070ustar00rootroot00000000000000#+clj (ns cljx.a (:use [clojure.pprint :only [pp]])) #+cljs (ns cljx.a (:use-macros [clojure.pprint :only [pp]])) #+cljs (defn p [x] (.log js/console x)) #+clj (defn p [x] (println x)) (defn both [x] (+ x x)) (reify #+clj clojure.lang.IFn #+cljs cljs.core.IFn (invoke [_ x] (inc x))) (defmacro increment [x] `(inc ~x)) (comment foo) ; making sure other tagged literals are left untouched #whatever [1 2 3] cljx-0.6.0/test/cljx/b.cljx000066400000000000000000000003031247153230500154750ustar00rootroot00000000000000(ns cljx.b (#+cljs :require-macros #+clj :require [foo.bar])) #+clj (defn clj-only [x] "kthx") #+cljs (defn cljs-only [x] "Internets time") (defn x [y] (#+clj + #+cljs - y y)) cljx-0.6.0/test/cljx/test.clj000066400000000000000000000004301247153230500160440ustar00rootroot00000000000000(ns cljx.test (:use clojure.test)) (deftest verify-cljx-output (let [proc (.. Runtime getRuntime (exec (str "diff -r test-output target" java.io.File/separator "test-output")))] (println (slurp (.getInputStream proc))) (is (zero? (.waitFor proc)))))