<commit_before>'use strict';

var Constants = require('./constants.js');
var LogWrapper = require('./log-wrapper.js');
var Promise = require('bluebird');
var path = require('path');
var fs = require('fs');

///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
///////////////////////////////////////////////////////////////////////////////

/**
 * Constructs the `OptionsHandler` with an (optional) logger.
 *
 * @param {(logger|cli|console)} [logger=console] - Logger object.
 * @returns {OptionsHandler} The instance.
 * @constructor
 * @class Class which defines some useful methods to initialize and prepare the
 *        transformation options used in this module.
 *

 *        **NOTE:** This class is not to be intended to be called from
 *        outside this module!
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 *
 * var optionsHandler = new OptionsHandler(logger);
 */
function OptionsHandler(logger) {
    /**
     * The logger instance.
     *
     * @member {(logger|cli|console)}
     * @private
     */
    this.logInstance = new LogWrapper(logger);

    var self = this;

    /**
     * Gets a file extension for a given output target.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing a proper file extension (including '.', e.g. _'.yaml'_).
     * @private
     */
    function getDestFileExt(options) {
        return new Promise(function (resolve, reject) {
            var dot = '.';
            switch (options.target) {
                case Constants.YAML:
                    return resolve(dot + Constants.YAML);
                case Constants.JS:
                    return resolve(dot + Constants.JS);
                case Constants.JSON:
                    return resolve(dot + Constants.JSON);
                default:
                    return reject(new Error('Invalid target option found while creating destination file extension: ' + options.target));
            }
        });
    }

    /**
     * Checks if the given type is a valid one.
     *
     * @param {string} type - One of `[ 'yaml' | 'json' | 'js']`.
     * @returns {boolean}   - `true` if is valid type, else `false`.
     * @see {@link Constants#TYPES}
     * @private
     */
    function isValidType(type) {
        return (Constants.TYPES.indexOf(type) >= 0);
    }

    /**
     * Ensure that an origin or target is correct.
     *
     * @param {{origin: string, target: string}} options - Contains the values.
     * @param type - The origin or target value.
     * @param typeName - The type name.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @private
     */
    function ensureType(options, type, typeName) {
        return new Promise(function (resolve, reject) {
            if (isValidType(type)) {
                resolve(options);
            } else {
                reject(new Error('Invalid ' + typeName + ' \'' + type + '\' found, must be one of ' + JSON.stringify(Constants.TYPES)));
            }
        });
    }

    /**
     * A simple map for extensions to type.
     *
     * @type {{yml: string, yaml: string, js: string, json: string}}
     * @private
     */
    var typeMap = {
        yml: Constants.YAML,
        yaml: Constants.YAML,
        js: Constants.JS,
        json: Constants.JSON
    };

    /**
     * Infer from path extension to a type using default value as fallback.
     *
     * @param {string} pathStr      - The file path with or without extension.
     * @param {boolean} origin      - If the type is origin (true) or target (false)
     * @param {string} defaultValue - The default value to use if it cannot inferred from path.
     * @returns {string}            - A type value.
     * @private
     */
    var getTypeFromFilePath = function (pathStr, origin, defaultValue) {
        var ext = path.extname(pathStr);
        self.logInstance.debug('extension: ' + ext);
        if (ext.charAt(0) === '.') {
            ext = ext.substr(1);
        }

        var type = typeMap[ext];
        if (!type) {
            self.logInstance.info('cannot resolve ' + (origin ? 'origin' : 'target') + ' type from file ' + pathStr + ', using default: ' + defaultValue);
            type = defaultValue;
        }
        return type;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // OPTIONS INIT & VALIDATION METHODS
    ///////////////////////////////////////////////////////////////////////////////

    /**
     * Copies the given `options` object and enriches with default values if
     * something required is "missing".
     *
     * @param options     - The object to copy.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @throws {Error}    - If options are not passed.
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.copyOptions(options)
     *     .then(function (copiedOptions) {
     *         ...
     *     });
     * @private
     */
    this.copyOptions = function (options) {
        return new Promise(function (resolve, reject) {
            if (options) {
                var srcType;
                var destType;
                if (options.src) {
                    srcType = getTypeFromFilePath(options.src, true, Constants.YAML);
                }
                self.logInstance.debug('srcType: ' + srcType);
                if (options.dest) {
                    self.logInstance.debug('options.dest: ' + options.dest);
                    destType = getTypeFromFilePath(options.dest, false, Constants.JS);
                }
                self.logInstance.debug('destType: ' + destType);
                resolve({
                    origin: (options.origin && (options.origin !== Constants.ORIGIN_DEFAULT)) ? options.origin : (srcType || Constants.YAML),
                    target: (options.target && (options.target !== Constants.TARGET_DEFAULT)) ? options.target : (destType || Constants.JS),
                    src: options.src,
                    dest: options.dest || Constants.DEFAULT_OPTIONS.dest,
                    indent: options.indent || Constants.DEFAULT_OPTIONS.indent
                });
            } else {
                reject(new Error('Missing options object.'));
            }
        });
    };

    /**
     * Ensures that the given input source is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @throws {Error}    - If the `options.src` is not defined or the file represented by `options.src` does not exist.
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureSrc(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureSrc = function (options) {
        return new Promise(function (resolve, reject) {

            // complain about missing source file
            if (!options.src) {
                return reject(new Error('Pls specify existing input file!'));
            }

            // check for existing source file
            try {
                fs.lstatSync(options.src); // TODO could we check this in Async mode?
            } catch (err) {
                if (err.code === 'ENOENT') {
                    err.message = 'The input file \'' + options.src + '\' does not exists or is not accessible, pls specify correct input file path, cause: ' + err.message;
                } else {
                    err.message = 'Some error occurred while accessing input file \'' + options.src + '\': ' + err.code + ', ' + err.message;
                }
                return reject(err);
            }

            resolve(options);
        });
    };

    /**
     * This method ensures that destination file path is created if not set in
     * options. If not, then it creates the path relative to the source file using
     * its name and appending a proper extension depending on the `json`
     * property of `options` (if `true` then '.js', else '.json').
     *
     * @param {{src: string, dest: string}} options - The required options.
     * @returns {Promise}                           - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureDest(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureDest = function (options) {
        if (options.dest === Constants.DEFAULT_OPTIONS.dest) {
            return getDestFileExt(options)
                .then(function (destExt) {
                    var destDirName = path.dirname(options.src);
                    var srcExt = path.extname(options.src);
                    var destName = path.basename(options.src, srcExt);
                    options.dest = path.join(destDirName, destName + destExt);
                    self.logInstance.debug('Destination file: ' + options.dest);
                    return options;//Promise.resolve(options);
                });
        } else {
            self.logInstance.debug('Destination file: ' + options.dest);
            return Promise.resolve(options);
        }
    };

    /**
     * Checks if the given origin is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureOrigin(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureOrigin = function (options) {
        return ensureType(options, options.origin, 'origin');
    };

    /**
     * Checks if the given target is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureTarget(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureTarget = function (options) {
        return ensureType(options, options.target, 'target');
    };

    /**
     * Checks if a valid indention value is given and corrects values if invalid (with default value: 4 SPACEs).
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureIndent(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureIndent = function (options) {
        if ((options.indent < Constants.MIN_YAML_INDENT) && (options.target === Constants.YAML)) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found for YAML, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        } else if (options.indent < 0) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        } else if (options.indent > Constants.MAX_INDENT) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        }
        self.logInstance.debug('options:: ' + JSON.stringify(options, null, 4));
        return Promise.resolve(options);
    };

    /**
     * Log the options with INFO level.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options - The properties to log with INFO.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.verboseOptions(options)
     *     .then(function (loggedOptions) {
     *         ...
     *     });
     * @private
     */
    this.verboseOptions = function (options) {
        return self.logInstance.verboseOptions(options);
    };
}

OptionsHandler.prototype = {};
OptionsHandler.prototype.constructor = OptionsHandler;
module.exports = OptionsHandler;

/**
 * This method ensures that the options object is set with all necessary and
 * correct values. The method does not alter the given object, but creates
 * and fills a new instance from the given values and/or default ones.
 *
 * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
 *        The minimum configuration for a transformation.
 * @returns {Promise} - A Promise containing a new and complete `options` object.
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 * var options = {...};
 * var optionsHandler = new OptionsHandler(logger);
 * optionsHandler.ensureOptions(options)
 *     .then(function (ensuredOptions) {
 *         ...
 *     });
 * @public
 */
OptionsHandler.prototype.ensureOptions = function (options) {
    return this.copyOptions(options)
        .then(this.ensureSrc)
        .then(this.ensureDest)
        .then(this.ensureOrigin)
        .then(this.ensureTarget)
        .then(this.ensureIndent)
        .then(this.verboseOptions);
};

/**
 * This method validates the transformation process described by the given
 * options and provides the validate and enriched options and according name
 * to resolve a proper function.
 *
 * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
 *        The minimum configuration for a transformation.
 * @returns {Promise} - A Promise containing the passed `options` object and a 'transformation' string in an array.
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 * var optionsHandler = new OptionsHandler(logger);
 * optionsHandler.validateTransformation(options)
 *     .spread(function (validatedOptions, transformation) {
 *         ...
 *     )):
 * @see {@link transformations}
 * @public
 */
OptionsHandler.prototype.validateTransformation = function (options) {
    return new Promise(function (resolve, reject) {
        var transformation = options.origin + '2' + options.target;
        if (Constants.TRANSFORMATIONS.indexOf(transformation) < 0) {
            reject(new Error('Unsupported target type transformation \'' + options.origin + ' -> ' + options.target + '\' configured in options.'));
        } else {
            resolve([options, transformation]);
        }
    });
};
<commit_msg>Fix API doc<commit_after>'use strict';

var Constants = require('./constants.js');
var LogWrapper = require('./log-wrapper.js');
var Promise = require('bluebird');
var path = require('path');
var fs = require('fs');

///////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
///////////////////////////////////////////////////////////////////////////////

/**
 * Constructs the `OptionsHandler` with an (optional) logger.
 *
 * @param {(logger|cli|console)} [logger=console] - Logger object.
 * @returns {OptionsHandler} The instance.
 * @constructor
 * @class Class which defines some useful methods to initialize and prepare the
 *        transformation options used in this module.
 *

 *        **NOTE:** This class is not to be intended to be called from
 *        outside this module!
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 *
 * var optionsHandler = new OptionsHandler(logger);
 */
function OptionsHandler(logger) {
    /**
     * The logger instance.
     *
     * @member {(logger|cli|console)}
     * @private
     */
    this.logInstance = new LogWrapper(logger);

    var self = this;

    /**
     * Gets a file extension for a given output target.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing a proper file extension (including '.', e.g. _'.yaml'_).
     * @private
     */
    function getDestFileExt(options) {
        return new Promise(function (resolve, reject) {
            var dot = '.';
            switch (options.target) {
                case Constants.YAML:
                    return resolve(dot + Constants.YAML);
                case Constants.JS:
                    return resolve(dot + Constants.JS);
                case Constants.JSON:
                    return resolve(dot + Constants.JSON);
                default:
                    return reject(new Error('Invalid target option found while creating destination file extension: ' + options.target));
            }
        });
    }

    /**
     * Checks if the given type is a valid one.
     *
     * @param {string} type - One of `[ 'yaml' | 'json' | 'js']`.
     * @returns {boolean}   - `true` if is valid type, else `false`.
     * @see {@link Constants#TYPES}
     * @private
     */
    function isValidType(type) {
        return (Constants.TYPES.indexOf(type) >= 0);
    }

    /**
     * Ensure that an origin or target is correct.
     *
     * @param {{origin: string, target: string}} options - Contains the values.
     * @param type - The origin or target value.
     * @param typeName - The type name.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @private
     */
    function ensureType(options, type, typeName) {
        return new Promise(function (resolve, reject) {
            if (isValidType(type)) {
                resolve(options);
            } else {
                reject(new Error('Invalid ' + typeName + ' \'' + type + '\' found, must be one of ' + JSON.stringify(Constants.TYPES)));
            }
        });
    }

    /**
     * A simple map for extensions to type.
     *
     * @type {{yml: string, yaml: string, js: string, json: string}}
     * @private
     */
    var typeMap = {
        yml: Constants.YAML,
        yaml: Constants.YAML,
        js: Constants.JS,
        json: Constants.JSON
    };

    /**
     * Infer from path extension to a type using default value as fallback.
     *
     * @param {string} pathStr      - The file path with or without extension.
     * @param {boolean} origin      - If the type is origin (true) or target (false)
     * @param {string} defaultValue - The default value to use if it cannot inferred from path.
     * @returns {string}            - A type value.
     * @private
     */
    var getTypeFromFilePath = function (pathStr, origin, defaultValue) {
        var ext = path.extname(pathStr);
        self.logInstance.debug('extension: ' + ext);
        if (ext.charAt(0) === '.') {
            ext = ext.substr(1);
        }

        var type = typeMap[ext];
        if (!type) {
            self.logInstance.info('cannot resolve ' + (origin ? 'origin' : 'target') + ' type from file ' + pathStr + ', using default: ' + defaultValue);
            type = defaultValue;
        }
        return type;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // OPTIONS INIT & VALIDATION METHODS
    ///////////////////////////////////////////////////////////////////////////////

    /**
     * Copies the given `options` object and enriches with default values if
     * something required is "missing".
     *
     * @param options     - The object to copy.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @throws {Error}    - If options are not passed.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.copyOptions(options)
     *     .then(function (copiedOptions) {
     *         ...
     *     });
     * @private
     */
    this.copyOptions = function (options) {
        return new Promise(function (resolve, reject) {
            if (options) {
                var srcType;
                var destType;
                if (options.src) {
                    srcType = getTypeFromFilePath(options.src, true, Constants.YAML);
                }
                self.logInstance.debug('srcType: ' + srcType);
                if (options.dest) {
                    self.logInstance.debug('options.dest: ' + options.dest);
                    destType = getTypeFromFilePath(options.dest, false, Constants.JS);
                }
                self.logInstance.debug('destType: ' + destType);
                resolve({
                    origin: (options.origin && (options.origin !== Constants.ORIGIN_DEFAULT)) ? options.origin : (srcType || Constants.YAML),
                    target: (options.target && (options.target !== Constants.TARGET_DEFAULT)) ? options.target : (destType || Constants.JS),
                    src: options.src,
                    dest: options.dest || Constants.DEFAULT_OPTIONS.dest,
                    indent: options.indent || Constants.DEFAULT_OPTIONS.indent
                });
            } else {
                reject(new Error('Missing options object.'));
            }
        });
    };

    /**
     * Ensures that the given input source is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @throws {Error}    - If the `options.src` is not defined or the file represented by `options.src` does not exist.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureSrc(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureSrc = function (options) {
        return new Promise(function (resolve, reject) {

            // complain about missing source file
            if (!options.src) {
                return reject(new Error('Pls specify existing input file!'));
            }

            // check for existing source file
            try {
                fs.lstatSync(options.src); // TODO could we check this in Async mode?
            } catch (err) {
                if (err.code === 'ENOENT') {
                    err.message = 'The input file \'' + options.src + '\' does not exists or is not accessible, pls specify correct input file path, cause: ' + err.message;
                } else {
                    err.message = 'Some error occurred while accessing input file \'' + options.src + '\': ' + err.code + ', ' + err.message;
                }
                return reject(err);
            }

            resolve(options);
        });
    };

    /**
     * This method ensures that destination file path is created if not set in
     * options. If not, then it creates the path relative to the source file using
     * its name and appending a proper extension depending on the `json`
     * property of `options` (if `true` then '.js', else '.json').
     *
     * @param {{src: string, dest: string}} options - The required options.
     * @returns {Promise}                           - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureDest(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureDest = function (options) {
        if (options.dest === Constants.DEFAULT_OPTIONS.dest) {
            return getDestFileExt(options)
                .then(function (destExt) {
                    var destDirName = path.dirname(options.src);
                    var srcExt = path.extname(options.src);
                    var destName = path.basename(options.src, srcExt);
                    options.dest = path.join(destDirName, destName + destExt);
                    self.logInstance.debug('Destination file: ' + options.dest);
                    return options;//Promise.resolve(options);
                });
        } else {
            self.logInstance.debug('Destination file: ' + options.dest);
            return Promise.resolve(options);
        }
    };

    /**
     * Checks if the given origin is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureOrigin(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureOrigin = function (options) {
        return ensureType(options, options.origin, 'origin');
    };

    /**
     * Checks if the given target is valid.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureTarget(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureTarget = function (options) {
        return ensureType(options, options.target, 'target');
    };

    /**
     * Checks if a valid indention value is given and corrects values if invalid (with default value: 4 SPACEs).
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
     *        The minimum configuration for a transformation.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.ensureIndent(options)
     *     .then(function (ensuredOptions) {
     *         ...
     *     });
     * @private
     */
    this.ensureIndent = function (options) {
        if ((options.indent < Constants.MIN_YAML_INDENT) && (options.target === Constants.YAML)) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found for YAML, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        } else if (options.indent < 0) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        } else if (options.indent > Constants.MAX_INDENT) {
            self.logInstance.info('Invalid indent \'' + options.indent + '\' found, reset to default: ' + Constants.DEFAULT_INDENT);
            options.indent = Constants.DEFAULT_INDENT;
        }
        self.logInstance.debug('options:: ' + JSON.stringify(options, null, 4));
        return Promise.resolve(options);
    };

    /**
     * Log the options with INFO level.
     *
     * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options - The properties to log with INFO.
     * @returns {Promise} - A Promise containing the passed `options` object.
     * @example
     * var OptionsHandler = require('./options-handler.js');
     * var logger = ...;
     * var options = {...};
     * var optionsHandler = new OptionsHandler(logger);
     * optionsHandler.verboseOptions(options)
     *     .then(function (loggedOptions) {
     *         ...
     *     });
     * @private
     */
    this.verboseOptions = function (options) {
        return self.logInstance.verboseOptions(options);
    };
}

