Initial commit (a clone of the jsmachines project as of v1.15.3)

This commit is contained in:
jeffpar 2014-09-27 14:52:57 -07:00
commit a5e3e6a59d
714 changed files with 130602 additions and 0 deletions

View file

@ -0,0 +1,13 @@
{
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"boss": true,
"eqnull": true,
"node": true
}

View file

@ -0,0 +1,143 @@
/*
* grunt-prepjs
* https://github.com/jeffpar/jsmachines
*
* Copyright (c) 2014 jeffpar
* Licensed under the MIT license.
*
* Genesis:
*
* sudo npm install -g grunt-init
* git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
* cd ~/Sites/pcjs/my_modules
* mkdir -p grunts/prepjs
* cd grunts/prepjs
* grunt-init gruntplugin
* npm install
*
* This file was generated at the "grunt-init gruntplugin" stage. Here's what that process looked like:
*
* Running "init:gruntplugin" (init) task
* This task will create one or more files in the current directory, based on the
* environment and the answers to a few questions. Note that answering "?" to any
* question will show question-specific help and answering "none" to most questions
* will leave its value blank.
*
* "gruntplugin" template notes:
* For more information about Grunt plugin best practices, please see the docs at
* http://gruntjs.com/creating-plugins
*
* Please answer the following:
* [?] Project name (grunt-prep) grunt-prepjs
* [?] Description (The best Grunt plugin ever.) JS Preprocessor
* [?] Version (0.1.0)
* [?] Project git repository (git://github.com/jeffpar/jsmachines.git)
* [?] Project homepage (https://github.com/jeffpar/jsmachines)
* [?] Project issues tracker (https://github.com/jeffpar/jsmachines/issues)
* [?] Licenses (MIT)
* [?] Author name (jeffpar)
* [?] Author email (jeffpar@mac.com)
* [?] Author url (none)
* [?] What versions of grunt does it require? (~0.4.4)
* [?] What versions of node does it run on? (>= 0.8.0)
* [?] Do you need to make any changes to the above before continuing? (y/N)
*
* Writing .gitignore...OK
* Writing .jshintrc...OK
* Writing Gruntfile.js...OK
* Writing README.md...OK
* Writing tasks/prepjs.js...OK
* Writing test/expected/custom_options...OK
* Writing test/expected/default_options...OK
* Writing test/fixtures/123...OK
* Writing test/fixtures/testing...OK
* Writing test/prepjs_test.js...OK
* Writing LICENSE-MIT...OK
* Writing package.json...OK
*
* Initialized from template "gruntplugin".
* You should now install project dependencies with npm install. After that, you
* may execute project tasks with grunt. For more information about installing
* and configuring Grunt, please see the Getting Started guide:
*
* http://gruntjs.com/getting-started
*
* Done, without errors.
*
* The process was a bit sloppy about trailing commas, though. I've cleaned those up, thanks to PhpStorm.
*
* Online tutorials further recommended the following in my project's root:
*
* npm install my_modules/grunts/prepjs --save-dev
*
* However, all that does is make another copy of my "grunt-prepjs" module, inside the "node_modules" folders;
* I can avoid that by simply including the following in my root Gruntfile.js:
*
* grunt.loadTasks("my_modules/grunts/prepjs/tasks");
*/
'use strict';
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
jshint: {
all: [
'Gruntfile.js',
'tasks/*.js',
'<%= nodeunit.tests %>'
],
options: {
jshintrc: '.jshintrc'
}
},
// Before generating any new files, remove any previously-created files.
clean: {
tests: ['tmp']
},
// Configuration to be run (and then tested).
prepjs: {
default_options: {
options: {
},
files: {
'tmp/default_options': ['test/fixtures/testing', 'test/fixtures/123']
}
},
custom_options: {
options: {
separator: ': ',
punctuation: ' !!!'
},
files: {
'tmp/custom_options': ['test/fixtures/testing', 'test/fixtures/123']
}
}
},
// Unit tests.
nodeunit: {
tests: ['test/*_test.js']
}
});
// Actually load this plugin's task(s).
grunt.loadTasks('tasks');
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
// Whenever the "test" task is run, first clean the "tmp" dir, then run this
// plugin's task(s), then test the result.
grunt.registerTask('test', ['clean', 'prepjs', 'nodeunit']);
// By default, lint and run all tests.
grunt.registerTask('default', ['jshint', 'test']);
};

View file

