Chenex is a feature expression library in the same vein as the excellent cljx project. This library tries to remain true to the cljx implementation while adding a few new features (see below). The name comes from a combination of chen (honk honk!) and cljx.
If you're used to cljx, then chenex feature expressions will have a slightly new syntax:
(chenex/in! [:clj] (+ 1 1))
The chenex/in!
function is for adding code to some platform. In
the case above, we added (+ 1 1)
to Clojure. Negation may also be used
with chenex/ex!
to omit the code from some platform. For example:
(chenex/ex! [:clj] (+ 1 1))
The chenex/ex!
above will cause the code to be written everywhere but in
Clojure. Assuming that Clojure and ClojureScript are the only two
platforms we're targeting, this means the code will only show up in
ClojureScript.
Now let's try an example with more platforms to display the include feature. Imagine that we have a project that targets osx, linux, and windows computers. We can use includes in feature expressions to build code for multiple platforms:
(chenex/in! [:windows :linux] (+ 1 1))
This puts the code in windows and linux builds but not in osx. An equivalent expression with negation would be:
(chenex/ex! [:osx] (+ 1 1))
What if we wanted different code for osx, linux, and windows? For
targeting multiple envs there is in-case!
(include case).
Here is what this may look like with using in-case!
:
(chenex/in-case! [:windows] (println "I'm in windows")
[:osx] (println "Shows in osx")
:else (println "This is linux"))
Like in cljx, all cross-platform code must go in .cljx
files. Any
non-cljx files will be copied over without any special parsing.
The REPL also must have a special entry in the project.clj
that
contains a set of one or more chenex builds. For example:
:chenex {:builds [...]
:repl #{:ios :m}
This can be set dynamically while working by either editing the
entry in your project.clj
by hand or with:
$ lein chenex repl ios m
One extra profile must be added to your project.clj
in order for
chenex to work correctly:
:profiles {:default [:base :system :user :provided :dev :plugin.chenex/default]}
(This is a temporary workaround to inject dependecies for chenex and hopefully will go away around the time of Leiningen 3.0.0.)
The :builds
option in your project.clj
looks almost identical to the
:builds
from cljx. Here is what an example build for Clojure might
look like:
:chenex {:builds [{:source-paths ["src/cljx"]
:output-path "target/classes"
:rules {:filetype "clj"
:features #{"clj"}
:inner-transforms [my.project/expand-regexes
my.project/inject-fn]}}]}
The only new item is :inner-transforms
, which sequentially applies any
functions to the code inside of feature expressions. In this case,
expand-regexes
and then inject-fn
would be applied to the source
code for Clojure expressions.
See the original cljx documentation for information about the options: https://github.com/lynaghk/cljx
By default, whenever code is compiled with chenex, Leiningen will print a compile message. If you're using chenex to write a tool of your own and would like to disable the compiling message add:
:chenex {:log false
...}
To run chenex once, use:
$ lein chenex compile
For running chenex with auto (via lein-auto), do:
$ lein auto chenex compile
If you're going to do a Clojure/ClojureScript build, I have written a default template to save some time. Just run:
$ lein chenex template cljx
That creates a a basic configuration written for you that looks like this (showing cljx):
[{:source-paths ["src"]
:output-path "target/generated-src"
:rules {:filetype "clj"
:features #{"clj"}
:inner-transforms []}}
{:source-paths ["src"]
:output-path "target/generated-src"
:rules {:filetype "cljs"
:features #{"cljs"}
:inner-transforms []}}
{:source-paths ["test"]
:output-path "target/generated-test"
:rules {:filetype "clj"
:features #{"clj"}
:inner-transforms []}}
{:source-paths ["test"]
:output-path "target/generated-test"
:rules {:filetype "cljs"
:features #{"cljs"}
:inner-transforms []}}]
For things to work properly, you must require chenex
like this:
(ns your.project
(:require [greenyouse.chenex :as chenex])
If you're using chenex to write a library that others projects can depend on (e.g. a library of UI components), you should be mindful of one major oddity.
All files must be either clj or cljs (not cljx). To help make this a
bit easier I added a package
command that converts all cljx files in
the source-paths to either clj or cljs. Use it like this:
# for clj files
$ lein chenex package clj
# for cljs files
$ lein chenex package cljs
A huge thanks to cljx, lein-auto, sjacket, and rewrite-clj, without which this project would not exist.