Comparing version 0.0.5 to 0.0.6
@@ -49,6 +49,7 @@ 'use strict'; | ||
if (!aliases.hasOwnProperty(name)) continue | ||
console.log(' %s %s', helpers.padRight(name, max) | ||
, aliases[name].ast.desc) | ||
var desc = aliases[name].ast.desc | ||
if (desc === null) desc = '' | ||
console.log(' %s %s', helpers.padRight(name, max), desc) | ||
} | ||
console.log() | ||
} |
@@ -31,7 +31,12 @@ #!/usr/bin/env node | ||
} | ||
process.stdout.on('error', function (err) { | ||
if (err.code !== 'EPIPE') throw err | ||
}) | ||
var ev = Commands[command](opts) | ||
var errored = false | ||
var finished = false | ||
var signal = null | ||
ev.on('error', function (err) { | ||
log('error', err) | ||
if (err.signal) signal = err.signal | ||
errored = true | ||
@@ -42,4 +47,4 @@ }).on('warning', function (err) { | ||
finished = true | ||
if (errored) | ||
process.exit(1) | ||
if (signal) return process.kill(process.pid, signal) | ||
if (errored) return process.exit(1) | ||
process.exit(0) | ||
@@ -70,12 +75,5 @@ }) | ||
Commands.aliases = require('./aliases') | ||
Commands.clean = require('./clean') | ||
Commands.print = require('./print') | ||
Commands.clean = function () { | ||
} | ||
Commands.trace = function () { | ||
} | ||
Commands.help = function () { | ||
@@ -82,0 +80,0 @@ var ev = new EventEmitter() |
@@ -6,6 +6,4 @@ 'use strict'; | ||
var glob = require('glob') | ||
var util = require('util') | ||
var EventEmitter = require('events').EventEmitter | ||
var map = require('../lib/graph/map') | ||
var ast = require('../lib/read/ast') | ||
var sort = require('../lib/update/sort') | ||
@@ -18,7 +16,4 @@ var expandCmds = require('../lib/update/expand-cmds') | ||
var errors = require('../lib/errors') | ||
var extractCliRefs = require('./extract-cli-refs') | ||
var NO_MATCH = 'no file matches the pattern `%s\'' | ||
var NO_SUCH_FILE = 'no such file `%s\', put patterns into "quotes" to avoid ' + | ||
'shell expansion' | ||
function refreshGraph(data) { | ||
@@ -41,3 +36,3 @@ if (!data) throw errors.invalidArg('data', data) | ||
var ev = new EventEmitter() | ||
var et = extractCliRefs(data) | ||
var et = extractCliRefs(data.graph, data.cliRefs) | ||
forwardEvents(ev, et, function cliRefGot(errored, files) { | ||
@@ -62,33 +57,1 @@ if (errored) return ev.emit('finish') | ||
} | ||
function extractCliRefs(data) { | ||
var ev = new EventEmitter() | ||
process.nextTick(function () { | ||
var files = [] | ||
if (data.cliRefs.length === 0) { | ||
return ev.emit('finish', data.graph.files) | ||
} | ||
data.cliRefs.forEach(function (ref) { | ||
var err | ||
if (!(ref instanceof ast.Ref) || ref.isA(ast.Ref.ALIAS)) | ||
throw errors.invalidArg('data', data) | ||
if (ref.isA(ast.Ref.PATH_GLOB)) { | ||
var newFiles = data.graph.getFilesByGlob(ref.value) | ||
if (newFiles.length === 0) { | ||
err = new Error(util.format(NO_MATCH, ref.value)) | ||
return ev.emit('warning', err) | ||
} | ||
files = files.concat(newFiles) | ||
return | ||
} | ||
var newFile = data.graph.getFile(ref.value) | ||
if (newFile === null) { | ||
err = new Error(util.format(NO_SUCH_FILE, ref.value)) | ||
return ev.emit('error', err) | ||
} | ||
files.push(newFile) | ||
}) | ||
ev.emit('finish', files) | ||
}) | ||
return ev | ||
} |
@@ -11,3 +11,3 @@ 'use strict'; | ||
var ev = new EventEmitter() | ||
var rg = readGraph(opts.file, common.LOG_PATH) | ||
var rg = readGraph(opts.file, common.LOG_PATH, opts.argv.remain) | ||
return forwardEvents(ev, rg, function (errored, data) { | ||
@@ -14,0 +14,0 @@ if (errored) return ev.emit('finish') |
@@ -12,3 +12,2 @@ 'use strict'; | ||
var EventEmitter = require('events').EventEmitter | ||
var queuedFnOf = require('../lib/queued-fn-of') | ||
var runEdges = require('../lib/update/run-edges') | ||
@@ -19,15 +18,30 @@ var forwardEvents = require('../lib/forward-events') | ||
var helpers = require('./helpers') | ||
var errors = require('../lib/errors') | ||
var SOME_UTD = 'Those are already up-to-date: %s.' | ||
var SOME_UTD = 'Those are already up-to-date: %s' | ||
var CMD_FAIL = 'command failed, code %d: %s' | ||
var CMD_SIGFAIL = 'command failed, signal %s: %s' | ||
var DRY_REM_ORPHAN = 'Would remove orphan: %s' | ||
var REM_ORPHAN = 'Removing orphan: %s' | ||
var SIGS = ['SIGINT', 'SIGHUP', 'SIGTERM', 'SIGQUIT'] | ||
function updateGraph(data, opts) { | ||
if (!opts) opts = {} | ||
var ev = new EventEmitter() | ||
forwardEvents(ev, update(data, opts), function () { | ||
if (opts['dry-run']) return ev.emit('finish') | ||
mkdirp(path.dirname(common.LOG_PATH), function (err) { | ||
if (err) return helpers.bailoutEv(ev, err) | ||
var s = data.log.save(fs.createWriteStream(common.LOG_PATH)) | ||
s.end(function () { | ||
ev.emit('finish') | ||
unlinkOrphans(data, opts, function (err) { | ||
if (err) return helpers.bailoutEv(ev, err) | ||
var uev = update(data, opts) | ||
var sigInfo = registerSigs(function onSignal(signal) { | ||
uev.emit('signal', signal) | ||
}) | ||
forwardEvents(ev, uev, function () { | ||
unregisterSigs(sigInfo) | ||
if (opts['dry-run']) return ev.emit('finish') | ||
mkdirp(path.dirname(common.LOG_PATH), function (err) { | ||
if (err) return helpers.bailoutEv(ev, err) | ||
var s = data.log.save(fs.createWriteStream(common.LOG_PATH)) | ||
s.end(function () { | ||
ev.emit('finish') | ||
}) | ||
}) | ||
@@ -40,2 +54,3 @@ }) | ||
function update(data, opts) { | ||
var res | ||
var ev = new EventEmitter() | ||
@@ -45,8 +60,17 @@ if (opts['robot']) console.log(' e %d', data.edges.length) | ||
var st = {data: data, runCount: 0, opts: opts | ||
, sigints: {}, stopFns: {} | ||
, dirs: {}, output: new Output(opts['dry-run'])} | ||
st.updateMessage = opts['robot'] ? updateRobotMessage : updateMessage | ||
var reFn = opts['dry-run'] ? dryRunEdge : runEdge | ||
var re = queuedFnOf(reFn.bind(null, st), os.cpus().length) | ||
var re = reFn.bind(null, st) | ||
st.updateMessage(st, null) | ||
var res = runEdges(data.edges, re) | ||
res = runEdges(data.edges, re, os.cpus().length) | ||
ev.on('signal', function (signal) { | ||
if (signal !== 'SIGINT') { | ||
res.emit('signal', signal) | ||
for (var j in st.stopFns) st.stopFns[j]() | ||
return | ||
} | ||
for (var i in st.sigints) st.sigints[i] = true | ||
}) | ||
return forwardEvents(ev, res, function (errored) { | ||
@@ -62,2 +86,76 @@ st.output.endUpdate() | ||
function runEdge(st, edge, cb) { | ||
st.sigints[edge.index] = false | ||
var cmd = st.data.cmds[edge.index] | ||
mkEdgeDirs(st, edge, function (err) { | ||
if (err) return cb(err) | ||
var stopped = false | ||
exec(cmd, function (err, stdout, stderr) { | ||
if (stopped) return | ||
delete st.stopFns[edge.index] | ||
var sigint = st.sigints[edge.index] | ||
delete st.sigints[edge.index] | ||
if (!err) st.runCount++ | ||
st.updateMessage(st, edge) | ||
if (stdout.length > 0 || stderr.length > 0) { | ||
st.output.endUpdate() | ||
process.stdout.write(stdout) | ||
process.stderr.write(stderr) | ||
} | ||
if (err) { | ||
var message | ||
if (err.code) message = util.format(CMD_FAIL, err.code, cmd) | ||
else message = util.format(CMD_SIGFAIL, err.signal, cmd) | ||
var nerr = new Error(message) | ||
if ((err.signal === 'SIGINT' || err.code === 130) && sigint) | ||
nerr.signal = 'SIGINT' | ||
return cb(nerr) | ||
} | ||
edge.outFiles.forEach(function (file) { | ||
st.data.log.update(file.path, st.data.imps[file.path]) | ||
}) | ||
return cb(null) | ||
}) | ||
st.stopFns[edge.index] = function stopRunEdge () { | ||
delete st.stopFns[edge.index] | ||
stopped = true | ||
st.updateMessage(st, edge) | ||
return cb(new Error('aborting, recipe process will detach')) | ||
} | ||
}) | ||
} | ||
function dryRunEdge(st, edge, cb) { | ||
process.nextTick(function () { | ||
st.runCount++ | ||
st.updateMessage(st, edge) | ||
return cb(null) | ||
}) | ||
} | ||
function unlinkOrphans(data, opts, cb) { | ||
if (typeof cb !== 'function') throw errors.invalidArg('cb', cb) | ||
var orphans = data.log.getPaths().filter(function (filePath) { | ||
var file = data.graph.getFile(filePath) | ||
return file === null | ||
}) | ||
if (orphans.length === 0) return process.nextTick(cb.bind(null, null)) | ||
var unlink = opts['dry-run'] ? dryUnlink : fs.unlink | ||
var msgTpl = opts['dry-run'] ? DRY_REM_ORPHAN : REM_ORPHAN | ||
;(function next(i) { | ||
if (i === orphans.length) return cb(null) | ||
if (!opts.robot) | ||
console.log(util.format(msgTpl, orphans[i])) | ||
unlink(orphans[i], function (err) { | ||
if (err) return cb(err) | ||
data.log.forget(orphans[i]) | ||
return next(i + 1) | ||
}) | ||
})(0) | ||
} | ||
function dryUnlink(filePath, cb) { | ||
setImmediate(cb.bind(null, null)) | ||
} | ||
function alreadyUpToDate(ev, data, opts) { | ||
@@ -80,29 +178,14 @@ if (opts['robot']) { | ||
function dryRunEdge(st, edge, cb) { | ||
process.nextTick(function () { | ||
st.runCount++ | ||
st.updateMessage(st, edge) | ||
return cb(null) | ||
function registerSigs(fn) { | ||
var info = {sigs: {}} | ||
SIGS.forEach(function (sig) { | ||
var bfn = info.sigs[sig] = fn.bind(null, sig) | ||
process.on(sig, bfn) | ||
}) | ||
return info | ||
} | ||
function runEdge(st, edge, cb) { | ||
var cmd = st.data.cmds[edge.index] | ||
mkEdgeDirs(st, edge, function (err) { | ||
if (err) return cb(err) | ||
exec(cmd, function (err, stdout, stderr) { | ||
if (!err) st.runCount++ | ||
st.updateMessage(st, edge) | ||
if (stdout.length > 0 || stderr.length > 0) { | ||
st.output.endUpdate() | ||
process.stdout.write(stdout) | ||
process.stderr.write(stderr) | ||
} | ||
if (err) | ||
return cb(new Error(util.format('command failed: %s', cmd))) | ||
edge.outFiles.forEach(function (file) { | ||
st.data.log.update(file.path, st.data.imps[file.path]) | ||
}) | ||
return cb(null) | ||
}) | ||
function unregisterSigs(info) { | ||
SIGS.forEach(function (sig) { | ||
process.removeListener(sig, info.sigs[sig]) | ||
}) | ||
@@ -109,0 +192,0 @@ } |
@@ -19,3 +19,3 @@ 'use strict'; | ||
var ev = new EventEmitter() | ||
var rg = readGraph(opts.file, common.LOG_PATH) | ||
var rg = readGraph(opts.file, common.LOG_PATH, opts.argv.remain) | ||
forwardEvents(ev, rg, function graphRead(errored, data) { | ||
@@ -25,3 +25,3 @@ if (errored) return ev.emit('finish') | ||
forwardEvents(ev, ug, function graphUpdated() { | ||
forwardEvents(ev, watchAndUpdate(data)) | ||
forwardEvents(ev, watchAndUpdate(data, opts)) | ||
}) | ||
@@ -32,3 +32,3 @@ }) | ||
function watchAndUpdate(data) { | ||
function watchAndUpdate(data, opts) { | ||
var ev = new EventEmitter() | ||
@@ -51,2 +51,3 @@ var patterns = getSourcePatterns(data.transs) | ||
if (err) return helpers.bailoutEv(ev, err) | ||
if (!opts.robot) console.error('Watching...') | ||
this.on('all', function (event, filePath) { | ||
@@ -53,0 +54,0 @@ if (truce) return |
@@ -148,5 +148,5 @@ 'use strict'; | ||
function fileListComparator(a, b) { | ||
if (a.filePath > b.filePath) return 1 | ||
if (a.filePath < b.filePath) return -1 | ||
if (a.path > b.path) return 1 | ||
if (a.path < b.path) return -1 | ||
return 0 | ||
} |
@@ -70,1 +70,12 @@ 'use strict'; | ||
} | ||
Interpolation.prototype.toString = function () { | ||
var str = '' | ||
this._parts.forEach(function (part) { | ||
if (part.val) | ||
str += '$(' + part.str + ')' | ||
else | ||
str += part.str | ||
}) | ||
return str | ||
} |
@@ -22,2 +22,10 @@ 'use strict'; | ||
Scope.prototype.getPairs = function () { | ||
var list = [] | ||
for (var name in this._values) { | ||
list.push({name: name, value: this._values[name]}) | ||
} | ||
return list | ||
} | ||
Scope.fromBinds = function (binds, parent) { | ||
@@ -24,0 +32,0 @@ var scope = new Scope(parent) |
@@ -6,2 +6,3 @@ 'use strict'; | ||
var Scope = require('../scope.js') | ||
var errors = require('../errors') | ||
@@ -19,13 +20,13 @@ function expandCmds(scope, recipes, edges) { | ||
if (!recipes.hasOwnProperty(recipeName)) | ||
throw new Error(util.format('unknown recipe `%s\'', recipeName)) | ||
throw errors.bind(util.format('unknown recipe `%s\'', recipeName)) | ||
var interpol = recipes[recipeName].command | ||
var scope = new Scope(unitScope) | ||
scope.set('in', edge.inFiles.map(function (file) { | ||
return file.path | ||
}).join(' ')) | ||
scope.set('out', edge.outFiles.map(function (file) { | ||
return file.path | ||
}).join(' ')) | ||
scope.set('in', edge.inFiles.map(escapedPathOf).join(' ')) | ||
scope.set('out', edge.outFiles.map(escapedPathOf).join(' ')) | ||
var command = interpol.expand(scope) | ||
return command | ||
} | ||
function escapedPathOf(file) { | ||
return file.path.replace(/([^\w.\/-])/g, '\\$1') | ||
} |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var concat = require('concat-stream') | ||
var errors = require('../errors') | ||
@@ -67,2 +68,3 @@ function Log(imps, opts) { | ||
Log.prototype.isGenerated = function (path) { | ||
if (typeof path !== 'string') throw errors.invalidArg('path', path) | ||
return this._imps.hasOwnProperty(path) | ||
@@ -72,2 +74,4 @@ } | ||
Log.prototype.isUpToDate = function (path, imp) { | ||
if (typeof path !== 'string') throw errors.invalidArg('path', path) | ||
if (typeof imp !== 'number') throw errors.invalidArg('imp', imp) | ||
if (!this._imps.hasOwnProperty(path)) return false | ||
@@ -78,3 +82,18 @@ return imp === this._imps[path] | ||
Log.prototype.update = function (path, imp) { | ||
if (typeof path !== 'string') throw errors.invalidArg('path', path) | ||
if (typeof imp !== 'number') throw errors.invalidArg('imp', imp) | ||
this._imps[path] = imp | ||
} | ||
Log.prototype.forget = function (path) { | ||
if (typeof path !== 'string') throw errors.invalidArg('path', path) | ||
delete this._imps[path] | ||
} | ||
Log.prototype.getPaths = function () { | ||
var paths = [] | ||
for (var path in this._imps) { | ||
paths.push(path) | ||
} | ||
return paths | ||
} |
'use strict'; | ||
module.exports = runEdges | ||
var util = require('util') | ||
var EventEmitter = require('events').EventEmitter | ||
function runEdges(edges, runEdge) { | ||
var SIG_ABORT = 'aborting because of signal: %s' | ||
function runEdges(edges, runEdge, maxPending) { | ||
var st = { | ||
runEdge: runEdge | ||
, edges: edges | ||
, events: new EventEmitter() | ||
, done: {} | ||
} | ||
var signal = null | ||
var task = new EventEmitter() | ||
task.on('signal', function (sig) { signal = sig }) | ||
for (var k = 0; k < edges.length; ++k) | ||
st.done[edges[k].index] = false | ||
var pending = 0 | ||
var queued = [] | ||
;(function nextEdges(edges) { | ||
queued = queued.concat(edges) | ||
edges = queued.splice(0, maxPending - pending) | ||
edges.forEach(function forEdge(edge) { | ||
@@ -21,14 +29,21 @@ pending++ | ||
pending-- | ||
if (!err) st.done[edge.index] = true | ||
if (err) { | ||
st.events.emit('error', err) | ||
if (pending === 0) st.events.emit('finish') | ||
return | ||
task.emit('error', err) | ||
if (err.signal) signal = err.signal | ||
} | ||
st.done[edge.index] = true | ||
nextEdges(getReadyOutEdges(st, edge.outFiles)) | ||
if (signal === null) | ||
return nextEdges(getReadyOutEdges(st, edge.outFiles)) | ||
if (pending > 0) return | ||
if (signal !== null) { | ||
var nerr = new Error(util.format(SIG_ABORT, signal)) | ||
nerr.signal = signal | ||
task.emit('error', nerr) | ||
} | ||
task.emit('finish') | ||
}) | ||
}) | ||
if (pending === 0) return st.events.emit('finish') | ||
if (pending === 0) return task.emit('finish') | ||
})(getReadyEdges(st, edges)) | ||
return st.events | ||
return task | ||
} | ||
@@ -35,0 +50,0 @@ |
{ | ||
"name": "mekano", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "maintain, update and regenerate groups of files", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
129
README.md
@@ -67,2 +67,8 @@ #  | ||
$ mekano clean | ||
Removing: dist/concat.min.js | ||
Removing: dist/concat.js | ||
Removing: build/foo.js | ||
Removing: build/bar.js | ||
Description | ||
@@ -113,7 +119,9 @@ ----------- | ||
npm install mekano | ||
$ npm install mekano | ||
The tool will be available as `node_modules/.bin/mekano`. It is not recommended | ||
to install it globally, because different projects may need different major | ||
and incompatible versions. | ||
to install it globally, because different projects may need different, | ||
incompatible versions (of course, [semver](http://semver.org/) is used). Is | ||
**is** recommended, however, to add it to your project's `package.json` (just | ||
use npm's [`--save` option](https://www.npmjs.org/doc/cli/npm-install.html)). | ||
@@ -137,31 +145,31 @@ To avoid typing the path every time when installed locally, one decent solution | ||
* **update** Update the specified targets. The whole project is updated if no | ||
* `update` Update the specified targets. The whole project is updated if no | ||
target is specified. | ||
* **watch** Keep updating files until a signal is caught. It watches files and | ||
* `watch` Keep updating files until a signal is caught. It watches files and | ||
updates targets when prerequisites change. If the Mekanofile itself changes, | ||
you need to relaunch mekano manually. | ||
* **status** Display the modified files and dirty targets. No target is | ||
updated. If **--silent** is specified, return a zero exit value if the | ||
* `status` Display the modified files and dirty targets. No target is | ||
updated. If `--silent` is specified, return a zero exit value if the | ||
targets are up to date; otherwise, return 1. | ||
* **clean** Remove the specified and dependent targets. For example, with the | ||
* `clean` Remove the specified and dependent targets. For example, with the | ||
above Mekanofile, `clean dist/concat.js` will remove this file and the | ||
minified one. All generated files are removed if no target is specified. | ||
* **aliases** Display a list of the defined aliases. | ||
* **print** *type* Display the mekanofile interpretation. Types: | ||
* **manifest** Output the mekanofile as it had been parsed. | ||
* **dot** Output the file graph in the graphviz dot format. | ||
* **help** Display mekano own help. | ||
* `aliases` Display a list of the defined aliases. | ||
* `print <type>` Display the mekanofile interpretation. Types: | ||
* `manifest` Output the mekanofile as it had been parsed. | ||
* `dot` Output the file graph in the graphviz dot format. | ||
* `help` Display mekano own help. | ||
General options: | ||
* **-y, --shy** Stop an update as soon as an error occurs. By default, | ||
* `-y, --shy` Stop an update as soon as an error occurs. By default, | ||
the update continues to get a maximum of errors at once. | ||
* **-n, --dry-run** Output commands that would be run. No target is updated | ||
* `-n, --dry-run` Output commands that would be run. No target is updated | ||
nor deleted. | ||
* **-f, --file** *mekanofile* Specify a different mekanofile. If '-' is | ||
* `-f, --file <mekanofile>` Specify a different mekanofile. If `-` is | ||
specified, the standard input is used. | ||
* **-r, --robot** Output machine-parseable text. | ||
* **-s, --silent** Be silent: don't write executed commands. | ||
* **-F, --force** Force things, like overwriting modified files. Dangerous. | ||
* **-v, --version** Output version and exit. | ||
* `-r, --robot` Output machine-parseable text. | ||
* `-s, --silent` Be silent: don't write executed commands. | ||
* `-F, --force` Force things, like overwriting modified files. Dangerous. | ||
* `-v, --version` Output version and exit. | ||
@@ -172,15 +180,22 @@ Binds and target names can be mixed on the command-line, but targets are always | ||
Without the option **-f**, *mekano* looks in sequence for the files | ||
**./Mekanofile** and **./mekanofile**. The first found is read. | ||
Without the option `-f`, *mekano* looks in sequence for the files | ||
`./Mekanofile` and `./mekanofile`. The first found is read. | ||
The standard output reports the recipes being executed as well as the completion | ||
percentage. The **-r** option makes the output more easily parseable. | ||
percentage. The `-r` option makes the output more easily parseable. | ||
If any of the SIGHUP, SIGTERM, SIGINT, and SIGQUIT signals is received, the | ||
tool returns cleanly and keeps track of updated files so far. | ||
If any of the SIGHUP, SIGTERM, and SIGQUIT signals is received, the tool stops | ||
updating but keeps track of updated files so far. However, it does not kill the | ||
running sub-processes. If SIGINT (generally Ctrl+C) is received, it follows a | ||
[*"wait and cooperative exit"* (WCE)](http://www.cons.org/cracauer/sigint.html) | ||
stategy; it waits for processes to complete, and stops only if they ended on | ||
SIGINT themselves. In **watch** mode, SIGINT only stops the update, if any; a | ||
second SIGINT may be needed to quit. For compatibility with sloppy signal | ||
handlers, return code 130 is also considered a SIGINT. | ||
At the moment, Mekano cannot update the Mekanofile itself and take account of it | ||
in a single run (with a relation such as `Mekanofile.in M4 -> Mekanofile`). This | ||
is because it won't reload the Mekanofile after its update. You need to run it | ||
twice in this case. This will be improved in the future. | ||
At the moment, Mekano cannot update the Mekanofile itself **and** take account | ||
of it in a single run (with a relation such as | ||
`Mekanofile.in M4 -> Mekanofile`). This is because it won't reload the | ||
Mekanofile after its update. You need to run it twice in this case. This will be | ||
improved in the future. | ||
@@ -207,6 +222,5 @@ Syntax | ||
recipe = recipe-name, ":", command, bind-list, ";" ; | ||
recipe = recipe-name, ":", command, ";" ; | ||
recipe-name = identifier ; | ||
command = interpolation ; | ||
bind-list = [ "{", { bind } , "}" ] | ||
identifier = { ? A-Z, a-z, 0-9, '-' or '_' ? } | ||
@@ -228,11 +242,5 @@ interpolation = "`", ? any character ?, "`" ; | ||
* **in** Space-separated shell-quoted list of the input file(s). | ||
* **out** Space-separated shell-quoted list of the output file(s). | ||
* `in` Space-separated shell-quoted list of the input file(s). | ||
* `out` Space-separated shell-quoted list of the output file(s). | ||
<!-- | ||
A recipe can also bind local values with braces, for example: | ||
Compile: `$cc -c $in -o $out` { cc = `gcc $cflags` }; | ||
--> | ||
Command lines are evaluated by the local shell, typically with `sh -c`. | ||
@@ -247,3 +255,3 @@ 'UpperCamel' case is suggested for naming recipes. Recipes can appear anywhere | ||
relation = ref-list, { transformation }, [ alias ], ";" | ||
transformation = recipe-name, ( "=>" | "->" ), ref-list, bind-list | ||
transformation = recipe-name, ( "=>" | "->" ), ref-list | ||
alias = "::", alias-name, [ alias-description ] | ||
@@ -264,2 +272,6 @@ ref-list = { path | path-glob | alias-name } | ||
It means: *"takes all the C files in the `source` folder, compile them to object | ||
files in `obj`; then link all those into a single binary `./hello_world`. This | ||
binary can be referred to as the alias `all`."* | ||
#### Expansions | ||
@@ -321,3 +333,3 @@ | ||
Generative pattern transposition is planned to be improved in the future. | ||
Generative pattern transposition is planned to be hugely improved in the future. | ||
@@ -353,2 +365,3 @@ ### Binds | ||
to the update log generated by the previous run of *mekano*, if any. | ||
* Remove *orphans*: old generated files that are not in the graph anymore. | ||
* Invoke recipes in order to update files. When possible, recipes are | ||
@@ -360,2 +373,15 @@ called asynchronously to make the update faster. | ||
The update log is located in `.mekano/log.json` and contains the imprint of | ||
each generated file. **Never delete the log file!** To do a whole rebuild, use | ||
the **clean** command instead. If you delete the log, bad things will | ||
happen, because *mekano* will consider all the generated files so far as | ||
sources. This means, for example, that minified files (`foo.min.js`) will be | ||
minified again (`foo.min.min.js`). | ||
When you are using a version control tool, like **git(1)**: if you are | ||
adding built files to the repository (and not just the sources), then you | ||
shall add the log file as well. Otherwise, people checking out the repo. | ||
will get trouble because *mekano* won't know which file is generated and which | ||
is not. | ||
Known limitations | ||
@@ -371,3 +397,4 @@ ---------------- | ||
* a bug appears, for the command `watch' only, when a | ||
[file is renamed](https://github.com/shama/gaze/issues/107). | ||
[file is renamed](https://github.com/shama/gaze/issues/107); | ||
* generative transformations accept only one pattern prerequisite. | ||
@@ -384,3 +411,5 @@ See also the [ROADMAP](./ROADMAP.md). | ||
* directories are handled automatically; | ||
* it detects command line changes. | ||
* it detects command line changes; | ||
* it can watch files out-of-the-box; | ||
* it also runs concurrently (GNU Make's `-j` option). | ||
@@ -391,8 +420,10 @@ ### Why using this instead of grunt? | ||
* no plugin system, you can use tools from any package, in any version; 'less | ||
is more' applies pretty well to this case. | ||
is more' applies pretty well to this case; | ||
* concurrency. | ||
### Why *not* using mekano? | ||
* it's still in beta and may be unstable; | ||
* too high-level, you have specific dependency needs; | ||
* no logic, no 'if', or too limited semantics; | ||
* no logic, no 'if', limited semantics; | ||
* might be too slow for medium or large projects. | ||
@@ -407,3 +438,3 @@ | ||
* inference is done the other way around than *make* (it infers targets based | ||
on prerequisites; make does the contrary with rules like `%.o: %c`). | ||
on prerequisites; make does the opposite with rules like `%.o: %c`). | ||
@@ -415,4 +446,4 @@ ### Shout out | ||
* the historic [GNU make](http://www.gnu.org/software/make/manual/make.html); | ||
* the super-fast [Ninja](http://martine.github.io/ninja/); | ||
* the insightful [tup](http://gittup.org/tup/); | ||
* the pragmatic [grunt](http://gruntjs.com/). | ||
* the fast [Ninja](http://martine.github.io/ninja/); | ||
* the cutting-edge [tup](http://gittup.org/tup/); | ||
* the down-to-earth [grunt](http://gruntjs.com/). |
110009
50
2677
434
7