@ -0,0 +1,22 @@
Copyright (c) 2014 jeffpar
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,89 @@
# grunt-prepjs
> JS Preprocessor
## Getting Started
This plugin requires Grunt `~0.4.4`
If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
```shell
npm install grunt-prepjs --save-dev
```
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
```js
grunt.loadNpmTasks('grunt-prepjs');
```
## The "prepjs" task
### Overview
In your project's Gruntfile, add a section named `prepjs` to the data object passed into `grunt.initConfig()`.
```js
grunt.initConfig({
prepjs: {
options: {
// Task-specific options go here.
},
your_target: {
// Target-specific file lists and/or options go here.
},
},
});
```
### Options
#### options.separator
Type: `String`
Default value: `', '`
A string value that is used to do something with whatever.
#### options.punctuation
Type: `String`
Default value: `'.'`
A string value that is used to do something else with whatever else.
### Usage Examples
#### Default Options
In this example, the default options are used to do something with whatever. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result would be `Testing, 1 2 3.`
```js
grunt.initConfig({
prepjs: {
options: {},
files: {
'dest/default_options': ['src/testing', 'src/123'],
},
},
});
```
#### Custom Options
In this example, custom options are used to do something else with whatever else. So if the `testing` file has the content `Testing` and the `123` file had the content `1 2 3`, the generated result in this case would be `Testing: 1 2 3 !!!`
```js
grunt.initConfig({
prepjs: {
options: {
separator: ': ',
punctuation: ' !!!',
},
files: {
'dest/default_options': ['src/testing', 'src/123'],
},
},
});
```
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
## Release History
_(Nothing yet)_

View file

@ -0,0 +1,41 @@
{
"name": "grunt-prepjs",
"description": "JS Preprocessor",
"version": "0.1.0",
"homepage": "https://github.com/jeffpar/jsmachines",
"author": {
"name": "jeffpar",
"email": "jeffpar@mac.com"
},
"repository": {
"type": "git",
"url": "git://github.com/jeffpar/jsmachines.git"
},
"bugs": {
"url": "https://github.com/jeffpar/jsmachines/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/jeffpar/jsmachines/blob/master/LICENSE-MIT"
}
],
"engines": {
"node": ">= 0.8.0"
},
"scripts": {
"test": "grunt test"
},
"devDependencies": {
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt": "~0.4.4"
},
"peerDependencies": {
"grunt": "~0.4.4"
},
"keywords": [
"gruntplugin"
]
}

View file

