blob: e271342c6ccaccae142b4d4a6e2473524184266e [file] [log] [blame]
/*
* 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 jake
, EventEmitter = require('events').EventEmitter
, fs = require('fs')
, path = require('path')
, taskNs = require('./task')
, Task = taskNs.Task
, FileTask = taskNs.FileTask
, DirectoryTask = taskNs.DirectoryTask
, api = require('./api')
, utils = require('./utils')
, Program = require('./program').Program
, Loader = require('./loader').Loader
, pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString());
var Namespace = function (name, parentNamespace) {
this.name = name;
this.parentNamespace = parentNamespace;
this.childNamespaces = {};
this.tasks = {};
this.resolve = function(relativeName) {
var parts = relativeName.split(':')
, name = parts.pop()
, ns = this
, task;
for(var i = 0, l = parts.length; ns && i < l; i++) {
ns = ns.childNamespaces[parts[i]];
}
return (ns && ns.tasks[name]) ||
(this.parentNamespace && this.parentNamespace.resolve(relativeName));
}
};
var Invocation = function (taskName, args) {
this.taskName = taskName;
this.args = args;
};
// And so it begins
jake = new EventEmitter();
// Globalize jake and top-level API methods (e.g., `task`, `desc`)
global.jake = jake;
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('./file_list').FileList;
this.PackageTask = require('./package_task').PackageTask;
this.NpmPublishTask = require('./npm_publish_task').NpmPublishTask;
this.TestTask = require('./test_task').TestTask;
this.Task = Task;
this.FileTask = FileTask;
this.DirectoryTask = DirectoryTask;
this.Namespace = Namespace;
this.parseAllTasks = function () {
var _parseNs = function (name, ns) {
var nsTasks = ns.tasks
, task
, nsNamespaces = ns.childNamespaces
, fullName;
// Iterate through the tasks in each namespace
for (var q in nsTasks) {
task = nsTasks[q];
// Preface only the namespaced tasks
fullName = name == 'default' ? q : name + ':' + q;
// Save with 'taskname' or 'namespace:taskname' key
task.fullName = fullName;
jake.Task[fullName] = task;
}
for (var p in nsNamespaces) {
fullName = name == 'default' ? p : name + ':' + p;
_parseNs(fullName, nsNamespaces[p]);
}
};
_parseNs('default', jake.defaultNamespace);
};
/**
* Displays the list of descriptions avaliable for tasks defined in
* a Jakefile
*/
this.showAllTaskDescriptions = function (f) {
var maxTaskNameLength = 0
, task
, str = ''
, padding
, name
, descr
, filter = typeof f == 'string' ? f : null;
for (var 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 (var p in jake.Task) {
if (filter && p.indexOf(filter) == -1) {
continue;
}
task = jake.Task[p];
name = '\033[32m' + p + '\033[39m ';
// Create padding-string with calculated length
padding = (new Array(maxTaskNameLength - p.length + 2)).join(' ');
descr = task.description
if (descr) {
descr = '\033[90m # ' + descr + '\033[39m \033[37m \033[39m';
console.log('jake ' + name + padding + descr);
}
}
};
this.createTask = function () {
var args = Array.prototype.slice.call(arguments)
, arg
, task
, type
, name
, action
, opts = {}
, prereqs = [];
type = args.shift()
// name, [deps], [action]
// Name (string) + deps (array) format
if (typeof args[0] == 'string') {
name = args.shift();
if (Array.isArray(args[0])) {
prereqs = args.shift();
}
}
// name:deps, [action]
// Legacy object-literal syntax, e.g.: {'name': ['depA', 'depB']}
else {
obj = args.shift()
for (var p in obj) {
prereqs = prereqs.concat(obj[p]);
name = p;
}
}
// Optional opts/callback or callback/opts
while ((arg = args.shift())) {
if (typeof arg == 'function') {
action = arg;
}
else {
opts = arg;
}
}
task = jake.currentNamespace.resolve(name);
if (task && !action) {
// Task already exists and no action, just update prereqs, and return it.
task.prereqs = task.prereqs.concat(prereqs);
return task;
}
switch (type) {
case 'directory':
action = function () {
jake.mkdirP(name);
};
task = new DirectoryTask(name, prereqs, action, opts);
break;
case 'file':
task = new FileTask(name, prereqs, action, opts);
break;
default:
task = new Task(name, prereqs, action, opts);
}
if (jake.currentTaskDescription) {
task.description = jake.currentTaskDescription;
jake.currentTaskDescription = null;
}
jake.currentNamespace.tasks[name] = task;
task.namespace = jake.currentNamespace;
// FIXME: Should only need to add a new entry for the current
// task-definition, not reparse the entire structure
jake.parseAllTasks();
return task;
};
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
loader.loadFile(opts.jakefile);
loader.loadDirectory(opts.jakelibdir);
program.run();
}
};
})());
module.exports = jake;