<commit_before>'use strict';

/*
 |----------------------------------------------------------------------------------------------------------------------
 | A helper class to manage task-, build- and deployment-related paths more easily throughout the whole project.
 |----------------------------------------------------------------------------------------------------------------------
 */

/**
 * More information on [JavaScript Open Standards]{@link https://github.com/jsopenstd/jsopenstd}.
 *
 * @namespace js.task
 * @version 1.0.3
 *
 * @author Richard King <richrdkng@gmail.com> [GitHub]{@link https://github.com/richrdkng}
 * @license [MIT]{@link https://github.com/jsopenstd/js-partial-foreach/blob/master/license.md}
 */

/*
 |----------------------------------------------------------------------------------------------------------------------
 | Essentials
 |----------------------------------------------------------------------------------------------------------------------
 */
const nodePath      = require('path'),
      appRoot       = require('app-root-path'),
      extend        = require('extend'),
      foreach       = require('js-partial-foreach'),
      isPresent     = require('js-partial-is-present'),
      isString      = require('js-partial-is-string'),
      isEmptyString = require('js-partial-is-empty-string'),
      isArray       = require('js-partial-is-array'),
      isObject      = require('js-partial-is-object'),
      arrayMerge    = require('js-partial-array-merge');
/*
 |----------------------------------------------------------------------------------------------------------------------
 | Exceptions
 |----------------------------------------------------------------------------------------------------------------------
 */
const InvalidGlobException     = require('./exception/InvalidGlobException'),
      InvalidPathNameException = require('./exception/InvalidPathNameException'),
      PathNotFoundException    = require('./exception/PathNotFoundException'),
      TypeException            = require('./exception/TypeException');

/**
 * @memberOf js.task
 * @class Path
 */
const self = class Path {

    /**
     * The default values of options, when adding paths.
     *
     * @const
     * @type {Object} DEFAULTS
     * @memberOf js/task/Paths
     */
    static get DEFAULTS() {
        return {
            /**
             * Whether use path.normalize(...) on passed globs.
             *
             * @type {boolean}
             * @default true
             */
            autoNormalizePath : true,

            /**
             *
             * @type {RegExp}
             * @default \s*(<[\s\-\\\/.*\w]+>)\s* /i
             */
            nameTokenPattern : /\s*(<[\s\-\\\/.*\w]+>)\s*/i,
        };
    }

    /**
     * Check whether the name is valid.
     *
     * @private
     * @method _checkName
     *
     * @param {string} name - The name of a path entry.
     *
     * @returns {void}
     */
    _checkName(name) {
        if ( ! isString(name) || isEmptyString(name) ) {
            throw new InvalidPathNameException(name);
        }
    }

    /**
     * Check whether the glob is valid.
     *
     * @private
     * @method _checkGlob
     *
     * @param {string} glob - The glob of a path.
     *
     * @returns {void}
     */
    _checkGlob(glob) {
        if ( ! isString(glob) && ! isArray(glob) ) {
            throw new InvalidGlobException(glob);
        }
    }

    /**
     * Check whether the options object is valid.
     *
     * @private
     * @method _checkOptions
     *
     * @param {string} options - The options object.
     *
     * @returns {void}
     */
    _checkOptions(options) {
        if ( isPresent(options) && ! isObject(options) ) {
            throw new TypeException(options);
        }
    }

    /**
     * Check whether the the path entry under name exists.
     *
     * @private
     * @method _checkPathExists
     *
     * @param {string} name   - The name of the path entry.
     * @param {string} [glob] - The glob of the path.
     *
     * @returns {void}
     */
    _checkPathExists(name, glob) {
        if ( ! this.hasPath(name) ) {
            throw new PathNotFoundException(name, glob);
        }
    }

    /**
     * @constructor
     */
    constructor() {
        /**
         * The root of the project determined when the Paths class was instantiated.
         *
         * @private {string}
         * @default ''
         */
        this._root = '';

        /**
         * The storage object for the named glob paths.
         *
         * @private {Object}
         * @default {}
         */
        this._paths = {};

        this.setRoot(appRoot.toString());
    }

    /**
     * Gets the glob path by the given name.
     *
     * Alias: {@link js/task/Paths#get}
     *
     * @method getPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {string|Array} The glob path.
     */
    getPath(name) {
        this._checkPathExists(name);

        return this._paths[name];
    }

    /**
     * Sets the given glob path by the given name.
     *
     * If the glob path by the same name already exists, it will be overridden.
     * Alias: {@link js/task/Paths#set}
     *
     * @method setPath
     * @memberOf js.task.Path
     *
     * @param {string}       name                     - The name of the glob path.
     * @param {string|Array} glob                     - The glob path. Can be an array of globs.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The filtered glob path.
     */
    setPath(name, glob, options) {
        this._checkName(name);
        this._checkGlob(glob);
        this._checkOptions(options);

        const processedGlob = this._processGlob(glob, options);

        this._paths[name] = processedGlob;

        return processedGlob;
    }

    /**
     * Returns whether a glob path by the given name was stored previously.
     *
     * Alias: {@link js/task/Paths#has}
     *
     * @method hasPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {boolean} Whether has a path under this name.
     */
    hasPath(name) {
        this._checkName(name);

        return name in this._paths;
    }

    /**
     * Returns whether a previously stored glob path by the given name contains the given glob path.
     *
     * If the previously stored glob path is the same (if it is a string) as -
     * or contains (if it is an array of glob paths) - the given glob path, it will return true.
     * Alias: {@link js/task/Paths#contains}
     *
     * @method containsPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     * @param {string} glob - The glob path.

     * @returns {boolean} Whether the path with a name contains the glob.
     */
    containsPath(name, glob) {
        this._checkName(name);
        this._checkGlob(glob);

        let filteredGlob,
            storedGlob;

        if (this.hasPath(name)) {
            filteredGlob = this._filterGlob(glob);
            storedGlob   = this.getPath(name);

            if (isArray(storedGlob)) {
                let contains = false;

                storedGlob.forEach((entry) => {
                    if (filteredGlob === entry) {
                        contains = true;
                    }
                });

                return contains;

            }

            return filteredGlob === storedGlob;
        }

        return false;
    }

    /**
     * Removes the glob path by the given name from the stored glob paths.
     *
     * Alias: {@link js/task/Paths#remove}
     *
     * @method removePath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {string|Array|null} The removed glob path.
     *                              If there was no matching named glob path in the stored glob paths, or
     *                              if the removal was unsuccessful by other means,
     *                              the return value will be null;
     */
    removePath(name) {
        this._checkName(name);

        let removedGlob = null;

        if (this.hasPath(name)) {
            removedGlob = this.getPath(name);

            delete this._paths[name];
        }

        return removedGlob;
    }

    /**
     * Appends the given glob path to the given name.
     *
     * If there is no stored glob path under the given name,
     * it will be added anyway, as just as it was added via setPath(...).
     * Alias: {@link js/task/Paths#appendTo}
     *
     * @method appendToPath
     * @memberOf js.task.Path
     *
     * @param {string}       name                     - The name of the glob path.
     * @param {string|Array} glob                     - The glob path. Can be an array of globs.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The new glob path, contains the appended glob path.
     */
    appendToPath(name, glob, options) {
        this._checkName(name);
        this._checkGlob(glob);
        this._checkOptions(options);

        let filteredGlob;

        if ( ! this.hasPath(name)) {
            filteredGlob = this.addPath(name, glob, options);

        } else {
            // only append to the given name, if the stored glob isn't equal to/doesn't contain the given glob
            if ( ! this.containsPath(name, glob)) {
                let storedGlob = this.getPath(name);

                // if the stored glob isn't an array, convert it
                if ( ! isArray(storedGlob) ) {
                    storedGlob = [storedGlob];
                }

                filteredGlob = this._processGlob(glob, options);

                // if the processed glob isn't an array, convert it
                if ( ! isArray(filteredGlob)) {
                    filteredGlob = [filteredGlob];
                }

                filteredGlob = arrayMerge(storedGlob, filteredGlob);

                this._paths[name] = filteredGlob;

            } else {
                filteredGlob = this.getPath(name);
            }
        }

        return filteredGlob;
    }

    /**
     * Removes the given glob path by the given name.
     *
     * After the successful removal of the given glob path,
     * if the given name will be empty (as the last glob path was removed),
     * it will be removed from the stored named glob paths.
     * Alias: {@link js/task/Paths#removeFrom}
     *
     * @method removeFromPath
     * @memberOf js.task.Path
     *
     * @param {string}       name - The name of the glob path.
     * @param {string|Array} glob - The glob path. Can be an array of globs.
     *
     * @returns {string|Array|null} The removed glob path.
     *                              If there was no matching named glob path in the stored glob paths, or
     *                              if the removal was unsuccessful by other means,
     *                              the return value will be null;
     */
    removeFromPath(name, glob) {
        this._checkName(name);
        this._checkGlob(glob);

        let removedGlob = null;

        if (this.hasPath(name) && this.containsPath(name, glob)) {
            let processedGlob = this._processGlob(glob),
                storedGlob    = this.getPath(name);

            if (isString(processedGlob) && isString(storedGlob)) {
                if (processedGlob === storedGlob) {
                    this.removePath(name);
                }

                removedGlob = processedGlob;

            } else {
                if ( ! isArray(processedGlob)) {
                    processedGlob = [processedGlob];
                }

                if ( ! isArray(storedGlob)) {
                    storedGlob = [storedGlob];
                }

                processedGlob.forEach((processedGlobEntry) => {
                    const index = storedGlob.indexOf(processedGlobEntry);

                    if (index > -1) {
                        storedGlob.splice(index, 1);
                    }
                });

                if (storedGlob.length === 0) {
                    this.removePath(name);
                }

                removedGlob = processedGlob;
            }
        }

        return removedGlob;
    }

    /**
     * Returns the root glob path.
     *
     * The root glob path is determined at the instantiation of the Paths class.
     * Be cautious regarding the value of this root glob path, after the original root glob path is changed.
     *
     * @method getRoot
     * @memberOf js.task.Path
     *
     * @returns {string} The root glob path.
     */
    getRoot() {
        return this._root;
    }

    /**
     * Sets the root glob path by the given glob path.
     *
     * As the root glob path is determined at the instantiation of the Paths class,
     * be cautious, when changing the root glob path to avoid unwanted errors.
     *
     * @method setRoot
     * @memberOf js.task.Path
     *
     * @param {string} glob - The root glob path.
     *
     * @returns {string} The new, filtered root glob path.
     */
    setRoot(glob) {
        this._root = this.setPath(`root`, glob);

        return this._root;
    }

    /**
     * Returns all of the stored named glob paths.
     *
     * The returned object will be a deep copy of the original object,
     * modifying the returned object will not change the original object.
     *
     * @method getAll
     * @memberOf js.task.Path
     *
     * @returns {Object} The object containing all of the named glob paths.
     */
    getAll() {
        return extend(true, {}, this._paths);
    }

    /**
     * Removes all the stored named glob paths.
     *
     * @method removeAll
     * @memberOf js.task.Path
     *
     * @param {boolean} [removeRoot=false] - Remove the `root` named glob path too.
     *                                       Be cautious, when removing the root glob path
     *                                       to avoid unwanted errors.
     *
     * @returns {Object} Returns the removed named glob paths.
     *                   For example: { `src` : `/root/src` }
     */
    removeAll(removeRoot) {
        const removedGlobs = this.getAll();

        this._paths = {};

        if ( ! removeRoot) {
            this._paths[`root`] = this._root;

            // if removeRoot === false, the `root` named glob path will be still stored, and since `root`
            // practically wasn't removed from the stored glob paths,
            // do not return it as the part of removedGlobs
            delete removedGlobs[`root`];
        }

        return removedGlobs;
    }

    /**
     * Resolves the path by name-tokens.
     *
     * @private
     *
     * @param {string} glob - The glob path. A name-token could be e.g.: <root>
     *                        If the resolved name-token is an array containing multiple paths,
     *                        only the 1st of those paths will be used -- for more information check the source.
     *
     * @returns {string} The resolved glob path.
     */
    _resolveNameTokens(glob) {
        return glob.replace(self.DEFAULTS.nameTokenPattern, (match) => {
            const name = match.substr(1, match.length - 2);

            this._checkPathExists(name, glob);

            let path = this.getPath(name);

            // if the p consists of multiple globs in an array, only the 1st glob in the array will be used
            if (isArray(path)) {
                path = path[0];
            }

            return path;
        });
    }

    /**
     * Filters the given glob path by the passed options.
     *
     * @private
     *
     * @param {string} glob                     - The glob path.
     * @param {Object} [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                            see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string} The filtered glob path.
     */
    _filterGlob(glob, options) {
        const opt = extend(true, {}, self.DEFAULTS, options);

        let filteredGlob = this._resolveNameTokens(glob);

        if (opt.autoNormalizePath) {
            filteredGlob = nodePath.normalize(filteredGlob);
        }

        return filteredGlob;
    }

    /**
     * Processes the given glob path by the given options.
     *
     * @private
     *
     * @param {string|Array} glob                     - The glob path.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The filtered glob path.
     */
    _processGlob(glob, options) {
        let filteredGlob;

        if (isArray(glob)) {
            filteredGlob = [];

            foreach(
                glob,
                (entry) => {
                    filteredGlob.push(this._filterGlob(entry, options));
                }
            );

        } else {
            filteredGlob = this._filterGlob(glob, options);
        }

        return filteredGlob;
    }
};