OptionsHandler.prototype = {};
OptionsHandler.prototype.constructor = OptionsHandler;
module.exports = OptionsHandler;

/**
 * This method ensures that the options object is set with all necessary and
 * correct values. The method does not alter the given object, but creates
 * and fills a new instance from the given values and/or default ones.
 *
 * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
 *        The minimum configuration for a transformation.
 * @returns {Promise} - A Promise containing a new and complete `options` object.
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 * var options = {...};
 * var optionsHandler = new OptionsHandler(logger);
 * optionsHandler.ensureOptions(options)
 *     .then(function (ensuredOptions) {
 *         ...
 *     });
 * @public
 */
OptionsHandler.prototype.ensureOptions = function (options) {
    return this.copyOptions(options)
        .then(this.ensureSrc)
        .then(this.ensureDest)
        .then(this.ensureOrigin)
        .then(this.ensureTarget)
        .then(this.ensureIndent)
        .then(this.verboseOptions);
};

/**
 * This method validates the transformation process described by the given
 * options and provides the validate and enriched options and according name
 * to resolve a proper function.
 *
 * @param {{origin: string, target: string, src: string, dest: string, indent: number}} options -
 *        The minimum configuration for a transformation.
 * @returns {Promise} - A Promise containing the passed `options` object and a 'transformation' string in an array.
 * @example
 * var OptionsHandler = require('./options-handler.js');
 * var logger = ...;
 * var optionsHandler = new OptionsHandler(logger);
 * optionsHandler.validateTransformation(options)
 *     .spread(function (validatedOptions, transformation) {
 *         ...
 *     )):
 * @see {@link transformations}
 * @public
 */
