mirror of
https://github.com/wassname/CanvasTextWrapper.git
synced 2026-07-28 11:14:38 +08:00
v0.1.0
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Nodejs libs.
|
||||
var path = require('path');
|
||||
|
||||
// This allows grunt to require() .coffee files.
|
||||
require('coffee-script');
|
||||
|
||||
// The module to be exported.
|
||||
var grunt = module.exports = {};
|
||||
|
||||
// Expose internal grunt libs.
|
||||
function gRequire(name) {
|
||||
return grunt[name] = require('./grunt/' + name);
|
||||
}
|
||||
|
||||
var util = require('grunt-legacy-util');
|
||||
grunt.util = util;
|
||||
grunt.util.task = require('./util/task');
|
||||
|
||||
var Log = require('grunt-legacy-log').Log;
|
||||
var log = new Log({grunt: grunt});
|
||||
grunt.log = log;
|
||||
|
||||
gRequire('template');
|
||||
gRequire('event');
|
||||
var fail = gRequire('fail');
|
||||
gRequire('file');
|
||||
var option = gRequire('option');
|
||||
var config = gRequire('config');
|
||||
var task = gRequire('task');
|
||||
var help = gRequire('help');
|
||||
gRequire('cli');
|
||||
var verbose = grunt.verbose = log.verbose;
|
||||
|
||||
// Expose some grunt metadata.
|
||||
grunt.package = require('../package.json');
|
||||
grunt.version = grunt.package.version;
|
||||
|
||||
// Expose specific grunt lib methods on grunt.
|
||||
function gExpose(obj, methodName, newMethodName) {
|
||||
grunt[newMethodName || methodName] = obj[methodName].bind(obj);
|
||||
}
|
||||
gExpose(task, 'registerTask');
|
||||
gExpose(task, 'registerMultiTask');
|
||||
gExpose(task, 'registerInitTask');
|
||||
gExpose(task, 'renameTask');
|
||||
gExpose(task, 'loadTasks');
|
||||
gExpose(task, 'loadNpmTasks');
|
||||
gExpose(config, 'init', 'initConfig');
|
||||
gExpose(fail, 'warn');
|
||||
gExpose(fail, 'fatal');
|
||||
|
||||
// Expose the task interface. I've never called this manually, and have no idea
|
||||
// how it will work. But it might.
|
||||
grunt.tasks = function(tasks, options, done) {
|
||||
// Update options with passed-in options.
|
||||
option.init(options);
|
||||
|
||||
// Display the grunt version and quit if the user did --version.
|
||||
var _tasks, _options;
|
||||
if (option('version')) {
|
||||
// Not --verbose.
|
||||
log.writeln('grunt v' + grunt.version);
|
||||
|
||||
if (option('verbose')) {
|
||||
// --verbose
|
||||
verbose.writeln('Install path: ' + path.resolve(__dirname, '..'));
|
||||
// Yes, this is a total hack, but we don't want to log all that verbose
|
||||
// task initialization stuff here.
|
||||
grunt.log.muted = true;
|
||||
// Initialize task system so that available tasks can be listed.
|
||||
grunt.task.init([], {help: true});
|
||||
// Re-enable logging.
|
||||
grunt.log.muted = false;
|
||||
|
||||
// Display available tasks (for shell completion, etc).
|
||||
_tasks = Object.keys(grunt.task._tasks).sort();
|
||||
verbose.writeln('Available tasks: ' + _tasks.join(' '));
|
||||
|
||||
// Display available options (for shell completion, etc).
|
||||
_options = [];
|
||||
Object.keys(grunt.cli.optlist).forEach(function(long) {
|
||||
var o = grunt.cli.optlist[long];
|
||||
_options.push('--' + (o.negate ? 'no-' : '') + long);
|
||||
if (o.short) { _options.push('-' + o.short); }
|
||||
});
|
||||
verbose.writeln('Available options: ' + _options.join(' '));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Init colors.
|
||||
log.initColors();
|
||||
|
||||
// Display help and quit if the user did --help.
|
||||
if (option('help')) {
|
||||
help.display();
|
||||
return;
|
||||
}
|
||||
|
||||
// A little header stuff.
|
||||
verbose.header('Initializing').writeflags(option.flags(), 'Command-line options');
|
||||
|
||||
// Determine and output which tasks will be run.
|
||||
var tasksSpecified = tasks && tasks.length > 0;
|
||||
tasks = task.parseArgs([tasksSpecified ? tasks : 'default']);
|
||||
|
||||
// Initialize tasks.
|
||||
task.init(tasks);
|
||||
|
||||
verbose.writeln();
|
||||
if (!tasksSpecified) {
|
||||
verbose.writeln('No tasks specified, running default tasks.');
|
||||
}
|
||||
verbose.writeflags(tasks, 'Running tasks');
|
||||
|
||||
// Handle otherwise unhandleable (probably asynchronous) exceptions.
|
||||
var uncaughtHandler = function(e) {
|
||||
fail.fatal(e, fail.code.TASK_FAILURE);
|
||||
};
|
||||
process.on('uncaughtException', uncaughtHandler);
|
||||
|
||||
// Report, etc when all tasks have completed.
|
||||
task.options({
|
||||
error: function(e) {
|
||||
fail.warn(e, fail.code.TASK_FAILURE);
|
||||
},
|
||||
done: function() {
|
||||
// Stop handling uncaught exceptions so that we don't leave any
|
||||
// unwanted process-level side effects behind. There is no need to do
|
||||
// this in the error callback, because fail.warn() will either kill
|
||||
// the process, or with --force keep on going all the way here.
|
||||
process.removeListener('uncaughtException', uncaughtHandler);
|
||||
|
||||
// Output a final fail / success report.
|
||||
fail.report();
|
||||
|
||||
if (done) {
|
||||
// Execute "done" function when done (only if passed, of course).
|
||||
done();
|
||||
} else {
|
||||
// Otherwise, explicitly exit.
|
||||
util.exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Execute all tasks, in order. Passing each task individually in a forEach
|
||||
// allows the error callback to execute multiple times.
|
||||
tasks.forEach(function(name) { task.run(name); });
|
||||
// Run tasks async internally to reduce call-stack, per:
|
||||
// https://github.com/gruntjs/grunt/pull/1026
|
||||
task.start({asyncDone:true});
|
||||
};
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// Nodejs libs.
|
||||
var path = require('path');
|
||||
|
||||
// External libs.
|
||||
var nopt = require('nopt');
|
||||
|
||||
// This is only executed when run via command line.
|
||||
var cli = module.exports = function(options, done) {
|
||||
// CLI-parsed options override any passed-in "default" options.
|
||||
if (options) {
|
||||
// For each default option...
|
||||
Object.keys(options).forEach(function(key) {
|
||||
if (!(key in cli.options)) {
|
||||
// If this option doesn't exist in the parsed cli.options, add it in.
|
||||
cli.options[key] = options[key];
|
||||
} else if (cli.optlist[key].type === Array) {
|
||||
// If this option's type is Array, append it to any existing array
|
||||
// (or create a new array).
|
||||
[].push.apply(cli.options[key], options[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Run tasks.
|
||||
grunt.tasks(cli.tasks, cli.options, done);
|
||||
};
|
||||
|
||||
// Default options.
|
||||
var optlist = cli.optlist = {
|
||||
help: {
|
||||
short: 'h',
|
||||
info: 'Display this help text.',
|
||||
type: Boolean
|
||||
},
|
||||
base: {
|
||||
info: 'Specify an alternate base path. By default, all file paths are relative to the Gruntfile. (grunt.file.setBase) *',
|
||||
type: path
|
||||
},
|
||||
color: {
|
||||
info: 'Disable colored output.',
|
||||
type: Boolean,
|
||||
negate: true
|
||||
},
|
||||
gruntfile: {
|
||||
info: 'Specify an alternate Gruntfile. By default, grunt looks in the current or parent directories for the nearest Gruntfile.js or Gruntfile.coffee file.',
|
||||
type: path
|
||||
},
|
||||
debug: {
|
||||
short: 'd',
|
||||
info: 'Enable debugging mode for tasks that support it.',
|
||||
type: [Number, Boolean]
|
||||
},
|
||||
stack: {
|
||||
info: 'Print a stack trace when exiting with a warning or fatal error.',
|
||||
type: Boolean
|
||||
},
|
||||
force: {
|
||||
short: 'f',
|
||||
info: 'A way to force your way past warnings. Want a suggestion? Don\'t use this option, fix your code.',
|
||||
type: Boolean
|
||||
},
|
||||
tasks: {
|
||||
info: 'Additional directory paths to scan for task and "extra" files. (grunt.loadTasks) *',
|
||||
type: Array
|
||||
},
|
||||
npm: {
|
||||
info: 'Npm-installed grunt plugins to scan for task and "extra" files. (grunt.loadNpmTasks) *',
|
||||
type: Array
|
||||
},
|
||||
write: {
|
||||
info: 'Disable writing files (dry run).',
|
||||
type: Boolean,
|
||||
negate: true
|
||||
},
|
||||
verbose: {
|
||||
short: 'v',
|
||||
info: 'Verbose mode. A lot more information output.',
|
||||
type: Boolean
|
||||
},
|
||||
version: {
|
||||
short: 'V',
|
||||
info: 'Print the grunt version. Combine with --verbose for more info.',
|
||||
type: Boolean
|
||||
},
|
||||
// Even though shell auto-completion is now handled by grunt-cli, leave this
|
||||
// option here for display in the --help screen.
|
||||
completion: {
|
||||
info: 'Output shell auto-completion rules. See the grunt-cli documentation for more information.',
|
||||
type: String
|
||||
},
|
||||
};
|
||||
|
||||
// Parse `optlist` into a form that nopt can handle.
|
||||
var aliases = {};
|
||||
var known = {};
|
||||
|
||||
Object.keys(optlist).forEach(function(key) {
|
||||
var short = optlist[key].short;
|
||||
if (short) {
|
||||
aliases[short] = '--' + key;
|
||||
}
|
||||
known[key] = optlist[key].type;
|
||||
});
|
||||
|
||||
var parsed = nopt(known, aliases, process.argv, 2);
|
||||
cli.tasks = parsed.argv.remain;
|
||||
cli.options = parsed;
|
||||
delete parsed.argv;
|
||||
|
||||
// Initialize any Array options that weren't initialized.
|
||||
Object.keys(optlist).forEach(function(key) {
|
||||
if (optlist[key].type === Array && !(key in cli.options)) {
|
||||
cli.options[key] = [];
|
||||
}
|
||||
});
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// Get/set config data. If value was passed, set. Otherwise, get.
|
||||
var config = module.exports = function(prop, value) {
|
||||
if (arguments.length === 2) {
|
||||
// Two arguments were passed, set the property's value.
|
||||
return config.set(prop, value);
|
||||
} else {
|
||||
// Get the property's value (or the entire data object).
|
||||
return config.get(prop);
|
||||
}
|
||||
};
|
||||
|
||||
// The actual config data.
|
||||
config.data = {};
|
||||
|
||||
// Escape any . in name with \. so dot-based namespacing works properly.
|
||||
config.escape = function(str) {
|
||||
return str.replace(/\./g, '\\.');
|
||||
};
|
||||
|
||||
// Return prop as a string.
|
||||
config.getPropString = function(prop) {
|
||||
return Array.isArray(prop) ? prop.map(config.escape).join('.') : prop;
|
||||
};
|
||||
|
||||
// Get raw, unprocessed config data.
|
||||
config.getRaw = function(prop) {
|
||||
if (prop) {
|
||||
// Prop was passed, get that specific property's value.
|
||||
return grunt.util.namespace.get(config.data, config.getPropString(prop));
|
||||
} else {
|
||||
// No prop was passed, return the entire config.data object.
|
||||
return config.data;
|
||||
}
|
||||
};
|
||||
|
||||
// Match '<%= FOO %>' where FOO is a propString, eg. foo or foo.bar but not
|
||||
// a method call like foo() or foo.bar().
|
||||
var propStringTmplRe = /^<%=\s*([a-z0-9_$]+(?:\.[a-z0-9_$]+)*)\s*%>$/i;
|
||||
|
||||
// Get config data, recursively processing templates.
|
||||
config.get = function(prop) {
|
||||
return config.process(config.getRaw(prop));
|
||||
};
|
||||
|
||||
// Expand a config value recursively. Used for post-processing raw values
|
||||
// already retrieved from the config.
|
||||
config.process = function(raw) {
|
||||
return grunt.util.recurse(raw, function(value) {
|
||||
// If the value is not a string, return it.
|
||||
if (typeof value !== 'string') { return value; }
|
||||
// If possible, access the specified property via config.get, in case it
|
||||
// doesn't refer to a string, but instead refers to an object or array.
|
||||
var matches = value.match(propStringTmplRe);
|
||||
var result;
|
||||
if (matches) {
|
||||
result = config.get(matches[1]);
|
||||
// If the result retrieved from the config data wasn't null or undefined,
|
||||
// return it.
|
||||
if (result != null) { return result; }
|
||||
}
|
||||
// Process the string as a template.
|
||||
return grunt.template.process(value, {data: config.data});
|
||||
});
|
||||
};
|
||||
|
||||
// Set config data.
|
||||
config.set = function(prop, value) {
|
||||
return grunt.util.namespace.set(config.data, config.getPropString(prop), value);
|
||||
};
|
||||
|
||||
// Deep merge config data.
|
||||
config.merge = function(obj) {
|
||||
grunt.util._.merge(config.data, obj);
|
||||
return config.data;
|
||||
};
|
||||
|
||||
// Initialize config data.
|
||||
config.init = function(obj) {
|
||||
grunt.verbose.write('Initializing config...').ok();
|
||||
// Initialize and return data.
|
||||
return (config.data = obj || {});
|
||||
};
|
||||
|
||||
// Test to see if required config params have been defined. If not, throw an
|
||||
// exception (use this inside of a task).
|
||||
config.requires = function() {
|
||||
var p = grunt.util.pluralize;
|
||||
var props = grunt.util.toArray(arguments).map(config.getPropString);
|
||||
var msg = 'Verifying propert' + p(props.length, 'y/ies') +
|
||||
' ' + grunt.log.wordlist(props) + ' exist' + p(props.length, 's') +
|
||||
' in config...';
|
||||
grunt.verbose.write(msg);
|
||||
var failProps = config.data && props.filter(function(prop) {
|
||||
return config.get(prop) == null;
|
||||
}).map(function(prop) {
|
||||
return '"' + prop + '"';
|
||||
});
|
||||
if (config.data && failProps.length === 0) {
|
||||
grunt.verbose.ok();
|
||||
return true;
|
||||
} else {
|
||||
grunt.verbose.or.write(msg);
|
||||
grunt.log.error().error('Unable to process task.');
|
||||
if (!config.data) {
|
||||
throw grunt.util.error('Unable to load config.');
|
||||
} else {
|
||||
throw grunt.util.error('Required config propert' +
|
||||
p(failProps.length, 'y/ies') + ' ' + failProps.join(', ') + ' missing.');
|
||||
}
|
||||
}
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// External lib.
|
||||
var EventEmitter2 = require('eventemitter2').EventEmitter2;
|
||||
|
||||
// Awesome.
|
||||
module.exports = new EventEmitter2({wildcard: true});
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// The module to be exported.
|
||||
var fail = module.exports = {};
|
||||
|
||||
// Error codes.
|
||||
fail.code = {
|
||||
FATAL_ERROR: 1,
|
||||
MISSING_GRUNTFILE: 2,
|
||||
TASK_FAILURE: 3,
|
||||
TEMPLATE_ERROR: 4,
|
||||
INVALID_AUTOCOMPLETE: 5,
|
||||
WARNING: 6,
|
||||
};
|
||||
|
||||
// DRY it up!
|
||||
function writeln(e, mode) {
|
||||
grunt.log.muted = false;
|
||||
var msg = String(e.message || e);
|
||||
if (!grunt.option('no-color')) { msg += '\x07'; } // Beep!
|
||||
if (mode === 'warn') {
|
||||
msg = 'Warning: ' + msg + ' ';
|
||||
msg += (grunt.option('force') ? 'Used --force, continuing.'.underline : 'Use --force to continue.');
|
||||
msg = msg.yellow;
|
||||
} else {
|
||||
msg = ('Fatal error: ' + msg).red;
|
||||
}
|
||||
grunt.log.writeln(msg);
|
||||
}
|
||||
|
||||
// If --stack is enabled, log the appropriate error stack (if it exists).
|
||||
function dumpStack(e) {
|
||||
if (grunt.option('stack')) {
|
||||
if (e.origError && e.origError.stack) {
|
||||
console.log(e.origError.stack);
|
||||
} else if (e.stack) {
|
||||
console.log(e.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A fatal error occurred. Abort immediately.
|
||||
fail.fatal = function(e, errcode) {
|
||||
writeln(e, 'fatal');
|
||||
dumpStack(e);
|
||||
grunt.util.exit(typeof errcode === 'number' ? errcode : fail.code.FATAL_ERROR);
|
||||
};
|
||||
|
||||
// Keep track of error and warning counts.
|
||||
fail.errorcount = 0;
|
||||
fail.warncount = 0;
|
||||
|
||||
// A warning occurred. Abort immediately unless -f or --force was used.
|
||||
fail.warn = function(e, errcode) {
|
||||
var message = typeof e === 'string' ? e : e.message;
|
||||
fail.warncount++;
|
||||
writeln(message, 'warn');
|
||||
// If -f or --force aren't used, stop script processing.
|
||||
if (!grunt.option('force')) {
|
||||
dumpStack(e);
|
||||
grunt.log.writeln().fail('Aborted due to warnings.');
|
||||
grunt.util.exit(typeof errcode === 'number' ? errcode : fail.code.WARNING);
|
||||
}
|
||||
};
|
||||
|
||||
// This gets called at the very end.
|
||||
fail.report = function() {
|
||||
if (fail.warncount > 0) {
|
||||
grunt.log.writeln().fail('Done, but with warnings.');
|
||||
} else {
|
||||
grunt.log.writeln().success('Done, without errors.');
|
||||
}
|
||||
};
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// Nodejs libs.
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// The module to be exported.
|
||||
var file = module.exports = {};
|
||||
|
||||
// External libs.
|
||||
file.glob = require('glob');
|
||||
file.minimatch = require('minimatch');
|
||||
file.findup = require('findup-sync');
|
||||
var YAML = require('js-yaml');
|
||||
var rimraf = require('rimraf');
|
||||
var iconv = require('iconv-lite');
|
||||
|
||||
// Windows?
|
||||
var win32 = process.platform === 'win32';
|
||||
|
||||
// Normalize \\ paths to / paths.
|
||||
var unixifyPath = function(filepath) {
|
||||
if (win32) {
|
||||
return filepath.replace(/\\/g, '/');
|
||||
} else {
|
||||
return filepath;
|
||||
}
|
||||
};
|
||||
|
||||
// Change the current base path (ie, CWD) to the specified path.
|
||||
file.setBase = function() {
|
||||
var dirpath = path.join.apply(path, arguments);
|
||||
process.chdir(dirpath);
|
||||
};
|
||||
|
||||
// Process specified wildcard glob patterns or filenames against a
|
||||
// callback, excluding and uniquing files in the result set.
|
||||
var processPatterns = function(patterns, fn) {
|
||||
// Filepaths to return.
|
||||
var result = [];
|
||||
// Iterate over flattened patterns array.
|
||||
grunt.util._.flatten(patterns).forEach(function(pattern) {
|
||||
// If the first character is ! it should be omitted
|
||||
var exclusion = pattern.indexOf('!') === 0;
|
||||
// If the pattern is an exclusion, remove the !
|
||||
if (exclusion) { pattern = pattern.slice(1); }
|
||||
// Find all matching files for this pattern.
|
||||
var matches = fn(pattern);
|
||||
if (exclusion) {
|
||||
// If an exclusion, remove matching files.
|
||||
result = grunt.util._.difference(result, matches);
|
||||
} else {
|
||||
// Otherwise add matching files.
|
||||
result = grunt.util._.union(result, matches);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
// Match a filepath or filepaths against one or more wildcard patterns. Returns
|
||||
// all matching filepaths.
|
||||
file.match = function(options, patterns, filepaths) {
|
||||
if (grunt.util.kindOf(options) !== 'object') {
|
||||
filepaths = patterns;
|
||||
patterns = options;
|
||||
options = {};
|
||||
}
|
||||
// Return empty set if either patterns or filepaths was omitted.
|
||||
if (patterns == null || filepaths == null) { return []; }
|
||||
// Normalize patterns and filepaths to arrays.
|
||||
if (!Array.isArray(patterns)) { patterns = [patterns]; }
|
||||
if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
|
||||
// Return empty set if there are no patterns or filepaths.
|
||||
if (patterns.length === 0 || filepaths.length === 0) { return []; }
|
||||
// Return all matching filepaths.
|
||||
return processPatterns(patterns, function(pattern) {
|
||||
return file.minimatch.match(filepaths, pattern, options);
|
||||
});
|
||||
};
|
||||
|
||||
// Match a filepath or filepaths against one or more wildcard patterns. Returns
|
||||
// true if any of the patterns match.
|
||||
file.isMatch = function() {
|
||||
return file.match.apply(file, arguments).length > 0;
|
||||
};
|
||||
|
||||
// Return an array of all file paths that match the given wildcard patterns.
|
||||
file.expand = function() {
|
||||
var args = grunt.util.toArray(arguments);
|
||||
// If the first argument is an options object, save those options to pass
|
||||
// into the file.glob.sync method.
|
||||
var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
|
||||
// Use the first argument if it's an Array, otherwise convert the arguments
|
||||
// object to an array and use that.
|
||||
var patterns = Array.isArray(args[0]) ? args[0] : args;
|
||||
// Return empty set if there are no patterns or filepaths.
|
||||
if (patterns.length === 0) { return []; }
|
||||
// Return all matching filepaths.
|
||||
var matches = processPatterns(patterns, function(pattern) {
|
||||
// Find all matching files for this pattern.
|
||||
return file.glob.sync(pattern, options);
|
||||
});
|
||||
// Filter result set?
|
||||
if (options.filter) {
|
||||
matches = matches.filter(function(filepath) {
|
||||
filepath = path.join(options.cwd || '', filepath);
|
||||
try {
|
||||
if (typeof options.filter === 'function') {
|
||||
return options.filter(filepath);
|
||||
} else {
|
||||
// If the file is of the right type and exists, this should work.
|
||||
return fs.statSync(filepath)[options.filter]();
|
||||
}
|
||||
} catch(e) {
|
||||
// Otherwise, it's probably not the right type.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
var pathSeparatorRe = /[\/\\]/g;
|
||||
|
||||
// The "ext" option refers to either everything after the first dot (default)
|
||||
// or everything after the last dot.
|
||||
var extDotRe = {
|
||||
first: /(\.[^\/]*)?$/,
|
||||
last: /(\.[^\/\.]*)?$/,
|
||||
};
|
||||
|
||||
// Build a multi task "files" object dynamically.
|
||||
file.expandMapping = function(patterns, destBase, options) {
|
||||
options = grunt.util._.defaults({}, options, {
|
||||
extDot: 'first',
|
||||
rename: function(destBase, destPath) {
|
||||
return path.join(destBase || '', destPath);
|
||||
}
|
||||
});
|
||||
var files = [];
|
||||
var fileByDest = {};
|
||||
// Find all files matching pattern, using passed-in options.
|
||||
file.expand(options, patterns).forEach(function(src) {
|
||||
var destPath = src;
|
||||
// Flatten?
|
||||
if (options.flatten) {
|
||||
destPath = path.basename(destPath);
|
||||
}
|
||||
// Change the extension?
|
||||
if ('ext' in options) {
|
||||
destPath = destPath.replace(extDotRe[options.extDot], options.ext);
|
||||
}
|
||||
// Generate destination filename.
|
||||
var dest = options.rename(destBase, destPath, options);
|
||||
// Prepend cwd to src path if necessary.
|
||||
if (options.cwd) { src = path.join(options.cwd, src); }
|
||||
// Normalize filepaths to be unix-style.
|
||||
dest = dest.replace(pathSeparatorRe, '/');
|
||||
src = src.replace(pathSeparatorRe, '/');
|
||||
// Map correct src path to dest path.
|
||||
if (fileByDest[dest]) {
|
||||
// If dest already exists, push this src onto that dest's src array.
|
||||
fileByDest[dest].src.push(src);
|
||||
} else {
|
||||
// Otherwise create a new src-dest file mapping object.
|
||||
files.push({
|
||||
src: [src],
|
||||
dest: dest,
|
||||
});
|
||||
// And store a reference for later use.
|
||||
fileByDest[dest] = files[files.length - 1];
|
||||
}
|
||||
});
|
||||
return files;
|
||||
};
|
||||
|
||||
// Like mkdir -p. Create a directory and any intermediary directories.
|
||||
file.mkdir = function(dirpath, mode) {
|
||||
if (grunt.option('no-write')) { return; }
|
||||
// Set directory mode in a strict-mode-friendly way.
|
||||
if (mode == null) {
|
||||
mode = parseInt('0777', 8) & (~process.umask());
|
||||
}
|
||||
dirpath.split(pathSeparatorRe).reduce(function(parts, part) {
|
||||
parts += part + '/';
|
||||
var subpath = path.resolve(parts);
|
||||
if (!file.exists(subpath)) {
|
||||
try {
|
||||
fs.mkdirSync(subpath, mode);
|
||||
} catch(e) {
|
||||
throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}, '');
|
||||
};
|
||||
|
||||
// Recurse into a directory, executing callback for each file.
|
||||
file.recurse = function recurse(rootdir, callback, subdir) {
|
||||
var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
|
||||
fs.readdirSync(abspath).forEach(function(filename) {
|
||||
var filepath = path.join(abspath, filename);
|
||||
if (fs.statSync(filepath).isDirectory()) {
|
||||
recurse(rootdir, callback, unixifyPath(path.join(subdir || '', filename || '')));
|
||||
} else {
|
||||
callback(unixifyPath(filepath), rootdir, subdir, filename);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// The default file encoding to use.
|
||||
file.defaultEncoding = 'utf8';
|
||||
// Whether to preserve the BOM on file.read rather than strip it.
|
||||
file.preserveBOM = false;
|
||||
|
||||
// Read a file, return its contents.
|
||||
file.read = function(filepath, options) {
|
||||
if (!options) { options = {}; }
|
||||
var contents;
|
||||
grunt.verbose.write('Reading ' + filepath + '...');
|
||||
try {
|
||||
contents = fs.readFileSync(String(filepath));
|
||||
// If encoding is not explicitly null, convert from encoded buffer to a
|
||||
// string. If no encoding was specified, use the default.
|
||||
if (options.encoding !== null) {
|
||||
contents = iconv.decode(contents, options.encoding || file.defaultEncoding);
|
||||
// Strip any BOM that might exist.
|
||||
if (!file.preserveBOM && contents.charCodeAt(0) === 0xFEFF) {
|
||||
contents = contents.substring(1);
|
||||
}
|
||||
}
|
||||
grunt.verbose.ok();
|
||||
return contents;
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Read a file, parse its contents, return an object.
|
||||
file.readJSON = function(filepath, options) {
|
||||
var src = file.read(filepath, options);
|
||||
var result;
|
||||
grunt.verbose.write('Parsing ' + filepath + '...');
|
||||
try {
|
||||
result = JSON.parse(src);
|
||||
grunt.verbose.ok();
|
||||
return result;
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Read a YAML file, parse its contents, return an object.
|
||||
file.readYAML = function(filepath, options) {
|
||||
var src = file.read(filepath, options);
|
||||
var result;
|
||||
grunt.verbose.write('Parsing ' + filepath + '...');
|
||||
try {
|
||||
result = YAML.load(src);
|
||||
grunt.verbose.ok();
|
||||
return result;
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Write a file.
|
||||
file.write = function(filepath, contents, options) {
|
||||
if (!options) { options = {}; }
|
||||
var nowrite = grunt.option('no-write');
|
||||
grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
|
||||
// Create path, if necessary.
|
||||
file.mkdir(path.dirname(filepath));
|
||||
try {
|
||||
// If contents is already a Buffer, don't try to encode it. If no encoding
|
||||
// was specified, use the default.
|
||||
if (!Buffer.isBuffer(contents)) {
|
||||
contents = iconv.encode(contents, options.encoding || file.defaultEncoding);
|
||||
}
|
||||
// Actually write file.
|
||||
if (!nowrite) {
|
||||
fs.writeFileSync(filepath, contents);
|
||||
}
|
||||
grunt.verbose.ok();
|
||||
return true;
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Read a file, optionally processing its content, then write the output.
|
||||
file.copy = function(srcpath, destpath, options) {
|
||||
if (!options) { options = {}; }
|
||||
// If a process function was specified, and noProcess isn't true or doesn't
|
||||
// match the srcpath, process the file's source.
|
||||
var process = options.process && options.noProcess !== true &&
|
||||
!(options.noProcess && file.isMatch(options.noProcess, srcpath));
|
||||
// If the file will be processed, use the encoding as-specified. Otherwise,
|
||||
// use an encoding of null to force the file to be read/written as a Buffer.
|
||||
var readWriteOptions = process ? options : {encoding: null};
|
||||
// Actually read the file.
|
||||
var contents = file.read(srcpath, readWriteOptions);
|
||||
if (process) {
|
||||
grunt.verbose.write('Processing source...');
|
||||
try {
|
||||
contents = options.process(contents, srcpath);
|
||||
grunt.verbose.ok();
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
|
||||
}
|
||||
}
|
||||
// Abort copy if the process function returns false.
|
||||
if (contents === false) {
|
||||
grunt.verbose.writeln('Write aborted.');
|
||||
} else {
|
||||
file.write(destpath, contents, readWriteOptions);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete folders and files recursively
|
||||
file.delete = function(filepath, options) {
|
||||
filepath = String(filepath);
|
||||
|
||||
var nowrite = grunt.option('no-write');
|
||||
if (!options) {
|
||||
options = {force: grunt.option('force') || false};
|
||||
}
|
||||
|
||||
grunt.verbose.write((nowrite ? 'Not actually deleting ' : 'Deleting ') + filepath + '...');
|
||||
|
||||
if (!file.exists(filepath)) {
|
||||
grunt.verbose.error();
|
||||
grunt.log.warn('Cannot delete nonexistent file.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only delete cwd or outside cwd if --force enabled. Be careful, people!
|
||||
if (!options.force) {
|
||||
if (file.isPathCwd(filepath)) {
|
||||
grunt.verbose.error();
|
||||
grunt.fail.warn('Cannot delete the current working directory.');
|
||||
return false;
|
||||
} else if (!file.isPathInCwd(filepath)) {
|
||||
grunt.verbose.error();
|
||||
grunt.fail.warn('Cannot delete files outside the current working directory.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Actually delete. Or not.
|
||||
if (!nowrite) {
|
||||
rimraf.sync(filepath);
|
||||
}
|
||||
grunt.verbose.ok();
|
||||
return true;
|
||||
} catch(e) {
|
||||
grunt.verbose.error();
|
||||
throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
|
||||
}
|
||||
};
|
||||
|
||||
// True if the file path exists.
|
||||
file.exists = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
return fs.existsSync(filepath);
|
||||
};
|
||||
|
||||
// True if the file is a symbolic link.
|
||||
file.isLink = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
|
||||
};
|
||||
|
||||
// True if the path is a directory.
|
||||
file.isDir = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
return file.exists(filepath) && fs.statSync(filepath).isDirectory();
|
||||
};
|
||||
|
||||
// True if the path is a file.
|
||||
file.isFile = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
return file.exists(filepath) && fs.statSync(filepath).isFile();
|
||||
};
|
||||
|
||||
// Is a given file path absolute?
|
||||
file.isPathAbsolute = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
return path.resolve(filepath) === filepath.replace(/[\/\\]+$/, '');
|
||||
};
|
||||
|
||||
// Do all the specified paths refer to the same path?
|
||||
file.arePathsEquivalent = function(first) {
|
||||
first = path.resolve(first);
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
if (first !== path.resolve(arguments[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Are descendant path(s) contained within ancestor path? Note: does not test
|
||||
// if paths actually exist.
|
||||
file.doesPathContain = function(ancestor) {
|
||||
ancestor = path.resolve(ancestor);
|
||||
var relative;
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
relative = path.relative(path.resolve(arguments[i]), ancestor);
|
||||
if (relative === '' || /\w+/.test(relative)) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Test to see if a filepath is the CWD.
|
||||
file.isPathCwd = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
try {
|
||||
return file.arePathsEquivalent(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Test to see if a filepath is contained within the CWD.
|
||||
file.isPathInCwd = function() {
|
||||
var filepath = path.join.apply(path, arguments);
|
||||
try {
|
||||
return file.doesPathContain(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// Nodejs libs.
|
||||
var path = require('path');
|
||||
|
||||
// Set column widths.
|
||||
var col1len = 0;
|
||||
exports.initCol1 = function(str) {
|
||||
col1len = Math.max(col1len, str.length);
|
||||
};
|
||||
exports.initWidths = function() {
|
||||
// Widths for options/tasks table output.
|
||||
exports.widths = [1, col1len, 2, 76 - col1len];
|
||||
};
|
||||
|
||||
// Render an array in table form.
|
||||
exports.table = function(arr) {
|
||||
arr.forEach(function(item) {
|
||||
grunt.log.writetableln(exports.widths, ['', grunt.util._.pad(item[0], col1len), '', item[1]]);
|
||||
});
|
||||
};
|
||||
|
||||
// Methods to run, in-order.
|
||||
exports.queue = [
|
||||
'initOptions',
|
||||
'initTasks',
|
||||
'initWidths',
|
||||
'header',
|
||||
'usage',
|
||||
'options',
|
||||
'optionsFooter',
|
||||
'tasks',
|
||||
'footer',
|
||||
];
|
||||
|
||||
// Actually display stuff.
|
||||
exports.display = function() {
|
||||
exports.queue.forEach(function(name) { exports[name](); });
|
||||
};
|
||||
|
||||
|
||||
// Header.
|
||||
exports.header = function() {
|
||||
grunt.log.writeln('Grunt: The JavaScript Task Runner (v' + grunt.version + ')');
|
||||
};
|
||||
|
||||
// Usage info.
|
||||
exports.usage = function() {
|
||||
grunt.log.header('Usage');
|
||||
grunt.log.writeln(' ' + path.basename(process.argv[1]) + ' [options] [task [task ...]]');
|
||||
};
|
||||
|
||||
// Options.
|
||||
exports.initOptions = function() {
|
||||
// Build 2-column array for table view.
|
||||
exports._options = Object.keys(grunt.cli.optlist).map(function(long) {
|
||||
var o = grunt.cli.optlist[long];
|
||||
var col1 = '--' + (o.negate ? 'no-' : '') + long + (o.short ? ', -' + o.short : '');
|
||||
exports.initCol1(col1);
|
||||
return [col1, o.info];
|
||||
});
|
||||
};
|
||||
|
||||
exports.options = function() {
|
||||
grunt.log.header('Options');
|
||||
exports.table(exports._options);
|
||||
};
|
||||
|
||||
exports.optionsFooter = function() {
|
||||
grunt.log.writeln().writelns(
|
||||
'Options marked with * have methods exposed via the grunt API and should ' +
|
||||
'instead be specified inside the Gruntfile wherever possible.'
|
||||
);
|
||||
};
|
||||
|
||||
// Tasks.
|
||||
exports.initTasks = function() {
|
||||
// Initialize task system so that the tasks can be listed.
|
||||
grunt.task.init([], {help: true});
|
||||
|
||||
// Build object of tasks by info (where they were loaded from).
|
||||
exports._tasks = [];
|
||||
Object.keys(grunt.task._tasks).forEach(function(name) {
|
||||
exports.initCol1(name);
|
||||
var task = grunt.task._tasks[name];
|
||||
exports._tasks.push(task);
|
||||
});
|
||||
};
|
||||
|
||||
exports.tasks = function() {
|
||||
grunt.log.header('Available tasks');
|
||||
if (exports._tasks.length === 0) {
|
||||
grunt.log.writeln('(no tasks found)');
|
||||
} else {
|
||||
exports.table(exports._tasks.map(function(task) {
|
||||
var info = task.info;
|
||||
if (task.multi) { info += ' *'; }
|
||||
return [task.name, info];
|
||||
}));
|
||||
|
||||
grunt.log.writeln().writelns(
|
||||
'Tasks run in the order specified. Arguments may be passed to tasks that ' +
|
||||
'accept them by using colons, like "lint:files". Tasks marked with * are ' +
|
||||
'"multi tasks" and will iterate over all sub-targets if no argument is ' +
|
||||
'specified.'
|
||||
);
|
||||
}
|
||||
|
||||
grunt.log.writeln().writelns(
|
||||
'The list of available tasks may change based on tasks directories or ' +
|
||||
'grunt plugins specified in the Gruntfile or via command-line options.'
|
||||
);
|
||||
};
|
||||
|
||||
// Footer.
|
||||
exports.footer = function() {
|
||||
grunt.log.writeln().writeln('For more information, see http://gruntjs.com/');
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// The actual option data.
|
||||
var data = {};
|
||||
|
||||
// Get or set an option value.
|
||||
var option = module.exports = function(key, value) {
|
||||
var no = key.match(/^no-(.+)$/);
|
||||
if (arguments.length === 2) {
|
||||
return (data[key] = value);
|
||||
} else if (no) {
|
||||
return data[no[1]] === false;
|
||||
} else {
|
||||
return data[key];
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize option data.
|
||||
option.init = function(obj) {
|
||||
return (data = obj || {});
|
||||
};
|
||||
|
||||
// List of options as flags.
|
||||
option.flags = function() {
|
||||
return Object.keys(data).filter(function(key) {
|
||||
// Don't display empty arrays.
|
||||
return !(Array.isArray(data[key]) && data[key].length === 0);
|
||||
}).map(function(key) {
|
||||
var val = data[key];
|
||||
return '--' + (val === false ? 'no-' : '') + key +
|
||||
(typeof val === 'boolean' ? '' : '=' + val);
|
||||
});
|
||||
};
|
||||
+458
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// Nodejs libs.
|
||||
var path = require('path');
|
||||
|
||||
// Extend generic "task" util lib.
|
||||
var parent = grunt.util.task.create();
|
||||
|
||||
// The module to be exported.
|
||||
var task = module.exports = Object.create(parent);
|
||||
|
||||
// A temporary registry of tasks and metadata.
|
||||
var registry = {tasks: [], untasks: [], meta: {}};
|
||||
|
||||
// The last specified tasks message.
|
||||
var lastInfo;
|
||||
|
||||
// Number of levels of recursion when loading tasks in collections.
|
||||
var loadTaskDepth = 0;
|
||||
|
||||
// Keep track of the number of log.error() calls.
|
||||
var errorcount;
|
||||
|
||||
// Override built-in registerTask.
|
||||
task.registerTask = function(name) {
|
||||
// Add task to registry.
|
||||
registry.tasks.push(name);
|
||||
// Register task.
|
||||
parent.registerTask.apply(task, arguments);
|
||||
// This task, now that it's been registered.
|
||||
var thisTask = task._tasks[name];
|
||||
// Metadata about the current task.
|
||||
thisTask.meta = grunt.util._.clone(registry.meta);
|
||||
// Override task function.
|
||||
var _fn = thisTask.fn;
|
||||
thisTask.fn = function(arg) {
|
||||
// Guaranteed to always be the actual task name.
|
||||
var name = thisTask.name;
|
||||
// Initialize the errorcount for this task.
|
||||
errorcount = grunt.fail.errorcount;
|
||||
// Return the number of errors logged during this task.
|
||||
Object.defineProperty(this, 'errorCount', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return grunt.fail.errorcount - errorcount;
|
||||
}
|
||||
});
|
||||
// Expose task.requires on `this`.
|
||||
this.requires = task.requires.bind(task);
|
||||
// Expose config.requires on `this`.
|
||||
this.requiresConfig = grunt.config.requires;
|
||||
// Return an options object with the specified defaults overwritten by task-
|
||||
// specific overrides, via the "options" property.
|
||||
this.options = function() {
|
||||
var args = [{}].concat(grunt.util.toArray(arguments)).concat([
|
||||
grunt.config([name, 'options'])
|
||||
]);
|
||||
var options = grunt.util._.extend.apply(null, args);
|
||||
grunt.verbose.writeflags(options, 'Options');
|
||||
return options;
|
||||
};
|
||||
// If this task was an alias or a multi task called without a target,
|
||||
// only log if in verbose mode.
|
||||
var logger = _fn.alias || (thisTask.multi && (!arg || arg === '*')) ? 'verbose' : 'log';
|
||||
// Actually log.
|
||||
grunt[logger].header('Running "' + this.nameArgs + '"' +
|
||||
(this.name !== this.nameArgs ? ' (' + this.name + ')' : '') + ' task');
|
||||
// If --debug was specified, log the path to this task's source file.
|
||||
grunt[logger].debug('Task source: ' + thisTask.meta.filepath);
|
||||
// Actually run the task.
|
||||
return _fn.apply(this, arguments);
|
||||
};
|
||||
return task;
|
||||
};
|
||||
|
||||
// Multi task targets can't start with _ or be a reserved property (options).
|
||||
function isValidMultiTaskTarget(target) {
|
||||
return !/^_|^options$/.test(target);
|
||||
}
|
||||
|
||||
// Normalize multi task files.
|
||||
task.normalizeMultiTaskFiles = function(data, target) {
|
||||
var prop, obj;
|
||||
var files = [];
|
||||
if (grunt.util.kindOf(data) === 'object') {
|
||||
if ('src' in data || 'dest' in data) {
|
||||
obj = {};
|
||||
for (prop in data) {
|
||||
if (prop !== 'options') {
|
||||
obj[prop] = data[prop];
|
||||
}
|
||||
}
|
||||
files.push(obj);
|
||||
} else if (grunt.util.kindOf(data.files) === 'object') {
|
||||
for (prop in data.files) {
|
||||
files.push({src: data.files[prop], dest: grunt.config.process(prop)});
|
||||
}
|
||||
} else if (Array.isArray(data.files)) {
|
||||
grunt.util._.flatten(data.files).forEach(function(obj) {
|
||||
var prop;
|
||||
if ('src' in obj || 'dest' in obj) {
|
||||
files.push(obj);
|
||||
} else {
|
||||
for (prop in obj) {
|
||||
files.push({src: obj[prop], dest: grunt.config.process(prop)});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
files.push({src: data, dest: grunt.config.process(target)});
|
||||
}
|
||||
|
||||
// If no src/dest or files were specified, return an empty files array.
|
||||
if (files.length === 0) {
|
||||
grunt.verbose.writeln('File: ' + '[no files]'.yellow);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Process all normalized file objects.
|
||||
files = grunt.util._(files).chain().forEach(function(obj) {
|
||||
if (!('src' in obj) || !obj.src) { return; }
|
||||
// Normalize .src properties to flattened array.
|
||||
if (Array.isArray(obj.src)) {
|
||||
obj.src = grunt.util._.flatten(obj.src);
|
||||
} else {
|
||||
obj.src = [obj.src];
|
||||
}
|
||||
}).map(function(obj) {
|
||||
// Build options object, removing unwanted properties.
|
||||
var expandOptions = grunt.util._.extend({}, obj);
|
||||
delete expandOptions.src;
|
||||
delete expandOptions.dest;
|
||||
|
||||
// Expand file mappings.
|
||||
if (obj.expand) {
|
||||
return grunt.file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) {
|
||||
// Copy obj properties to result.
|
||||
var result = grunt.util._.extend({}, obj);
|
||||
// Make a clone of the orig obj available.
|
||||
result.orig = grunt.util._.extend({}, obj);
|
||||
// Set .src and .dest, processing both as templates.
|
||||
result.src = grunt.config.process(mapObj.src);
|
||||
result.dest = grunt.config.process(mapObj.dest);
|
||||
// Remove unwanted properties.
|
||||
['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) {
|
||||
delete result[prop];
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// Copy obj properties to result, adding an .orig property.
|
||||
var result = grunt.util._.extend({}, obj);
|
||||
// Make a clone of the orig obj available.
|
||||
result.orig = grunt.util._.extend({}, obj);
|
||||
|
||||
if ('src' in result) {
|
||||
// Expose an expand-on-demand getter method as .src.
|
||||
Object.defineProperty(result, 'src', {
|
||||
enumerable: true,
|
||||
get: function fn() {
|
||||
var src;
|
||||
if (!('result' in fn)) {
|
||||
src = obj.src;
|
||||
// If src is an array, flatten it. Otherwise, make it into an array.
|
||||
src = Array.isArray(src) ? grunt.util._.flatten(src) : [src];
|
||||
// Expand src files, memoizing result.
|
||||
fn.result = grunt.file.expand(expandOptions, src);
|
||||
}
|
||||
return fn.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ('dest' in result) {
|
||||
result.dest = obj.dest;
|
||||
}
|
||||
|
||||
return result;
|
||||
}).flatten().value();
|
||||
|
||||
// Log this.file src and dest properties when --verbose is specified.
|
||||
if (grunt.option('verbose')) {
|
||||
files.forEach(function(obj) {
|
||||
var output = [];
|
||||
if ('src' in obj) {
|
||||
output.push(obj.src.length > 0 ? grunt.log.wordlist(obj.src) : '[no src]'.yellow);
|
||||
}
|
||||
if ('dest' in obj) {
|
||||
output.push('-> ' + (obj.dest ? String(obj.dest).cyan : '[no dest]'.yellow));
|
||||
}
|
||||
if (output.length > 0) {
|
||||
grunt.verbose.writeln('Files: ' + output.join(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
// This is the most common "multi task" pattern.
|
||||
task.registerMultiTask = function(name, info, fn) {
|
||||
// If optional "info" string is omitted, shuffle arguments a bit.
|
||||
if (fn == null) {
|
||||
fn = info;
|
||||
info = 'Custom multi task.';
|
||||
}
|
||||
// Store a reference to the task object, in case the task gets renamed.
|
||||
var thisTask;
|
||||
task.registerTask(name, info, function(target) {
|
||||
// Guaranteed to always be the actual task name.
|
||||
var name = thisTask.name;
|
||||
// Arguments (sans target) as an array.
|
||||
this.args = grunt.util.toArray(arguments).slice(1);
|
||||
// If a target wasn't specified, run this task once for each target.
|
||||
if (!target || target === '*') {
|
||||
return task.runAllTargets(name, this.args);
|
||||
} else if (!isValidMultiTaskTarget(target)) {
|
||||
throw new Error('Invalid target "' + target + '" specified.');
|
||||
}
|
||||
// Fail if any required config properties have been omitted.
|
||||
this.requiresConfig([name, target]);
|
||||
// Return an options object with the specified defaults overwritten by task-
|
||||
// and/or target-specific overrides, via the "options" property.
|
||||
this.options = function() {
|
||||
var targetObj = grunt.config([name, target]);
|
||||
var args = [{}].concat(grunt.util.toArray(arguments)).concat([
|
||||
grunt.config([name, 'options']),
|
||||
grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {}
|
||||
]);
|
||||
var options = grunt.util._.extend.apply(null, args);
|
||||
grunt.verbose.writeflags(options, 'Options');
|
||||
return options;
|
||||
};
|
||||
// Expose the current target.
|
||||
this.target = target;
|
||||
// Recreate flags object so that the target isn't set as a flag.
|
||||
this.flags = {};
|
||||
this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
|
||||
// Expose data on `this` (as well as task.current).
|
||||
this.data = grunt.config([name, target]);
|
||||
// Expose normalized files object.
|
||||
this.files = task.normalizeMultiTaskFiles(this.data, target);
|
||||
// Expose normalized, flattened, uniqued array of src files.
|
||||
Object.defineProperty(this, 'filesSrc', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value();
|
||||
}.bind(this)
|
||||
});
|
||||
// Call original task function, passing in the target and any other args.
|
||||
return fn.apply(this, this.args);
|
||||
});
|
||||
|
||||
thisTask = task._tasks[name];
|
||||
thisTask.multi = true;
|
||||
};
|
||||
|
||||
// Init tasks don't require properties in config, and as such will preempt
|
||||
// config loading errors.
|
||||
task.registerInitTask = function(name, info, fn) {
|
||||
task.registerTask(name, info, fn);
|
||||
task._tasks[name].init = true;
|
||||
};
|
||||
|
||||
// Override built-in renameTask to use the registry.
|
||||
task.renameTask = function(oldname, newname) {
|
||||
var result;
|
||||
try {
|
||||
// Actually rename task.
|
||||
result = parent.renameTask.apply(task, arguments);
|
||||
// Add and remove task.
|
||||
registry.untasks.push(oldname);
|
||||
registry.tasks.push(newname);
|
||||
// Return result.
|
||||
return result;
|
||||
} catch(e) {
|
||||
grunt.log.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// If a property wasn't passed, run all task targets in turn.
|
||||
task.runAllTargets = function(taskname, args) {
|
||||
// Get an array of sub-property keys under the given config object.
|
||||
var targets = Object.keys(grunt.config.getRaw(taskname) || {});
|
||||
// Fail if there are no actual properties to iterate over.
|
||||
if (targets.length === 0) {
|
||||
grunt.log.error('No "' + taskname + '" targets found.');
|
||||
return false;
|
||||
}
|
||||
// Iterate over all valid target properties, running a task for each.
|
||||
targets.filter(isValidMultiTaskTarget).forEach(function(target) {
|
||||
// Be sure to pass in any additionally specified args.
|
||||
task.run([taskname, target].concat(args || []).join(':'));
|
||||
});
|
||||
};
|
||||
|
||||
// Load tasks and handlers from a given tasks file.
|
||||
var loadTaskStack = [];
|
||||
function loadTask(filepath) {
|
||||
// In case this was called recursively, save registry for later.
|
||||
loadTaskStack.push(registry);
|
||||
// Reset registry.
|
||||
registry = {tasks: [], untasks: [], meta: {info: lastInfo, filepath: filepath}};
|
||||
var filename = path.basename(filepath);
|
||||
var msg = 'Loading "' + filename + '" tasks...';
|
||||
var regCount = 0;
|
||||
var fn;
|
||||
try {
|
||||
// Load taskfile.
|
||||
fn = require(path.resolve(filepath));
|
||||
if (typeof fn === 'function') {
|
||||
fn.call(grunt, grunt);
|
||||
}
|
||||
grunt.verbose.write(msg).ok();
|
||||
// Log registered/renamed/unregistered tasks.
|
||||
['un', ''].forEach(function(prefix) {
|
||||
var list = grunt.util._.chain(registry[prefix + 'tasks']).uniq().sort().value();
|
||||
if (list.length > 0) {
|
||||
regCount++;
|
||||
grunt.verbose.writeln((prefix ? '- ' : '+ ') + grunt.log.wordlist(list));
|
||||
}
|
||||
});
|
||||
if (regCount === 0) {
|
||||
grunt.verbose.warn('No tasks were registered or unregistered.');
|
||||
}
|
||||
} catch(e) {
|
||||
// Something went wrong.
|
||||
grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
|
||||
}
|
||||
// Restore registry.
|
||||
registry = loadTaskStack.pop() || {};
|
||||
}
|
||||
|
||||
// Log a message when loading tasks.
|
||||
function loadTasksMessage(info) {
|
||||
// Only keep track of names of top-level loaded tasks and collections,
|
||||
// not sub-tasks.
|
||||
if (loadTaskDepth === 0) { lastInfo = info; }
|
||||
grunt.verbose.subhead('Registering ' + info + ' tasks.');
|
||||
}
|
||||
|
||||
// Load tasks and handlers from a given directory.
|
||||
function loadTasks(tasksdir) {
|
||||
try {
|
||||
var files = grunt.file.glob.sync('*.{js,coffee}', {cwd: tasksdir, maxDepth: 1});
|
||||
// Load tasks from files.
|
||||
files.forEach(function(filename) {
|
||||
loadTask(path.join(tasksdir, filename));
|
||||
});
|
||||
} catch(e) {
|
||||
grunt.log.verbose.error(e.stack).or.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load tasks and handlers from a given directory.
|
||||
task.loadTasks = function(tasksdir) {
|
||||
loadTasksMessage('"' + tasksdir + '"');
|
||||
if (grunt.file.exists(tasksdir)) {
|
||||
loadTasks(tasksdir);
|
||||
} else {
|
||||
grunt.log.error('Tasks directory "' + tasksdir + '" not found.');
|
||||
}
|
||||
};
|
||||
|
||||
// Load tasks and handlers from a given locally-installed Npm module (installed
|
||||
// relative to the base dir).
|
||||
task.loadNpmTasks = function(name) {
|
||||
loadTasksMessage('"' + name + '" local Npm module');
|
||||
var root = path.resolve('node_modules');
|
||||
var pkgfile = path.join(root, name, 'package.json');
|
||||
var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};
|
||||
|
||||
// Process collection plugins.
|
||||
if (pkg.keywords && pkg.keywords.indexOf('gruntcollection') !== -1) {
|
||||
loadTaskDepth++;
|
||||
Object.keys(pkg.dependencies).forEach(function(depName) {
|
||||
// Npm sometimes pulls dependencies out if they're shared, so find
|
||||
// upwards if not found locally.
|
||||
var filepath = grunt.file.findup('node_modules/' + depName, {
|
||||
cwd: path.resolve('node_modules', name),
|
||||
nocase: true
|
||||
});
|
||||
if (filepath) {
|
||||
// Load this task plugin recursively.
|
||||
task.loadNpmTasks(path.relative(root, filepath));
|
||||
}
|
||||
});
|
||||
loadTaskDepth--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Process task plugins.
|
||||
var tasksdir = path.join(root, name, 'tasks');
|
||||
if (grunt.file.exists(tasksdir)) {
|
||||
loadTasks(tasksdir);
|
||||
} else {
|
||||
grunt.log.error('Local Npm module "' + name + '" not found. Is it installed?');
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize tasks.
|
||||
task.init = function(tasks, options) {
|
||||
if (!options) { options = {}; }
|
||||
|
||||
// Were only init tasks specified?
|
||||
var allInit = tasks.length > 0 && tasks.every(function(name) {
|
||||
var obj = task._taskPlusArgs(name).task;
|
||||
return obj && obj.init;
|
||||
});
|
||||
|
||||
// Get any local Gruntfile or tasks that might exist. Use --gruntfile override
|
||||
// if specified, otherwise search the current directory or any parent.
|
||||
var gruntfile = allInit ? null : grunt.option('gruntfile') ||
|
||||
grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
|
||||
|
||||
var msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
|
||||
if (gruntfile && grunt.file.exists(gruntfile)) {
|
||||
grunt.verbose.writeln().write(msg).ok();
|
||||
// Change working directory so that all paths are relative to the
|
||||
// Gruntfile's location (or the --base option, if specified).
|
||||
process.chdir(grunt.option('base') || path.dirname(gruntfile));
|
||||
// Load local tasks, if the file exists.
|
||||
loadTasksMessage('Gruntfile');
|
||||
loadTask(gruntfile);
|
||||
} else if (options.help || allInit) {
|
||||
// Don't complain about missing Gruntfile.
|
||||
} else if (grunt.option('gruntfile')) {
|
||||
// If --config override was specified and it doesn't exist, complain.
|
||||
grunt.log.writeln().write(msg).error();
|
||||
grunt.fatal('Unable to find "' + gruntfile + '" Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
|
||||
} else if (!grunt.option('help')) {
|
||||
grunt.verbose.writeln().write(msg).error();
|
||||
grunt.log.writelns(
|
||||
'A valid Gruntfile could not be found. Please see the getting ' +
|
||||
'started guide for more information on how to configure grunt: ' +
|
||||
'http://gruntjs.com/getting-started'
|
||||
);
|
||||
grunt.fatal('Unable to find Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
|
||||
}
|
||||
|
||||
// Load all user-specified --npm tasks.
|
||||
(grunt.option('npm') || []).forEach(task.loadNpmTasks);
|
||||
// Load all user-specified --tasks.
|
||||
(grunt.option('tasks') || []).forEach(task.loadTasks);
|
||||
};
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var grunt = require('../grunt');
|
||||
|
||||
// The module to be exported.
|
||||
var template = module.exports = {};
|
||||
|
||||
// External libs.
|
||||
template.date = require('dateformat');
|
||||
|
||||
// Format today's date.
|
||||
template.today = function(format) {
|
||||
return template.date(new Date(), format);
|
||||
};
|
||||
|
||||
// Template delimiters.
|
||||
var allDelimiters = {};
|
||||
|
||||
// Initialize template delimiters.
|
||||
template.addDelimiters = function(name, opener, closer) {
|
||||
var delimiters = allDelimiters[name] = {};
|
||||
// Used by grunt.
|
||||
delimiters.opener = opener;
|
||||
delimiters.closer = closer;
|
||||
// Generate RegExp patterns dynamically.
|
||||
var a = delimiters.opener.replace(/(.)/g, '\\$1');
|
||||
var b = '([\\s\\S]+?)' + delimiters.closer.replace(/(.)/g, '\\$1');
|
||||
// Used by Lo-Dash.
|
||||
delimiters.lodash = {
|
||||
evaluate: new RegExp(a + b, 'g'),
|
||||
interpolate: new RegExp(a + '=' + b, 'g'),
|
||||
escape: new RegExp(a + '-' + b, 'g')
|
||||
};
|
||||
};
|
||||
|
||||
// The underscore default template syntax should be a pretty sane default for
|
||||
// the config system.
|
||||
template.addDelimiters('config', '<%', '%>');
|
||||
|
||||
// Set Lo-Dash template delimiters.
|
||||
template.setDelimiters = function(name) {
|
||||
// Get the appropriate delimiters.
|
||||
var delimiters = allDelimiters[name in allDelimiters ? name : 'config'];
|
||||
// Tell Lo-Dash which delimiters to use.
|
||||
grunt.util._.templateSettings = delimiters.lodash;
|
||||
// Return the delimiters.
|
||||
return delimiters;
|
||||
};
|
||||
|
||||
// Process template + data with Lo-Dash.
|
||||
template.process = function(tmpl, options) {
|
||||
if (!options) { options = {}; }
|
||||
// Set delimiters, and get a opening match character.
|
||||
var delimiters = template.setDelimiters(options.delimiters);
|
||||
// Clone data, initializing to config data or empty object if omitted.
|
||||
var data = Object.create(options.data || grunt.config.data || {});
|
||||
// Expose grunt so that grunt utilities can be accessed, but only if it
|
||||
// doesn't conflict with an existing .grunt property.
|
||||
if (!('grunt' in data)) { data.grunt = grunt; }
|
||||
// Keep track of last change.
|
||||
var last = tmpl;
|
||||
try {
|
||||
// As long as tmpl contains template tags, render it and get the result,
|
||||
// otherwise just use the template string.
|
||||
while (tmpl.indexOf(delimiters.opener) >= 0) {
|
||||
tmpl = grunt.util._.template(tmpl, data);
|
||||
// Abort if template didn't change - nothing left to process!
|
||||
if (tmpl === last) { break; }
|
||||
last = tmpl;
|
||||
}
|
||||
} catch (e) {
|
||||
// In upgrading to Lo-Dash (or Underscore.js 1.3.3), \n and \r in template
|
||||
// tags now causes an exception to be thrown. Warn the user why this is
|
||||
// happening. https://github.com/documentcloud/underscore/issues/553
|
||||
if (String(e) === 'SyntaxError: Unexpected token ILLEGAL' && /\n|\r/.test(tmpl)) {
|
||||
grunt.log.errorlns('A special character was detected in this template. ' +
|
||||
'Inside template tags, the \\n and \\r special characters must be ' +
|
||||
'escaped as \\\\n and \\\\r. (grunt 0.4.0+)');
|
||||
}
|
||||
// Slightly better error message.
|
||||
e.message = 'An error occurred while processing a template (' + e.message + ').';
|
||||
grunt.warn(e, grunt.fail.code.TEMPLATE_ERROR);
|
||||
}
|
||||
// Normalize linefeeds and return.
|
||||
return grunt.util.normalizelf(tmpl);
|
||||
};
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* grunt
|
||||
* http://gruntjs.com/
|
||||
*
|
||||
* Copyright (c) 2014 "Cowboy" Ben Alman
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Construct-o-rama.
|
||||
function Task() {
|
||||
// Information about the currently-running task.
|
||||
this.current = {};
|
||||
// Tasks.
|
||||
this._tasks = {};
|
||||
// Task queue.
|
||||
this._queue = [];
|
||||
// Queue placeholder (for dealing with nested tasks).
|
||||
this._placeholder = {placeholder: true};
|
||||
// Queue marker (for clearing the queue programmatically).
|
||||
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();
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
// 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 = null;
|
||||
}
|
||||
// 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') + '.';
|
||||
}
|
||||
} else if (!info) {
|
||||
info = 'Custom task.';
|
||||
}
|
||||
// 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;
|
||||
};
|
||||
|
||||
// Has the specified task been registered?
|
||||
Task.prototype.exists = function(name) {
|
||||
return name in this._tasks;
|
||||
};
|
||||
|
||||
// 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) {
|
||||
if (!this._tasks[oldname]) {
|
||||
throw new Error('Cannot rename missing "' + oldname + '" task.');
|
||||
}
|
||||
// Rename task.
|
||||
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']
|
||||
Task.prototype.parseArgs = function(args) {
|
||||
// Return the first argument if it's an array, otherwise return an array
|
||||
// of all arguments.
|
||||
return Array.isArray(args[0]) ? args[0] : [].slice.call(args);
|
||||
};
|
||||
|
||||
// Split a colon-delimited string into an array, unescaping (but not
|
||||
// splitting on) any \: escaped colons.
|
||||
Task.prototype.splitArgs = function(str) {
|
||||
if (!str) { return []; }
|
||||
// Store placeholder for \\ followed by \:
|
||||
str = str.replace(/\\\\/g, '\uFFFF').replace(/\\:/g, '\uFFFE');
|
||||
// Split on :
|
||||
return str.split(':').map(function(s) {
|
||||
// Restore place-held : followed by \\
|
||||
return s.replace(/\uFFFE/g, ':').replace(/\uFFFF/g, '\\');
|
||||
});
|
||||
};
|
||||
|
||||
// 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) {
|
||||
// Get task name / argument parts.
|
||||
var parts = this.splitArgs(name);
|
||||
// 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 Error('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 programmatically.
|
||||
Task.prototype.mark = function() {
|
||||
this._push(this._marker);
|
||||
// Make chainable!
|
||||
return this;
|
||||
};
|
||||
|
||||
// Run a task function, handling this.async / return value.
|
||||
Task.prototype.runTaskFn = function(context, fn, done, asyncDone) {
|
||||
// Async flag.
|
||||
var async = false;
|
||||
|
||||
// Update the internal status object and run the next task.
|
||||
var complete = function(success) {
|
||||
var err = null;
|
||||
if (success === false) {
|
||||
// Since false was passed, the task failed generically.
|
||||
err = new Error('Task "' + context.nameArgs + '" failed.');
|
||||
} else if (success instanceof Error || {}.toString.call(success) === '[object Error]') {
|
||||
// An error object was passed, so the task failed specifically.
|
||||
err = success;
|
||||
success = false;
|
||||
} else {
|
||||
// The task succeeded.
|
||||
success = true;
|
||||
}
|
||||
// The task has ended, reset the current task object.
|
||||
this.current = {};
|
||||
// A task has "failed" only if it returns false (async) or if the
|
||||
// function returned by .async is passed false.
|
||||
this._success[context.nameArgs] = success;
|
||||
// If task failed, call error handler.
|
||||
if (!success && this._options.error) {
|
||||
this._options.error.call({name: context.name, nameArgs: context.nameArgs}, err);
|
||||
}
|
||||
// only call done async if explicitly requested to
|
||||
// see: https://github.com/gruntjs/grunt/pull/1026
|
||||
if (asyncDone) {
|
||||
process.nextTick(function () {
|
||||
done(err, success);
|
||||
});
|
||||
} else {
|
||||
done(err, success);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// When called, sets the async flag and returns a function that can
|
||||
// be used to continue processing the queue.
|
||||
context.async = function() {
|
||||
async = true;
|
||||
// The returned function should execute asynchronously in case
|
||||
// someone tries to do this.async()(); inside a task (WTF).
|
||||
return function(success) {
|
||||
setTimeout(function() { complete(success); }, 1);
|
||||
};
|
||||
};
|
||||
|
||||
// Expose some information about the currently-running task.
|
||||
this.current = context;
|
||||
|
||||
try {
|
||||
// Get the current task and run it, setting `this` inside the task
|
||||
// function to be something useful.
|
||||
var success = fn.call(context);
|
||||
// If the async flag wasn't set, process the next task in the queue.
|
||||
if (!async) {
|
||||
complete(success);
|
||||
}
|
||||
} catch (err) {
|
||||
complete(err);
|
||||
}
|
||||
};
|
||||
|
||||
// Begin task queue processing. Ie. run all tasks.
|
||||
Task.prototype.start = function(opts) {
|
||||
if (!opts) {
|
||||
opts = {};
|
||||
}
|
||||
// Abort if already running.
|
||||
if (this._running) { return false; }
|
||||
// Actually process the next task.
|
||||
var nextTask = function() {
|
||||
// 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);
|
||||
|
||||
// Expose some information about the currently-running task.
|
||||
var context = {
|
||||
// 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
|
||||
};
|
||||
|
||||
// Actually run the task function (handling this.async, etc)
|
||||
this.runTaskFn(context, function() {
|
||||
return thing.task.fn.apply(this, this.args);
|
||||
}, nextTask, !!opts.asyncDone);
|
||||
|
||||
}.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 Error('Required task "' + name +
|
||||
'" ' + (success === false ? 'failed' : 'must be run first') + '.');
|
||||
}
|
||||
}.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));
|
||||
Reference in New Issue
Block a user