const Path = new self();

// add `.exception` namespace for easy exception access
extend(
    Path,
    {
        exception :
        {
            InvalidGlobException,
            InvalidPathNameException,
            PathNotFoundException,
            TypeException,
        },
    }
);

module.exports = Path;
<commit_msg>Fix minor semantic in doc<commit_after>'use strict';

/*
 |----------------------------------------------------------------------------------------------------------------------
 | A helper class to manage task-, build- and deployment-related paths more easily throughout the whole project.
 |----------------------------------------------------------------------------------------------------------------------
 */

/**
 * More information on [JavaScript Open Standards]{@link https://github.com/jsopenstd/jsopenstd}.
 *
 * @namespace js.task
 * @version 1.0.3
 *
 * @author Richard King <richrdkng@gmail.com> [GitHub]{@link https://github.com/richrdkng}
 * @license [MIT]{@link https://github.com/jsopenstd/js-partial-foreach/blob/master/license.md}
 */

/*
 |----------------------------------------------------------------------------------------------------------------------
 | Essential modules
 |----------------------------------------------------------------------------------------------------------------------
 */
const nodePath      = require('path'),
      appRoot       = require('app-root-path'),
      extend        = require('extend'),
      foreach       = require('js-partial-foreach'),
      isPresent     = require('js-partial-is-present'),
      isString      = require('js-partial-is-string'),
      isEmptyString = require('js-partial-is-empty-string'),
      isArray       = require('js-partial-is-array'),
      isObject      = require('js-partial-is-object'),
      arrayMerge    = require('js-partial-array-merge');
/*
 |----------------------------------------------------------------------------------------------------------------------
 | Exceptions
 |----------------------------------------------------------------------------------------------------------------------
 */
const InvalidGlobException     = require('./exception/InvalidGlobException'),
      InvalidPathNameException = require('./exception/InvalidPathNameException'),
      PathNotFoundException    = require('./exception/PathNotFoundException'),
      TypeException            = require('./exception/TypeException');

/**
 * @memberOf js.task
 * @class Path
 */