OptionsHandler.prototype.validateTransformation = function (options) {
    return new Promise(function (resolve, reject) {
        var transformation = options.origin + '2' + options.target;
        if (Constants.TRANSFORMATIONS.indexOf(transformation) < 0) {
            reject(new Error('Unsupported target type transformation \'' + options.origin + ' -> ' + options.target + '\' configured in options.'));
        } else {
            resolve([options, transformation]);
        }
    });
};
<|endoftext|>


<commit_before>import fs from 'fs-extra';
import path from 'path';
import assert from 'assert';
import Logger from 'color-logger';
import ASTUtil from './Util/ASTUtil.js';
import ESParser from './Parser/ESParser';
import PathResolver from './Util/PathResolver.js';
import DocFactory from './Factory/DocFactory.js';
import InvalidCodeLogger from './Util/InvalidCodeLogger.js';
import Plugin from './Plugin/Plugin.js';

const logger = new Logger('ESDoc');

/**
 * API Documentation Generator.
 *
 * @example
 * let config = {source: './src', destination: './esdoc'};
 * ESDoc.generate(config, (results, config)=>{
 *   console.log(results);
 * });
 */
export default class ESDoc {
  /**
   * Generate documentation.
   * @param {ESDocConfig} config - config for generation.
   */
  static generate(config) {
    assert(config.source);
    assert(config.destination);

    this._checkOldConfig(config);

    Plugin.init(config.plugins);
    Plugin.onStart();
    config = Plugin.onHandleConfig(config);

    this._setDefaultConfig(config);

    Logger.debug = !!config.debug;
    const includes = config.includes.map((v) => new RegExp(v));
    const excludes = config.excludes.map((v) => new RegExp(v));

    let packageName = null;
    let mainFilePath = null;
    if (config.package) {
      try {
        const packageJSON = fs.readFileSync(config.package, {encode: 'utf8'});
        const packageConfig = JSON.parse(packageJSON);
        packageName = packageConfig.name;
        mainFilePath = packageConfig.main;
      } catch (e) {
        // ignore
      }
    }

    let results = [];
    const asts = [];
    const sourceDirPath = path.resolve(config.source);

    this._walk(config.source, (filePath)=>{
      const relativeFilePath = path.relative(sourceDirPath, filePath);
      let match = false;
      for (const reg of includes) {
        if (relativeFilePath.match(reg)) {
          match = true;
          break;
        }
      }
      if (!match) return;

      for (const reg of excludes) {
        if (relativeFilePath.match(reg)) return;
      }

      console.log(`parse: ${filePath}`);
      const temp = this._traverse(config.source, filePath, packageName, mainFilePath);
      if (!temp) return;
      results.push(...temp.results);

      asts.push({filePath: `source${path.sep}${relativeFilePath}`, ast: temp.ast});
    });

    // config.index
    if (config.index) {
      results.push(this._generateForIndex(config));
    }

    // config.package
    if (config.package) {
      results.push(this._generateForPackageJSON(config));
    }

    results = this._resolveDuplication(results);

    results = Plugin.onHandleDocs(results);

    // index.json
    {
      const dumpPath = path.resolve(config.destination, 'index.json');
      fs.outputFileSync(dumpPath, JSON.stringify(results, null, 2));
    }

    // ast
    for (const ast of asts) {
      const json = JSON.stringify(ast.ast, null, 2);
      const filePath = path.resolve(config.destination, `ast/${ast.filePath}.json`);
      fs.outputFileSync(filePath, json);
    }

    // publish
    this._publish(config);

    Plugin.onComplete();
  }

  /**
   * check ESDoc config. and if it is old, exit with warning message.
   * @param {ESDocConfig} config - check config
   * @private
   */
  static _checkOldConfig(config) {
    let exit = false;

    const keys = [
      ['access', 'esdoc-standard-plugin'],
      ['autoPrivate', 'esdoc-standard-plugin'],
      ['unexportIdentifier', 'esdoc-standard-plugin'],
      ['undocumentIdentifier', 'esdoc-standard-plugin'],
      ['builtinExternal', 'esdoc-standard-plugin'],
      ['coverage', 'esdoc-standard-plugin'],
      ['test', 'esdoc-standard-plugin'],
      ['title', 'esdoc-standard-plugin'],
      ['manual', 'esdoc-standard-plugin'],
      ['lint', 'esdoc-standard-plugin'],
      ['includeSource', 'esdoc-exclude-source-plugin'],
      ['styles', 'esdoc-inject-style-plugin'],
      ['scripts', 'esdoc-inject-script-plugin'],
      ['experimentalProposal', 'esdoc-ecmascript-proposal-plugin']
    ];

    for (const [key, plugin] of keys) {
      if (key in config) {
        console.log(`error: config.${key} is invalid. Please use ${plugin}. how to migration: https://esdoc.org/manual/migration.html`);
        exit = true;
      }
    }

    if (exit) process.exit(1);
  }

  /**
   * set default config to specified config.
   * @param {ESDocConfig} config - specified config.
   * @private
   */
  static _setDefaultConfig(config) {
    if (!config.includes) config.includes = ['\\.js$'];

    if (!config.excludes) config.excludes = ['\\.config\\.js$', '\\.test\\.js$'];

    if (!config.index) config.index = './README.md';

    if (!config.package) config.package = './package.json';
  }

  /**
   * walk recursive in directory.
   * @param {string} dirPath - target directory path.
   * @param {function(entryPath: string)} callback - callback for find file.
   * @private
   */
  static _walk(dirPath, callback) {
    const entries = fs.readdirSync(dirPath);

    for (const entry of entries) {
      const entryPath = path.resolve(dirPath, entry);
      const stat = fs.statSync(entryPath);

      if (stat.isFile()) {
        callback(entryPath);
      } else if (stat.isDirectory()) {
        this._walk(entryPath, callback);
      }
    }
  }

  /**
   * traverse doc comment in JavaScript file.
   * @param {string} inDirPath - root directory path.
   * @param {string} filePath - target JavaScript file path.
   * @param {string} [packageName] - npm package name of target.
   * @param {string} [mainFilePath] - npm main file path of target.
   * @returns {Object} - return document that is traversed.
   * @property {DocObject[]} results - this is contained JavaScript file.
   * @property {AST} ast - this is AST of JavaScript file.
   * @private
   */
  static _traverse(inDirPath, filePath, packageName, mainFilePath) {
    logger.i(`parsing: ${filePath}`);
    let ast;
    try {
      ast = ESParser.parse(filePath);
    } catch (e) {
      InvalidCodeLogger.showFile(filePath, e);
      return null;
    }

    const pathResolver = new PathResolver(inDirPath, filePath, packageName, mainFilePath);
    const factory = new DocFactory(ast, pathResolver);

    ASTUtil.traverse(ast, (node, parent)=>{
      try {
        factory.push(node, parent);
      } catch (e) {
        InvalidCodeLogger.show(filePath, node);
        throw e;
      }
    });

    return {results: factory.results, ast: ast};
  }

  /**
   * generate index doc
   * @param {ESDocConfig} config
   * @returns {Tag}
   * @private
   */
  static _generateForIndex(config) {
    const indexContent = fs.readFileSync(config.index, {encode: 'utf8'}).toString();
    const tag = {
      kind: 'index',
      content: indexContent,
      longname: path.resolve(config.index),
      name: config.index,
      static: true,
      access: 'public'
    };

    return tag;
  }

  /**
   * generate package doc
   * @param {ESDocConfig} config
   * @returns {Tag}
   * @private
   */
  static _generateForPackageJSON(config) {
    let packageJSON = '';
    let packagePath = '';
    try {
      packageJSON = fs.readFileSync(config.package, {encoding: 'utf-8'});
      packagePath = path.resolve(config.package);
    } catch (e) {
      // ignore
    }

    const tag = {
      kind: 'packageJSON',
      content: packageJSON,
      longname: packagePath,
      name: path.basename(packagePath),
      static: true,
      access: 'public'
    };

    return tag;
  }

  /**
   * resolve duplication docs
   * @param {Tag[]} docs
   * @returns {Tag[]}
   * @private
   */
  static _resolveDuplication(docs) {
    const memberDocs = docs.filter((doc) => doc.kind === 'member');
    const removeIds = [];

    for (const memberDoc of memberDocs) {
      // member duplicate with getter/setter/method.
      // when it, remove member.
      // getter/setter/method are high priority.
      const sameLongnameDoc = docs.find((doc) => doc.longname === memberDoc.longname && doc.kind !== 'member');
      if (sameLongnameDoc) {
        removeIds.push(memberDoc.__docId__);
        continue;
      }

      const dup = docs.filter((doc) => doc.longname === memberDoc.longname && doc.kind === 'member');
      if (dup.length > 1) {
        const ids = dup.map(v => v.__docId__);
        ids.sort((a, b) => {
          return a < b ? -1 : 1;
        });
        ids.shift();
        removeIds.push(...ids);
      }
    }

    return docs.filter((doc) => !removeIds.includes(doc.__docId__));
  }

  /**
   * publish content
   * @param {ESDocConfig} config
   * @private
   */
  static _publish(config) {
    try {
      const write = (filePath, content, option) =>{
        const _filePath = path.resolve(config.destination, filePath);
        content = Plugin.onHandleContent(content, _filePath);

        console.log(`output: ${_filePath}`);
        fs.outputFileSync(_filePath, content, option);
      };

      const copy = (srcPath, destPath) => {
        const _destPath = path.resolve(config.destination, destPath);
        console.log(`output: ${_destPath}`);
        fs.copySync(srcPath, _destPath);
      };

      const read = (filePath) => {
        const _filePath = path.resolve(config.destination, filePath);
        return fs.readFileSync(_filePath).toString();
      };

      Plugin.onPublish(write, copy, read);
    } catch (e) {
      InvalidCodeLogger.showError(e);
      process.exit(1);
    }
  }
}
<commit_msg>check if README.md exists before attempting to read it<commit_after>import fs from 'fs-extra';
import path from 'path';
import assert from 'assert';
import Logger from 'color-logger';
import ASTUtil from './Util/ASTUtil.js';
import ESParser from './Parser/ESParser';
import PathResolver from './Util/PathResolver.js';
import DocFactory from './Factory/DocFactory.js';
import InvalidCodeLogger from './Util/InvalidCodeLogger.js';
import Plugin from './Plugin/Plugin.js';