@ -0,0 +1,401 @@
/**
* @fileoverview Pre-process JavaScript file(s) with well-defined constants inlined
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
* @version 1.1
* Created 2014-Mar-22
*
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
*
* This file is part of C1Pjs, PCjs, and other related components written
* by Jeff Parsons and originally published at cpusim.org and jsmachines.net.
*
* C1Pjs and PCjs are free software: you can redistribute them and/or modify
* them under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* C1Pjs and PCjs are distributed in the hope that they will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with C1Pjs and PCjs. If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every source
* code file of every copy or modified version of this work, and to display
* that copyright notice on every screen that loads or runs any version
* of this software (see Computer.sCopyright).
*
* Some C1Pjs and PCjs files also attempt to load external resource files, such
* as character-image files and ROM files. Those external resource files are
* not considered part of the PCjs program for purposes of the GNU General Public
* License, and the author does not claim any copyright as to their contents.
*/
/*
* Options
* ---
* The 'includeObjectConstants' option allows replacement of constants defined
* within objects, instead of only global constants. Use with caution, because
* constants defined within an object scope may not be unique. This does attempt
* to catch any constant collisions and completely disable their replacement, but
* it's not foolproof.
*
* History
* ---
* Although we run all our code through Google's Closure Compiler, which does a
* great job of inlining not only variables but also code to improve performance,
* JavaScript doesn't have the notion of "constants", so we have to define all our
* constants as properties and simply trust that the Closure Compiler will inline
* them all. I'm not completely trusting, so I've created this script that allows
* us to verify the compiler produces substantially the same code whether or not
* we inline all our constants first.
*
* See "inline.php" for the original PHP version of this module.
*
* Conventions
* ---
* This script looks for global constant definitions of the form "Component.XXX = YYY;".
*
* The current approach requires all inlined constants to be defined as properties on
* the associated class constructor (ie, as "class constants"). Life might be simpler
* if the Closure Compiler honored "@const" references for properties, and who knows,
* perhaps the latest version does now; but on the other hand, having my own convention
* relieves me from having to annotate every single constant with a "@const" JSDoc tag.
*
* TODO: Update the C1Pjs sources to use class constants instead of "object constants",
* because in order for C1Pjs to benefit from constant inlining, we must reply on the
* 'includeObjectConstants' option (hack) to expand the contexts that constants may live
* in, which is inherently less safe. Moreover, that option prevents us from removing
* the original constant definitions, because constants like "this.PORT_CRA" could be used
* in other contexts that this script will NOT catch (eg, "controller.PORT_CRA").
*
* TODO: Think about adding another quick hack to this tool, to convert all:
*
* at-param {Debugger} dbg
* to:
* at-param {Component} dbg
*
* prior to compilation. The only reason I declared my "dbg" variables generically,
* as Component objects rather than Debugger objects, was to work around compilation
* errors in the non-Debugger builds.
*
* Implementation
* ---
* This script uses a very simplistic replacement approach that doesn't perform any
* parsing, tokenizing or other pre-processing of the source code, which would otherwise
* be required if we wanted to guarantee that all our replacements precisely mirrored what
* JavaScript actually replaces at run-time. For example, JavaScript allows any so-called
* constant to be redefined at any point, and we don't attempt to catch modifications.
*
* This is why it's important that we limit inlining to only those constants described
* above, and why such constants must never be altered by the code using them.
*
* Debugging
* ===
* Use the following command to debug this task (after making Chrome your default browser):
*
* node-debug $(which grunt) prepjs
*
* which required installing "node-inspector" first:
*
* sudo npm install -g node-inspector
*
* You may also want to enable the heapdump code below, which required installing "heapdump" first:
*
* npm install heapdump --save-dev
*
* TODO: Resolve once and for all the "process out of memory" error that occurs if we don't divide
* the src input into smaller chunks. See the WARNING below.
*/
'use strict';
// var heapdump = require("heapdump");
/**
* compareConstants(a, b)
*
* @param {Array} a
* @param {Array} b
* @returns {number}
*/
var compareConstants = function(a, b)
{
return b[0].length - a[0].length;
};
/**
* indexOfConstant(sConstant, aConstants)
*
* @param {string} sConstant
* @param {Array} aConstants
* @returns {number} index of aConstants entry, or -1 if not found
*/
var indexOfConstant = function(sConstant, aConstants)
{
for (var i = 0; i < aConstants.length; i++) {
if (sConstant == aConstants[i][0]) {
return i;
}
}
return -1;
};
/**
* findConstants(sInput)
*
* @param {string} sInput
* @param {Array} aConstants
* @param {boolean} fObjectConstants
* @param {boolean} fReplaceConstants
* @param {function} [fnLog]
* @returns {string} modified input
*/
var findConstants = function(sInput, aConstants, fObjectConstants, fReplaceConstants, fnLog) {
var sWarning = "";
var sConstDef = "[A-Z][A-Za-z0-9_]*";
if (fObjectConstants) {
sConstDef = "(?:" + sConstDef + "|this)";
}
var aConstDef;
var reConstDef = new RegExp("[ \t]*(" + sConstDef + "\\.[A-Z_][A-Z0-9_\\.]*)\\s*=\\s*(.*?)\\s*;[\t ]*(?://[^\n]*|)\n", "g");
while (aConstDef = reConstDef.exec(sInput)) {
var i;
var sFind = aConstDef[1];
var sReplace = aConstDef[2];
if (fReplaceConstants && !fObjectConstants) {
sInput = sInput.substr(0, aConstDef.index) + sInput.substr(aConstDef.index + aConstDef[0].length);
reConstDef.lastIndex -= aConstDef[0].length;
}
if ((i = indexOfConstant(sFind, aConstants)) >= 0) {
sWarning += "/*\n * warning: multiple definitions for '" + sFind + "' (" + sReplace + ")\n */\n";
aConstants[i][2] = -1; // set a negative replacement count to disable this constant definition
continue;
}
/*
* If the replacement string is entirely quoted, or parenthesized, or a single constant, then we can leave
* the replacement string as-is; otherwise, let's wrap it with parentheses (we could probably wrap everything
* with parentheses, but I like to avoid doing that whenever it's completely unnecessary).
*/
if (!sReplace.match(/^["'\(].*["'\)]$/) && sReplace.match(/[^A-Za-z0-9\.]/)) {
sReplace = "(" + sReplace + ")";
}
if (fnLog) {
fnLog("found '" + sFind + "' => '" + sReplace + "'");
/*
if (sFind == "Video.CRT.CURSOR_END") {
fnLog("here's where we usually run of memory (" + process.cwd() + ")");
heapdump.writeSnapshot();
}
*/
}
aConstants.push([sFind, sReplace, 0]);
}
if (sWarning) sInput = sWarning + sInput;
return sInput;
};
/**
* replaceConstants(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
*
* @param {string} sInput
* @param {Array} aConstants
* @param {boolean} fObjectConstants
* @param {boolean} [fInConstant]
* @param {function} [fnLog]
* @returns {string} modified input
*/
var replaceConstants = function(sInput, aConstants, fObjectConstants, fInConstant, fnLog)
{
do {
var cReplacements = 0;
for (var i = 0; i < aConstants.length; i++) {
if (aConstants[i][2] < 0) continue; // skip any replacement for which we recorded multiple definitions (ie, negative replacement count)
var sFind = aConstants[i][0];
var sReplace = aConstants[i][1];
var cchFind = sFind.length;
var cchReplace = sReplace.length;
var iNext = 0;
while ((iNext = sInput.indexOf(sFind, iNext)) >= 0) {
if (fObjectConstants) {
/*
* As discussed earlier, the 'includeObjectConstants' option precludes removing any constant definitions,
* so we must additionally ensure that we don't inadvertently perform replacements on those definitions.
*/
if (sInput.substr(iNext, 1024).match(/([A-Za-z][A-Za-z0-9_]*\.[A-Z_][A-Z0-9_\.]*)\s*=\s*(.*?)\s*;[\t ]*(?:\/\/[^\n]*|)\n/)) {
iNext += cchFind;
continue;
}
}
if (fnLog) {
fnLog("replaced '" + sFind + "' with '" + sReplace + "'");
}
sInput = sInput.substr(0, iNext) + sReplace + sInput.substr(iNext + cchFind);
aConstants[i][2]++;
cReplacements++;
iNext += cchReplace;
}
/*
* If we've just done any replacements within another constant, then start the process over again
* with the longest constant, to ensure we don't perform any partial replacements.
*/
if (fInConstant && cReplacements) break;
}
} while (cReplacements);
return sInput;
};
module.exports = function(grunt) {
// Please see the Grunt documentation for more information regarding task
// creation: http://gruntjs.com/creating-tasks
grunt.registerMultiTask('prepjs', 'JS Preprocessor', function() {
/*
* Merge task-specific and/or target-specific options with these defaults.
*/
var options = this.options({
includeObjectConstants: false,
listConstants: true,
replaceConstants: true
});
/*
* Iterate over all specified file groups.
*
* See http://gruntjs.com/inside-tasks#this.files, which says in part:
*
* Your task should iterate over the this.files array, utilizing the src and dest
* properties of each object in that array. The this.files property will always be an array.
* The src property will also always be an array, in case your task cares about multiple
* source files per destination file.
*
* And http://gruntjs.com/configuring-tasks#files-array-format, which explains that all files
* objects support src and dest but the 'Files Array' format supports a few additional properties:
*
* filter: Either a valid fs.Stats method name or a function that is passed the matched
* src filepath and returns true or false;
*
* nonull: If set to true then the operation will include non-matching patterns. Combined
* with grunt's --verbose flag, this option can help debug file path issues;
*
* dot: Allow patterns to match filenames starting with a period, even if the pattern does
* not explicitly have a period in that spot;
*
* matchBase: If set, patterns without slashes will be matched against the basename of the path
* if it contains slashes. For example, a?b would match the path /xyz/123/acb, but not /xyz/acb/123;
*
* expand: Process a dynamic src-dest file mapping, see "Building the files object dynamically"
* for more information.
*/
this.files.forEach(function(file) {
/*
* Allow any of our options to be set at the target level as well
*/
var fObjectConstants = file.includeObjectConstants;
if (fObjectConstants === undefined) fObjectConstants = options.includeObjectConstants;
var fListConstants = file.listConstants;
if (fListConstants === undefined) fListConstants = options.listConstants;
var fReplaceConstants = file.replaceConstants;
if (fReplaceConstants === undefined) fReplaceConstants = options.replaceConstants;
/*
* Read all file contents into src
*/
var src = file.src.filter(function(sFilePath) {
/*
* Warn on and remove invalid source files (if nonull was set)
*/
if (!grunt.file.exists(sFilePath)) {
grunt.log.warn('Source file "' + sFilePath + '" not found.');
return false;
}
return true;
}).map(function(sFilePath) {
/*
* Read a file
*/
return grunt.file.read(sFilePath);
}).join("\n");
/*
* Find all the constants in src, and remove their definitions from src if possible
* (ie, if fReplaceConstants is true and fObjectConstants is false).
*
* aConstants is the array of constant definitions, where each definition is another
* 3-element array:
*
* [0]: the original string (ie, the name of the constant)
* [1]: the replacement string (ie, the value of the constant)
* [2]: a replacement count, initialized to zero; set to -1 if a duplicate is found
*
* WARNING: Grunt (ie, Node) fails with the following error:
*
* FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory
*
* when processing a large src stream (eg, on the order 1.5Mb), and it always seems to
* die in findConstants()'s call to RegExp's exec() method. I couldn't glean any clues
* from the heapdump (other than observing that, yes, running Grunt generates a shitload of
* objects and is probably not very well-tuned), so I've implemented a simple work-around:
* divide the src stream into two parts. If necessary, this work-around can easily be
* generalized to N parts. If the problem is a direct side-effect of passing very large
* strings to the RegExp library, then this is clearly one way to avoid that problem.
*/
var aConstants = [];
var i = src.length/2;
i = src.indexOf("\n", i) + 1; // divide the src after the first linefeed beyond the midpoint
var src1 = src.substr(0, i);
var src2 = src.substr(i);
// grunt.log.writeln("findConstants(src1): " + src1.length + " chars");
src1 = findConstants(src1, aConstants, fObjectConstants, fReplaceConstants);
// grunt.log.writeln("findConstants(src2): " + src2.length + " chars");
src2 = findConstants(src2, aConstants, fObjectConstants, fReplaceConstants);
/*
* Sort the constants in order of longest definition to shortest, because we perform all the replacements
* in array order, and we can't allow definitions that are subsets of longer definitions to be replaced first.
*/
// grunt.log.writeln("aConstants.sort()");
aConstants.sort(compareConstants);
// grunt.log.writeln("replacing constants in other constants");
aConstants.forEach(function(constant) {
constant[1] = replaceConstants(constant[1], aConstants, fObjectConstants, true);
});
if (fReplaceConstants) {
// grunt.log.writeln("replaceConstants(src1)");
src1 = replaceConstants(src1, aConstants, fObjectConstants, false);
// grunt.log.writeln("replaceConstants(src2)");
src2 = replaceConstants(src2, aConstants, fObjectConstants, false);
// grunt.log.writeln("replaceConstants() complete");
}
var sListing = "";
if (fListConstants) {
// grunt.log.writeln("listing constants");
sListing += "/*\n * List of grunt-prepjs replacements:\n *\n";
for (i = 0; i < aConstants.length; i++) {
sListing += " * " + aConstants[i][0] + " => " + aConstants[i][1] + " (" + aConstants[i][2] + " occurrences)\n";
}
sListing += " */\n";
}
/*
* Write the destination file
*/
// grunt.log.writeln("writing " + f.dest);
grunt.file.write(file.dest, sListing + src1 + src2);
/*
* Print a success message
*/
grunt.log.writeln('File "' + file.dest + '" created.');
});
});
};

View file

@ -0,0 +1 @@
Testing: 1 2 3 !!!

View file

@ -0,0 +1 @@
Testing, 1 2 3.

View file

@ -0,0 +1 @@
1 2 3

View file

@ -0,0 +1 @@
Testing

View file

@ -0,0 +1,48 @@
'use strict';
var grunt = require('grunt');
/*
======== A Handy Little Nodeunit Reference ========
https://github.com/caolan/nodeunit
Test methods:
test.expect(numAssertions)
test.done()
Test assertions:
test.ok(value, [message])
test.equal(actual, expected, [message])
test.notEqual(actual, expected, [message])
test.deepEqual(actual, expected, [message])
test.notDeepEqual(actual, expected, [message])
test.strictEqual(actual, expected, [message])
test.notStrictEqual(actual, expected, [message])
test.throws(block, [error], [message])
test.doesNotThrow(block, [error], [message])
test.ifError(value)
*/
exports.prepjs = {
setUp: function(done) {
// setup here if necessary
done();
},
default_options: function(test) {
test.expect(1);
var actual = grunt.file.read('tmp/default_options');
var expected = grunt.file.read('test/expected/default_options');
test.equal(actual, expected, 'should describe what the default behavior is.');
test.done();
},
custom_options: function(test) {
test.expect(1);
var actual = grunt.file.read('tmp/custom_options');
var expected = grunt.file.read('test/expected/custom_options');
test.equal(actual, expected, 'should describe what the custom option(s) behavior is.');
test.done();
},
};