const self = class Path {

    /**
     * The default values of options, when adding paths.
     *
     * @const
     * @type {Object} DEFAULTS
     * @memberOf js/task/Paths
     */
    static get DEFAULTS() {
        return {
            /**
             * Whether use path.normalize(...) on passed globs.
             *
             * @type {boolean}
             * @default true
             */
            autoNormalizePath : true,

            /**
             *
             * @type {RegExp}
             * @default \s*(<[\s\-\\\/.*\w]+>)\s* /i
             */
            nameTokenPattern : /\s*(<[\s\-\\\/.*\w]+>)\s*/i,
        };
    }

    /**
     * Check whether the name is valid.
     *
     * @private
     * @method _checkName
     *
     * @param {string} name - The name of a path entry.
     *
     * @returns {void}
     */
    _checkName(name) {
        if ( ! isString(name) || isEmptyString(name) ) {
            throw new InvalidPathNameException(name);
        }
    }

    /**
     * Check whether the glob is valid.
     *
     * @private
     * @method _checkGlob
     *
     * @param {string} glob - The glob of a path.
     *
     * @returns {void}
     */
    _checkGlob(glob) {
        if ( ! isString(glob) && ! isArray(glob) ) {
            throw new InvalidGlobException(glob);
        }
    }

    /**
     * Check whether the options object is valid.
     *
     * @private
     * @method _checkOptions
     *
     * @param {string} options - The options object.
     *
     * @returns {void}
     */
    _checkOptions(options) {
        if ( isPresent(options) && ! isObject(options) ) {
            throw new TypeException(options);
        }
    }

    /**
     * Check whether the the path entry under name exists.
     *
     * @private
     * @method _checkPathExists
     *
     * @param {string} name   - The name of the path entry.
     * @param {string} [glob] - The glob of the path.
     *
     * @returns {void}
     */
    _checkPathExists(name, glob) {
        if ( ! this.hasPath(name) ) {
            throw new PathNotFoundException(name, glob);
        }
    }

    /**
     * @constructor
     */
    constructor() {
        /**
         * The root of the project determined when the Paths class was instantiated.
         *
         * @private {string}
         * @default ''
         */
        this._root = '';

        /**
         * The storage object for the named glob paths.
         *
         * @private {Object}
         * @default {}
         */
        this._paths = {};

        this.setRoot(appRoot.toString());
    }

    /**
     * Gets the glob path by the given name.
     *
     * Alias: {@link js/task/Paths#get}
     *
     * @method getPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {string|Array} The glob path.
     */
    getPath(name) {
        this._checkPathExists(name);

        return this._paths[name];
    }

    /**
     * Sets the given glob path by the given name.
     *
     * If the glob path by the same name already exists, it will be overridden.
     * Alias: {@link js/task/Paths#set}
     *
     * @method setPath
     * @memberOf js.task.Path
     *
     * @param {string}       name                     - The name of the glob path.
     * @param {string|Array} glob                     - The glob path. Can be an array of globs.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The filtered glob path.
     */
    setPath(name, glob, options) {
        this._checkName(name);
        this._checkGlob(glob);
        this._checkOptions(options);

        const processedGlob = this._processGlob(glob, options);

        this._paths[name] = processedGlob;

        return processedGlob;
    }

    /**
     * Returns whether a glob path by the given name was stored previously.
     *
     * Alias: {@link js/task/Paths#has}
     *
     * @method hasPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {boolean} Whether has a path under this name.
     */
    hasPath(name) {
        this._checkName(name);

        return name in this._paths;
    }

    /**
     * Returns whether a previously stored glob path by the given name contains the given glob path.
     *
     * If the previously stored glob path is the same (if it is a string) as -
     * or contains (if it is an array of glob paths) - the given glob path, it will return true.
     * Alias: {@link js/task/Paths#contains}
     *
     * @method containsPath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     * @param {string} glob - The glob path.

     * @returns {boolean} Whether the path with a name contains the glob.
     */
    containsPath(name, glob) {
        this._checkName(name);
        this._checkGlob(glob);

        let filteredGlob,
            storedGlob;

        if (this.hasPath(name)) {
            filteredGlob = this._filterGlob(glob);
            storedGlob   = this.getPath(name);

            if (isArray(storedGlob)) {
                let contains = false;

                storedGlob.forEach((entry) => {
                    if (filteredGlob === entry) {
                        contains = true;
                    }
                });

                return contains;

            }

            return filteredGlob === storedGlob;
        }

        return false;
    }

    /**
     * Removes the glob path by the given name from the stored glob paths.
     *
     * Alias: {@link js/task/Paths#remove}
     *
     * @method removePath
     * @memberOf js.task.Path
     *
     * @param {string} name - The name of the glob path.
     *
     * @returns {string|Array|null} The removed glob path.
     *                              If there was no matching named glob path in the stored glob paths, or
     *                              if the removal was unsuccessful by other means,
     *                              the return value will be null;
     */
    removePath(name) {
        this._checkName(name);

        let removedGlob = null;

        if (this.hasPath(name)) {
            removedGlob = this.getPath(name);

            delete this._paths[name];
        }

        return removedGlob;
    }

    /**
     * Appends the given glob path to the given name.
     *
     * If there is no stored glob path under the given name,
     * it will be added anyway, as just as it was added via setPath(...).
     * Alias: {@link js/task/Paths#appendTo}
     *
     * @method appendToPath
     * @memberOf js.task.Path
     *
     * @param {string}       name                     - The name of the glob path.
     * @param {string|Array} glob                     - The glob path. Can be an array of globs.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The new glob path, contains the appended glob path.
     */
    appendToPath(name, glob, options) {
        this._checkName(name);
        this._checkGlob(glob);
        this._checkOptions(options);

        let filteredGlob;

        if ( ! this.hasPath(name)) {
            filteredGlob = this.addPath(name, glob, options);

        } else {
            // only append to the given name, if the stored glob isn't equal to/doesn't contain the given glob
            if ( ! this.containsPath(name, glob)) {
                let storedGlob = this.getPath(name);

                // if the stored glob isn't an array, convert it
                if ( ! isArray(storedGlob) ) {
                    storedGlob = [storedGlob];
                }

                filteredGlob = this._processGlob(glob, options);

                // if the processed glob isn't an array, convert it
                if ( ! isArray(filteredGlob)) {
                    filteredGlob = [filteredGlob];
                }

                filteredGlob = arrayMerge(storedGlob, filteredGlob);

                this._paths[name] = filteredGlob;

            } else {
                filteredGlob = this.getPath(name);
            }
        }

        return filteredGlob;
    }

    /**
     * Removes the given glob path by the given name.
     *
     * After the successful removal of the given glob path,
     * if the given name will be empty (as the last glob path was removed),
     * it will be removed from the stored named glob paths.
     * Alias: {@link js/task/Paths#removeFrom}
     *
     * @method removeFromPath
     * @memberOf js.task.Path
     *
     * @param {string}       name - The name of the glob path.
     * @param {string|Array} glob - The glob path. Can be an array of globs.
     *
     * @returns {string|Array|null} The removed glob path.
     *                              If there was no matching named glob path in the stored glob paths, or
     *                              if the removal was unsuccessful by other means,
     *                              the return value will be null;
     */
    removeFromPath(name, glob) {
        this._checkName(name);
        this._checkGlob(glob);

        let removedGlob = null;

        if (this.hasPath(name) && this.containsPath(name, glob)) {
            let processedGlob = this._processGlob(glob),
                storedGlob    = this.getPath(name);

            if (isString(processedGlob) && isString(storedGlob)) {
                if (processedGlob === storedGlob) {
                    this.removePath(name);
                }

                removedGlob = processedGlob;

            } else {
                if ( ! isArray(processedGlob)) {
                    processedGlob = [processedGlob];
                }

                if ( ! isArray(storedGlob)) {
                    storedGlob = [storedGlob];
                }

                processedGlob.forEach((processedGlobEntry) => {
                    const index = storedGlob.indexOf(processedGlobEntry);

                    if (index > -1) {
                        storedGlob.splice(index, 1);
                    }
                });

                if (storedGlob.length === 0) {
                    this.removePath(name);
                }

                removedGlob = processedGlob;
            }
        }

        return removedGlob;
    }

    /**
     * Returns the root glob path.
     *
     * The root glob path is determined at the instantiation of the Paths class.
     * Be cautious regarding the value of this root glob path, after the original root glob path is changed.
     *
     * @method getRoot
     * @memberOf js.task.Path
     *
     * @returns {string} The root glob path.
     */
    getRoot() {
        return this._root;
    }

    /**
     * Sets the root glob path by the given glob path.
     *
     * As the root glob path is determined at the instantiation of the Paths class,
     * be cautious, when changing the root glob path to avoid unwanted errors.
     *
     * @method setRoot
     * @memberOf js.task.Path
     *
     * @param {string} glob - The root glob path.
     *
     * @returns {string} The new, filtered root glob path.
     */
    setRoot(glob) {
        this._root = this.setPath(`root`, glob);

        return this._root;
    }

    /**
     * Returns all of the stored named glob paths.
     *
     * The returned object will be a deep copy of the original object,
     * modifying the returned object will not change the original object.
     *
     * @method getAll
     * @memberOf js.task.Path
     *
     * @returns {Object} The object containing all of the named glob paths.
     */
    getAll() {
        return extend(true, {}, this._paths);
    }

    /**
     * Removes all the stored named glob paths.
     *
     * @method removeAll
     * @memberOf js.task.Path
     *
     * @param {boolean} [removeRoot=false] - Remove the `root` named glob path too.
     *                                       Be cautious, when removing the root glob path
     *                                       to avoid unwanted errors.
     *
     * @returns {Object} Returns the removed named glob paths.
     *                   For example: { `src` : `/root/src` }
     */
    removeAll(removeRoot) {
        const removedGlobs = this.getAll();

        this._paths = {};

        if ( ! removeRoot) {
            this._paths[`root`] = this._root;

            // if removeRoot === false, the `root` named glob path will be still stored, and since `root`
            // practically wasn't removed from the stored glob paths,
            // do not return it as the part of removedGlobs
            delete removedGlobs[`root`];
        }

        return removedGlobs;
    }

    /**
     * Resolves the path by name-tokens.
     *
     * @private
     *
     * @param {string} glob - The glob path. A name-token could be e.g.: <root>
     *                        If the resolved name-token is an array containing multiple paths,
     *                        only the 1st of those paths will be used -- for more information check the source.
     *
     * @returns {string} The resolved glob path.
     */
    _resolveNameTokens(glob) {
        return glob.replace(self.DEFAULTS.nameTokenPattern, (match) => {
            const name = match.substr(1, match.length - 2);

            this._checkPathExists(name, glob);

            let path = this.getPath(name);

            // if the p consists of multiple globs in an array, only the 1st glob in the array will be used
            if (isArray(path)) {
                path = path[0];
            }

            return path;
        });
    }

    /**
     * Filters the given glob path by the passed options.
     *
     * @private
     *
     * @param {string} glob                     - The glob path.
     * @param {Object} [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                            see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string} The filtered glob path.
     */
    _filterGlob(glob, options) {
        const opt = extend(true, {}, self.DEFAULTS, options);

        let filteredGlob = this._resolveNameTokens(glob);

        if (opt.autoNormalizePath) {
            filteredGlob = nodePath.normalize(filteredGlob);
        }

        return filteredGlob;
    }

    /**
     * Processes the given glob path by the given options.
     *
     * @private
     *
     * @param {string|Array} glob                     - The glob path.
     * @param {Object}       [options=Paths.DEFAULTS] - The options that used to filter the glob path.
     *                                                  see: {@link js/task/Paths.DEFAULTS}
     *
     * @returns {string|Array} The filtered glob path.
     */
    _processGlob(glob, options) {
        let filteredGlob;

        if (isArray(glob)) {
            filteredGlob = [];

            foreach(
                glob,
                (entry) => {
                    filteredGlob.push(this._filterGlob(entry, options));
                }
            );

        } else {
            filteredGlob = this._filterGlob(glob, options);
        }

        return filteredGlob;
    }
};

const Path = new self();

// add `.exception` namespace for easy exception access
extend(
    Path,
    {
        exception :
        {
            InvalidGlobException,
            InvalidPathNameException,
            PathNotFoundException,
            TypeException,
        },
    }
);

module.exports = Path;
<|endoftext|>


<commit_before>/* global socket, systemLang */

function InfoAdapter(main) {

    var that = this;
    this.main = main;

    this.checkVersion = function (smaller, bigger) {
        smaller = smaller.split('.');
        bigger = bigger.split('.');
        smaller[0] = parseInt(smaller[0], 10);
        bigger[0] = parseInt(bigger[0], 10);

        if (smaller[0] > bigger[0]) {
            return false;
        } else if (smaller[0] === bigger[0]) {
            smaller[1] = parseInt(smaller[1], 10);
            bigger[1] = parseInt(bigger[1], 10);
            if (smaller[1] > bigger[1]) {
                return false;
            } else if (smaller[1] === bigger[1]) {
                smaller[2] = parseInt(smaller[2], 10);
                bigger[2] = parseInt(bigger[2], 10);
                return (smaller[2] < bigger[2]);
            } else {
                return true;
            }
        } else {
            return true;
        }
    };

    this.checkVersionBetween = function (inst, vers1, vers2) {
        return inst === vers1 || inst === vers2 || (that.checkVersion(vers1, inst) && that.checkVersion(inst, vers2));
    };

    this.showPopup = function (obj) {
        that.main.socket.emit('getState', 'info.0.last_popup', function (err, dateObj) {
            if (!err && dateObj) {
                that.checkAndSetData(obj, dateObj.val);
            }
        });
    };

    this.checkAndSetData = async function (messagesObj, date) {
        const messages = await that.checkMessages(messagesObj, date);
        if (messages.length === 1) {
            const message = messages[0];
            that.main.showMessage(message.content, message.title, message.class);
        } else if (messages.length > 1) {
            let content = "
";
            const idArray = [];
            messages.forEach(function (message) {
                if (idArray.indexOf(message.id) === -1) {
                    content += "
";
                    content += "
" + message.title + "
";
                    content += "
" + message.content + "
";
                    content += "
";
                    idArray.push(message.id);
                }
            });
            content += "
";
            that.main.showMessage(content, _("Please read these important notes:"), "error");
        }
        if (messages.length > 0) {
            that.main.socket.emit('setState', 'info.0.last_popup', {val: new Date().toISOString(), ack: true});
        }
    };

    this.checkMessages = async function (obj, date) {
        const messagesToShow = [];

        try {
            const messages = JSON.parse(obj);
            const today = new Date().getTime();
            let lastMessage = 0;
            if (date) {
                lastMessage = new Date(date).getTime();
            }
            if (messages.length > 0) {
                await asyncForEach(messages, async function (message) {
                    let showIt = true;
                    if (showIt && message['created'] && new Date(message['created']).getTime() < lastMessage) {
                        showIt = false;
                    } else if (showIt && message['date-start'] && new Date(message['date-start']).getTime() > today) {
                        showIt = false;
                    } else if (showIt && message['date-end'] && new Date(message['date-end']).getTime() < today) {
                        showIt = false;
                    } else if (showIt && message.conditions && Object.keys(message.conditions).length > 0) {
                        const adapters = that.main.tabs.adapters.curInstalled;
                        await asyncForEach(Object.keys(message.conditions), function (key) {
                            const adapter = adapters[key];
                            const condition = message.conditions[key];
                            if (!adapter && condition !== "!installed") {
                                showIt = false;
                            } else if (adapter && condition === "!installed") {
                                showIt = false;
                            } else if (adapter && condition.startsWith("equals")) {
                                const vers = condition.substring(7, condition.length - 1).trim();
                                showIt = (adapter.version === vers);
                            } else if (adapter && condition.startsWith("bigger")) {
                                const vers = condition.substring(7, condition.length - 1).trim();
                                showIt = that.checkVersion(vers, adapter.version);
                            } else if (adapter && condition.startsWith("smaller")) {
                                const vers = condition.substring(8, condition.length - 1).trim();
                                showIt = that.checkVersion(adapter.version, vers);
                            } else if (adapter && condition.startsWith("between")) {
                                const vers1 = condition.substring(8, condition.indexOf(',')).trim();
                                const vers2 = condition.substring(condition.indexOf(',') + 1, condition.length - 1).trim();
                                showIt = that.checkVersionBetween(adapter.version, vers1, vers2);
                            }
                        });
                    }

                    if (showIt) {
                        messagesToShow.push({"id": message.id, "title": message.title[systemLang], "content": message.content[systemLang], "class": message.class, "icon": message['fa-icon'], "created": message.created});
                    }
                });
            }

        } catch (err) {
        }

        return messagesToShow;
    };

    this.init = function () {
        if (that.main.objects["info.0.newsfeed"] && that.main.objects["info.0.last_popup"]) {
            that.main.socket.emit('subscribe', 'info.0.newsfeed');

            that.main.socket.on('stateChange', function (id, obj) {
                if (id === "info.0.newsfeed") {
                    that.showPopup(obj.val);
                }
            });

            that.main.socket.emit('getState', 'info.0.newsfeed', async function (err, obj) {
                if (!err && obj) {
                    that.showPopup(obj.val);
                }
            });
        } else if (!that.main.systemConfig.common.infoAdapterInstall) {

            if (that.main.objects['system.adapter.info.0']) {
                // if installed version too old
                that.main.confirmMessage(_('
Your version of the info adapter is outdated. It is strongly recommended to update this to get on the enjoyment of the innovations. Only with the new version, for example, messages from the ioBroker team can be displayed directly.

Would you like to update the info adapter?
'), _('Update info adapter'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'upgrade info');
                    }
                });
            } else {
                // if info adapter is not installed
                that.main.confirmMessage(_('
You have not installed an Info Adapter. The adapter shows you information about the system and is required to display important messages from the ioBroker team.

Do you want to install the info adapter?
'), _('Info adapter not found'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'add info 0');
                    }
                });
            }

            main.socket.emit('getObject', 'system.config', function (err, obj) {
                //ask only one time
                if (err || !obj) {
                    main.showError(_('Cannot confirm: ' + err));
                    return;
                }
                obj.common = obj.common || {};
                obj.common.infoAdapterInstall = true;
                main.socket.emit('setObject', 'system.config', obj, function (err) {
                    if (err) {
                        main.showError(err);
                    }
                });
            });
        }
    };

}<commit_msg>Reload after install<commit_after>/* global socket, systemLang */

