Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'file-task'

Conflicts:

	lib/jake.js
	tests/Jakefile
  • Loading branch information...
commit c150ca04bed83772b3fcdf7709938c14303d5452 2 parents 76743a0 + 29e5bd3
@mde mde authored
View
6 README.markdown
@@ -52,14 +52,16 @@ Or, get the code, and `npm link` in the code root.
### Jakefile syntax
-Use `task` to define tasks. Call it with three arguments (and one more optional argument):
+Use `task` or `file` to define tasks. Call it with three arguments (and one more optional argument):
task(name, dependencies, handler, [async]);
-Where `name` is the string name of the task, `dependencies` is an array of the dependencies, and `handler` is a function to run for the task.
+Where `name` is the string name of the task (or file), `dependencies` is an array of the dependencies, and `handler` is a function to run for the task.
The `async` argument is optional, and when set to `true` (`async === true`) indicates the task executes asynchronously. Asynchronous tasks need to call `complete()` to signal they have completed.
+Tasks created with `task` are always executed when asked for (or depended on). Tasks created with `file` are only executed if no file with the given name exists or if any of the files it depends on are more recent than the file named by the task. Also, if any dependency is a regular task, the file task will always be executed.
+
Use `desc` to add a string description of the task.
View
152 lib/jake.js
@@ -167,22 +167,112 @@ jake = new function () {
typeof obj.splice === 'function' &&
!(obj.propertyIsEnumerable('length'));
}
+
+ , _taskHasDeps = function (deps) {
+ return !!(deps && _isArray(deps) && deps.length);
+ }
+
+ , _handleFileTask = function (err, stats, name, deps, callback) {
+ // If the task has dependencies these are invoked in order.
+ // If any of them were changed after the current file, or
+ // if the current file does not exist, then push the current
+ // task to the list of tasks and update the time of last change.
+ if (_taskHasDeps(deps)) {
+ stats = stats || {ctime: 0};
+ for (var i = 0, ii = deps.length, depsLeft = deps.length, maxTime = stats.ctime;
+ i < ii; i++) {
+ _parseDeps(deps[i], false, function (ctime) {
+ depsLeft -= 1;
+ maxTime = (maxTime == null || maxTime < ctime) ? ctime : maxTime;
+ if (depsLeft == 0) {
+ if (maxTime > stats.ctime) {
+ _taskList.push(name);
+ }
+ callback(maxTime);
+ }
+ });
+ }
+ }
+ // If it does not have dependencies and could not
+ // be found, simply execute the task and use the
+ // current time as the last time it changed.
+ else if (err) {
+ if (err.errno == 2) {
+ _taskList.push(name);
+ callback(new Date());
+ }
+ // Errors are rethrown.
+ else {
+ throw new Error(err.message);
+ }
+ }
+ // No dependencies and the file already existed, then don't
+ // do anything and just return the time of last changed.
+ else {
+ callback(stats.ctime);
+ }
+ }
+
/**
* Parses all dependencies of a task (and their dependencies, etc.)
* recursively -- depth-first, so deps run first
* @param {String} name The name of the current task whose
* dependencies are being parsed.
*/
- , _parseDeps = function (name) {
- var task = _this.getTask(name)
- , deps = task.deps;
- if (deps && _isArray(deps) && deps.length) {
- for (var i = 0, ii = deps.length; i < ii; i++) {
- _parseDeps(deps[i]);
+ , _parseDeps = function (name, root, callback) {
+ var task = _this.getTask(name),
+ deps = task ? task.deps : [];
+
+ // No task found
+ if (!task) {
+
+ // If this is the root task, it's a failure if the task cannot be found.
+ if (root) {
+ throw new Error('Task "' + name + '" is not defined in the Jakefile.');
+ }
+ // If this is not the root task, we'll just assume the name is a file.
+ // Search for a file instead and provide the callback with the
+ // last time it changed
+ fs.lstat(name, function(err, stats) {
+ if (err) {
+ throw new Error(err.message);
+ }
+ callback(stats.ctime);
+ });
}
- }
- _taskList.push(name);
- };
+ // The task was found
+ else {
+ // File task
+ if (task.isFile) {
+ fs.lstat(name, function(err, stats) {
+ _handleFileTask(err, stats, name, deps, callback);
+ });
+ }
+ // Normal task
+ else {
+ // If the task has dependencies, execute those first and then
+ // push the task to the task list. In case it will be used as a
+ // dependancy for a file task, the last time of change is set to
+ // the current time in order to force files to update as well.
+ if (_taskHasDeps(deps)) {
+ for (var i = 0, ii = deps.length, ctr = deps.length; i < ii; i++) {
+ _parseDeps(deps[i], false, function () {
+ ctr -= 1;
+ if (ctr == 0) {
+ _taskList.push(name);
+ callback(new Date());
+ }
+ });
+ }
+ }
+ // If the task does not have dependencies, just push it.
+ else {
+ _taskList.push(name);
+ callback(new Date());
+ }
+ }
+ }
+ };
// Public properties
// =================
@@ -215,12 +305,13 @@ jake = new function () {
// Parse all the dependencies up front. This allows use of a simple
// queue to run all the tasks in order, and treat sync/async essentially
// the same.
- _parseDeps(name);
- if (!_taskList.length) {
- this.die('No tasks to run.');
- }
- // Kick off running the list of tasks
- this.runNextTask(args);
+ _parseDeps(name, true, function() {
+ if (!_taskList.length) {
+ _this.die('No tasks to run.');
+ }
+ // Kick off running the list of tasks
+ _this.runNextTask(args);
+ });
};
/**
@@ -240,13 +331,24 @@ jake = new function () {
}
var task = jake.namespaceTasks[nsName][taskName];
- if (!task) {
- throw new Error('Task "' + name + '" is not defined in the Jakefile.');
- }
return task;
};
/**
+ * Looks up a function object based on its name or namespace:name.
+ * Returns null rather than throwing an exception if no such object exists.
+ * @param {String} name The name of the task to look up
+ */
+ this.tryGetTask = function (name) {
+ try {
+ return this.getTask(name);
+ }
+ catch (e) {
+ return null;
+ }
+ };
+
+ /**
* Runs the next task in the _taskList queue until none are left
* Synchronous tasks require calling "complete" afterward, and async
* ones are expected to do that themselves
@@ -359,12 +461,13 @@ jake = new function () {
* @constructor
* A Jake task
*/
-jake.Task = function (name, deps, handler, async) {
+jake.Task = function (name, deps, handler, async, isFile) {
this.name = name;
this.deps = deps;
this.handler = handler;
this.desription = null;
this.async = async === true;
+ this.isFile = isFile;
};
@@ -380,6 +483,15 @@ global.task = function (name, deps, handler, async) {
jake.namespaceTasks[jake.currentNamespace][name] = task;
};
+global.file = function (name, deps, handler, async) {
+ var task = new jake.Task(name, deps, handler, async, true);
+ if (jake.currentTaskDescription) {
+ task.description = jake.currentTaskDescription;
+ jake.currentTaskDescription = null;
+ }
+ jake.namespaceTasks[jake.currentNamespace][name] = task;
+};
+
global.desc = function (str) {
jake.currentTaskDescription = str;
};
@@ -453,7 +565,7 @@ try {
if (isCoffee) {
try {
CoffeeScript = require('coffee-script');
- }
+ }
catch (e) {
jake.die('CoffeeScript is missing! Try `npm install coffee-script`');
}
View
11 tests/Jakefile
@@ -1,4 +1,5 @@
-var sys = require('sys');
+var sys = require('sys'),
+ fs = require('fs');
desc('This is the default task.');
task('default', [], function () {
@@ -30,6 +31,14 @@ task('uiop', ['default'], function () {
console.log(sys.inspect(arguments));
});
+desc('File task, concating two files together');
+file('concat.txt', ['src1.txt', 'src2.txt'], function() {
+ console.log('doing concat.txt file-task');
+ var data1 = fs.readFileSync('src1.txt');
+ var data2 = fs.readFileSync('src2.txt');
+ fs.writeFileSync('concat.txt', data1 + data2);
+});
+
namespace('foo', function () {
desc('This the foo:bar task');
task('bar', ['default', 'foo:qux', 'foo:baz'], function () {
View
1  tests/src1.txt
@@ -0,0 +1 @@
+1
View
1  tests/src2.txt
@@ -0,0 +1 @@
+2
Please sign in to comment.
Something went wrong with that request. Please try again.