const logger = new Logger('ESDoc');

/**
 * API Documentation Generator.
 *
 * @example
 * let config = {source: './src', destination: './esdoc'};
 * ESDoc.generate(config, (results, config)=>{
 *   console.log(results);
 * });
 */
export default class ESDoc {
  /**
   * Generate documentation.
   * @param {ESDocConfig} config - config for generation.
   */
  static generate(config) {
    assert(config.source);
    assert(config.destination);

    this._checkOldConfig(config);

    Plugin.init(config.plugins);
    Plugin.onStart();
    config = Plugin.onHandleConfig(config);

    this._setDefaultConfig(config);

    Logger.debug = !!config.debug;
    const includes = config.includes.map((v) => new RegExp(v));
    const excludes = config.excludes.map((v) => new RegExp(v));

    let packageName = null;
    let mainFilePath = null;
    if (config.package) {
      try {
        const packageJSON = fs.readFileSync(config.package, {encode: 'utf8'});
        const packageConfig = JSON.parse(packageJSON);
        packageName = packageConfig.name;
        mainFilePath = packageConfig.main;
      } catch (e) {
        // ignore
      }
    }

    let results = [];
    const asts = [];
    const sourceDirPath = path.resolve(config.source);

    this._walk(config.source, (filePath)=>{
      const relativeFilePath = path.relative(sourceDirPath, filePath);
      let match = false;
      for (const reg of includes) {
        if (relativeFilePath.match(reg)) {
          match = true;
          break;
        }
      }
      if (!match) return;

      for (const reg of excludes) {
        if (relativeFilePath.match(reg)) return;
      }

      console.log(`parse: ${filePath}`);
      const temp = this._traverse(config.source, filePath, packageName, mainFilePath);
      if (!temp) return;
      results.push(...temp.results);

      asts.push({filePath: `source${path.sep}${relativeFilePath}`, ast: temp.ast});
    });

    // config.index
    if (config.index) {
      results.push(this._generateForIndex(config));
    }

    // config.package
    if (config.package) {
      results.push(this._generateForPackageJSON(config));
    }

    results = this._resolveDuplication(results);

    results = Plugin.onHandleDocs(results);

    // index.json
    {
      const dumpPath = path.resolve(config.destination, 'index.json');
      fs.outputFileSync(dumpPath, JSON.stringify(results, null, 2));
    }

    // ast
    for (const ast of asts) {
      const json = JSON.stringify(ast.ast, null, 2);
      const filePath = path.resolve(config.destination, `ast/${ast.filePath}.json`);
      fs.outputFileSync(filePath, json);
    }

    // publish
    this._publish(config);

    Plugin.onComplete();
  }

  /**
   * check ESDoc config. and if it is old, exit with warning message.
   * @param {ESDocConfig} config - check config
   * @private
   */
  static _checkOldConfig(config) {
    let exit = false;

    const keys = [
      ['access', 'esdoc-standard-plugin'],
      ['autoPrivate', 'esdoc-standard-plugin'],
      ['unexportIdentifier', 'esdoc-standard-plugin'],
      ['undocumentIdentifier', 'esdoc-standard-plugin'],
      ['builtinExternal', 'esdoc-standard-plugin'],
      ['coverage', 'esdoc-standard-plugin'],
      ['test', 'esdoc-standard-plugin'],
      ['title', 'esdoc-standard-plugin'],
      ['manual', 'esdoc-standard-plugin'],
      ['lint', 'esdoc-standard-plugin'],
      ['includeSource', 'esdoc-exclude-source-plugin'],
      ['styles', 'esdoc-inject-style-plugin'],
      ['scripts', 'esdoc-inject-script-plugin'],
      ['experimentalProposal', 'esdoc-ecmascript-proposal-plugin']
    ];

    for (const [key, plugin] of keys) {
      if (key in config) {
        console.log(`error: config.${key} is invalid. Please use ${plugin}. how to migration: https://esdoc.org/manual/migration.html`);
        exit = true;
      }
    }

    if (exit) process.exit(1);
  }

  /**
   * set default config to specified config.
   * @param {ESDocConfig} config - specified config.
   * @private
   */
  static _setDefaultConfig(config) {
    if (!config.includes) config.includes = ['\\.js$'];

    if (!config.excludes) config.excludes = ['\\.config\\.js$', '\\.test\\.js$'];

    if (!config.index) config.index = './README.md';

    if (!config.package) config.package = './package.json';
  }

  /**
   * walk recursive in directory.
   * @param {string} dirPath - target directory path.
   * @param {function(entryPath: string)} callback - callback for find file.
   * @private
   */
  static _walk(dirPath, callback) {
    const entries = fs.readdirSync(dirPath);

    for (const entry of entries) {
      const entryPath = path.resolve(dirPath, entry);
      const stat = fs.statSync(entryPath);

      if (stat.isFile()) {
        callback(entryPath);
      } else if (stat.isDirectory()) {
        this._walk(entryPath, callback);
      }
    }
  }

  /**
   * traverse doc comment in JavaScript file.
   * @param {string} inDirPath - root directory path.
   * @param {string} filePath - target JavaScript file path.
   * @param {string} [packageName] - npm package name of target.
   * @param {string} [mainFilePath] - npm main file path of target.
   * @returns {Object} - return document that is traversed.
   * @property {DocObject[]} results - this is contained JavaScript file.
   * @property {AST} ast - this is AST of JavaScript file.
   * @private
   */
  static _traverse(inDirPath, filePath, packageName, mainFilePath) {
    logger.i(`parsing: ${filePath}`);
    let ast;
    try {
      ast = ESParser.parse(filePath);
    } catch (e) {
      InvalidCodeLogger.showFile(filePath, e);
      return null;
    }

    const pathResolver = new PathResolver(inDirPath, filePath, packageName, mainFilePath);
    const factory = new DocFactory(ast, pathResolver);

    ASTUtil.traverse(ast, (node, parent)=>{
      try {
        factory.push(node, parent);
      } catch (e) {
        InvalidCodeLogger.show(filePath, node);
        throw e;
      }
    });

    return {results: factory.results, ast: ast};
  }

  /**
   * generate index doc
   * @param {ESDocConfig} config
   * @returns {Tag}
   * @private
   */
  static _generateForIndex(config) {
    let indexContent = "";
    if (fs.existsSync(config.index)) {
      indexContent = fs.readFileSync(config.index, {encode: 'utf8'}).toString();
    }
    const tag = {
      kind: 'index',
      content: indexContent,
      longname: path.resolve(config.index),
      name: config.index,
      static: true,
      access: 'public'
    };

    return tag;
  }

  /**
   * generate package doc
   * @param {ESDocConfig} config
   * @returns {Tag}
   * @private
   */
  static _generateForPackageJSON(config) {
    let packageJSON = '';
    let packagePath = '';
    try {
      packageJSON = fs.readFileSync(config.package, {encoding: 'utf-8'});
      packagePath = path.resolve(config.package);
    } catch (e) {
      // ignore
    }

    const tag = {
      kind: 'packageJSON',
      content: packageJSON,
      longname: packagePath,
      name: path.basename(packagePath),
      static: true,
      access: 'public'
    };

    return tag;
  }

  /**
   * resolve duplication docs
   * @param {Tag[]} docs
   * @returns {Tag[]}
   * @private
   */
  static _resolveDuplication(docs) {
    const memberDocs = docs.filter((doc) => doc.kind === 'member');
    const removeIds = [];

    for (const memberDoc of memberDocs) {
      // member duplicate with getter/setter/method.
      // when it, remove member.
      // getter/setter/method are high priority.
      const sameLongnameDoc = docs.find((doc) => doc.longname === memberDoc.longname && doc.kind !== 'member');
      if (sameLongnameDoc) {
        removeIds.push(memberDoc.__docId__);
        continue;
      }

      const dup = docs.filter((doc) => doc.longname === memberDoc.longname && doc.kind === 'member');
      if (dup.length > 1) {
        const ids = dup.map(v => v.__docId__);
        ids.sort((a, b) => {
          return a < b ? -1 : 1;
        });
        ids.shift();
        removeIds.push(...ids);
      }
    }

    return docs.filter((doc) => !removeIds.includes(doc.__docId__));
  }

  /**
   * publish content
   * @param {ESDocConfig} config
   * @private
   */
  static _publish(config) {
    try {
      const write = (filePath, content, option) =>{
        const _filePath = path.resolve(config.destination, filePath);
        content = Plugin.onHandleContent(content, _filePath);

        console.log(`output: ${_filePath}`);
        fs.outputFileSync(_filePath, content, option);
      };

      const copy = (srcPath, destPath) => {
        const _destPath = path.resolve(config.destination, destPath);
        console.log(`output: ${_destPath}`);
        fs.copySync(srcPath, _destPath);
      };

      const read = (filePath) => {
        const _filePath = path.resolve(config.destination, filePath);
        return fs.readFileSync(_filePath).toString();
      };

      Plugin.onPublish(write, copy, read);
    } catch (e) {
      InvalidCodeLogger.showError(e);
      process.exit(1);
    }
  }
}
<|endoftext|>


<commit_before>/*!
 * micromatch <https://github.com/jonschlinkert/micromatch>
 *
 * Copyright (c) 2014-2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var utils = require('./utils');
var Glob = require('./glob');

/**
 * Expose `expand`
 */

module.exports = expand;

/**
 * Expand a glob pattern to resolve braces and
 * similar patterns before converting to regex.
 *
 * @param  {String|Array} `pattern`
 * @param  {Array} `files`
 * @param  {Options} `opts`
 * @return {Array}
 */