function InfoAdapter(main) {

    var that = this;
    this.main = main;

    this.checkVersion = function (smaller, bigger) {
        smaller = smaller.split('.');
        bigger = bigger.split('.');
        smaller[0] = parseInt(smaller[0], 10);
        bigger[0] = parseInt(bigger[0], 10);

        if (smaller[0] > bigger[0]) {
            return false;
        } else if (smaller[0] === bigger[0]) {
            smaller[1] = parseInt(smaller[1], 10);
            bigger[1] = parseInt(bigger[1], 10);
            if (smaller[1] > bigger[1]) {
                return false;
            } else if (smaller[1] === bigger[1]) {
                smaller[2] = parseInt(smaller[2], 10);
                bigger[2] = parseInt(bigger[2], 10);
                return (smaller[2] < bigger[2]);
            } else {
                return true;
            }
        } else {
            return true;
        }
    };

    this.checkVersionBetween = function (inst, vers1, vers2) {
        return inst === vers1 || inst === vers2 || (that.checkVersion(vers1, inst) && that.checkVersion(inst, vers2));
    };

    this.showPopup = function (obj) {
        that.main.socket.emit('getState', 'info.0.last_popup', function (err, dateObj) {
            if (!err && dateObj) {
                that.checkAndSetData(obj, dateObj.val);
            }
        });
    };

    this.checkAndSetData = async function (messagesObj, date) {
        const messages = await that.checkMessages(messagesObj, date);
        if (messages.length === 1) {
            const message = messages[0];
            that.main.showMessage(message.content, message.title, message.class);
        } else if (messages.length > 1) {
            let content = "
";
            const idArray = [];
            messages.forEach(function (message) {
                if (idArray.indexOf(message.id) === -1) {
                    content += "
";
                    content += "
" + message.title + "
";
                    content += "
" + message.content + "
";
                    content += "
";
                    idArray.push(message.id);
                }
            });
            content += "
";
            that.main.showMessage(content, _("Please read these important notes:"), "error");
        }
        if (messages.length > 0) {
            that.main.socket.emit('setState', 'info.0.last_popup', {val: new Date().toISOString(), ack: true});
        }
    };

    this.checkMessages = async function (obj, date) {
        const messagesToShow = [];

        try {
            const messages = JSON.parse(obj);
            const today = new Date().getTime();
            let lastMessage = 0;
            if (date) {
                lastMessage = new Date(date).getTime();
            }
            if (messages.length > 0) {
                await asyncForEach(messages, async function (message) {
                    let showIt = true;
                    if (showIt && message['created'] && new Date(message['created']).getTime() < lastMessage) {
                        showIt = false;
                    } else if (showIt && message['date-start'] && new Date(message['date-start']).getTime() > today) {
                        showIt = false;
                    } else if (showIt && message['date-end'] && new Date(message['date-end']).getTime() < today) {
                        showIt = false;
                    } else if (showIt && message.conditions && Object.keys(message.conditions).length > 0) {
                        const adapters = that.main.tabs.adapters.curInstalled;
                        await asyncForEach(Object.keys(message.conditions), function (key) {
                            const adapter = adapters[key];
                            const condition = message.conditions[key];
                            if (!adapter && condition !== "!installed") {
                                showIt = false;
                            } else if (adapter && condition === "!installed") {
                                showIt = false;
                            } else if (adapter && condition.startsWith("equals")) {
                                const vers = condition.substring(7, condition.length - 1).trim();
                                showIt = (adapter.version === vers);
                            } else if (adapter && condition.startsWith("bigger")) {
                                const vers = condition.substring(7, condition.length - 1).trim();
                                showIt = that.checkVersion(vers, adapter.version);
                            } else if (adapter && condition.startsWith("smaller")) {
                                const vers = condition.substring(8, condition.length - 1).trim();
                                showIt = that.checkVersion(adapter.version, vers);
                            } else if (adapter && condition.startsWith("between")) {
                                const vers1 = condition.substring(8, condition.indexOf(',')).trim();
                                const vers2 = condition.substring(condition.indexOf(',') + 1, condition.length - 1).trim();
                                showIt = that.checkVersionBetween(adapter.version, vers1, vers2);
                            }
                        });
                    }

                    if (showIt) {
                        messagesToShow.push({"id": message.id, "title": message.title[systemLang], "content": message.content[systemLang], "class": message.class, "icon": message['fa-icon'], "created": message.created});
                    }
                });
            }

        } catch (err) {
        }

        return messagesToShow;
    };

    this.init = function () {
        if (that.main.objects["info.0.newsfeed"] && that.main.objects["info.0.last_popup"]) {
            that.main.socket.emit('subscribe', 'info.0.newsfeed');

            that.main.socket.on('stateChange', function (id, obj) {
                if (id === "info.0.newsfeed") {
                    that.showPopup(obj.val);
                }
            });

            that.main.socket.emit('getState', 'info.0.newsfeed', async function (err, obj) {
                if (!err && obj) {
                    that.showPopup(obj.val);
                }
            });
        } else if (!that.main.systemConfig.common.infoAdapterInstall) {

            if (that.main.objects['system.adapter.info.0']) {
                // if installed version too old
                that.main.confirmMessage(_('
Your version of the info adapter is outdated. It is strongly recommended to update this to get on the enjoyment of the innovations. Only with the new version, for example, messages from the ioBroker team can be displayed directly.

Would you like to update the info adapter?
'), _('Update info adapter'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'upgrade info', function (exitCode) {
                            if (!exitCode) {
                                location.reload(true);
                            }
                        });
                    }
                });
            } else {
                // if info adapter is not installed
                that.main.confirmMessage(_('
You have not installed an Info Adapter. The adapter shows you information about the system and is required to display important messages from the ioBroker team.

Do you want to install the info adapter?
'), _('Info adapter not found'), 'info', function (result) {
                    if (result) {
                        that.main.cmdExec(null, 'add info 0', function (exitCode) {
                            if (!exitCode) {
                                location.reload(true);
                            }
                        });
                    }
                });
            }

            main.socket.emit('getObject', 'system.config', function (err, obj) {
                //ask only one time
                if (err || !obj) {
                    main.showError(_('Cannot confirm: ' + err));
                    return;
                }
                obj.common = obj.common || {};
                obj.common.infoAdapterInstall = true;
                main.socket.emit('setObject', 'system.config', obj, function (err) {
                    if (err) {
                        main.showError(err);
                    }
                });
            });
        }
    };

}<|endoftext|>


<commit_before>require('./directives');
require('./controllers');
require('./services');

var dependencies = [
  'afkl.lazyImage',
  'angular-growl',
  'angulartics',
  'angulartics.segment.io',
  'hyloControllers',
  'hyloDirectives',
  'hyloServices',
  'infinite-scroll',
  'mentio',
  'newrelic-timing',
  'ngAnimate',
  'ngIdle',
  'ngResource',
  'ngSanitize',
  'ngTagsInput',
  'ngTouch',
  'ui.bootstrap',
  'ui.router'
];

if (hyloEnv.environment == 'test') {
  dependencies.push('ngMock');
  dependencies.push('ngMockE2E');
  dependencies.push('ngAnimateMock');
}

var app = angular.module('hyloApp', dependencies);

require('./routes')(app);
require('./animations')(app);
require('./filters')(app);
require('./features/mentions/userMentions')(app);
require('./services/removeTrailingSlash')(app);
require('./services/myHttpInterceptor')(app);

app.config(function ($locationProvider, growlProvider, $idleProvider) {
  $locationProvider.html5Mode(true);
  growlProvider.globalTimeToLive(5000);

  $idleProvider.idleDuration(45); // in seconds
  $idleProvider.warningDuration(1); // in seconds
});

app.run(function($anchorScroll) {
  $anchorScroll.yOffset = 54; // = @nav-height in nav.less
  $anchorScroll();
});

