MattiSG/Watai

View on GitHub
src/lib/Preprocessor.js

Summary

Maintainability
A
2 hrs
Test Coverage
var path = require('path');

/** A parser for arguments that loads “plugins”, i.e. Node modules, based on options passed in UNIX long style (i.e. with two dashes).
*
*@class    Loads plugins based on options passed in UNIX long style.
*/
var Preprocessor = function Preprocessor(pluginsDir) {
    this.pluginsDir = pluginsDir;
};

/** Loads plugins based on arguments passed to the main Node process.
*
*@return    {Hash}    A hash whose keys are the processed plugins, mapped to their result; plus a magic key 'remaining' containing all non-processed args.
*@see        processAll
*/
Preprocessor.prototype.processArgv = function processArgv() {
    var args = process.argv.slice(2);    // extract CLI arguments, see http://docs.nodejitsu.com/articles/command-line/how-to-parse-command-line-arguments

    return this.processAll(args);
};

/** Loads plugins based on any passed options.
* More precisely: any option prefixed with `--` will be recognized as a plugin, and if a matching file is found, the corresponding argument will be removed from the passed in array.
* The convention is for such plugins to reside in a `plugins/<plugin_name>/index.js` file, and to export a function that define their expected arguments. The corresponding number of arguments will be popped from the arguments list.
* The loaded plugin function may return an array of values, which will be added in the resulting arguments array.
*
*@return    {Hash}    A hash whose keys are the processed plugins, mapped to their result; plus a magic key 'remaining' containing all non-processed args.
*/
Preprocessor.prototype.processAll = function processAll(args) {
    var result = {
        remaining: []
    };

    for (var i = 0; i < args.length; i++) {
        var optionMatch = args[i].match(/^--([^-].*)/);

        if (! optionMatch) {    // this is not an option
            result.remaining.push(args[i]);    // pass it without doing anything on it
            continue;
        }

        var pluginName = optionMatch[1],    // [1] is the value of the first capturing parentheses
            plugin;

        try {
            plugin = require(path.join(this.pluginsDir, pluginName));
        } catch (e) {    // no matching plugin to handle this option
            result.remaining.push(args[i]);    // pass it without doing anything on it
            continue;    // go straight to the next option
        }

        var params = args.slice(i + 1, i + 1 + plugin.length);    // extract the required arguments from the CLI
        i += plugin.length;    // update the options pointer: params for this option will be ignored

        try {
            var optionResults = plugin.apply(null, params);
        } catch (err) {
            err.message += ' (when trying to parse option "--' + pluginName + '"';

            if (params.length)
                err.message += ' with params "' + params.join('", "') + '"';

            err.message += ')';

            throw err;
        }

        if (optionResults)
            result[pluginName] = optionResults;
    }

    return result;
};


module.exports = Preprocessor;    // CommonJS export