aeac39faeb7f0bc2d104e2275596eccdfc0fd635.svn-base
13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
* grunt
* http://gruntjs.com/
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
*/
(function(exports) {
// Construct-o-rama.
function Task() {
// Information about the currently-running task.
this.current = {};
// Helpers.
this._helpers = {};
// Tasks.
this._tasks = {};
// Task queue.
this._queue = [];
// Queue placeholder (for dealing with nested tasks).
this._placeholder = {placeholder: true};
// Queue marker (for clearing the queue programatically).
this._marker = {marker: true};
// Options.
this._options = {};
// Is the queue running?
this._running = false;
// Success status of completed tasks.
this._success = {};
}
// Expose the constructor function.
exports.Task = Task;
// Create a new Task instance.
exports.create = function() {
return new Task();
};
// Error constructors.
function TaskError(message) {
Error.captureStackTrace(this, TaskError);
this.message = message;
}
TaskError.prototype = Object.create(Error.prototype);
TaskError.prototype.name = 'TaskError';
function HelperError(message) {
Error.captureStackTrace(this, HelperError);
this.message = message;
}
HelperError.prototype = Object.create(Error.prototype);
HelperError.prototype.name = 'HelperError';
// Expose the ability to create a new taskError.
Task.prototype.taskError = function(message, e) {
var error = new TaskError(message);
error.origError = e;
return error;
};
// Register a new helper.
Task.prototype.registerHelper = function(name, fn) {
// Add task into cache.
this._helpers[name] = fn;
// Make chainable!
return this;
};
// Rename a helper. This might be useful if you want to override the default
// behavior of a helper, while retaining the old name (to avoid having to
// completely recreate an already-made task just because you needed to
// override or extend a built-in helper).
Task.prototype.renameHelper = function(oldname, newname) {
// Rename helper.
this._helpers[newname] = this._helpers[oldname];
// Remove old name.
delete this._helpers[oldname];
// Make chainable!
return this;
};
// If the task runner is running or an error handler is not defined, throw
// an exception. Otherwise, call the error handler directly.
Task.prototype._throwIfRunning = function(obj) {
if (this._running || !this._options.error) {
// Throw an exception that the task runner will catch.
throw obj;
} else {
// Not inside the task runner. Call the error handler and abort.
this._options.error.call({name: null}, obj);
}
};
// Execute a helper.
Task.prototype.helper = function(isDirective, name) {
var args = [].slice.call(arguments, 1);
if (isDirective !== true) {
name = isDirective;
isDirective = false;
} else {
args = args.slice(1);
}
var helper = this._helpers[name];
if (!helper) {
// Helper not found.
this._throwIfRunning(new HelperError('Helper "' + name + '" not found.'));
return;
}
// Provide a few useful values on this.
var context = {args: args, flags: {}};
// Maybe you want to use flags instead of positional args?
args.forEach(function(arg) { context.flags[arg] = true; });
// Let the user know if it was used as a directive.
if (isDirective) { context.directive = true; }
// Invoke helper with any remaining arguments and return its value.
return helper.apply(context, args);
};
// If a <foo:bar:baz> directive is passed, return ["foo", "bar", "baz"],
// otherwise null.
var directiveRe = /^<(.*)>$/;
Task.prototype.getDirectiveParts = function(str) {
var matches = str.match(directiveRe);
// If the string doesn't look like a directive, return null.
if (!matches) { return null; }
// Split the name into parts.
var parts = matches[1].split(':');
// If a matching helper was found, return the parts, otherwise null.
return this._helpers[parts[0]] ? parts : null;
};
// If value matches the <handler:arg1:arg2> format, and the specified handler
// exists, it's a directive. Execute the matching handler and return its
// value. If not, but an optional callback was passed, pass the value into
// the callback and return its result. If no callback was specified, return
// the value.
Task.prototype.directive = function(value, fn) {
// Get parts if a string was passed and it looks like a directive.
var directive = typeof value === 'string' ? this.getDirectiveParts(value) : null;
if (directive) {
// If it looks like a directive and a matching helper exists, call the
// helper by applying all directive parts and return its value.
return this.helper.apply(this, [true].concat(directive));
} else if (fn) {
// Not a directive, but a callback was passed. Pass the value into the
// callback and return its result.
return fn(value);
}
// A callback wasn't specified or a valid handler wasn't found, so just
// return the value.
return value;
};
// Register a new task.
Task.prototype.registerTask = function(name, info, fn) {
// If optional "info" string is omitted, shuffle arguments a bit.
if (fn == null) {
fn = info;
info = '';
}
// String or array of strings was passed instead of fn.
var tasks;
if (typeof fn !== 'function') {
// Array of task names.
tasks = this.parseArgs([fn]);
// This task function just runs the specified tasks.
fn = this.run.bind(this, fn);
fn.alias = true;
// Generate an info string if one wasn't explicitly passed.
if (!info) {
info = 'Alias for "' + tasks.join(' ') + '" task' +
(tasks.length === 1 ? '' : 's') + '.';
}
}
// Add task into cache.
this._tasks[name] = {name: name, info: info, fn: fn};
// Make chainable!
return this;
};
// Is the specified task an alias?
Task.prototype.isTaskAlias = function(name) {
return !!this._tasks[name].fn.alias;
};
// Rename a task. This might be useful if you want to override the default
// behavior of a task, while retaining the old name. This is a billion times
// easier to implement than some kind of in-task "super" functionality.
Task.prototype.renameTask = function(oldname, newname) {
// Rename helper.
this._tasks[newname] = this._tasks[oldname];
// Update name property of task.
this._tasks[newname].name = newname;
// Remove old name.
delete this._tasks[oldname];
// Make chainable!
return this;
};
// Argument parsing helper. Supports these signatures:
// fn('foo') // ['foo']
// fn('foo bar baz') // ['foo', 'bar', 'baz']
// fn('foo', 'bar', 'baz') // ['foo', 'bar', 'baz']
// fn(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
Task.prototype.parseArgs = function(_arguments) {
// If there are multiple (or zero) arguments, convert the _arguments object
// into an array and return that.
return _arguments.length !== 1 ? [].slice.call(_arguments) :
// Return the first argument if it's an Array.
Array.isArray(_arguments[0]) ? _arguments[0] :
// Split the first argument on space.
typeof _arguments[0] === 'string' ? _arguments[0].split(/\s+/) :
// Just return an array containing the first argument. (todo: deprecate)
[_arguments[0]];
};
// Given a task name, determine which actual task will be called, and what
// arguments will be passed into the task callback. "foo" -> task "foo", no
// args. "foo:bar:baz" -> task "foo:bar:baz" with no args (if "foo:bar:baz"
// task exists), otherwise task "foo:bar" with arg "baz" (if "foo:bar" task
// exists), otherwise task "foo" with args "bar" and "baz".
Task.prototype._taskPlusArgs = function(name) {
// Task name / argument parts.
var parts = name.split(':');
// Start from the end, not the beginning!
var i = parts.length;
var task;
do {
// Get a task.
task = this._tasks[parts.slice(0, i).join(':')];
// If the task doesn't exist, decrement `i`, and if `i` is greater than
// 0, repeat.
} while (!task && --i > 0);
// Just the args.
var args = parts.slice(i);
// Maybe you want to use them as flags instead of as positional args?
var flags = {};
args.forEach(function(arg) { flags[arg] = true; });
// The task to run and the args to run it with.
return {task: task, nameArgs: name, args: args, flags: flags};
};
// Append things to queue in the correct spot.
Task.prototype._push = function(things) {
// Get current placeholder index.
var index = this._queue.indexOf(this._placeholder);
if (index === -1) {
// No placeholder, add task+args objects to end of queue.
this._queue = this._queue.concat(things);
} else {
// Placeholder exists, add task+args objects just before placeholder.
[].splice.apply(this._queue, [index, 0].concat(things));
}
};
// Enqueue a task.
Task.prototype.run = function() {
// Parse arguments into an array, returning an array of task+args objects.
var things = this.parseArgs(arguments).map(this._taskPlusArgs, this);
// Throw an exception if any tasks weren't found.
var fails = things.filter(function(thing) { return !thing.task; });
if (fails.length > 0) {
this._throwIfRunning(new TaskError('Task "' + fails[0].nameArgs + '" not found.'));
return this;
}
// Append things to queue in the correct spot.
this._push(things);
// Make chainable!
return this;
};
// Add a marker to the queue to facilitate clearing it programatically.
Task.prototype.mark = function() {
this._push(this._marker);
// Make chainable!
return this;
};
// Begin task queue processing. Ie. run all tasks.
Task.prototype.start = function() {
// Abort if already running.
if (this._running) { return false; }
// Actually process the next task.
var nextTask = function() {
// Async flag.
var async = false;
// Get next task+args object from queue.
var thing;
// Skip any placeholders or markers.
do {
thing = this._queue.shift();
} while (thing === this._placeholder || thing === this._marker);
// If queue was empty, we're all done.
if (!thing) {
this._running = false;
if (this._options.done) {
this._options.done();
}
return;
}
// Add a placeholder to the front of the queue.
this._queue.unshift(this._placeholder);
// Update the internal status object and run the next task.
var complete = function(status, errorObj) {
this.current = {};
// A task has "failed" only if it returns false (async) or if the
// function returned by .async is passed false.
this._success[thing.nameArgs] = status !== false;
// If task failed, call error handler.
if (status === false && this._options.error) {
this._options.error.call({name: thing.task.name, nameArgs: thing.nameArgs}, errorObj ||
new TaskError('Task "' + thing.nameArgs + '" failed.'));
}
// Run the next task.
nextTask();
}.bind(this);
// Expose some information about the currently-running task.
this.current = {
// The current task name plus args, as-passed.
nameArgs: thing.nameArgs,
// The current task name.
name: thing.task.name,
// The current task arguments.
args: thing.args,
// The current arguments, available as named flags.
flags: thing.flags,
// When called, sets the async flag and returns a function that can
// be used to continue processing the queue.
async: function() {
async = true;
return complete;
}
};
try {
// Get the current task and run it, setting `this` inside the task
// function to be something useful.
var status = thing.task.fn.apply(this.current, thing.args);
// If the async flag wasn't set, process the next task in the queue.
if (!async) {
complete(status);
}
} catch (e) {
if (e instanceof TaskError || e instanceof HelperError) {
complete(false, e);
} else {
throw e;
}
}
}.bind(this);
// Update flag.
this._running = true;
// Process the next task.
nextTask();
};
// Clear remaining tasks from the queue.
Task.prototype.clearQueue = function(options) {
if (!options) { options = {}; }
if (options.untilMarker) {
this._queue.splice(0, this._queue.indexOf(this._marker) + 1);
} else {
this._queue = [];
}
// Make chainable!
return this;
};
// Test to see if all of the given tasks have succeeded.
Task.prototype.requires = function() {
this.parseArgs(arguments).forEach(function(name) {
var success = this._success[name];
if (!success) {
throw new TaskError('Required task "' + name +
'" ' + (success === false ? 'failed' : 'missing') + '.');
}
}.bind(this));
};
// Override default options.
Task.prototype.options = function(options) {
Object.keys(options).forEach(function(name) {
this._options[name] = options[name];
}.bind(this));
};
}(typeof exports === 'object' && exports || this));