Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added support for file-tasks, just like in make, rake and all of thei…

…r cousins.
  • Loading branch information...
commit 0c8ca80e3234d0e5e5d762694b2ca48357b5f1a8 1 parent 303d9a6
Jakob Mattsson authored
6 README.markdown
View
@@ -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.
113 lib/jake.js
View
@@ -169,15 +169,76 @@ jake = new function () {
* @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) {
+ // If this is the root task, it's a failure if the task cannot be found.
+ // If this is not the root task, we'll just assume the name is a file.
+ var task = root ? _this.getTask(name) : _this.tryGetTask(name),
+ deps = task ? task.deps : [];
+
+ if (task == null) {
+ // No task could be found, so 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);
+ });
+ } else if (task.isFile) {
+ // The task was found and it was a file task.
+ fs.lstat(name, function(err, stats) {
+ if (deps && _isArray(deps) && deps.length) {
+ // 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.
+ 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);
+ }
+ });
+ }
+ } else if (err && err.errno == 2) {
+ // 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.
+ _taskList.push(name);
+ callback(new Date());
+ } else if (err) {
+ // Errors are rethrown.
+ throw new Error(err.message);
+ } else {
+ // No dependencies and the file already existed, then don't
+ // do anything and just return the time of last changed.
+ callback(stats.ctime);
+ }
+ });
+ } else {
+ if (deps && _isArray(deps) && deps.length) {
+ // If the task have 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.
+ 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());
+ }
+ });
+ }
+ } else {
+ // If the task does not have dependencies, just push it.
+ _taskList.push(name);
+ callback(new Date());
}
}
- _taskList.push(name);
};
// Public properties
@@ -211,12 +272,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);
+ });
};
/**
@@ -243,6 +305,19 @@ jake = new function () {
};
/**
+ * 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
@@ -344,12 +419,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;
};
@@ -365,6 +441,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;
};
11 tests/Jakefile
View
@@ -1,4 +1,5 @@
-var sys = require('sys');
+var sys = require('sys'),
+ fs = require('fs');
desc('This is the default task.');
task('default', [], function () {
@@ -6,6 +7,14 @@ task('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 () {
1  tests/src1.txt
View
@@ -0,0 +1 @@
+1
1  tests/src2.txt
View
@@ -0,0 +1 @@
+2
Please sign in to comment.
Something went wrong with that request. Please try again.