app.run(function(clickthroughTracker) {
  clickthroughTracker.track(location);
});

app.run(function($rootScope, $state, growl, $bodyClass) {

  $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
    if (error && _.include([401, 403], error.status)) {
      if ($rootScope.currentUser) {
        growl.addErrorMessage("You don't have permission to view that.");
      } else {
        $state.go('login', {next: format('%s%s', window.location.pathname, window.location.search)});
      }
      return;
    }

    // the part of the code that caused this should be prepared to
    // handle it (e.g. login attempt with invalid password)
    if (error && error.status == 422) {
      return;
    }

    if (error === 'login required') {
      // see e.g. routes/home.js
      return;
    }
  });

  $rootScope.$state = $state;
  $rootScope.$bodyClass = $bodyClass;
});
<commit_msg>add WebViewJavascriptBridge support, and 'loaded' message<commit_after>require('./directives');
require('./controllers');
require('./services');

var dependencies = [
  'afkl.lazyImage',
  'angular-growl',
  'angulartics',
  'angulartics.segment.io',
  'hyloControllers',
  'hyloDirectives',
  'hyloServices',
  'infinite-scroll',
  'mentio',
  'newrelic-timing',
  'ngAnimate',
  'ngIdle',
  'ngResource',
  'ngSanitize',
  'ngTagsInput',
  'ngTouch',
  'ui.bootstrap',
  'ui.router'
];

if (hyloEnv.environment == 'test') {
  dependencies.push('ngMock');
  dependencies.push('ngMockE2E');
  dependencies.push('ngAnimateMock');
}

var app = angular.module('hyloApp', dependencies);

require('./routes')(app);
require('./animations')(app);
require('./filters')(app);
require('./features/mentions/userMentions')(app);
require('./services/removeTrailingSlash')(app);
require('./services/myHttpInterceptor')(app);

app.config(function ($locationProvider, growlProvider, $idleProvider) {
  $locationProvider.html5Mode(true);
  growlProvider.globalTimeToLive(5000);

  $idleProvider.idleDuration(45); // in seconds
  $idleProvider.warningDuration(1); // in seconds
});

app.run(function($anchorScroll) {
  $anchorScroll.yOffset = 54; // = @nav-height in nav.less
  $anchorScroll();
});

app.run(function(clickthroughTracker) {
  clickthroughTracker.track(location);
});

app.run(function($rootScope, $state, growl, $bodyClass) {

  $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
    if (error && _.include([401, 403], error.status)) {
      if ($rootScope.currentUser) {
        growl.addErrorMessage("You don't have permission to view that.");
      } else {
        $state.go('login', {next: format('%s%s', window.location.pathname, window.location.search)});
      }
      return;
    }

    // the part of the code that caused this should be prepared to
    // handle it (e.g. login attempt with invalid password)
    if (error && error.status == 422) {
      return;
    }

    if (error === 'login required') {
      // see e.g. routes/home.js
      return;
    }
  });

  $rootScope.$state = $state;
  $rootScope.$bodyClass = $bodyClass;

  $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams, error) {
    if (fromState.name == "") {

      function connectWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) {
          callback(WebViewJavascriptBridge)
        } else {
          document.addEventListener('WebViewJavascriptBridgeReady', function() {
            callback(WebViewJavascriptBridge)
          }, false)
        }
      };

      connectWebViewJavascriptBridge(function(bridge) {

        /* Init your app here */

        bridge.init(function(message, responseCallback) {

          // currently does not do anything with messages from app

        });

        bridge.send("loaded");

      });
    };
  });
});
<|endoftext|>


<commit_before>// Will get replaced by gulpfile with relevant environment url
export const STUY_SPEC_API_URL = "__API_URL_HERE__";

// TODO: a Newspaper model in API that contains an issuu_config
export const ISSUU_CONFIG = 61003548;

export const STUY_SPEC_API_HEADERS = {
  headers: {
    "X-Key-Inflection": "camel",
  },
};

export const PROFILE_SLUGS = {
  illustration: "illustrators",
  photo: "photographers",
  illustrators: "illustration",
  photographers: "photo",
};

export const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
export const SPEC_REFERENCE_PATTERN =
  /<spec-reference id=(\d*)><\/spec-reference>/;
export const SPEC_IMG_CAROUSEL_PATTERN =
  /<spec-img-carousel><\/spec-img-carousel>/;
<commit_msg>updated issuu<commit_after>// Will get replaced by gulpfile with relevant environment url
export const STUY_SPEC_API_URL = "__API_URL_HERE__";

// TODO: a Newspaper model in API that contains an issuu_config
export const ISSUU_CONFIG = 61744139;

export const STUY_SPEC_API_HEADERS = {
  headers: {
    "X-Key-Inflection": "camel",
  },
};

export const PROFILE_SLUGS = {
  illustration: "illustrators",
  photo: "photographers",
  illustrators: "illustration",
  photographers: "photo",
};

export const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
export const SPEC_REFERENCE_PATTERN =
  /<spec-reference id=(\d*)><\/spec-reference>/;
export const SPEC_IMG_CAROUSEL_PATTERN =
  /<spec-img-carousel><\/spec-img-carousel>/;
<|endoftext|>


<commit_before>/*jslint browser: true */

// Export a global module.
var tangelo = {};

(function () {
    "use strict";

    // Tangelo version number.
    tangelo.version = function () {
        return "0.5-dev1";
    };

    tangelo.fatalError = function (module, msg) {
        if (msg === undefined) {
            msg = module;
            throw new Error(msg);
        }

        throw new Error("[" + module + "] " + msg);
    };

    // A function that generates an error-generating function, to be used for
    // missing dependencies (Google Maps API, JQuery UI, etc.).
    tangelo.unavailable = function (cfg) {
        var plugin = cfg.plugin,
            required = cfg.required,
            i,
            t;

        if (tangelo.isArray(required)) {
            if (required.length === 1) {
                required = required[0];
            } else if (required.length === 2) {
                required = required[0] + " and " + required[1];
            } else {
                t = "";
                for (i = 0; i < required.length - 1; i += 1) {
                    t += required[i] + ", ";
                }
                t += "and " + required[required.length - 1];

                required = t;
            }
        }

        return function () {
            tangelo.fatalError("JavaScript include error: " + plugin + " requires " + required);
        };
    };

    tangelo.identity = function (d) { return d; };

    tangelo.isNumber = function (value) {
        return typeof value === 'number';
    };

    tangelo.isBoolean = function (value) {
        return typeof value === 'boolean';
    };

    tangelo.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };

    tangelo.isObject = function (value) {
        return Object.prototype.toString.call(value) === '[object Object]';
    };

    tangelo.isString = function (value) {
        return Object.prototype.toString.call(value) === '[object String]';
    };

    tangelo.isFunction = function (value) {
        return Object.prototype.toString.call(value) === '[object Function]';
    };

    tangelo.absoluteUrl = function (path) {
        var trailing_slash = window.location.pathname[window.location.pathname.length - 1] === "/";

        if (path.length > 0) {
            if (path[0] !== "/" && path[0] !== "~") {
                path = window.location.pathname + (trailing_slash ? "" : "/") + path;
            }
        }

        return path;
    };

    tangelo.accessor = function (spec, defaultValue) {
        var parts,
            func,
            key;

        // Need a way to "clone" a function, so we can put properties on the
        // clone without affecting the original.  Code adapted from
        // http://stackoverflow.com/a/11230005/1886928).
        Function.prototype.clone = function () {
            var cloneObj = this,
                temp;

            if (this.__isClone) {
                cloneObj = this.__clonedFrom;
            }

            temp = function () {
                return cloneObj.apply(this, arguments);
            };

            for (key in this) {
                temp[key] = this[key];
            }

            temp.__isClone = true;
            temp.__clonedFrom = cloneObj;

            return temp;
        };

        if (spec === undefined || Object.keys(spec).length === 0) {
            func = function () {
                tangelo.fatalError("tangelo.accessor()", "I am an undefined accessor - you shouldn't be calling me!");
            };
            func.undefined = true;
        } else if (tangelo.isFunction(spec)) {
            func = spec.clone();
        } else if (!spec) {
            func = function () { return defaultValue; };
        } else if (spec.hasOwnProperty("value")) {
            func = function () { return spec.value; };
        } else if (spec.hasOwnProperty("index")) {
            func = function (d, i) { return i; };
        } else if (spec.hasOwnProperty("field")) {
            if (spec.field === ".") {
                func = function (d) { return d; };
                //return tangelo.identity;
            } else {
                parts = spec.field.split(".");
                func = function (d) {
                    var i;
                    for (i = 0; i < parts.length; i += 1) {
                        d = d[parts[i]];
                        if (d === undefined) {
                            return defaultValue;
                        }
                    }
                    return d;
                };
            }
        } else {
            tangelo.fatalError("tangelo.accessor()", "unknown accessor spec " + spec);
        }

        func.accessor = true;
        return func;
    };

    tangelo.appendFunction = function (f1, f2) {
        var that = this;
        if (!f1) {
            return f2;
        }
        if (!f2) {
            return f1;
        }
        return function () {
            f1.apply(that, arguments);
            f2.apply(that, arguments);
        };
    };

    // Check for the required version number.
    tangelo.requireCompatibleVersion = function (reqvstr) {
        var reqv,
            tanv,
            compatible,
            parse;

        // This function parses out the structure of a version string.
        //
        // Major version 0 version numbers contain only two parts: 0.MINOR
        //
        // Major version 1 version numbers contain three parts:
        // MAJOR.MINOR.PATCH
        //
        // Any version number may also have a trailing hyphen followed by one or
        // more non-space, non-hyphen characters: 0.MINOR-TAG, or
        // MAJOR.MINOR.PATCH-TAG
        //
        // The minor and patch numbers may be omitted; they will be filled in
        // with 0s as appropriate.
        //
        // Negative numbers and non-number strings are not allowed in the
        // version number components.
        tangelo.parse = function (s) {
            var parts,
                ver,
                tag,
                i,
                components;

            parts = s.split("-");
            if (parts.length > 1) {
                ver = parts.slice(0, -1).join("-");
                tag = parts.slice(-1)[0];
            } else {
                ver = parts[0];
                tag = parts[1];
            }

            if (!ver) {
                return null;
            }

            if (tag !== undefined && (tag.length === 0 || tag.indexOf(" ") !== -1)) {
                return null;
            }

            ver = ver.split(".").map(function (x) {
                return +x;
            });

            if (ver.length === 0) {
                return null;
            }

            for (i = 0; i < ver.length; i += 1) {
                if (isNaN(ver[i]) || ver[i] < 0) {
                    return null;
                }
            }

            components = ver[0] === 0 ? 2 : 3;
            if (ver.length > components) {
                return null;
            }

            for (i = ver.length; i < components; i += 1) {
                ver[i] = 0;
            }

            return {
                version: ver,
                tag: tag
            };
        };

        // Parse out the structures of the required version string, and the
        // current Tangelo version string.
        reqv = tangelo.parse(reqvstr);
        tanv = tangelo.parse(tangelo.version());

        // If either of them fails to parse, raise a fatal error.
        if (!tanv) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "tangelo version number is invalid: " + tangelo.version());
        } else if (!reqv) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "invalid version string: " + reqvstr);
        }

        // Run the compatibility rules.
        if (reqv.tag || tanv.tag || reqv.version[0] === 0 || tanv.version[0] === 0) {
            // If either version has a tag, or if the major version is 0, then
            // the versions must match exactly.
            compatible = reqv.tag === tanv.tag &&
                         reqv.version[0] === tanv.version[0] &&
                         reqv.version[1] === tanv.version[1] &&
                         reqv.version[2] === tanv.version[2];
        } else {
            // If there are no tags, and the major version is greater than 0,
            // then the major versions MUST match, and the required minor
            // version MUST be at most the Tangelo minor version.  If the minor
            // versions are equal, then the required patch level MUST be at most
            // the Tangelo patch level.
            compatible = reqv.version[0] === tanv.version[0] &&
                         (reqv.version[1] < tanv.version[1] || (reqv.version[1] === tanv.version[1] && reqv.version[2] <= tanv.version[2]));
        }

        return compatible;
    };
}(window.$));
<commit_msg>Removing tangelo.appendFunction()<commit_after>/*jslint browser: true */