function expand(pattern, options) {
  if (typeof pattern !== 'string') {
    throw new TypeError('micromatch.expand(): argument should be a string.');
  }

  var glob = new Glob(pattern, options || {});
  var opts = glob.options;

  if (typeof opts.braces === 'undefined' && typeof opts.nobraces === 'undefined') {
    opts.braces = true;
  }

  // return early if glob pattern matches special patterns
  if (specialCase(pattern) && opts.safemode) {
    return new RegExp(utils.escapeRe(pattern), 'g');
  }

  if (glob.pattern === '.*') {
    return {
      pattern: '\\.' + star,
      tokens: tok,
      options: opts
    };
  }

  if (glob.pattern === '.') {
    return {
      pattern: '\\.',
      tokens: tok,
      options: opts
    };
  }

  if (glob.pattern === '*') {
    return {
      pattern: oneStar(opts.dot),
      tokens: tok,
      options: opts
    };
  }

  if (opts.nonegate !== true) {
    opts.negated = glob.negated;
  }

  // parse the glob pattern into tokens
  glob.parse();

  var tok = glob.tokens;
  tok.is.negated = opts.negated;

  if (tok.is.dotfile) {
    glob.options.dot = true;
    opts.dot = true;
  }

  if (glob.pattern.charAt(0) === '.' && glob.pattern.charAt(1) !== '/') {
    glob.pattern = '\\' + glob.pattern;
  }

  opts.dot = !!opts.dot;

  // check for braces with a dotfile pattern
  if (/[{,]\./.test(glob.pattern)) {
    opts.makeRe = false;
    opts.dot = true;
  }

  // expand braces, e.g `{1..5}`
  glob.track('before extglob');
  if (tok.is.extglob) {
    glob.extglob();
  }

  glob.track('before brackets');
  if (tok.is.brackets) {
    glob.brackets();
  }

  glob.track('before braces');
  if (tok.is.braces) {
    glob.braces();
  }

  glob.track('after braces');

  glob._replace('[!', '[^');
  glob._replace('(?', '(%~');
  glob._replace('/[', '/(?!\\.)(?=.)[', true);
  glob._replace('/?', '/(?!\\.)(?=.)[^/]', true);
  glob._replace('/.', '/(?=.)\\.', true);
  glob._replace('[]', '\\[\\]');

  // windows drives
  glob._replace(/^(\w):([\\\/]+?)/gi, '(?=.)$1:$2', true);

  // negate slashes in exclusion ranges
  if (glob.pattern.indexOf('[^') !== -1) {
    glob.pattern = negateSlash(glob.pattern);
  }

  if (opts.globstar !== false && glob.pattern === '**') {
     glob.pattern = globstar(opts.dot);

  } else {
    // '/*/*/*' => '(?:/*){3}'
    glob._replace(/(\/\*)+/g, function (match) {
      var len = match.length / 2;
      if (len === 1) { return match; }
      return '(?:\\/*){' + len + '}';
    });

    glob.pattern = balance(glob.pattern, '[', ']');
    glob.escape(glob.pattern);

    // if the pattern has `**`
    if (tok.is.globstar) {
      glob.pattern = collapse(glob.pattern, '/**');
      glob.pattern = collapse(glob.pattern, '**/');
      glob._replace(/\*{2,}/g, '**');

      // 'foo/*'
      glob._replace(/(\w+)\*(?!\/)/g, '$1[^/]*?', true);
      glob._replace(/\*\*\/\*(\w)/g, globstar(opts.dot) + '\\/(?!\\.)(?=.)[^/]*?$1', true);
      glob._replace(/\*\*\/(.)/g, '(?:**\\/|)$1');

      // 'foo/**' or '{**,*}', but not 'foo**'
      if (tok.path.dirname !== '' || /,\*\*|\*\*,/.test(glob.orig)) {
        glob._replace('**', globstar(opts.dot), true);
      }
    }

    // ends with /*
    glob._replace(/\/\*$/, '\\/' + oneStar(opts.dot), true);
    // ends with *, no slashes
    glob._replace(/(?!\/)\*$/, star, true);
    // has 'n*.' (partial wildcard w/ file extension)
    glob._replace(/([^\/]+)\*/, '$1' + oneStar(true), true);
    // has '*'
    glob._replace('*', oneStar(opts.dot), true);
    glob._replace('?.', '?\\.', true);
    glob._replace('?:', '?:', true);

    glob._replace(/\?+/g, function (match) {
      var len = match.length;
      if (len === 1) {
        return qmark;
      }
      return qmark + '{' + len + '}';
    });

    // escape '.abc' => '\\.abc'
    glob._replace(/\.([*\w]+)/g, '\\.$1');
    // fix '[^\\\\/]'
    glob._replace(/\[\^[\\\/]+\]/g, qmark);
    // '///' => '\/'
    glob._replace(/\/+/g, '\\/');
    // '\\\\\\' => '\\'
    glob._replace(/\\{2,}/g, '\\');
  }

  // unescape previously escaped patterns
  glob.unescape(glob.pattern);
  glob._replace('__UNESC_STAR__', '*');

  // escape dots that follow qmarks
  glob._replace('?.', '?\\.');

  // remove unnecessary slashes in character classes
  glob._replace('[^\\/]', qmark);

  if (glob.pattern.length > 1) {
    if (glob.pattern.indexOf('\\/') === 0 && glob.pattern.indexOf('\\/(?!\\.)(?=.)') !== 0) {
      glob.pattern = '\\/(?!\\.)(?=.)' + glob.pattern.slice(2);
    } else if (/^[\[?*]/.test(glob.pattern)) {
      glob.pattern = '(?!\\.)(?=.)' + glob.pattern;
    }
  }
  return glob;
}

/**
 * Special cases. This is somewhat of a placeholder
 * for more advanced logic.
 */

function specialCase(glob) {
  if (glob === '\\') {
    return true;
  }
  return false;
}

/**
 * Collapse repeated character sequences.
 *
 * ```js
 * collapse('a/../../../b', '../');
 * //=> 'a/../b'
 * ```
 *
 * @param  {String} `str`
 * @param  {String} `ch` Character sequence to collapse
 * @return {String}
 */

function collapse(str, ch) {
  var res = str.split(ch);
  var isFirst = res[0] === '';
  var isLast = res[res.length - 1] === '';
  res = res.filter(Boolean);
  if (isFirst) res.unshift('');
  if (isLast) res.push('');
  return res.join(ch);
}

/**
 * Negate slashes in exclusion ranges, per glob spec:
 *
 * ```js
 * negateSlash('[^foo]');
 * //=> '[^\\/foo]'
 * ```
 *
 * @param  {String} `str` glob pattern
 * @return {String}
 */

function negateSlash(str) {
  return str.replace(/\[\^([^\]]*?)\]/g, function (match, inner) {
    if (inner.indexOf('/') === -1) {
      inner = '\\/' + inner;
    }
    return '[^' + inner + ']';
  });
}

/**
 * Escape imbalanced braces/bracket. This is a very
 * basic, naive implementation that only does enough
 * to serve the purpose.
 */

function balance(str, a, b) {
  var aarr = str.split(a);
  var alen = aarr.join('').length;
  var blen = str.split(b).join('').length;

  if (alen !== blen) {
    str = aarr.join('\\' + a);
    return str.split(b).join('\\' + b);
  }
  return str;
}

/**
 * Special patterns to be converted to regex.
 * Heuristics are used to simplify patterns
 * and speed up processing.
 */

var qmark       = '[^/]';
var star        = qmark + '*?';
var nodot       = '(?!\\.)(?=.)';
var dotfileGlob = '(?:\\/|^)\\.{1,2}(?:$|\\/)';
var twoStarDot  = '(?:(?!' + dotfileGlob + ').)*?';

/**
 * Create a regex for `*`.
 *
 * If `dot` is true, or the pattern does not begin with
 * a leading star, then return the simpler regex.
 */

function oneStar(dotfile) {
  return dotfile ? '(?!(?:\\/|^)\\.{1,2}(?:$|\\/))(?=.)' + star : (nodot + star);
}

function globstar(dotfile) {
  if (dotfile) { return twoStarDot; }
  return '(?:(?!(?:\\/|^)\\.).)*?';
}
<commit_msg>start work on enabling extended globs by default<commit_after>/*!
 * micromatch <https://github.com/jonschlinkert/micromatch>
 *
 * Copyright (c) 2014-2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var utils = require('./utils');
var Glob = require('./glob');

/**
 * Expose `expand`
 */

module.exports = expand;

/**
 * Expand a glob pattern to resolve braces and
 * similar patterns before converting to regex.
 *
 * @param  {String|Array} `pattern`
 * @param  {Array} `files`
 * @param  {Options} `opts`
 * @return {Array}
 */

function expand(pattern, options) {
  if (typeof pattern !== 'string') {
    throw new TypeError('micromatch.expand(): argument should be a string.');
  }

  var glob = new Glob(pattern, options || {});
  var opts = glob.options;
  opts.dot = opts.dot || opts.dotfiles;
  opts.extglob = true;

  if (typeof opts.braces === 'undefined' && typeof opts.nobraces === 'undefined') {
    opts.braces = true;
  }

  // return early if glob pattern matches special patterns
  if (specialCase(pattern) && opts.safemode) {
    return new RegExp(utils.escapeRe(pattern), 'g');
  }

  if (glob.pattern === '.*') {
    return {
      pattern: '\\.' + star,
      tokens: tok,
      options: opts
    };
  }

  if (glob.pattern === '.') {
    return {
      pattern: '\\.',
      tokens: tok,
      options: opts
    };
  }

  if (glob.pattern === '*') {
    return {
      pattern: oneStar(opts.dot),
      tokens: tok,
      options: opts
    };
  }

  // parse the glob pattern into tokens
  glob.parse();
  var tok = glob.tokens;
  tok.is.negated = opts.negated;

  if (tok.is.dotfile) {
    opts.dot = true;
  }

  // check for braces with a dotfile pattern
  if (/[{,]\./.test(glob.pattern)) {
    opts.makeRe = false;
    opts.dot = true;
  }

  if (opts.nonegate !== true) {
    opts.negated = glob.negated;
  }

  // if the leading character is a dot or a slash, escape it
  if (glob.pattern.charAt(0) === '.' && glob.pattern.charAt(1) !== '/') {
    glob.pattern = '\\' + glob.pattern;
  }

  /**
   * Extended globs
   */

  // expand brackets, e.g `[[:alpha:]]`
  glob.track('before brackets');
  if (tok.is.brackets) {
    glob.brackets();
  }
  glob.track('after brackets');

  // expand braces, e.g `{1..5}`
  glob.track('before braces');
  if (tok.is.braces) {
    glob.braces();
  }
  glob.track('after braces');

  // expand extglobs, e.g `foo/!(a|b)`
  glob.track('before extglob');
  if (tok.is.extglob) {
    glob.extglob();
  }
  glob.track('after extglob');

  glob._replace('[!', '[^');
  glob._replace('(?', '(%~');
  glob._replace('[]', '\\[\\]');
  glob._replace('/[', '/(?!\\.)(?=.)[', true);
  glob._replace('/?', '/(?!\\.)(?=.)[^/]', true);
  glob._replace('/.', '/(?=.)\\.', true);

  // windows drives
  glob._replace(/^(\w):([\\\/]+?)/gi, '(?=.)$1:$2', true);

  // negate slashes in exclusion ranges
  if (glob.pattern.indexOf('[^') !== -1) {
    glob.pattern = negateSlash(glob.pattern);
  }

  if (opts.globstar !== false && glob.pattern === '**') {
     glob.pattern = globstar(opts.dot);

  } else {
    // '/*/*/*' => '(?:/*){3}'
    glob._replace(/(\/\*)+/g, function (match) {
      var len = match.length / 2;
      if (len === 1) { return match; }
      return '(?:\\/*){' + len + '}';
    });

    glob.pattern = balance(glob.pattern, '[', ']');
    glob.escape(glob.pattern);

    // if the pattern has `**`
    if (tok.is.globstar) {
      glob.pattern = collapse(glob.pattern, '/**');
      glob.pattern = collapse(glob.pattern, '**/');
      glob._replace(/\*{2,}/g, '**');

      // 'foo/*'
      glob._replace(/(\w+)\*(?!\/)/g, '$1[^/]*?', true);
      glob._replace(/\*\*\/\*(\w)/g, globstar(opts.dot) + '\\/(?!\\.)(?=.)[^/]*?$1', true);
      glob._replace(/\*\*\/(.)/g, '(?:**\\/|)$1');

      // 'foo/**' or '{**,*}', but not 'foo**'
      if (tok.path.dirname !== '' || /,\*\*|\*\*,/.test(glob.orig)) {
        glob._replace('**', globstar(opts.dot), true);
      }
    }

    // ends with /*
    glob._replace(/\/\*$/, '\\/' + oneStar(opts.dot), true);
    // ends with *, no slashes
    glob._replace(/(?!\/)\*$/, star, true);
    // has 'n*.' (partial wildcard w/ file extension)
    glob._replace(/([^\/]+)\*/, '$1' + oneStar(true), true);
    // has '*'
    glob._replace('*', oneStar(opts.dot), true);
    glob._replace('?.', '?\\.', true);
    glob._replace('?:', '?:', true);

    glob._replace(/\?+/g, function (match) {
      var len = match.length;
      if (len === 1) {
        return qmark;
      }
      return qmark + '{' + len + '}';
    });

    // escape '.abc' => '\\.abc'
    glob._replace(/\.([*\w]+)/g, '\\.$1');
    // fix '[^\\\\/]'
    glob._replace(/\[\^[\\\/]+\]/g, qmark);
    // '///' => '\/'
    glob._replace(/\/+/g, '\\/');
    // '\\\\\\' => '\\'
    glob._replace(/\\{2,}/g, '\\');
  }

  // unescape previously escaped patterns
  glob.unescape(glob.pattern);
  glob._replace('__UNESC_STAR__', '*');

  // escape dots that follow qmarks
  glob._replace('?.', '?\\.');

  // remove unnecessary slashes in character classes
  glob._replace('[^\\/]', qmark);

  if (glob.pattern.length > 1) {
    if (glob.pattern.indexOf('\\/') === 0 && glob.pattern.indexOf('\\/(?!\\.)(?=.)') !== 0) {
      glob.pattern = '\\/(?!\\.)(?=.)' + glob.pattern.slice(2);
    } else if (/^[\[?*]/.test(glob.pattern) && opts.dot !== true) {
      glob.pattern = '(?!\\.)(?=.)' + glob.pattern;
    }
  }
  return glob;
}

/**
 * Special cases. This is somewhat of a placeholder
 * for more advanced logic.
 */

function specialCase(glob) {
  if (glob === '\\') {
    return true;
  }
  return false;
}

/**
 * Collapse repeated character sequences.
 *
 * ```js
 * collapse('a/../../../b', '../');
 * //=> 'a/../b'
 * ```
 *
 * @param  {String} `str`
 * @param  {String} `ch` Character sequence to collapse
 * @return {String}
 */

function collapse(str, ch) {
  var res = str.split(ch);
  var isFirst = res[0] === '';
  var isLast = res[res.length - 1] === '';
  res = res.filter(Boolean);
  if (isFirst) res.unshift('');
  if (isLast) res.push('');
  return res.join(ch);
}

/**
 * Negate slashes in exclusion ranges, per glob spec:
 *
 * ```js
 * negateSlash('[^foo]');
 * //=> '[^\\/foo]'
 * ```
 *
 * @param  {String} `str` glob pattern
 * @return {String}
 */

function negateSlash(str) {
  return str.replace(/\[\^([^\]]*?)\]/g, function (match, inner) {
    if (inner.indexOf('/') === -1) {
      inner = '\\/' + inner;
    }
    return '[^' + inner + ']';
  });
}

/**
 * Escape imbalanced braces/bracket. This is a very
 * basic, naive implementation that only does enough
 * to serve the purpose.
 */

function balance(str, a, b) {
  var aarr = str.split(a);
  var alen = aarr.join('').length;
  var blen = str.split(b).join('').length;

  if (alen !== blen) {
    str = aarr.join('\\' + a);
    return str.split(b).join('\\' + b);
  }
  return str;
}

/**
 * Special patterns to be converted to regex.
 * Heuristics are used to simplify patterns
 * and speed up processing.
 */

var qmark       = '[^/]';
var star        = qmark + '*?';
var nodot       = '(?!\\.)(?=.)';
var dotfileGlob = '(?:\\/|^)\\.{1,2}(?:$|\\/)';
var twoStarDot  = '(?:(?!' + dotfileGlob + ').)*?';

/**
 * Create a regex for `*`.
 *
 * If `dot` is true, or the pattern does not begin with
 * a leading star, then return the simpler regex.
 */

function oneStar(dotfile) {
  return dotfile ? '(?!(?:\\/|^)\\.{1,2}(?:$|\\/))(?=.)' + star : (nodot + star);
}

function globstar(dotfile) {
  if (dotfile) { return twoStarDot; }
  return '(?:(?!(?:\\/|^)\\.).)*?';
}
<|endoftext|>


<commit_before>var fs = require("fs"),
    chalk = require('chalk'),
    yaml = require('js-yaml');

module.exports = {

  defaultOptions : {
    'min_wait' : 1000,
    'max_wait' : 3000
  },

  // Validates input file is a swagger spec
  validFile : function(filename) {
    try {
      // Exists?
      stats = fs.lstatSync(filename);
      // Extension is .yaml or .json ?
      return (['yaml','json'].indexOf(filename.split('.').pop().toLowerCase()) != -1);
    } catch (e) {
      return false;
    }
  },

  // Convert a file
  convertFile : function(filename, options) {
    var contents = fs.readFileSync(filename);
    ext = filename.split('.').pop().toLowerCase();
    if (ext == "yaml") {
      return this.convertYAML(contents, options);
    } else {
      return this.convertJSON(contents, options);
    }
  },

  // Convert a JSON string
  convertJSON : function(contents, options) {
    return this.convertSpec(JSON.parse(contents), options);

  },

  // Convert a YAML string
  convertYAML : function(contents, options) {
    console.info(chalk.yellow("\nWarning: Currently $ref is not supported when using the YAML version and it imght results in bugs.\n"));
    return this.convertSpec(yaml.safeLoad(contents), options);
  },

  // Convert a swagger JSON Object
  convertSpec : function(swgObj, options) {
    var actualOptions = options;
    var locustfile = ""

    if (!actualOptions['host'] && swgObj['host']) { actualOptions['host'] = 'https://' + swgObj['host']}
    locustfile += this.locustfileHeader(options);
    for(path in swgObj.paths) {
      if (swgObj.paths[path]['get']) {
        locustfile += this.HTTPLocustGetFromPath(path, swgObj.paths[path]['get'], swgObj['basePath']);
      }
    }
    locustfile += this.locustfileFooter(options);

    return locustfile;
  },

  // Outputs a locustfile header (including class def)
  locustfileHeader : function(options) {
    var header = ""
    header += "from locust import HttpLocust, TaskSet, task\n"
    header += "\n"
    header += "class MyTaskSet(TaskSet):\n"

    return header;
  },

  // Outputs the locust class and the settings (host, min, max, etc).
  locustfileFooter : function(options) {
    var footer = ""
    footer += "class MyLocust(HttpLocust):\n"
    footer += "    task_set = MyTaskSet\n"
    for(currOpt in options){
      footer += "    "+currOpt+" = "+options[currOpt]+"\n"
    }
    return footer;
  },

  // Creates an HTTP Get task from swagger path object
  HTTPLocustGetFromPath : function(pathName, pathObj, basePath) {
    var task = "";
    task += "    @task\n";
    task += "    def "+this.taskNameFromPath(pathName, 'get')+"(self):\n";
    task += '        self.client.get("'+basePath+this.resolvedPathWithDefaults(pathName, pathObj)+'")'+"\n\n";

    return task;
  },

  // Normalizes paths to py method name (GET /path/to/{id} will become get_path_to_id)
  taskNameFromPath : function (path, method) {
    return method+path.toString().replace(/\//g,"_").replace(/[\{}]/g, "")
  },

  // Replaces Path-Parameters' holders in path with their default values.
  // Example: /path/to/{id} -> /path/to/1
  resolvedPathWithDefaults : function(pathName, pathObj) {
    var resolvedPath = pathName;

    // If path has path params, replace them with their default values
    if (pathName.indexOf("{") != -1) {
      var pathParamsNames = pathName.split("{");
      pathParamsNames = pathParamsNames.slice(1, pathParamsNames.length).map(function(pt){return pt.split("}").shift()});
      for (currParam in pathParamsNames) {
        resolvedPath = resolvedPath.replace('{'+pathParamsNames[currParam]+'}', this.getParamDefault(pathParamsNames[currParam], pathObj['parameters']));
      }
    }

    // Collect required Query String Parameters (and their defaults)
    var requiredQSParams = [];
    for (currPathParam in pathObj['parameters']){
      if (pathObj['parameters'][currPathParam]['in'] == 'query' && pathObj['parameters'][currPathParam]['required']) {
        requiredQSParams.push(pathObj['parameters'][currPathParam]['name']+'='+pathObj['parameters'][currPathParam]['default']);
      }
    }

    // Add Query String to path
    if (requiredQSParams.length > 0) {
      resolvedPath += "?"+requiredQSParams.join("&")
    }

    return resolvedPath;
  },

  // Finds the default value of parameter by its name.
  getParamDefault : function(paramName, pathParameters)  {
    for (currPathParam in pathParameters) {
      if (pathParameters[currPathParam]['name'] == paramName) {
        return pathParameters[currPathParam]['default'];
      }
    }

    throw new Error("Couldn't find default value of path param "+paramName);
  }
}
<commit_msg>quote "host"<commit_after>var fs = require("fs"),
    chalk = require('chalk'),
    yaml = require('js-yaml');

module.exports = {

  defaultOptions : {
    'min_wait' : 1000,
    'max_wait' : 3000
  },

  // Validates input file is a swagger spec
  validFile : function(filename) {
    try {
      // Exists?
      stats = fs.lstatSync(filename);
      // Extension is .yaml or .json ?
      return (['yaml','json'].indexOf(filename.split('.').pop().toLowerCase()) != -1);
    } catch (e) {
      return false;
    }
  },

  // Convert a file
  convertFile : function(filename, options) {
    var contents = fs.readFileSync(filename);
    ext = filename.split('.').pop().toLowerCase();
    if (ext == "yaml") {
      return this.convertYAML(contents, options);
    } else {
      return this.convertJSON(contents, options);
    }
  },

  // Convert a JSON string
  convertJSON : function(contents, options) {
    return this.convertSpec(JSON.parse(contents), options);

  },

  // Convert a YAML string
  convertYAML : function(contents, options) {
    console.info(chalk.yellow("\nWarning: Currently $ref is not supported when using the YAML version and it imght results in bugs.\n"));
    return this.convertSpec(yaml.safeLoad(contents), options);
  },

  // Convert a swagger JSON Object
  convertSpec : function(swgObj, options) {
    var actualOptions = options;
    var locustfile = ""

    if (!actualOptions['host'] && swgObj['host']) { actualOptions['host'] = 'https://' + swgObj['host']}
    locustfile += this.locustfileHeader(options);
    for(path in swgObj.paths) {
      if (swgObj.paths[path]['get']) {
        locustfile += this.HTTPLocustGetFromPath(path, swgObj.paths[path]['get'], swgObj['basePath']);
      }
    }
    locustfile += this.locustfileFooter(options);

    return locustfile;
  },

  // Outputs a locustfile header (including class def)
  locustfileHeader : function(options) {
    var header = ""
    header += "from locust import HttpLocust, TaskSet, task\n"
    header += "\n"
    header += "class MyTaskSet(TaskSet):\n"

    return header;
  },

  // Outputs the locust class and the settings (host, min, max, etc).
  locustfileFooter : function(options) {
    var footer = ""
    footer += "class MyLocust(HttpLocust):\n"
    footer += "    task_set = MyTaskSet\n"
    for(currOpt in options){
      if (currOpt == "host") {
        footer += "    "+currOpt+' = "'+options[currOpt]+'"'+"\n"
      } else {
        footer += "    "+currOpt+" = "+options[currOpt]+"\n"
      }
    }
    return footer;
  },

  // Creates an HTTP Get task from swagger path object
  HTTPLocustGetFromPath : function(pathName, pathObj, basePath) {
    var task = "";
    task += "    @task\n";
    task += "    def "+this.taskNameFromPath(pathName, 'get')+"(self):\n";
    task += '        self.client.get("'+basePath+this.resolvedPathWithDefaults(pathName, pathObj)+'")'+"\n\n";

    return task;
  },

  // Normalizes paths to py method name (GET /path/to/{id} will become get_path_to_id)
  taskNameFromPath : function (path, method) {
    return method+path.toString().replace(/\//g,"_").replace(/[\{}]/g, "")
  },

  // Replaces Path-Parameters' holders in path with their default values.
  // Example: /path/to/{id} -> /path/to/1
  resolvedPathWithDefaults : function(pathName, pathObj) {
    var resolvedPath = pathName;

    // If path has path params, replace them with their default values
    if (pathName.indexOf("{") != -1) {
      var pathParamsNames = pathName.split("{");
      pathParamsNames = pathParamsNames.slice(1, pathParamsNames.length).map(function(pt){return pt.split("}").shift()});
      for (currParam in pathParamsNames) {
        resolvedPath = resolvedPath.replace('{'+pathParamsNames[currParam]+'}', this.getParamDefault(pathParamsNames[currParam], pathObj['parameters']));
      }
    }

    // Collect required Query String Parameters (and their defaults)
    var requiredQSParams = [];
    for (currPathParam in pathObj['parameters']){
      if (pathObj['parameters'][currPathParam]['in'] == 'query' && pathObj['parameters'][currPathParam]['required']) {
        requiredQSParams.push(pathObj['parameters'][currPathParam]['name']+'='+pathObj['parameters'][currPathParam]['default']);
      }
    }

    // Add Query String to path
    if (requiredQSParams.length > 0) {
      resolvedPath += "?"+requiredQSParams.join("&")
    }

    return resolvedPath;
  },

  // Finds the default value of parameter by its name.
  getParamDefault : function(paramName, pathParameters)  {
    for (currPathParam in pathParameters) {
      if (pathParameters[currPathParam]['name'] == paramName) {
        return pathParameters[currPathParam]['default'];
      }
    }

    throw new Error("Couldn't find default value of path param "+paramName);
  }
}
<|endoftext|>


<commit_before>//= require ../view

(function () {
  'use strict';

  var app = window.app;

  var _ = window._;
  var PIXI = window.PIXI;
  var requestAnimFrame = window.requestAnimFrame;

  app.GamesShowView = app.View.extend({
    width: app.config.game.width,

    height: app.config.game.height,

    listeners: {
      users: {add: 'addCharacter', 'remove change:isDead': 'removeCharacter'}
    },

    initialize: function () {
      this.stage = new PIXI.Stage(0xffffff);
      this.renderer = new PIXI.autoDetectRenderer(this.width, this.height);
      this.characters = new app.Character.Collection();
      this.container = new PIXI.DisplayObjectContainer();
      this.container.addChild(this.layer1 = new PIXI.DisplayObjectContainer());
      this.container.addChild(this.layer2 = new PIXI.DisplayObjectContainer());
      this.container.position.x = this.width / 2;
      this.container.position.y = this.height - 100;
      this.stage.addChild(this.container);
      this.$el.append(this.renderer.view);
      this.users = this.model.get('users');
      var line = new PIXI.Graphics();
      line.lineStyle(2, 0xFF0000);
      line.moveTo(-(this.width / 2), 0);
      line.lineTo(this.width / 2, 0);
      this.layer1.addChild(line);
      this.createSawblades();
      _.bindAll(this, 'render');
      this.render();
    },

    render: function () {
      this.renderer.render(this.stage);
      requestAnimFrame(this.render);
    },

    createSawblades: function () {
      var sawBlades = _.flatten(_.map([-1, 1], function (dir) {
        return _.times(30, function (y) {
          var sawBlade = new PIXI.Sprite(PIXI.TextureCache['saw-blade.png']);
          sawBlade.anchor.x = sawBlade.anchor.y = 0.5;
          sawBlade.position.x = dir * (app.config.game.width / 2);
          sawBlade.position.y = -((0.5 + y) * (sawBlade.height / 2));
          sawBlade.scale.x = dir;
          sawBlade.rotation = Math.PI * 2 * Math.random();
          this.layer2.addChildAt(
            sawBlade,
            _.random(this.layer2.children.length)
          );
          return sawBlade;
        }, this);
      }, this));
      this.listenTo(this.model, 'step', function (dt) {
        _.each(sawBlades, function (sawBlade) {
          sawBlade.rotation += sawBlade.scale.x * Math.PI * 10 * dt;
        });
      });
    },

    addCharacter: function (user) {
      var character = new app.Character({user: user});
      this.characters.add(character);
      this.layer2.addChild(character.get('sprite'));
    },

    removeCharacter: function (user) {
      var character = this.characters.findWhere({user: user});
      if (!character) return;
      var sprite = character.get('sprite');
      this.characters.remove(character);
      this.layer2.removeChild(sprite);
      var killForce = user.get('killForce');
      new app.ExplosionPiece.Collection(null, {
        sprite: sprite,
        rows: 20,
        columns: 10,
        game: this.model,
        container: this.layer2,
        xv: (killForce && killForce.xv) || 0,
        yv: (killForce && -killForce.yv) || 0
      });
    }
  });
})();
<commit_msg>Add users already in memory to game view on init<commit_after>//= require ../view

(function () {
  'use strict';

  var app = window.app;

  var _ = window._;
  var PIXI = window.PIXI;
  var requestAnimFrame = window.requestAnimFrame;

  app.GamesShowView = app.View.extend({
    width: app.config.game.width,

    height: app.config.game.height,

    listeners: {
      users: {add: 'addCharacter', 'remove change:isDead': 'removeCharacter'}
    },

    initialize: function () {
      this.stage = new PIXI.Stage(0xffffff);
      this.renderer = new PIXI.autoDetectRenderer(this.width, this.height);
      this.characters = new app.Character.Collection();
      this.container = new PIXI.DisplayObjectContainer();
      this.container.addChild(this.layer1 = new PIXI.DisplayObjectContainer());
      this.container.addChild(this.layer2 = new PIXI.DisplayObjectContainer());
      this.container.position.x = this.width / 2;
      this.container.position.y = this.height - 100;
      this.stage.addChild(this.container);
      this.$el.append(this.renderer.view);
      this.users = this.model.get('users');
      var line = new PIXI.Graphics();
      line.lineStyle(2, 0xFF0000);
      line.moveTo(-(this.width / 2), 0);
      line.lineTo(this.width / 2, 0);
      this.layer1.addChild(line);
      this.createSawblades();
      _.each(this.users.where({isDead: false}), this.addCharacter, this);
      _.bindAll(this, 'render');
      this.render();
    },

    render: function () {
      this.renderer.render(this.stage);
      requestAnimFrame(this.render);
    },

    createSawblades: function () {
      var sawBlades = _.flatten(_.map([-1, 1], function (dir) {
        return _.times(30, function (y) {
          var sawBlade = new PIXI.Sprite(PIXI.TextureCache['saw-blade.png']);
          sawBlade.anchor.x = sawBlade.anchor.y = 0.5;
          sawBlade.position.x = dir * (app.config.game.width / 2);
          sawBlade.position.y = -((0.5 + y) * (sawBlade.height / 2));
          sawBlade.scale.x = dir;
          sawBlade.rotation = Math.PI * 2 * Math.random();
          this.layer2.addChildAt(
            sawBlade,
            _.random(this.layer2.children.length)
          );
          return sawBlade;
        }, this);
      }, this));
      this.listenTo(this.model, 'step', function (dt) {
        _.each(sawBlades, function (sawBlade) {
          sawBlade.rotation += sawBlade.scale.x * Math.PI * 10 * dt;
        });
      });
    },

    addCharacter: function (user) {
      var character = new app.Character({user: user});
      this.characters.add(character);
      this.layer2.addChild(character.get('sprite'));
    },

    removeCharacter: function (user) {
      var character = this.characters.findWhere({user: user});
      if (!character) return;
      var sprite = character.get('sprite');
      this.characters.remove(character);
      this.layer2.removeChild(sprite);
      var killForce = user.get('killForce');
      new app.ExplosionPiece.Collection(null, {
        sprite: sprite,
        rows: 20,
        columns: 10,
        game: this.model,
        container: this.layer2,
        xv: (killForce && killForce.xv) || 0,
        yv: (killForce && -killForce.yv) || 0
      });
    }
  });
})();
<|endoftext|>


<commit_before>/* eslint-disable no-use-extend-native/no-use-extend-native */

const path = require('path');

const { without, compact } = require('lodash');
const fs = require('fs-extra');
const pluralize = require('pluralize');
const stringify = require('csv-stringify/lib/sync');
const sqlString = require('sqlstring-sqlite');
const Promise = require('bluebird');
const untildify = require('untildify');

const { openDb } = require('./db');
const models = require('../models/models');
const { prepDirectory, generateFolderName } = require('./file-utils');
const logUtils = require('./log-utils');
const { setDefaultConfig } = require('./utils.js');

module.exports = async initialConfig => {
  const config = setDefaultConfig(initialConfig);
  const log = logUtils.log(config);
  const logWarning = logUtils.logWarning(config);
  const db = await openDb(config);

  // Get agency name for export folder from first line of agency.txt
  const agencies = await db.all('SELECT agency_name FROM agency;').catch(() => {
    if (config.sqlitePath === ':memory:') {
      throw new Error('No agencies found in SQLite. You are using an in-memory database - if running this from command line be sure to specify a value for `sqlitePath` in config.json other than ":memory:".');
    }

    throw new Error('No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `gtfs.import(config);`');
  });

  const agencyCount = agencies.length;
  if (agencyCount === 0) {
    throw new Error('No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `gtfs.import(config);`');
  } else if (agencyCount > 1) {
    logWarning('More than one agency is defined in config.json. Export will merge all into one GTFS file.');
  }

  log(`Starting GTFS export for ${pluralize('agency', agencyCount, true)} using SQLite database at ${config.sqlitePath}`);

  const folderName = generateFolderName(agencies[0].agency_name);
  const defaultExportPath = path.join(process.cwd(), 'gtfs-export', folderName);
  const exportPath = untildify(config.exportPath || defaultExportPath);

  await prepDirectory(exportPath);

  // Loop through each GTFS file
  const exportedFiles = await Promise.mapSeries(models, async model => {
    const filepath = path.join(exportPath, `${model.filenameBase}.txt`);
    const tableName = sqlString.escapeId(model.filenameBase);
    const lines = await db.all(`SELECT * FROM ${tableName};`);

    if (!lines || lines.length === 0) {
      if (!model.nonstandard) {
        log(`Skipping (no data) - ${model.filenameBase}.txt\r`);
      }

      return;
    }

    const excludeColumns = [
      'id',
      'arrival_timestamp',
      'departure_timestamp',
      'start_timestamp',
      'end_timestamp'
    ];

    const columns = without(model.schema.map(column => column.name), ...excludeColumns);
    const fileText = stringify(lines, { columns, header: true });
    await fs.writeFile(filepath, fileText);

    log(`Exporting - ${model.filenameBase}.txt\r`);

    return `${model.filenameBase}.txt`;
  });

  if (compact(exportedFiles).length === 0) {
    log('No GTFS data exported. Be sure to first import data into SQLite.');
    return;
  }

  log(`Completed GTFS export to ${exportPath}`);

  log(`Completed GTFS export for ${pluralize('agency', agencyCount, true)}\n`);
};
<commit_msg>Exclude timestamp fields from export<commit_after>/* eslint-disable no-use-extend-native/no-use-extend-native */

const path = require('path');

const { without, compact } = require('lodash');
const fs = require('fs-extra');
const pluralize = require('pluralize');
const stringify = require('csv-stringify/lib/sync');
const sqlString = require('sqlstring-sqlite');
const Promise = require('bluebird');
const untildify = require('untildify');

const { openDb } = require('./db');
const models = require('../models/models');
const { prepDirectory, generateFolderName } = require('./file-utils');
const logUtils = require('./log-utils');
const { setDefaultConfig } = require('./utils.js');

module.exports = async initialConfig => {
  const config = setDefaultConfig(initialConfig);
  const log = logUtils.log(config);
  const logWarning = logUtils.logWarning(config);
  const db = await openDb(config);

  // Get agency name for export folder from first line of agency.txt
  const agencies = await db.all('SELECT agency_name FROM agency;').catch(() => {
    if (config.sqlitePath === ':memory:') {
      throw new Error('No agencies found in SQLite. You are using an in-memory database - if running this from command line be sure to specify a value for `sqlitePath` in config.json other than ":memory:".');
    }

    throw new Error('No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `gtfs.import(config);`');
  });

  const agencyCount = agencies.length;
  if (agencyCount === 0) {
    throw new Error('No agencies found in SQLite. Be sure to first import data into SQLite using `gtfs-import` or `gtfs.import(config);`');
  } else if (agencyCount > 1) {
    logWarning('More than one agency is defined in config.json. Export will merge all into one GTFS file.');
  }

  log(`Starting GTFS export for ${pluralize('agency', agencyCount, true)} using SQLite database at ${config.sqlitePath}`);

  const folderName = generateFolderName(agencies[0].agency_name);
  const defaultExportPath = path.join(process.cwd(), 'gtfs-export', folderName);
  const exportPath = untildify(config.exportPath || defaultExportPath);

  await prepDirectory(exportPath);

  // Loop through each GTFS file
  const exportedFiles = await Promise.mapSeries(models, async model => {
    const filepath = path.join(exportPath, `${model.filenameBase}.txt`);
    const tableName = sqlString.escapeId(model.filenameBase);
    const lines = await db.all(`SELECT * FROM ${tableName};`);

    if (!lines || lines.length === 0) {
      if (!model.nonstandard) {
        log(`Skipping (no data) - ${model.filenameBase}.txt\r`);
      }

      return;
    }

    const excludeColumns = [
      'id',
      'arrival_timestamp',
      'departure_timestamp',
      'start_timestamp',
      'end_timestamp',
      'service_arrival_timestamp',
      'service_departure_timestamp',
      'boarding_timestamp',
      'alighting_timestamp',
      'ridership_start_timestamp',
      'ridership_end_timestamp'
    ];

    const columns = without(model.schema.map(column => column.name), ...excludeColumns);
    const fileText = stringify(lines, { columns, header: true });
    await fs.writeFile(filepath, fileText);

    log(`Exporting - ${model.filenameBase}.txt\r`);

    return `${model.filenameBase}.txt`;
  });

  if (compact(exportedFiles).length === 0) {
    log('No GTFS data exported. Be sure to first import data into SQLite.');
    return;
  }

  log(`Completed GTFS export to ${exportPath}`);

  log(`Completed GTFS export for ${pluralize('agency', agencyCount, true)}\n`);
};
<|endoftext|>