General-purpose build system
npm install cherry
|5||downloads in the last week|
|10||downloads in the last month|
|Version||0.0.5 last updated 9 months ago|
|Dependencies||underscore, coffee-script, mkdirp, bagpipe|
Cherry is a general-purpose
build cook tool. It provides a CoffeeScript-based DSL for defining recipes.
Cherry takes a file (a
cherry.coffee) with recipes and turns input files in current directory to output files.
- a friendly CoffeeScript-based DSL
- allows to write very custom build flows while being terse and declarative
- automatic dependencies resolution
- watch mode
- pattern matching between input and output paths
- asynchronous operation (while supporting synchronous operation as well)
- flow library that eases composition of build steps
Install from npm with
-g switch to put it in the
npm install -g cherry
Supported commands for
cherry are listed when none is given.
PS> cherry No command given. Need at least one of those: clean Deletes all targets build Builds all targets watch Watches all inputs and rebuilds outdated targets dump Print the dependency tree
Commands can be chained in any combination, for example
cherry clean build watch. Mind that
watch is supposed to be always the last one because it never ends (only when the process is killed by Ctrl+C).
A recipe looks like this:
recipe in: 'src/*.coffee' out: 'lib/*.js' run: (flow (read 'utf8'), (compile coffee.compile), (save 'utf8'))
A recipe have three mandatory parts:
in it's a pattern of input files paths. Current directory is scanned for files that matches this pattern.
* marks a wild card group.
out tells what will be produced (a target) from matched input file(s). It can contain wild card groups as well. Wild card groups are matched against corresponding groups in
in pattern in a manner similar to
replace method of strings, in the upper example file
src/cherry.coffee will produce
A shortage of groups in output pattern indicates a will to produce one target from many files. For example this recipe will make
app.js of many
recipe in: 'src/*.coffee' out: 'app.js' run: (flow (read 'utf8'), (compile coffee.compile), (join '\n'), (save 'utf8'))
The rule here is that all input paths that matches the same target are grouped as that target's dependencies. All of them will be passed to function given at
run field. You can a little more crazy things like this (
run field as above):
recipe in: 'src/*/*.coffee' out: 'src/*.js'
This will produce one
*.js file for each subdirs of
src dir. (Where the name of file before
.js is name of that dir.)
run field &
run field is a function that will produce a result for a recipe. It is once called for every target and have to accept following arguments:
run: (input_paths, callback) ->
input_paths is an array of all paths of all dependencies for current target.
callback is a function of one optional argument
err that needs to be called after completion of building the target.
err needs to be an error information and only passed on failure.
Any exceptions thrown by
run function will be treated the same as passing an error to
callback. (So catching exception inside of
run is not necessary).
Using callback instead of return value enables asynchronous operation which is much appreciated (allows interleaving building of multiple targets).
run function is called with
this field set to a clone of a target object. Each target (a matched output path) has one object of that kind and is composed of the same fields that are printed by
path-- matched target's output path
recipe-- recipe for that target
deps-- array of dependencies paths
nexts-- array of target paths that depend on this one
awaiting-- array of dependencies that need to be build before this one can start (it's actually empty when
run function is supposed not to change any of these fields but can add new.
For further reference, the arguments and the behaviour mentioned here are called the "
Interdependencies & Caching
Cherry manages interdependencies automatically. If any target depends on any other, then that dependency will be built first. This works also in watch mode: rebuilding a target triggers rebuilding of all others dependent on it.
Actually saving intermediate targets to disk is not necessary. Successful build of a target is not marked when file is saved but when callback given to
run is called. You can cache result in memory and retrieve it later without disk operation. (This can speed up things significantly but also increase memory consumption tremendously so do it with care.)
Caching makes sense even more on watch mode. If you only use
cherry build then all unsaved targets will be built causing a rebuild of all targets that depend on them.
Flow is a library of functions that are meant to ease asynchronous processing of files that take many steps to complete. Each of them either is a function that complies with
run function interface or returns one.
In the Flow library this interface is a little enhanced in that callbacks additionally to
err receive also
Two most significant functions are
do_all. First allows to queue functions and call them in order while the second calls one function across an array of data simultaneously and collect results to new array.
flow = (steps...) -> (data, final_callback) ->
Function returned by
flow executes all
steps in order while passing data between them. Each step receives data from previous step and is required to pass (after
err argument) new data to the callback given to it. (Steps have to be compatible with the
First step is called with
data passed to the resulting function.
final_callback is called with
err argument) that was returned (passed to it's callback) by the last step.
do_all = (iterator) -> (data, callback) ->
Returned function will invoke the
iterator (complying with
run interface) once for every element of
data array, collect all results to an array an invoke
callback with this array.
Indexes of input data are kept in output data arrangement.
read = (encoding) -> (input_paths, callback) ->
Returned function reads all input files using optional
encoding. If no encoding is given then
Buffer objects are created instead of strings.
If given path was saved using
remember function then remembered value is used instead of reading from file system.
save = (encoding) -> (data, callback) ->
Returned function saves the first element of
data to path pointed in
this.path. To save all elements of
remember = (data, callback) ->
data in memory using path from
this.path. Further reads using
read on that path will retrieve the remembered value. The value can be any object.
join = (glue = '\n') -> (data, callback) ->
Returned function joins all elements of
data with glue as in
compile = (compiler, args...) -> (sources, callback) ->
Returns a wrapper for a synchronous compiler. For each element of
sources compiler will be called with
source, args... arguments. Returned values are in an array to next step.
take = (amount) -> (data, callback) ->
Returned function limits the number of elements in
amount of elements.
filter = (pattern) -> (data, callback) ->
Returned function filters elements of
pattern can be a one-param function returning a bool, a RegExp or a string. If it's a string then it's works like the
in field patterns of recipes.
For spawning auxiliary processes you can use the standard node-wise method or the
spawn = (command, args, callback) ->
command is the system command to invoke,
args is an array of arguments and
callback takes following arguments:
(code, stdout, stderr)
code is the exit code returned by application,
stdout is a string with all stdout output from application and
stderr is all stderr output from application.
For convenience there's the
spawn.default handler that wraps given
callback and returns a function that will print the stdout invoke this callback with an error message from stderr if
code is different than
spawn.default = (callback) -> (code, stdout, stderr) ->
MIT, see the