// Export a global module.
var tangelo = {};

(function () {
    "use strict";

    // Tangelo version number.
    tangelo.version = function () {
        return "0.5-dev1";
    };

    tangelo.fatalError = function (module, msg) {
        if (msg === undefined) {
            msg = module;
            throw new Error(msg);
        }

        throw new Error("[" + module + "] " + msg);
    };

    // A function that generates an error-generating function, to be used for
    // missing dependencies (Google Maps API, JQuery UI, etc.).
    tangelo.unavailable = function (cfg) {
        var plugin = cfg.plugin,
            required = cfg.required,
            i,
            t;

        if (tangelo.isArray(required)) {
            if (required.length === 1) {
                required = required[0];
            } else if (required.length === 2) {
                required = required[0] + " and " + required[1];
            } else {
                t = "";
                for (i = 0; i < required.length - 1; i += 1) {
                    t += required[i] + ", ";
                }
                t += "and " + required[required.length - 1];

                required = t;
            }
        }

        return function () {
            tangelo.fatalError("JavaScript include error: " + plugin + " requires " + required);
        };
    };

    tangelo.identity = function (d) { return d; };

    tangelo.isNumber = function (value) {
        return typeof value === 'number';
    };

    tangelo.isBoolean = function (value) {
        return typeof value === 'boolean';
    };

    tangelo.isArray = function (value) {
        return Object.prototype.toString.call(value) === '[object Array]';
    };

    tangelo.isObject = function (value) {
        return Object.prototype.toString.call(value) === '[object Object]';
    };

    tangelo.isString = function (value) {
        return Object.prototype.toString.call(value) === '[object String]';
    };

    tangelo.isFunction = function (value) {
        return Object.prototype.toString.call(value) === '[object Function]';
    };

    tangelo.absoluteUrl = function (path) {
        var trailing_slash = window.location.pathname[window.location.pathname.length - 1] === "/";

        if (path.length > 0) {
            if (path[0] !== "/" && path[0] !== "~") {
                path = window.location.pathname + (trailing_slash ? "" : "/") + path;
            }
        }

        return path;
    };

    tangelo.accessor = function (spec, defaultValue) {
        var parts,
            func,
            key;

        // Need a way to "clone" a function, so we can put properties on the
        // clone without affecting the original.  Code adapted from
        // http://stackoverflow.com/a/11230005/1886928).
        Function.prototype.clone = function () {
            var cloneObj = this,
                temp;

            if (this.__isClone) {
                cloneObj = this.__clonedFrom;
            }

            temp = function () {
                return cloneObj.apply(this, arguments);
            };

            for (key in this) {
                temp[key] = this[key];
            }

            temp.__isClone = true;
            temp.__clonedFrom = cloneObj;

            return temp;
        };

        if (spec === undefined || Object.keys(spec).length === 0) {
            func = function () {
                tangelo.fatalError("tangelo.accessor()", "I am an undefined accessor - you shouldn't be calling me!");
            };
            func.undefined = true;
        } else if (tangelo.isFunction(spec)) {
            func = spec.clone();
        } else if (!spec) {
            func = function () { return defaultValue; };
        } else if (spec.hasOwnProperty("value")) {
            func = function () { return spec.value; };
        } else if (spec.hasOwnProperty("index")) {
            func = function (d, i) { return i; };
        } else if (spec.hasOwnProperty("field")) {
            if (spec.field === ".") {
                func = function (d) { return d; };
                //return tangelo.identity;
            } else {
                parts = spec.field.split(".");
                func = function (d) {
                    var i;
                    for (i = 0; i < parts.length; i += 1) {
                        d = d[parts[i]];
                        if (d === undefined) {
                            return defaultValue;
                        }
                    }
                    return d;
                };
            }
        } else {
            tangelo.fatalError("tangelo.accessor()", "unknown accessor spec " + spec);
        }

        func.accessor = true;
        return func;
    };

    // Check for the required version number.
    tangelo.requireCompatibleVersion = function (reqvstr) {
        var reqv,
            tanv,
            compatible,
            parse;

        // This function parses out the structure of a version string.
        //
        // Major version 0 version numbers contain only two parts: 0.MINOR
        //
        // Major version 1 version numbers contain three parts:
        // MAJOR.MINOR.PATCH
        //
        // Any version number may also have a trailing hyphen followed by one or
        // more non-space, non-hyphen characters: 0.MINOR-TAG, or
        // MAJOR.MINOR.PATCH-TAG
        //
        // The minor and patch numbers may be omitted; they will be filled in
        // with 0s as appropriate.
        //
        // Negative numbers and non-number strings are not allowed in the
        // version number components.
        tangelo.parse = function (s) {
            var parts,
                ver,
                tag,
                i,
                components;

            parts = s.split("-");
            if (parts.length > 1) {
                ver = parts.slice(0, -1).join("-");
                tag = parts.slice(-1)[0];
            } else {
                ver = parts[0];
                tag = parts[1];
            }

            if (!ver) {
                return null;
            }

            if (tag !== undefined && (tag.length === 0 || tag.indexOf(" ") !== -1)) {
                return null;
            }

            ver = ver.split(".").map(function (x) {
                return +x;
            });

            if (ver.length === 0) {
                return null;
            }

            for (i = 0; i < ver.length; i += 1) {
                if (isNaN(ver[i]) || ver[i] < 0) {
                    return null;
                }
            }

            components = ver[0] === 0 ? 2 : 3;
            if (ver.length > components) {
                return null;
            }

            for (i = ver.length; i < components; i += 1) {
                ver[i] = 0;
            }

            return {
                version: ver,
                tag: tag
            };
        };

        // Parse out the structures of the required version string, and the
        // current Tangelo version string.
        reqv = tangelo.parse(reqvstr);
        tanv = tangelo.parse(tangelo.version());

        // If either of them fails to parse, raise a fatal error.
        if (!tanv) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "tangelo version number is invalid: " + tangelo.version());
        } else if (!reqv) {
            tangelo.fatalError("tangelo.requireCompatibleVersion()", "invalid version string: " + reqvstr);
        }

        // Run the compatibility rules.
        if (reqv.tag || tanv.tag || reqv.version[0] === 0 || tanv.version[0] === 0) {
            // If either version has a tag, or if the major version is 0, then
            // the versions must match exactly.
            compatible = reqv.tag === tanv.tag &&
                         reqv.version[0] === tanv.version[0] &&
                         reqv.version[1] === tanv.version[1] &&
                         reqv.version[2] === tanv.version[2];
        } else {
            // If there are no tags, and the major version is greater than 0,
            // then the major versions MUST match, and the required minor
            // version MUST be at most the Tangelo minor version.  If the minor
            // versions are equal, then the required patch level MUST be at most
            // the Tangelo patch level.
            compatible = reqv.version[0] === tanv.version[0] &&
                         (reqv.version[1] < tanv.version[1] || (reqv.version[1] === tanv.version[1] && reqv.version[2] <= tanv.version[2]));
        }

        return compatible;
    };
}(window.$));
<|endoftext|>


<commit_before>function showAcronym () {
    var value = document.getElementById('acronymInput').value;
	if (value)
	   document.getElementById('result').innerHTML = value;
    }
}
<commit_msg>Update functions.js<commit_after>document.getElementById("searchButton").addEventListener("click", function(){
	var value = document.getElementById('acronymInput').value;
	if (value)
	    document.getElementById('result').innerHTML = value;

});
<|endoftext|>


