Comparing version 0.0.1 to 0.0.2
@@ -17,3 +17,3 @@ #!/usr/bin/env node | ||
var SHORTHANDS = { | ||
'f': ['--force'], 'n': ['--dry-run'], 's': ['--silent'] | ||
'f': ['--file'], 'n': ['--dry-run'], 's': ['--silent'] | ||
, 'F': ['--force'], 'v': ['--version'] | ||
@@ -37,2 +37,3 @@ } | ||
var errored = false | ||
var finished = false | ||
ev.on('error', function (err) { | ||
@@ -44,2 +45,3 @@ log('error', err) | ||
}).on('finish', function () { | ||
finished = true | ||
if (errored) | ||
@@ -49,2 +51,6 @@ process.exit(1) | ||
}) | ||
process.on('exit', function () { | ||
if (!finished) | ||
throw new Error('finish event not called; this is a bug!') | ||
}) | ||
} | ||
@@ -63,9 +69,6 @@ | ||
Commands.update = require('./update') | ||
Commands.status = require('./status') | ||
Commands.status = function () { | ||
} | ||
Commands.clean = function () { | ||
} | ||
@@ -72,0 +75,0 @@ |
@@ -7,134 +7,47 @@ 'use strict'; | ||
var os = require('os') | ||
var glob = require('glob') | ||
var path = require('path') | ||
var mkdirp = require('mkdirp') | ||
var readline = require('readline') | ||
var exec = require('child_process').exec | ||
var EventEmitter = require('events').EventEmitter | ||
var read = require('../lib/read') | ||
var queuedFnOf = require('../lib/queued-fn-of') | ||
var map = require('../lib/graph/map') | ||
var sort = require('../lib/update/sort') | ||
var imprint = require('../lib/update/imprint') | ||
var Log = require('../lib/update/log') | ||
var expandCmds = require('../lib/update/expand-cmds') | ||
var identify = require('../lib/update/identify') | ||
var runEdges = require('../lib/update/run-edges') | ||
var Scope = require('../lib/scope') | ||
var forwardEvents = require('../lib/forward-events') | ||
var Output = require('./output') | ||
var readData = require('./read-data') | ||
var common = require('./common') | ||
var DEFAULT_PATHS = ['Mekanofile', 'mekanofile'] | ||
var LOG_PATH = '.mekano/log.json' | ||
var NO_MANIFEST = 'Mekanofile not found' | ||
function update(opts) { | ||
var ev = new EventEmitter() | ||
openSomeInput(opts.file, function (err, input, filePath) { | ||
if (err) { | ||
ev.emit('error', err) | ||
ev.emit('finish') | ||
} | ||
mkdirp('.mekano', function (err) { | ||
if (err) { | ||
ev.emit('error', err) | ||
return forwardEvents(ev, readData(opts.file, common.LOG_PATH) | ||
, function (errored, data) { | ||
if (errored) return ev.emit('finish') | ||
forwardEvents(ev, updateGraph(data), function graphUpdated() { | ||
var s = data.log.save(fs.createWriteStream(common.LOG_PATH)) | ||
s.end(function () { | ||
ev.emit('finish') | ||
} | ||
updateInput(input, filePath).on('error', function (err) { | ||
ev.emit('error', err) | ||
}).on('warning', function (err) { | ||
ev.emit('warning', err) | ||
}).on('finish', function () { | ||
ev.emit('finish') | ||
}) | ||
}) | ||
}) | ||
return ev | ||
} | ||
function openSomeInput(filePath, cb) { | ||
if (filePath === '-') return cb(null, process.stdin, '<stdin>') | ||
if (filePath) return openInput(filePath, cb) | ||
;(function next(i) { | ||
if (i >= DEFAULT_PATHS.length) return cb(new Error(NO_MANIFEST)) | ||
openInput(DEFAULT_PATHS[i], function (err, stream) { | ||
if (err) return next(i + 1) | ||
return cb(null, stream, DEFAULT_PATHS[i]) | ||
}) | ||
})(0) | ||
} | ||
function openInput(filePath, cb) { | ||
var stream = fs.createReadStream(filePath) | ||
stream.on('open', function () { return cb(null, stream, filePath) }) | ||
stream.on('error', function (err) { return cb(err) }) | ||
} | ||
function updateInput(input, filePath) { | ||
var errored = false | ||
function updateGraph(data) { | ||
var ev = new EventEmitter() | ||
read(input).on('error', function (err) { | ||
err.filePath = filePath | ||
ev.emit('error', err) | ||
errored = true | ||
}).on('warning', function (err) { | ||
err.filePath = filePath | ||
ev.emit('warning', err) | ||
}).on('finish', function (transs, unit) { | ||
if (errored) return ev.emit('finish') | ||
Log.fromStream(fs.createReadStream(LOG_PATH), function (err, log) { | ||
if (err) log = new Log() | ||
var scope = Scope.fromBinds(unit.binds) | ||
var errored = false | ||
map(glob, log, transs).on('error', function (err) { | ||
ev.emit('error', err) | ||
errored = true | ||
}).on('finish', function (graph) { | ||
if (errored) return ev.emit('finish') | ||
var errored2 = false | ||
updateGraph(log, scope, unit.recipes, graph) | ||
.on('error', function (err) { | ||
ev.emit('error', err) | ||
errored2 = true | ||
}).on('finish', function () { | ||
log.save(fs.createWriteStream(LOG_PATH)) | ||
.end(function () { | ||
ev.emit('finish') | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
return ev | ||
if (data.edges.length === 0) { | ||
console.log(common.EVERYTHING_UTD) | ||
process.nextTick(ev.emit.bind(null, 'finish')) | ||
return ev | ||
} | ||
var st = {data: data, runCount: 0 | ||
, dirs: {}, output: new Output()} | ||
var re = queuedFnOf(runEdge.bind(null, st), os.cpus().length) | ||
st.output.update(makeUpdateMessage(st)) | ||
return forwardEvents(ev, runEdges(data.edges, re), function (errored) { | ||
st.output.endUpdate() | ||
if (!errored) console.log('Done.') | ||
ev.emit('finish') | ||
}, function () { st.output.endUpdate() }) | ||
} | ||
function updateGraph(log, scope, recipes, graph) { | ||
var files = sort(graph.files) | ||
var cmds = expandCmds(scope, recipes, graph.edges) | ||
var ev = new EventEmitter() | ||
imprint(fs, files, cmds, function (err, imps) { | ||
if (err) { ev.emit('error', err); return ev.emit('finish') } | ||
var edges = identify(log, files, imps) | ||
if (edges.length === 0) { | ||
console.log('Everything is up to date.') | ||
return ev.emit('finish') | ||
} | ||
var st = {cmds: cmds, edges: edges, runCount: 0, log: log | ||
, imps: imps, dirs: {}} | ||
var re = queuedFnOf(runEdge.bind(null, st), os.cpus().length) | ||
updateOutput(0, '') | ||
var errored = false | ||
runEdges(edges, re).on('finish', function () { | ||
console.log() | ||
if (!errored) console.log('Done.') | ||
ev.emit('finish') | ||
}).on('error', function (err) { | ||
console.log() | ||
errored = true | ||
ev.emit('error', err) | ||
}) | ||
}) | ||
return ev | ||
} | ||
function runEdge(st, edge, cb) { | ||
var cmd = st.cmds[edge.index] | ||
var cmd = st.data.cmds[edge.index] | ||
mkEdgeDirs(st, edge, function (err) { | ||
@@ -144,11 +57,5 @@ if (err) return cb(err) | ||
if (!err) st.runCount++ | ||
var perc = (st.runCount / st.edges.length) | ||
readline.clearLine(process.stdout, 0) | ||
readline.cursorTo(process.stdout, 0) | ||
updateOutput(perc, util.format('%s %s -> %s' | ||
, edge.trans.ast.recipeName | ||
, edge.inFiles.map(pathOf).join(' ') | ||
, edge.outFiles.map(pathOf).join(' '))) | ||
st.output.update(makeUpdateMessage(st, edge)) | ||
if (stdout.length > 0 || stderr.length > 0) { | ||
process.stdout.write('\n') | ||
st.output.endUpdate() | ||
process.stdout.write(stdout) | ||
@@ -160,3 +67,3 @@ process.stderr.write(stderr) | ||
edge.outFiles.forEach(function (file) { | ||
st.log.update(file.path, st.imps[file.path]) | ||
st.data.log.update(file.path, st.data.imps[file.path]) | ||
}) | ||
@@ -182,9 +89,12 @@ return cb(null) | ||
function updateOutput(perc, label) { | ||
readline.clearLine(process.stdout, 0) | ||
readline.cursorTo(process.stdout, 0) | ||
process.stdout.write(util.format('%s %s% %s' | ||
, 'Updating...' | ||
, pad((perc * 100).toFixed(1), 5) | ||
, label), 'utf8') | ||
function makeUpdateMessage(st, edge) { | ||
var perc = (st.runCount / st.data.edges.length) | ||
var label = edge? util.format('%s %s -> %s' | ||
, edge.trans.ast.recipeName | ||
, edge.inFiles.map(pathOf).join(' ') | ||
, edge.outFiles.map(pathOf).join(' ')) : '' | ||
var message = util.format('Updating... %s% %s' | ||
, pad((perc * 100).toFixed(1), 5) | ||
, label) | ||
return message | ||
} | ||
@@ -197,14 +107,4 @@ | ||
function makePercBar(perc, len) { | ||
var str = '|' | ||
var i = 0 | ||
for (; i < len && perc > i / len; ++i) | ||
str += '=' | ||
for (; i < len; ++i) | ||
str += ' ' | ||
return str + '|' | ||
} | ||
function pathOf(file) { | ||
return file.path | ||
} |
@@ -5,2 +5,3 @@ 'use strict'; | ||
, parse: parse | ||
, bind: bind | ||
} | ||
@@ -26,1 +27,7 @@ | ||
} | ||
function bind(message) { | ||
var err = new Error(message) | ||
err.name = 'BindError' | ||
return err | ||
} |
@@ -6,3 +6,5 @@ 'use strict'; | ||
var Graph = require('./graph') | ||
var Token = require('../read/token') | ||
var Log = require('../update/log') | ||
var ast = require('../read/ast') | ||
var interRep = require('../read/inter-rep') | ||
var asyncMap = require('slide').asyncMap | ||
@@ -21,2 +23,5 @@ var mergeLists = require('../merge-lists') | ||
function map(glob, log, transs) { | ||
if (typeof glob !== 'function') throw errors.invalidArg('glob', glob) | ||
if (!(log instanceof Log)) throw errors.invalidArg('log', log) | ||
if (!(transs instanceof Array)) throw errors.invalidArg('transs', transs) | ||
var graph = new Graph() | ||
@@ -41,2 +46,6 @@ var st = { | ||
function processTrans(st, trans, cb) { | ||
if (!st) throw errors.invalidArg('st', st) | ||
if (!(trans instanceof interRep.PlainTrans)) | ||
throw errors.invalidArg('trans', trans) | ||
if (typeof cb !== 'function') throw errors.invalidArg('cb', cb) | ||
if (trans.ast.multi) | ||
@@ -50,15 +59,17 @@ return processMultiTrans(st, trans, cb) | ||
for (var i = 0; i < trans.targets.length; ++i) { | ||
var token = trans.targets[i] | ||
if (token.type === Token.PATH_GLOB) | ||
return cb(errors.parse(BAD_GLOB_TARGET, token.location)) | ||
var file = st.graph.getFileByPath(token.value) | ||
var ref = trans.targets[i] | ||
if (!(ref instanceof ast.Ref)) throw errors.invalidArg('trans', trans) | ||
if (ref.type === ast.Ref.PATH_GLOB) | ||
return cb(errors.parse(BAD_GLOB_TARGET, ref.location)) | ||
var file = st.graph.getFileByPath(ref.value) | ||
outFiles.push(file) | ||
} | ||
var edge = st.graph.pushEdge(trans, outFiles) | ||
asyncMap(trans.prereqs, function (token, cb) { | ||
if (token.type === Token.PATH) { | ||
edge.pushFileIn(st.graph.getFileByPath(token.value)) | ||
asyncMap(trans.prereqs, function (ref, cb) { | ||
if (!(ref instanceof ast.Ref)) throw errors.invalidArg('trans', trans) | ||
if (ref.type === ast.Ref.PATH) { | ||
edge.pushFileIn(st.graph.getFileByPath(ref.value)) | ||
return cb(null) | ||
} | ||
mixedGlob(st, token.value, function (err, files) { | ||
mixedGlob(st, ref.value, function (err, files) { | ||
if (err) return cb(err) | ||
@@ -74,16 +85,18 @@ edge.pushFilesIn(files) | ||
function processMultiTrans(st, trans, cb) { | ||
var patToken = null | ||
var patRef = null | ||
var inFiles = [] | ||
for (var i = 0; i < trans.prereqs.length; ++i) { | ||
if (trans.prereqs[i].type === Token.PATH) { | ||
if (!(trans.prereqs[i] instanceof ast.Ref)) | ||
throw errors.invalidArg('trans', trans) | ||
if (trans.prereqs[i].type === ast.Ref.PATH) { | ||
inFiles.push(st.graph.getFileByPath(trans.prereqs[i].value)) | ||
continue | ||
} | ||
if (patToken !== null) | ||
if (patRef !== null) | ||
return process.nextTick(cb.bind(null, new Error(MULTI_ONE_GLOB))) | ||
patToken = trans.prereqs[i] | ||
patRef = trans.prereqs[i] | ||
} | ||
var outConvs = null | ||
try { | ||
var mc = makeTokenConv.bind(null, patToken) | ||
var mc = makeTokenConv.bind(null, patRef) | ||
outConvs = trans.targets.map(mc) | ||
@@ -94,3 +107,3 @@ } catch (err) { | ||
} | ||
mixedGlob(st, patToken.value, function (err, files) { | ||
mixedGlob(st, patRef.value, function (err, files) { | ||
if (err) return cb(err) | ||
@@ -122,8 +135,11 @@ files.forEach(function (file) { | ||
function makeTokenConv(patToken, otherToken) { | ||
if (otherToken.type === Token.PATH) | ||
throw errors.parse(MULTI_CANT_HAVE_PATH, otherToken.location) | ||
try { return makeConv(patToken.value, otherToken.value) } | ||
function makeTokenConv(patRef, otherRef) { | ||
if (!(patRef instanceof ast.Ref)) throw errors.invalidArg('patRef', patRef) | ||
if (!(otherRef instanceof ast.Ref)) | ||
throw errors.invalidArg('otherRef', otherRef) | ||
if (otherRef.type === ast.Ref.PATH) | ||
throw errors.parse(MULTI_CANT_HAVE_PATH, otherRef.location) | ||
try { return makeConv(patRef.value, otherRef.value) } | ||
catch (err) { | ||
throw errors.parse(err.message, patToken.location) | ||
throw errors.parse(err.message, patRef.location) | ||
} | ||
@@ -133,2 +149,4 @@ } | ||
function mergeFileLists(a, b) { | ||
if (!(a instanceof Array)) throw errors.invalidArg('a', a) | ||
if (!(b instanceof Array)) throw errors.invalidArg('b', b) | ||
return mergeLists(a, b, fileListComparator) | ||
@@ -135,0 +153,0 @@ } |
@@ -9,4 +9,8 @@ 'use strict'; | ||
, Alias: Alias | ||
, Ref: Ref | ||
} | ||
var errors = require('../errors') | ||
var Interpolation = require('./interpolation') | ||
var Location = require('./location') | ||
var defProp = require('../def-prop') | ||
@@ -21,2 +25,7 @@ | ||
function Recipe(name, command, location) { | ||
if (typeof name !== 'string') throw errors.invalidArg('name', name) | ||
if (!(command instanceof Interpolation)) | ||
throw errors.invalidArg('command', command) | ||
if (location && !(location instanceof Location)) | ||
throw errors.invalidArg('location', location) | ||
defProp(this, 'name', name) | ||
@@ -28,2 +37,7 @@ defProp(this, 'command', command) | ||
function Bind(name, value, location) { | ||
if (typeof name !== 'string') throw errors.invalidArg('name', name) | ||
if (!(value instanceof Interpolation)) | ||
throw errors.invalidArg('value', value) | ||
if (location && !(location instanceof Location)) | ||
throw errors.invalidArg('location', location) | ||
defProp(this, 'name', name) | ||
@@ -48,4 +62,20 @@ defProp(this, 'value', value) | ||
function Alias(name, desc) { | ||
if (typeof name !== 'string') throw errors.invalidArg('name', name) | ||
if (desc && typeof desc !== 'string') throw errors.invalidArg('desc', desc) | ||
defProp(this, 'name', name) | ||
defProp(this, 'desc', desc) | ||
} | ||
Ref.ALIAS = 1 | ||
Ref.PATH = 2 | ||
Ref.PATH_GLOB = 3 | ||
function Ref(type, value, location) { | ||
if (typeof type !== 'number') throw errors.invalidArg('type', type) | ||
if (typeof value !== 'string') throw errors.invalidArg('value', value) | ||
if (location && !(location instanceof Location)) | ||
throw errors.invalidArg('location', location) | ||
defProp(this, 'type', type) | ||
defProp(this, 'value', value) | ||
defProp(this, 'location', location) | ||
} |
@@ -6,18 +6,22 @@ 'use strict'; | ||
var interRep = require('./inter-rep') | ||
var Token = require('./token') | ||
var ast = require('./ast') | ||
var errors = require('../errors') | ||
var ALIAS_CYCLE = 'alias reference cycle, for example: %s' | ||
var NO_PATH_INTERPOL = 'interpolation as a path not supported in this version' | ||
var INVALID_TOKEN = 'invalid token in path list' | ||
var INVALID_TOKEN = 'invalid reference in path list' | ||
var UNKNOWN_ALIAS = 'unknown alias `%s\'' | ||
function expandPaths(rels, aliases) { | ||
if (!(rels instanceof Array)) throw errors.invalidArg('rels', rels) | ||
if (!aliases || !(aliases instanceof Object)) | ||
throw errors.invalidArg('aliases', aliases) | ||
return rels.map(function mapRelation (rel) { | ||
if (!rel || !(rel instanceof ast.Relation)) | ||
throw errors.invalidArg('rels', rels) | ||
if (rel.transList.length === 0) return null | ||
var newTranss = rel.transList.map(function (trans) { | ||
var tokens = expandTokens(trans.targets, aliases) | ||
return new interRep.ExpTrans(trans, tokens) | ||
var refs = expandRefs(trans.targets, aliases) | ||
return new interRep.ExpTrans(trans, refs) | ||
}) | ||
var newPrereqs = expandTokens(rel.prereqList, aliases) | ||
var newPrereqs = expandRefs(rel.prereqList, aliases) | ||
return new interRep.ExpRelation(newPrereqs, newTranss) | ||
@@ -27,15 +31,13 @@ }).filter(function (rel) { return rel !== null }) | ||
function expandTokens(tokens, aliases, stack) { | ||
function expandRefs(refs, aliases, stack) { | ||
if (!stack) stack = [] | ||
var newTokens = [] | ||
for (var i = 0; i < tokens.length; ++i) { | ||
var token = tokens[i] | ||
if (token.type === Token.PATH || token.type === Token.PATH_GLOB) { | ||
newTokens.push(token) | ||
} else if (token.type === Token.IDENTIFIER) { | ||
var exp = expandAlias(token, aliases, stack) | ||
for (var i = 0; i < refs.length; ++i) { | ||
var ref = refs[i] | ||
if (ref.type === ast.Ref.PATH || ref.type === ast.Ref.PATH_GLOB) { | ||
newTokens.push(ref) | ||
} else if (ref.type === ast.Ref.ALIAS) { | ||
var exp = expandAlias(ref, aliases, stack) | ||
for (var j = 0; j < exp.length; ++j) newTokens.push(exp[j]) | ||
} else if (token.type === Token.INTERPOLATION) { | ||
throw errors.parse(NO_PATH_INTERPOL, token.location) | ||
} else { throw errors.parse(INVALID_TOKEN, token.location) } | ||
} else { throw errors.parse(INVALID_TOKEN, ref.location) } | ||
} | ||
@@ -56,3 +58,3 @@ return newTokens | ||
stack = stack.concat([token]) | ||
var expansion = expandTokens(aliases[token.value].refs, aliases, stack) | ||
var expansion = expandRefs(aliases[token.value].refs, aliases, stack) | ||
return expansion | ||
@@ -59,0 +61,0 @@ } |
@@ -7,2 +7,3 @@ 'use strict'; | ||
var util = require('util') | ||
var path = require('path') | ||
var Token = require('./token') | ||
@@ -22,2 +23,3 @@ var Interpolation = require('./interpolation') | ||
var DUP_BIND = 'duplicate value bind `%s\'' | ||
var OUT_OF_ROOT = 'cannot refer to path `%s\', outside of project root' | ||
@@ -267,3 +269,7 @@ function parse(stream, opts) { | ||
self._accept(Token.FAT_ARROW, 1)) return cb(null, list) | ||
list.push(this._tokens[0]) | ||
try { list.push(makeRef(this._tokens[0])) } | ||
catch (err) { | ||
if (err.name !== 'ParseError') throw err | ||
return cb(err) | ||
} | ||
this._consume(1) | ||
@@ -331,1 +337,13 @@ return next() | ||
} | ||
function makeRef(token) { | ||
if (token.type === Token.IDENTIFIER) | ||
return new ast.Ref(ast.Ref.ALIAS, token.value, token.location) | ||
var filePath = path.relative('.', token.value) | ||
if (filePath.substr(0, 3) === '../') { | ||
var message = util.format(OUT_OF_ROOT, token.value) | ||
throw errors.parse(message, token.location) | ||
} | ||
var type = token.type === Token.PATH ? ast.Ref.PATH : ast.Ref.PATH_GLOB | ||
return new ast.Ref(type, filePath, token.location) | ||
} |
@@ -5,2 +5,3 @@ 'use strict'; | ||
var util = require('util') | ||
var errors = require('./errors') | ||
@@ -19,3 +20,3 @@ function Scope(parent) { | ||
if (this._parent) return this._parent.get(name) | ||
throw new Error(util.format('unbound value `%s\'', name)) | ||
throw errors.bind(util.format('unbound value `%s\'', name)) | ||
} | ||
@@ -54,7 +55,9 @@ | ||
function cycleError(st, index) { | ||
var str = st.stack[index].name | ||
for (var j = st.stack.length - 1; j >= index; --j) { | ||
str += ' -> ' + st.stack[j].name | ||
var str = '' | ||
for (var j = index; j < st.stack.length; ++j) { | ||
var name = st.stack[j] | ||
str += util.format('%s:%s -> ', st.binds[name].location, name) | ||
} | ||
return new Error('circular value reference ' + str) | ||
str += st.stack[index] | ||
return new errors.bind('circular value reference: ' + str) | ||
} |
@@ -6,3 +6,2 @@ 'use strict'; | ||
var util = require('util') | ||
var Token = require('../read/token') | ||
@@ -9,0 +8,0 @@ var MAX_IMPRINT = Math.pow(2, 32) |
@@ -6,4 +6,6 @@ 'use strict'; | ||
function Log(imps) { | ||
function Log(imps, opts) { | ||
this._imps = imps || {} | ||
if (!opts) opts = {} | ||
this._fs = opts.fs ? opts.fs : require('fs') | ||
} | ||
@@ -39,2 +41,27 @@ | ||
Log.prototype.refresh = function (cb) { | ||
var st = {count: 0, cb: cb} | ||
for (var path in this._imps) { | ||
if (!this._imps.hasOwnProperty(path)) continue | ||
st.count++ | ||
this._refreshFile(st, path) | ||
} | ||
if (st.count === 0) return process.nextTick(cb.bind(null, null)) | ||
} | ||
Log.prototype._refreshFile = function (st, path) { | ||
var self = this | ||
this._fs.lstat(path, function (err) { | ||
if (err && err.code !== 'ENOENT') { | ||
if (cb === null) return | ||
var cb = st.cb | ||
st.cb = null | ||
return cb(err) | ||
} | ||
if (err) delete self._imps[path] | ||
st.count-- | ||
if (st.count === 0 && st.cb !== null) return st.cb(null) | ||
}) | ||
} | ||
Log.prototype.isGenerated = function (path) { | ||
@@ -41,0 +68,0 @@ return this._imps.hasOwnProperty(path) |
@@ -47,2 +47,3 @@ 'use strict'; | ||
var edge = edges[i] | ||
if (!st.done.hasOwnProperty(edge.index)) continue | ||
st.done[edge.index] = false | ||
@@ -49,0 +50,0 @@ if (isEdgeReady(st, edge)) readyEdges.push(edge) |
{ | ||
"name": "mekano", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "maintain, update and regenerate groups of files", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
105
README.md
@@ -1,7 +0,7 @@ | ||
mekano | ||
====== | ||
#  | ||
[](https://travis-ci.org/jeanlauliac/mekano) | ||
**Work in progress, this is very early.** | ||
**This is still an alpha version of the tool, keep in mind the current version | ||
is not feature-complete. Work is in progress toward that goal.** | ||
@@ -15,9 +15,13 @@ Synopsis | ||
* is a make-like update tool: you have a bunch of files in some directories, | ||
you want to generate other files from them; | ||
* liberally aims to lessen the frustration that can occur working with GNU | ||
* is a **make-like update tool**: you have a bunch of files in some | ||
directories, you want to **generate other files** from them, **fast** (no | ||
unnecessary work); | ||
* liberally aims to **lessen the frustration** that can occur working with GNU | ||
*make(1)* on small or medium projects; | ||
* tries to be balanced between speed and convenience; | ||
* is not tied to any specific technology and may be used to compile C/C++, | ||
build a web application Javascript/CSS assets, or brew your coffee. | ||
* tries to be balanced between **speed and convenience**; | ||
* works **best** with a **powerful shell** (like bash & co.), that it does not | ||
supplant; | ||
* is **not** tied to any **specific technology** and may be used to compile | ||
C/C++, build a web application Javascript/CSS assets, or **brew your | ||
coffee**. | ||
@@ -29,9 +33,9 @@ Example | ||
bin = `node_module/.bin`; | ||
bin = `node_modules/.bin`; | ||
Concat: `cat $in > $out`; | ||
Coffee: `$bin/coffee $in > $out`; | ||
Minify: `$bin/minify < $in > $out`; | ||
Coffee: `$bin/coffee -cp $in > $out`; | ||
Minify: `$bin/uglifyjs < $in > $out`; | ||
source/**/*.coffee | ||
src/**/*.coffee | ||
Coffee => build/**/*.js | ||
@@ -42,3 +46,3 @@ Concat -> dist/concat.js; | ||
Minify => dist/*.min.js | ||
:: all `the minified JS`; | ||
:: all `Update all files`; | ||
@@ -48,7 +52,7 @@ In your preferred shell: | ||
$ ls | ||
Mekanofile source | ||
Mekanofile src | ||
$ mekano update | ||
Updating... 25.0% Coffee source/foo.coffee -> build/foo.js | ||
Updating... 50.0% Coffee source/bar.coffee -> build/bar.js | ||
Updating... 25.0% Coffee src/foo.coffee -> build/foo.js | ||
Updating... 50.0% Coffee src/bar.coffee -> build/bar.js | ||
Updating... 75.0% Concat build/foo.js build/bar.js -> dist/concat.js | ||
@@ -62,3 +66,3 @@ Updating... 100.0% Minify dist/concat.js -> dist/concat.min.js | ||
$ ls | ||
Mekanofile source build dist .mekano | ||
Mekanofile src build dist .mekano | ||
@@ -83,3 +87,3 @@ $ ls dist | ||
*mekano* focuses on correctness rather than other factors like speed. It | ||
*mekano* focuses above all on correctness and convenience, then speed. It | ||
properly takes account of removed and added files; tracks command-line changes; | ||
@@ -90,14 +94,17 @@ automatically create output directories; and provides sane semantics for | ||
*mekano* only knows how to update files. It is not well suited for 'tasks' (eg. | ||
'test', 'publish'). Plain scripts are probably a better idea (eg. sh, JS, | ||
python) for those. | ||
*mekano* only knows how to update files. It is not well suited for so-called | ||
'tasks' (eg. 'test', 'publish'). Plain scripts are probably a better idea (with | ||
bash, Javascript, Python…) for those. Using | ||
[npm-scripts](https://www.npmjs.org/doc/misc/npm-scripts.html) is suggested | ||
as well. | ||
The mekanofile is generally meant to be written by hand, but there is very | ||
little support for build-time decision-making (no 'if', no macros). However, you | ||
can easily use a dedicaced macro or procedural language to generate the | ||
mekanofile. | ||
The mekanofile is generally meant to be written by hand, but there is, for now, | ||
very little support for build-time decision-making (no 'if', no macros). | ||
However, you can easily use a dedicaced macro or procedural language to generate | ||
the mekanofile, like [m4](http://www.gnu.org/software/m4/manual/m4.html), | ||
Python, Javascript… | ||
This specific implementation is made with JavaScript on top of Node.js, | ||
but is usable for any purpose, from C/C++ compilation to web assets build. | ||
Node.js makes it easier to be multiplatform. | ||
This specific implementation is made with JavaScript on top of Node.js, but keep | ||
in mind it is usable for any purpose, from C/C++ compilation to web assets | ||
build. Node.js just makes it easier to be multiplatform. | ||
@@ -128,22 +135,18 @@ Install | ||
mekano <command> [options] [macro=value...] [target_name...] | ||
mekano <command> [options] [bind=value...] [target_name...] | ||
Commands: | ||
* **update** Update the specified targets. All files are updated if no | ||
target is specified. Options: | ||
* **-k, --greedy** Continue to update feasible targets if an | ||
error occurs. This is useful to get a maximum of errors at once. | ||
* **-n, --dry-run** Output commands that would be run. | ||
No target is updated. | ||
* **-w, --watch** Watch files and update targets on prerequisite changes. | ||
Keep running until a signal is caught. | ||
* **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 | ||
updates targets when prerequisites change. | ||
* **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 intermediary targets. Options: | ||
* **-n, --dry-run** Output files to be removed. No file is removed. | ||
* **clean** Remove the specified and intermediary targets. | ||
* **aliases** Display a list of the defined aliases. | ||
* **trace** Display the mekanofile interpretation. Options: | ||
* **-d, --dot** Output the file graph in the graphviz dot format. | ||
* **print** *type* Display the mekanofile interpretation. Types: | ||
* **manifest** Output the mekanofile as it had been interpreted. | ||
* **dot** Output the file graph in the graphviz dot format. | ||
* **help** Display mekano own help. | ||
@@ -153,18 +156,24 @@ | ||
* **-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 | ||
nor deleted. | ||
* **-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. | ||
Macros and target names can be mixed on the command-line, but targets are always | ||
evaluated last. | ||
Binds and target names can be mixed on the command-line, but targets are always | ||
evaluated last. Values cannot refer to values inside the mekanofile, but the | ||
contrary is possible. | ||
Without the option **-f**, *mekano* looks in sequence for the files | ||
**./Mekano** and **./mekano**. The first found is read. | ||
**./Mekanofile** and **./mekanofile**. The first found is read. | ||
Signals | ||
------- | ||
The standard output reports the recipes being executed as well as the completion | ||
percentage. The **-r** option makes the output easily parseable. | ||
If any of the SIGHUP, SIGTERM, SIGINT, and SIGQUIT signals is received, the | ||
targets being processed are removed and the tool returns. | ||
targets being processed are removed and the tool returns cleanly. | ||
@@ -171,0 +180,0 @@ Syntax |
82525
40
1991
372
6