@luminati-io/jake
Version:
JavaScript build tool, similar to Make or Rake
295 lines (256 loc) • 8.64 kB
JavaScript
/*
* Jake JavaScript build tool
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var EventEmitter = require('events').EventEmitter;
// And so it begins
global.jake = new EventEmitter();
// Do not use : on windows, it will break path tasks
// TODO: move to namespace
jake.nsSep = /^win/.test(process.platform) ? '|' : ':';
var fs = require('fs')
, path = require('path')
, chalk = require('chalk')
, taskNs = require('./task')
, Task = taskNs.Task
, FileTask = taskNs.FileTask
, DirectoryTask = taskNs.DirectoryTask
, Rule = require('./rule').Rule
, Namespace = require('./namespace').Namespace
, api = require('./api')
, utils = require('./utils')
, Program = require('./program').Program
, Loader = require('./loader').Loader
, pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString());
var MAX_RULE_RECURSION_LEVEL = 16;
var Invocation = function (taskName, args) {
this.taskName = taskName;
this.args = args;
};
var task_constructors = {
'task': Task,
'file': FileTask,
'directory': DirectoryTask,
};
// Globalize jake and top-level API methods (e.g., `task`, `desc`)
utils.mixin(global, api);
// Copy utils onto base jake
utils.mixin(jake, utils);
// File utils should be aliased directly on base jake as well
utils.mixin(jake, utils.file);
utils.mixin(jake, new (function () {
this._invocationChain = [];
// Private variables
// =================
// Local reference for scopage
var self = this;
// Public properties
// =================
this.version = pkg.version;
// Used when Jake exits with a specific error-code
this.errorCode = undefined;
// Loads Jakefiles/jakelibdirs
this.loader = new Loader();
// Name/value map of all the various tasks defined in a Jakefile.
// Non-namespaced tasks are placed into 'default.'
this.defaultNamespace = new Namespace('default', null);
// For namespaced tasks -- tasks with no namespace are put into the
// 'default' namespace so lookup code can work the same for both
// namespaced and non-namespaced.
this.currentNamespace = this.defaultNamespace;
// Saves the description created by a 'desc' call that prefaces a
// 'task' call that defines a task.
this.currentTaskDescription = null;
this.program = new Program();
this.FileList = require('filelist').FileList;
this.PackageTask = require('./package_task').PackageTask;
this.PublishTask = require('./publish_task').PublishTask;
this.WatchTask = require('./watch_task').WatchTask;
this.TestTask = require('./test_task').TestTask;
this.Task = Task;
this.FileTask = FileTask;
this.DirectoryTask = DirectoryTask;
this.Namespace = Namespace;
this.Rule = Rule;
this.registerTask = function(task){
var ns = task.namespace;
var full = task.name;
while (ns&&ns.name!='default') {
full = ns.name + jake.nsSep + full;
ns = ns.parentNamespace;
}
task.fullName = full;
jake.Task[full] = task;
if (process.version>='v0.12.')
task.setMaxListeners(1000);
};
/**
* Displays the list of descriptions avaliable for tasks defined in
* a Jakefile
*/
this.showAllTaskDescriptions = function (f) {
var p
, maxTaskNameLength = 0
, task
, str = ''
, padding
, name
, descr
, filter = typeof f == 'string' ? f : null;
for (p in jake.Task) {
task = jake.Task[p];
// Record the length of the longest task name -- used for
// pretty alignment of the task descriptions
maxTaskNameLength = p.length > maxTaskNameLength ?
p.length : maxTaskNameLength;
}
// Print out each entry with descriptions neatly aligned
for (p in jake.Task) {
if (filter && p.indexOf(filter) == -1) {
continue;
}
task = jake.Task[p];
//name = '\033[32m' + p + '\033[39m ';
name = chalk.green(p);
// Create padding-string with calculated length
padding = (new Array(maxTaskNameLength - p.length + 2)).join(' ');
descr = task.description;
if (descr) {
descr = chalk.gray(descr);
console.log('jake ' + name + padding + descr);
}
}
};
function is_func(x){ return typeof x==='function'; }
this.createTask = function (constructor) {
var args = Array.prototype.slice.call(arguments, 1);
var name, obj, prereqs = [];
if (typeof args[0] == 'string') {
// name, [deps], [action]
// Name (string) + deps (array) format name = args.shift();
name = args.shift();
if (Array.isArray(args[0]) || (is_func(args[0]) &&
(is_func(args[1])||is_func(args[2]))))
prereqs = args.shift();
} else {
// name:deps, [action]
// Legacy object-literal syntax, e.g.: {'name': ['depA', 'depB']}
obj = args.shift();
for (var p in obj) {
prereqs.push.apply(prereqs, obj[p]);
name = p;
}
}
// Optional opts/callback or callback/opts
var arg, action, opts = {};
while ((arg = args.shift())) {
if (is_func(arg))
action = arg;
else
opts = arg;
}
var task = jake.currentNamespace.resolveTask(name);
if (task && !action) {
// Task already exists and no action, just update prereqs, and return it.
if (Array.isArray(task._prereqs))
task._prereqs = utils.uniq(task._prereqs.concat(prereqs));
return task;
}
if (typeof constructor == 'string')
constructor = task_constructors[constructor];
if (Array.isArray(prereqs))
prereqs = utils.uniq(prereqs);
task = new constructor(name, prereqs, action, opts);
if (jake.currentTaskDescription) {
task.description = jake.currentTaskDescription;
jake.currentTaskDescription = null;
}
jake.currentNamespace.tasks[name] = task;
task.namespace = jake.currentNamespace;
jake.registerTask(task);
return task;
};
this.attemptRule = function (name, ns, level) {
var prereqRule
, prereq;
if (level > MAX_RULE_RECURSION_LEVEL) {
return null;
}
// Check Rule
prereqRule = ns.matchRule(name);
if (prereqRule) {
prereq = prereqRule.createTask(name, level);
}
return prereq || null;
};
this.createDummyFileTask = function(name, ns) {
var task_name, task, ns_path = '';
var file_path = name.split(jake.nsSep).pop(); // Strip any namespace
if (ns) {
ns_path = typeof ns=='string' ? ns : ns.path || '';
if (ns_path.length)
ns_path += jake.nsSep;
}
task_name = ns_path+file_path;
task = jake.Task[task_name];
// If there's not already an existing dummy FileTask for it,
// create one, but only if file exists
// XXX replace with always cfile when file task added
var task_func = global.cfile||jake.FileTask;
if (!task && fs.existsSync(file_path)) {
task = new task_func(file_path);
task.dummy = true;
task.fullName = task_name;
jake.Task[task_name] = task;
}
return task || null;
};
this.init = function () {
var self = this;
process.addListener('uncaughtException', function (err) {
self.program.handleErr(err);
});
};
this.run = function () {
var args = Array.prototype.slice.call(arguments)
, program = this.program
, loader = this.loader
, preempt
, opts;
program.parseArgs(args);
program.init();
preempt = program.firstPreemptiveOption();
if (preempt) {
preempt();
}
else {
opts = program.opts;
// Load Jakefile and jakelibdir files
var jakefileLoaded = loader.loadFile(opts.jakefile);
var jakelibdirLoaded = loader.loadDirectory(opts.jakelibdir);
if(!jakefileLoaded && !jakelibdirLoaded) {
fail('No Jakefile. Specify a valid path with -f/--jakefile, ' +
'or place one in the current directory.');
}
jake.emit('loaded');
if (!opts['no-run'])
program.run();
else
jake.emit('complete');
}
};
})());
module.exports = jake;