<commit_before>(function(angular){
  /*
   * Deck module
   * Contains individual deck state and logic
   *
   * The Deck pays attention to humanistic elements of interacting with a deck
   * of cards. For the purposes of this module the order of cards is defined
   * to be the same whether face up or face down.
   *
   * The front of the card is the direction of the front of the deck array.
   * i.e. .pop()'ing off a face-down deck will return the top card
   * and .pop()'ing off a face-up deck will return the bottom card
   *
   * Understanding this we can then better introspect the movement of cards
   * through different interactions with the deck.
   *
   * For example, dealing cards reverses the order the cards were orignally
   * in in the deck where as drawing one card at a time into a hand preserves
   * that order.
   *
   * Note: Any references to top/bottom in the module or tests assume the deck
   * is face down.
   */
  angular
    .module('game.deck', [])
    .factory('DeckManager', DeckManager)
  ;

  DeckManager.$inject = [];
  function DeckManager() {
    var Deck = function(stock, name) {
      this.name = name;
      this.stock = stock;
      this.burn_pile = [];
    };

    Deck.prototype.shuffle = function (){
      // mezclar2 - from http://jsperf.com/array-shuffle-comparator/14
      var arr = this.stock;
      for (
        var i, tmp, n = arr.length;
        n;
        i = Math.floor(Math.random() * n), tmp = arr[--n], arr[n] = arr[i], arr[i] = tmp
      );
      return this;
    };

    Deck.prototype.sort = function (fn){
        fn = fn || this.fn || function(a, b) { return a - b; };

      this.stock.sort(fn);
      return this;
    };

    Deck.prototype.combine = function (prepend){
      if (prepend === true)
        this.stock = this.burn_pile.concat(this.stock);
      else
        this.stock = this.stock.concat(this.burn_pile);

      this.burn_pile = [];

      return this;
    };

    Deck.prototype.draw = function (to_draw, from_front){
      var hand;

      to_draw = to_draw || 1;

      hand = this.stock.splice(from_front ? 0 : -to_draw, to_draw);

      if (from_front)
        hand.reverse();

      return hand.length > 1 ? hand : hand[0];
    };

    Deck.prototype.deal = function (to_deal, to_hands, from_front){
      var hands = []
          , pile
          ;

      to_deal = to_deal || 1;
      to_hands = to_hands || 1;

      // XXX: Desperately needs error raising here
      // specifically if (to_deal % to_hands) != 0
      to_deal *= to_hands;

      pile = this.stock.splice(from_front ? 0 : -to_deal, to_deal);

      for (var x = 0, hand; x < to_deal; x++) {
        if (x < to_hands)
          hands.push([]);
        hand = x % to_hands;
        hands[hand].push(from_front ? pile.shift() : pile.pop());
      }

      return to_hands > 1 ? hands : hands[0];
    };

    Deck.prototype.discard = function (cards, prepend){
      if (cards.length === undefined) {
        cards = [cards];
      }

      if (prepend)
        this.burn_pile = cards.concat(this.burn_pile);
      else
        this.burn_pile = this.burn_pile.concat(cards);
      return this;
    };

    Deck.prototype.burn = function (to_burn, prepend){
      if (to_burn === undefined)
        to_burn = 1;
      if (prepend)
        this.burn_pile = this.stock.splice(-to_burn).concat(this.burn_pile);
      else
        this.burn_pile = this.burn_pile.concat(this.stock.splice(-to_burn));

      return this;
    };

    return Deck;
  }
}(angular));
<commit_msg>game.deck: expose deck related functions on cards in the deck<commit_after>(function(angular){
  /*
   * Deck module
   * Contains individual deck state and logic
   *
   * The Deck pays attention to humanistic elements of interacting with a deck
   * of cards. For the purposes of this module the order of cards is defined
   * to be the same whether face up or face down.
   *
   * The front of the card is the direction of the front of the deck array.
   * i.e. .pop()'ing off a face-down deck will return the top card
   * and .pop()'ing off a face-up deck will return the bottom card
   *
   * Understanding this we can then better introspect the movement of cards
   * through different interactions with the deck.
   *
   * For example, dealing cards reverses the order the cards were orignally
   * in in the deck where as drawing one card at a time into a hand preserves
   * that order.
   *
   * Note: Any references to top/bottom in the module or tests assume the deck
   * is face down.
   */
  angular
    .module('game.deck', [])
    .factory('DeckManager', DeckManager)
  ;

  DeckManager.$inject = [];
  function DeckManager() {
    var Deck = function(stock, name) {
      var self = this;
      self.name = name;
      self.stock = stock;
      self.burn_pile = [];

      // HACK: expose deck functions to each card so it can be manipulated
      // in the absence of information about the deck.
      self.stock.forEach(function(card) {
        card.deck = self;
        card.discard = function() { self.discard(card); };
      });
    };

    Deck.prototype.shuffle = function (){
      // mezclar2 - from http://jsperf.com/array-shuffle-comparator/14
      var arr = this.stock;
      for (
        var i, tmp, n = arr.length;
        n;
        i = Math.floor(Math.random() * n), tmp = arr[--n], arr[n] = arr[i], arr[i] = tmp
      );
      return this;
    };

    Deck.prototype.sort = function (fn){
        fn = fn || this.fn || function(a, b) { return a - b; };

      this.stock.sort(fn);
      return this;
    };

    Deck.prototype.combine = function (prepend){
      if (prepend === true)
        this.stock = this.burn_pile.concat(this.stock);
      else
        this.stock = this.stock.concat(this.burn_pile);

      this.burn_pile = [];

      return this;
    };

    Deck.prototype.draw = function (to_draw, from_front){
      var hand;

      to_draw = to_draw || 1;

      hand = this.stock.splice(from_front ? 0 : -to_draw, to_draw);

      if (from_front)
        hand.reverse();

      return hand.length > 1 ? hand : hand[0];
    };

    Deck.prototype.deal = function (to_deal, to_hands, from_front){
      var hands = []
          , pile
          ;

      to_deal = to_deal || 1;
      to_hands = to_hands || 1;

      // XXX: Desperately needs error raising here
      // specifically if (to_deal % to_hands) != 0
      to_deal *= to_hands;

      pile = this.stock.splice(from_front ? 0 : -to_deal, to_deal);

      for (var x = 0, hand; x < to_deal; x++) {
        if (x < to_hands)
          hands.push([]);
        hand = x % to_hands;
        hands[hand].push(from_front ? pile.shift() : pile.pop());
      }

      return to_hands > 1 ? hands : hands[0];
    };

    Deck.prototype.discard = function (cards, prepend){
      if (cards.length === undefined) {
        cards = [cards];
      }

      if (prepend)
        this.burn_pile = cards.concat(this.burn_pile);
      else
        this.burn_pile = this.burn_pile.concat(cards);
      return this;
    };

    Deck.prototype.burn = function (to_burn, prepend){
      if (to_burn === undefined)
        to_burn = 1;
      if (prepend)
        this.burn_pile = this.stock.splice(-to_burn).concat(this.burn_pile);
      else
        this.burn_pile = this.burn_pile.concat(this.stock.splice(-to_burn));

      return this;
    };

    return Deck;
  }
}(angular));
<|endoftext|>


<commit_before>'use strict'
module.exports = ficInflate

const Bluebird = require('bluebird')
const qw = require('qw')

const progress = use('progress')

// Take a fic and fetch all its chapters and ensure its per-chapter metadata
// is fully complete.

function ficInflate (fic, fetch, tracker) {
  return Bluebird.resolve(fic).then(fic => {
    const fics = [fic].concat(fic.fics)
    const completion = new Map()
    if (tracker) {
      for (let fic of fics) {
        completion.set(fic, tracker.newItem(fic.title, fic.chapters.length))
      }
    }
    return Bluebird.map(fics, fic => {
      const tracker = completion.get(fic)
      process.emit('debug', 'Inflating', fic.title, 'chapters:', fic.chapters.length)
      let words = 0
      return Bluebird.map(fic.chapters, chapterInfo => {
        try {
          const chapterContent = chapterInfo.getContent(fetch)
          const resolveFrom = tracker ? progress.completeWorkWhenResolved(chapterContent, tracker) : chapterContent
          return resolveFrom.then(chapter => {
            process.emit('debug', `Got content for #${chapterInfo.order}: ${chapterInfo.name}`)
            if (tracker) progress.show(fic.title, `${chapterInfo.name}`)
            chapterInfo.words = chapter.words
            chapterInfo.tags = chapter.tags
            words += chapter.words
            if (chapterInfo.link == null) chapterInfo.link = chapter.chapterLink
            for (let prop of qw`name author authorUrl created modified headings`) {
              if (chapterInfo[prop] == null) chapterInfo[prop] = chapter[prop]
            }
            if (chapterInfo.author === fic.author || chapterInfo.authorUrl === fic.authorUrl) {
              fic.author = chapterInfo.author
              fic.authorUrl = chapterInfo.authorUrl
              chapterInfo.author = null
              chapterInfo.authorUrl = null
            }
          }).catch(err => {
            chapterInfo.error = err
            process.emit('error', err.message)
          })
        } catch (err) {
          chapterInfo.error = err
          process.emit('error', chapterInfo)
        }
      }).finally(() => {
        if (!fic.words) fic.words = words
        if (fic.chapters.length) {
          const firstChapter = fic.chapters[0]
          const lastChapter = fic.chapters[fic.chapters.length - 1]
          if (firstChapter.created == null && firstChapter.modified == null && fic.created != null) {
            firstChapter.created = fic.created
          }
          if (lastChapter.created == null && lastChapter.modified == null && fic.modified != null) {
            lastChapter.modified = fic.modified
          }
        }
        if (tracker) tracker.finish()
      })
    })
  }).then(() => fic)
}
<commit_msg>fic-inflate: Better error logging<commit_after>'use strict'
module.exports = ficInflate

const Bluebird = require('bluebird')
const qw = require('qw')

const progress = use('progress')

// Take a fic and fetch all its chapters and ensure its per-chapter metadata
// is fully complete.

function ficInflate (fic, fetch, tracker) {
  return Bluebird.resolve(fic).then(fic => {
    const fics = [fic].concat(fic.fics)
    const completion = new Map()
    if (tracker) {
      for (let fic of fics) {
        completion.set(fic, tracker.newItem(fic.title, fic.chapters.length))
      }
    }
    return Bluebird.map(fics, fic => {
      const tracker = completion.get(fic)
      process.emit('debug', 'Inflating', fic.title, 'chapters:', fic.chapters.length)
      let words = 0
      return Bluebird.map(fic.chapters, chapterInfo => {
        try {
          const chapterContent = chapterInfo.getContent(fetch)
          const resolveFrom = tracker ? progress.completeWorkWhenResolved(chapterContent, tracker) : chapterContent
          return resolveFrom.then(chapter => {
            process.emit('debug', `Got content for #${chapterInfo.order}: ${chapterInfo.name}`)
            if (tracker) progress.show(fic.title, `${chapterInfo.name}`)
            chapterInfo.words = chapter.words
            chapterInfo.tags = chapter.tags
            words += chapter.words
            if (chapterInfo.link == null) chapterInfo.link = chapter.chapterLink
            for (let prop of qw`name author authorUrl created modified headings`) {
              if (chapterInfo[prop] == null) chapterInfo[prop] = chapter[prop]
            }
            if (chapterInfo.author === fic.author || chapterInfo.authorUrl === fic.authorUrl) {
              fic.author = chapterInfo.author
              fic.authorUrl = chapterInfo.authorUrl
              chapterInfo.author = null
              chapterInfo.authorUrl = null
            }
          }).catch(err => {
            chapterInfo.error = err
            process.emit('error', 'fetch', chapterInfo.fetchWith(), err)
          })
        } catch (err) {
          chapterInfo.error = err
          process.emit('error', 'getContent', chapterInfo.fetchWith(), err)
        }
      }).finally(() => {
        if (!fic.words) fic.words = words
        if (fic.chapters.length) {
          const firstChapter = fic.chapters[0]
          const lastChapter = fic.chapters[fic.chapters.length - 1]
          if (firstChapter.created == null && firstChapter.modified == null && fic.created != null) {
            firstChapter.created = fic.created
          }
          if (lastChapter.created == null && lastChapter.modified == null && fic.modified != null) {
            lastChapter.modified = fic.modified
          }
        }
        if (tracker) tracker.finish()
      })
    })
  }).then(() => fic)
}
<|endoftext|>


<commit_before>'use strict';

Darwinator.Preloader = function() {
  this.asset = null;
  this.ready = false;
};

Darwinator.Preloader.prototype = {

  preload: function () {
    this.asset = this.add.sprite(320, 240, 'preloader');
    this.asset.anchor.setTo(0.5, 0.5);

    this.load.onLoadComplete.addOnce(this.onLoadComplete, this);
    this.load.setPreloadSprite(this.asset);
    this.load.image('enemy', 'assets/player.png');
    this.load.image('enemy_strength', 'assets/player_strength.png');
    this.load.image('enemy_agility', 'assets/player_agility.png');
    this.load.image('enemy_intellect', 'assets/player_intellect.png');
    this.load.bitmapFont('minecraftia', 'assets/minecraftia.png', 'assets/minecraftia.xml');
    this.load.tilemap('level1', 'assets/level2.json', null, Phaser.Tilemap.TILED_JSON);
    this.game.load.image('tiles', 'assets/landscape.png');
    this.load.image('sword','assets/sword.png');
    this.load.image('arrow','assets/arrow.png');
    this.load.spritesheet('player', 'assets/darwin.png', 32, 32, 16);
    this.game.load.image('plus-button', 'assets/plus-button.png');
    this.load.spritesheet('continue-game-button', 'assets/continue-game-button.png', 322, 66, 2);
  },

  create: function () {
    this.asset.cropEnabled = false;
  },

  update: function () {
    if (!!this.ready) {
      this.game.state.start('menu');
    }
  },

  onLoadComplete: function () {
    this.ready = true;
  }
};
<commit_msg>Use new assets<commit_after>'use strict';

Darwinator.Preloader = function() {
  this.asset = null;
  this.ready = false;
};

Darwinator.Preloader.prototype = {

  preload: function () {
    this.asset = this.add.sprite(320, 240, 'preloader');
    this.asset.anchor.setTo(0.5, 0.5);

    this.load.onLoadComplete.addOnce(this.onLoadComplete, this);
    this.load.setPreloadSprite(this.asset);
    this.load.image('enemy', 'assets/player.png');
    this.load.image('enemy_strength', 'assets/player_strength.png');
    this.load.image('enemy_agility', 'assets/player_agility.png');
    this.load.image('enemy_intellect', 'assets/player_intellect.png');
    this.load.bitmapFont('minecraftia', 'assets/minecraftia.png', 'assets/minecraftia.xml');
    this.load.tilemap('level1', 'assets/galapa.json', null, Phaser.Tilemap.TILED_JSON);
    this.game.load.image('GroundTiles', 'assets/GroundTiles.png');
    this.game.load.image('WaterTiles', 'assets/WaterTiles.png');
    this.game.load.image('ObjectTiles', 'assets/ObjectTiles.png');
    this.load.image('sword','assets/sword.png');
    this.load.image('arrow','assets/arrow.png');
    this.load.spritesheet('player', 'assets/darwin.png', 32, 32, 16);
    this.game.load.image('plus-button', 'assets/plus-button.png');
    this.load.spritesheet('continue-game-button', 'assets/continue-game-button.png', 322, 66, 2);
  },

  create: function () {
    this.asset.cropEnabled = false;
  },

  update: function () {
    if (!!this.ready) {
      this.game.state.start('menu');
    }
  },

  onLoadComplete: function () {
    this.ready = true;
  }
};
<|endoftext|>


<commit_before>'use strict'
module.exports = ficToEpub
const Streampub = require('streampub')
const chapterFilename = require('./chapter-filename.js')
const sanitizeHtml = require('sanitize-html')
const ms = require('mississippi')
const url = require('url')
const fs = require('fs')
const commaNumber = require('comma-number')
const html = require('html-template-tag')

function ficToEpub (meta) {
  const epub = new Streampub({
    id: meta.id,
    title: meta.title,
    author: meta.author,
    authorUrl: meta.authorUrl,
    description: meta.description,
    source: meta.link,
    subject: meta.tags && meta.tags.length && meta.tags.join(','),
    publisher: meta.publisher,
    published: meta.started || meta.created,
    modified: meta.modified,
    includeTOC: meta.includeTOC,
    numberTOC: meta.numberTOC
  })

  if (meta.cover && !/:/.test(meta.cover)) {
    epub.write(Streampub.newCoverImage(fs.createReadStream(meta.cover)))
  } else {
    let titleContent = ''
    titleContent += html`

<title>${meta.title}</title>
`
    titleContent += html`<section epub:type="title"><h1>${meta.title}</h1></section>`
    const author = meta.authorUrl ? html`<a href="${meta.authorUrl}">${meta.author}</a>` : meta.author
    titleContent += `<h3>by <span epub:type="credits">${author}</span></h3>`
    titleContent += html`<table>`
    if (meta.link) {
      const wrappableLink = meta.link.replace(/(.....)/g, '$1<wbr>')
      titleContent += `<tr><th>Source</th><td><a href="${meta.link}">${wrappableLink}</a></td></tr>`
    }
    if (meta.created) titleContent += html`<tr><th>Published</th><td>${meta.created}</td></tr>`
    if (meta.modified) titleContent += html`<tr><th>Updated</th><td>${meta.modified}</td></tr>`
    if (meta.tags && meta.tags.length) {
      titleContent += html`<tr><th>Tags</th><td><section epub:type="keywords"><em>${meta.tags.join(', ')}</em></section></td></tr>`
    }
    if (meta.words) titleContent += html`<tr><th>Words</th><td>${commaNumber(meta.words)}</td></tr>`
    titleContent += `</table>`
    if (meta.description) titleContent += `<section epub:type="abstract"><p>${meta.description}</p></section>`
    titleContent += `</body>`
    const titlePage = `${titleContent}`
    epub.write(Streampub.newChapter('Title Page', titlePage, 0, 'top.xhtml'))
  }
  return ms.pipeline.obj(ms.through.obj(transformChapter(meta)), epub)
}

function transformChapter (meta) {
  return function (chapter, _, done) {
    if (chapter.image) {
      this.push(Streampub.newFile(chapter.filename, chapter.content))
      return done()
    }
    const index = chapter.order != null && (1 + chapter.order)
    const name = chapter.name
    const filename = chapterFilename(chapter)
    const toSanitize = '' +
      (name ? html`<title>${name}</title></head>` : '') +
      '
' + chapter.content + '
' +
      ''
    const content = sanitizeHtml(toSanitize, meta.site.sanitizeHtmlConfig())
    this.push(Streampub.newChapter(name, content, index, filename))
    done()
  }
}
<commit_msg>fic-to-epub: Only include author name on title pages when we have one<commit_after>'use strict'
module.exports = ficToEpub
const Streampub = require('streampub')
const chapterFilename = require('./chapter-filename.js')
const sanitizeHtml = require('sanitize-html')
const ms = require('mississippi')
const url = require('url')
const fs = require('fs')
const commaNumber = require('comma-number')
const html = require('html-template-tag')

function ficToEpub (meta) {
  const epub = new Streampub({
    id: meta.id,
    title: meta.title,
    author: meta.author,
    authorUrl: meta.authorUrl,
    description: meta.description,
    source: meta.link,
    subject: meta.tags && meta.tags.length && meta.tags.join(','),
    publisher: meta.publisher,
    published: meta.started || meta.created,
    modified: meta.modified,
    includeTOC: meta.includeTOC,
    numberTOC: meta.numberTOC
  })

  if (meta.cover && !/:/.test(meta.cover)) {
    epub.write(Streampub.newCoverImage(fs.createReadStream(meta.cover)))
  } else {
    let titleContent = ''
    titleContent += html`

<title>${meta.title}</title>
`
    titleContent += html`<section epub:type="title"><h1>${meta.title}</h1></section>`
    if (meta.author) {
      const author = meta.authorUrl ? html`<a href="${meta.authorUrl}">${meta.author}</a>` : meta.author
      titleContent += `<h3>by <span epub:type="credits">${author}</span></h3>`
    }
    titleContent += html`<table>`
    if (meta.link) {
      const wrappableLink = meta.link.replace(/(.....)/g, '$1<wbr>')
      titleContent += `<tr><th>Source</th><td><a href="${meta.link}">${wrappableLink}</a></td></tr>`
    }
    if (meta.created) titleContent += html`<tr><th>Published</th><td>${meta.created}</td></tr>`
    if (meta.modified) titleContent += html`<tr><th>Updated</th><td>${meta.modified}</td></tr>`
    if (meta.tags && meta.tags.length) {
      titleContent += html`<tr><th>Tags</th><td><section epub:type="keywords"><em>${meta.tags.join(', ')}</em></section></td></tr>`
    }
    if (meta.words) titleContent += html`<tr><th>Words</th><td>${commaNumber(meta.words)}</td></tr>`
    titleContent += `</table>`
    if (meta.description) titleContent += `<section epub:type="abstract"><p>${meta.description}</p></section>`
    titleContent += `</body>`
    const titlePage = `${titleContent}`
    epub.write(Streampub.newChapter('Title Page', titlePage, 0, 'top.xhtml'))
  }
  return ms.pipeline.obj(ms.through.obj(transformChapter(meta)), epub)
}

function transformChapter (meta) {
  return function (chapter, _, done) {
    if (chapter.image) {
      this.push(Streampub.newFile(chapter.filename, chapter.content))
      return done()
    }
    const index = chapter.order != null && (1 + chapter.order)
    const name = chapter.name
    const filename = chapterFilename(chapter)
    const toSanitize = '' +
      (name ? html`<title>${name}</title></head>` : '') +
      '
' + chapter.content + '
' +
      ''
    const content = sanitizeHtml(toSanitize, meta.site.sanitizeHtmlConfig())
    this.push(Streampub.newChapter(name, content, index, filename))
    done()
  }
}
<|endoftext|>