<commit_before>'use strict';

const { Transform } = require('stream');

const PP = require('polygon-points');

//const PC = require('./lib/pixel-change');

const PC = require('./build/Release/pixel_change');

class PamDiff extends Transform {

    /**
     *
     * @param [options] {Object}
     * @param [callback] {Function}
     */
    constructor(options, callback) {
        super(options);
        Transform.call(this, {objectMode: true});
        this.difference = PamDiff._parseOptions('difference', options);//global option, can be overridden per region
        this.percent = PamDiff._parseOptions('percent', options);//global option, can be overridden per region
        this.regions = PamDiff._parseOptions('regions', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared.
        this.callback = callback;//callback function to be called when pixel difference is detected
        this._parseChunk = this._parseFirstChunk;//first parsing will be reading settings and configuring internal pixel reading
    }

    /**
     *
     * @param option {String}
     * @param options {Object}
     * @return {*}
     * @private
     */
    static _parseOptions(option, options) {
        if (options && options.hasOwnProperty(option)) {
            return options[option];
        }
        return null;
    }

    /**
     *
     * @param number {Number}
     * @param def {Number}
     * @param low {Number}
     * @param high {Number}
     * @return {Number}
     * @private
     */
    static _validateNumber(number, def, low, high) {
        if (isNaN(number)) {
            return def;
        } else if (number < low) {
            return low;
        } else if (number > high) {
            return high;
        } else {
            return number;
        }
    }

    /**
     *
     * @deprecated
     * @param string {String}
     */
    setGrayscale(string) {
        console.warn('grayscale option has been removed, "average" has proven to most accurate and is the default');
    }

    /**
     *
     * @param number {Number}
     */
    set difference(number) {
        this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255);
    }

    /**
     *
     * @return {Number}
     */
    get difference() {
        return this._difference;
    }

    /**
     *
     * @param number {Number}
     * @return {PamDiff}
     */
    setDifference(number) {
        this.difference = number;
        return this;
    }

    /**
     *
     * @param number {Number}
     */
    set percent(number) {
        this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100);
    }

    /**
     *
     * @return {Number}
     */
    get percent() {
        return this._percent;
    }

    /**
     *
     * @param number {Number}
     * @return {PamDiff}
     */
    setPercent(number) {
        this.percent = number;
        return this;
    }

    /**
     *
     * @param array {Array}
     */
    set regions(array) {
        if (!array) {
            delete this._regions;
            delete this._regionsArr;
            delete this._regionsLen;
            delete this._minDiff;
        } else if (!Array.isArray(array) || array.length < 1) {
            throw new Error(`Regions must be an array of at least 1 region object {name: 'region1', difference: 10, percent: 10, polygon: [[0, 0], [0, 50], [50, 50], [50, 0]]}`);
        } else {
            this._regions = array;
            this._processRegions();
        }
    }

    /**
     *
     * @return {Array}
     */
    get regions() {
        return this._regions;
    }

    /**
     *
     * @param array {Array}
     * @return {PamDiff}
     */
    setRegions(array) {
        this.regions = array;
        return this;
    }

    /**
     *
     * @param func {Function}
     */
    set callback(func) {
        if (!func) {
            delete this._callback;
        } else if (typeof func === 'function' && func.length === 1) {
            this._callback = func;
        } else {
            throw new Error('Callback must be a function that accepts 1 argument.');
        }
    }

    /**
     *
     * @return {Function}
     */
    get callback() {
        return this._callback;
    }

    /**
     *
     * @param func {Function}
     * @return {PamDiff}
     */
    setCallback(func) {
        this.callback = func;
        return this;
    }

    /**
     *
     * @return {PamDiff}
     */
    resetCache() {
        delete this._oldPix;
        delete this._newPix;
        delete this._width;
        delete this._height;
        delete this._wxh;
        delete this._bufLen;
        delete this._regionsArr;
        delete this._regionsLen;
        delete this._minDiff;
        this._parseChunk = this._parseFirstChunk;
        return this;
    }

    /**
     *
     * @param chunk
     * @private
     */
    _bwPixelDiff(chunk) {
        throw new Error("black and white pixel diff not available, yet");
    }

    /**
     *
     * @param chunk
     * @private
     */
    _grayPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results = [];
        if (this._regions) {
            results = PC.compareGrayRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareGrayPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    _rgbPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results = [];
        if (this._regions) {
            results = PC.compareRgbRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareRgbPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    /**
     *
     * @param chunk
     * @private
     */
    _rgbaPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results;
        if (this._regions) {
            results = PC.compareRgbaRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareRgbaPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    _processRegions() {
        if (this._regions && this._width && this._height) {
            this._regionsArr = [];
            this._minDiff = 255;
            for (const region of this._regions) {
                if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) {
                    throw new Error('Region must include a name and a polygon property');
                }
                const polygonPoints = new PP(region.polygon);
                const bitset = polygonPoints.getBitset(this._width, this._height);
                const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255);
                const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100);
                this._minDiff = Math.min(this._minDiff, difference);
                this._regionsArr.push(
                    {
                        name: region.name,
                        diff: difference,
                        percent: percent,
                        count: bitset.count,
                        bitset: bitset.buffer
                    }
                );
            }
            this._regionsLen = this._regions.length;
        }
    }

    /**
     *
     * @param chunk
     * @private
     */
    _parseFirstChunk(chunk) {
        this._width = parseInt(chunk.width);
        this._height = parseInt(chunk.height);
        this._oldPix = chunk.pixels;
        this._wxh = this._width * this._height;
        this._processRegions();
        switch (chunk.tupltype) {
            case 'blackandwhite' :
                this._bufLen = this._wxh;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height");
                }
                this._parseChunk = this._bwPixelDiff;
                break;
            case 'grayscale' :
                this._bufLen = this._wxh;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height");
                }
                this._parseChunk = this._grayPixelDiff;
                break;
            case 'rgb' :
                this._bufLen = this._wxh * 3;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height * 3");
                }
                this._parseChunk = this._rgbPixelDiff;
                break;
            case 'rgb_alpha' :
                this._bufLen = this._wxh * 4;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height * 4");
                }
                this._parseChunk = this._rgbaPixelDiff;
                break;
            default :
                throw Error(`Unsupported tupltype: ${chunk.tupltype}. Supported tupltypes include grayscale(gray), blackandwhite(monob), rgb(rgb24), and rgb_alpha(rgba).`);
        }
    }

    /**
     *
     * @param chunk
     * @param encoding
     * @param callback
     * @private
     */
    _transform(chunk, encoding, callback) {
        console.time('parse');
        this._parseChunk(chunk);
        console.timeEnd('parse');
        callback();
    }

    /**
     *
     * @param callback
     * @private
     */
    _flush(callback) {
        this.resetCache();
        callback();
    }
}

/**
 *
 * @type {PamDiff}
 */
module.exports = PamDiff;<commit_msg>add test lib for benchmarking vs js static methods<commit_after>'use strict';

const { Transform } = require('stream');

const PP = require('polygon-points');

//const PC = require('./lib/pixel-change');

const PC = require('./build/Release/pixel_change');

const PC2 = require('./lib/pixel-change');

class PamDiff extends Transform {

    /**
     *
     * @param [options] {Object}
     * @param [callback] {Function}
     */
    constructor(options, callback) {
        super(options);
        Transform.call(this, {objectMode: true});
        this.difference = PamDiff._parseOptions('difference', options);//global option, can be overridden per region
        this.percent = PamDiff._parseOptions('percent', options);//global option, can be overridden per region
        this.regions = PamDiff._parseOptions('regions', options);//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared.
        this.callback = callback;//callback function to be called when pixel difference is detected
        this._parseChunk = this._parseFirstChunk;//first parsing will be reading settings and configuring internal pixel reading
    }

    /**
     *
     * @param option {String}
     * @param options {Object}
     * @return {*}
     * @private
     */
    static _parseOptions(option, options) {
        if (options && options.hasOwnProperty(option)) {
            return options[option];
        }
        return null;
    }

    /**
     *
     * @param number {Number}
     * @param def {Number}
     * @param low {Number}
     * @param high {Number}
     * @return {Number}
     * @private
     */
    static _validateNumber(number, def, low, high) {
        if (isNaN(number)) {
            return def;
        } else if (number < low) {
            return low;
        } else if (number > high) {
            return high;
        } else {
            return number;
        }
    }

    /**
     *
     * @deprecated
     * @param string {String}
     */
    setGrayscale(string) {
        console.warn('grayscale option has been removed, "average" has proven to most accurate and is the default');
    }

    /**
     *
     * @param number {Number}
     */
    set difference(number) {
        this._difference = PamDiff._validateNumber(parseInt(number), 5, 1, 255);
    }

    /**
     *
     * @return {Number}
     */
    get difference() {
        return this._difference;
    }

    /**
     *
     * @param number {Number}
     * @return {PamDiff}
     */
    setDifference(number) {
        this.difference = number;
        return this;
    }

    /**
     *
     * @param number {Number}
     */
    set percent(number) {
        this._percent = PamDiff._validateNumber(parseInt(number), 5, 1, 100);
    }

    /**
     *
     * @return {Number}
     */
    get percent() {
        return this._percent;
    }

    /**
     *
     * @param number {Number}
     * @return {PamDiff}
     */
    setPercent(number) {
        this.percent = number;
        return this;
    }

    /**
     *
     * @param array {Array}
     */
    set regions(array) {
        if (!array) {
            delete this._regions;
            delete this._regionsArr;
            delete this._regionsLen;
            delete this._minDiff;
        } else if (!Array.isArray(array) || array.length < 1) {
            throw new Error(`Regions must be an array of at least 1 region object {name: 'region1', difference: 10, percent: 10, polygon: [[0, 0], [0, 50], [50, 50], [50, 0]]}`);
        } else {
            this._regions = array;
            this._processRegions();
        }
    }

    /**
     *
     * @return {Array}
     */
    get regions() {
        return this._regions;
    }

    /**
     *
     * @param array {Array}
     * @return {PamDiff}
     */
    setRegions(array) {
        this.regions = array;
        return this;
    }

    /**
     *
     * @param func {Function}
     */
    set callback(func) {
        if (!func) {
            delete this._callback;
        } else if (typeof func === 'function' && func.length === 1) {
            this._callback = func;
        } else {
            throw new Error('Callback must be a function that accepts 1 argument.');
        }
    }

    /**
     *
     * @return {Function}
     */
    get callback() {
        return this._callback;
    }

    /**
     *
     * @param func {Function}
     * @return {PamDiff}
     */
    setCallback(func) {
        this.callback = func;
        return this;
    }

    /**
     *
     * @return {PamDiff}
     */
    resetCache() {
        delete this._oldPix;
        delete this._newPix;
        delete this._width;
        delete this._height;
        delete this._wxh;
        delete this._bufLen;
        delete this._regionsArr;
        delete this._regionsLen;
        delete this._minDiff;
        this._parseChunk = this._parseFirstChunk;
        return this;
    }

    /**
     *
     * @param chunk
     * @private
     */
    _bwPixelDiff(chunk) {
        throw new Error("black and white pixel diff not available, yet");
    }

    /**
     *
     * @param chunk
     * @private
     */
    _grayPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results = [];
        if (this._regions) {
            results = PC.compareGrayRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareGrayPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    _rgbPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results = [];
        if (this._regions) {
            results = PC.compareRgbRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareRgbPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    /**
     *
     * @param chunk
     * @private
     */
    _rgbaPixelDiff(chunk) {
        this._newPix = chunk.pixels;
        let results;
        if (this._regions) {
            results = PC.compareRgbaRegions(this._minDiff, this._regionsLen, this._regionsArr, this._bufLen, this._oldPix, this._newPix);
        } else {
            results = PC.compareRgbaPixels(this._difference, this._percent, this._wxh, this._bufLen, this._oldPix, this._newPix);
        }
        if (results.length) {
            const data = {trigger: results, pam:chunk.pam};
            if (this._callback) {
                this._callback(data);
            }
            if (this._readableState.pipesCount > 0) {
                this.push(data);
            }
            if (this.listenerCount('diff') > 0) {
                this.emit('diff', data);
            }
        }
        this._oldPix = this._newPix;
    }

    _processRegions() {
        if (this._regions && this._width && this._height) {
            this._regionsArr = [];
            this._minDiff = 255;
            for (const region of this._regions) {
                if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) {
                    throw new Error('Region must include a name and a polygon property');
                }
                const polygonPoints = new PP(region.polygon);
                const bitset = polygonPoints.getBitset(this._width, this._height);
                const difference = PamDiff._validateNumber(parseInt(region.difference), this._difference, 1, 255);
                const percent = PamDiff._validateNumber(parseInt(region.percent), this._percent, 1, 100);
                this._minDiff = Math.min(this._minDiff, difference);
                this._regionsArr.push(
                    {
                        name: region.name,
                        diff: difference,
                        percent: percent,
                        count: bitset.count,
                        bitset: bitset.buffer
                    }
                );
            }
            this._regionsLen = this._regions.length;
        }
    }

    /**
     *
     * @param chunk
     * @private
     */
    _parseFirstChunk(chunk) {
        this._width = parseInt(chunk.width);
        this._height = parseInt(chunk.height);
        this._oldPix = chunk.pixels;
        this._wxh = this._width * this._height;
        this._processRegions();
        switch (chunk.tupltype) {
            case 'blackandwhite' :
                this._bufLen = this._wxh;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height");
                }
                this._parseChunk = this._bwPixelDiff;
                break;
            case 'grayscale' :
                this._bufLen = this._wxh;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height");
                }
                this._parseChunk = this._grayPixelDiff;
                break;
            case 'rgb' :
                this._bufLen = this._wxh * 3;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height * 3");
                }
                this._parseChunk = this._rgbPixelDiff;
                break;
            case 'rgb_alpha' :
                this._bufLen = this._wxh * 4;
                if (this._bufLen !== this._oldPix.length) {
                    throw new Error("Pixel count does not match width * height * 4");
                }
                this._parseChunk = this._rgbaPixelDiff;
                break;
            default :
                throw Error(`Unsupported tupltype: ${chunk.tupltype}. Supported tupltypes include grayscale(gray), blackandwhite(monob), rgb(rgb24), and rgb_alpha(rgba).`);
        }
    }

    /**
     *
     * @param chunk
     * @param encoding
     * @param callback
     * @private
     */
    _transform(chunk, encoding, callback) {
        console.time('parse');
        this._parseChunk(chunk);
        console.timeEnd('parse');
        callback();
    }

    /**
     *
     * @param callback
     * @private
     */
    _flush(callback) {
        this.resetCache();
        callback();
    }
}

/**
 *
 * @type {PamDiff}
 */
module.exports = PamDiff;<|endoftext|>


<commit_before>var Q = require('q'),
    INHERIT = require('inherit'),
    U = require('../util'),
    PATH = require('path'),
    FS = require('fs'),

    BlockNode = require('./block').BlockNodeName,
    fileNodes = require('./file'),
    BemCreateNode = require('./create'),
    BemBuildNode = require('./build'),
    BemDeclNode = require('./decl'),
    BorschikNode = require('./borschik'),

    registry = require('../nodesregistry'),
    LOGGER = require('../logger'),

    BundleNodeName = exports.BundleNodeName = 'BundleNode';

exports.__defineGetter__(BundleNodeName, function() {
    return registry.getNodeClass(BundleNodeName);
});

registry.decl(BundleNodeName, BlockNode, /** @lends BundleNode.prototype */ {

    nodeType: 6,

    make: function() {

        return this.ctx.arch.withLock(this.alterArch(), this);

    },

    alterArch: function() {

        var ctx = this.ctx;

        return function() {

            // create real node for page
            var arch = ctx.arch,
                bundleNode;

            if (arch.hasNode(this.path)) {
                bundleNode = arch.getNode(this.path);
            } else {
                bundleNode = new fileNodes.FileNode({
                    root: this.root,
                    path: this.path
                });
                arch.setNode(bundleNode, arch.getParents(this));
            }

            // generate targets for page files
            this.getTechs().map(function(tech) {
                var techNode = this.createTechNode(tech, bundleNode, this);
                if (techNode) {
                    this.createOptimizerNode(tech, techNode, bundleNode);
                }
            }, this);

            return Q.when(this.takeSnapshot('after alterArch BundleNode ' + this.getId()));
        };

    },

    lastModified: function() {
        return Q.resolve(0);
    },

    createTechNode: function(tech, bundleNode, magicNode) {

        var f = 'create-' + tech + '-node';
        f = typeof this[f] === 'function'? f : 'createDefaultTechNode';

        LOGGER.fdebug('Using %s() to create node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    createOptimizerNode: function(tech, sourceNode, bundleNode) {

        var f = 'create-' + tech + '-optimizer-node';
        f = typeof this[f] === 'function'? f : 'createDefaultOptimizerNode';

        LOGGER.fdebug('Using %s() to create optimizer node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    getBundlePath: function(tech) {
        return this.level.getPath(this.getNodePrefix(), tech);
    },

    getTechs: function() {
        return [
            'bemjson.js',
            'bemdecl.js',
            'deps.js',
            'bemhtml.js',
            'css',
            'ie.css',
            'js',
            'html'
        ];
    },

    cleanup: function() {
        var arch = this.ctx.arch;
        if (!arch.hasNode(this.path)) return;
        arch.removeTree(this.path);
    },

    getLevels: function(prefix) {
        return [].concat(
            this.level.getConfig().bundleBuildLevels,
            [ PATH.join(PATH.dirname(prefix), 'blocks') ]);
    },

    /**
     * Checks that dependencies for specified node are met (appropriate nodes exist in the arch) or that node's file is
     * already exists on the file system.
     * @param node
     * @return {Node} Specified node if dependencies are met, FileNode if not but file does exist, null otherwise.
     */
    useFileOrBuild: function(node) {

        var deps = node.getDependencies();

        for(var i = 0, l = deps.length; i < l; i++) {
            var d = deps[i];

            if (!this.ctx.arch.hasNode(d)) {
                LOGGER.fverbose('Dependency %s is required to build %s but does not exist, checking if target already built', d, node.getId());

                if (!PATH.existsSync(node.getPath())) {
                    LOGGER.fwarn('%s will not be built because dependency file %s does not exist', node.path, d);
                    return null;
                }

                return new fileNodes.FileNode({
                    root: this.root,
                    path: node.getId()
                });
            }
        }

        return node;

    },

    /**
     * Create a bem build node, add it to the arch, add
     * dependencies to it. Then create a meta node and link
     * it to the build node.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} declPath
     * @param {String} bundleNode
     * @param {String} magicNode
     * @param {Boolean} [forked]
     */
    setBemBuildNode: function(techName, techPath, declPath, bundleNode, magicNode, forked) {

        var arch = this.ctx.arch,
            buildNode = new BemBuildNode.BemBuildNode({
                root: this.root,
                bundlesLevel: this.level,
                levels: this.getLevels(PATH.resolve(this.root, this.getNodePrefix())),
                declPath: declPath,
                techPath: techPath,
                techName: techName,
                output: this.getNodePrefix(),
                forked: forked
            }),
            metaNode = buildNode.getMetaNode();

        // Set bem build node to arch and add dependencies to it
        arch.setNode(buildNode)
            .addChildren(buildNode, buildNode.getDependencies());

        // Add file aliases to arch and link with buildNode as parents
        buildNode.getFiles().forEach(function(f) {

            if (buildNode.getId() === f) return;

            var alias = new fileNodes.FileNode({ path: f, root: this.root });
            arch.setNode(alias).addParents(buildNode, alias);

        }, this);

        bundleNode && arch.addParents(buildNode, bundleNode);
        magicNode && arch.addChildren(buildNode, magicNode);

        // Set bem build meta node to arch
        arch.setNode(metaNode)
            .addParents(metaNode, buildNode)
            .addChildren(metaNode, metaNode.getDependencies());

        return buildNode;

    },

    /**
     * Create a bem create node, add it to the arch,
     * add dependencies to it.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setBemCreateNode: function(techName, techPath, bundleNode, magicNode, force) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemCreateNode.BemCreateNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: techPath,
                techName: techName,
                force: force
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        // Add file aliases to arch and link with node as parents
        node.getFiles && node.getFiles().forEach(function(f) {

            if (node.getId() === f) return;

            var alias = new fileNodes.FileNode({ path: f, root: this.root });
            arch.setNode(alias).addParents(node, alias);

        }, this);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    /**
     * Create file node, add it to the arch, add dependencies to it.
     *
     * @param {String} tech
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setFileNode: function(tech, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            filePath = this.getBundlePath(tech);

        if (!PATH.existsSync(PATH.resolve(this.root, filePath))) return;

        var node = new fileNodes.FileNode({
            root: this.root,
            path: filePath
        });

        arch.setNode(node);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    createDefaultTechNode: function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('deps.js'),
            bundleNode,
            magicNode);

    },

    createDefaultOptimizerNode: function(tech, sourceNode, bundleNode) {},

    createBorschikOptimizerNode: function(tech, sourceNode, bundleNode) {

        var files = sourceNode.getFiles? sourceNode.getFiles() : [sourceNode.path];

        LOGGER.fdebug('Creating borschik nodes for %s', files);

        return files.map(function(file) {

                var node = new (registry.getNodeClass('BorschikNode'))({
                    root: this.root,
                    input: file,
                    tech: tech,
                    forked: true
                });

                this.ctx.arch
                    .setNode(node)
                    .addParents(node, bundleNode)
                    .addChildren(node, sourceNode);

                return node;

            }, this);

    },

    'create-bemjson.js-node': function(tech, bundleNode, magicNode) {
        return this.setFileNode.apply(this, arguments);
    },

    'create-bemdecl.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('bemdecl.js'),
            bundleNode,
            magicNode);

    },

    'create-html-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this.createBorschikOptimizerNode('js', sourceNode, bundleNode);
    },

    'create-priv.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-bemhtml.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this.createBorschikOptimizerNode('css-fast', sourceNode, bundleNode);
    },

    'create-ie.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var nodes = this['create-css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.link(this.getBundlePath('css'), nodes);
        return nodes;

    },

    'create-ie6.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var nodes = this['create-ie.css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.link(this.getBundlePath('ie.css'), nodes);
        return nodes;

    },

    'create-ie7.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie8.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie9.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie.css-optimizer-node'].apply(this, arguments);
    }

});

var MergedBundleNodeName = exports.MergedBundleNodeName = 'MergedBundleNode';

exports.__defineGetter__(MergedBundleNodeName, function() {
    return registry.getNodeClass(MergedBundleNodeName);
});

registry.decl(MergedBundleNodeName, BundleNodeName, /** @lends MergedBundleNode.prototype */ {

    make: function() {
        var path = PATH.resolve(this.root, this.path);

        if (!PATH.existsSync(path)) FS.mkdirSync(path);

        return this.__base();
    },

    /**
     * Overriden. Creates BemDecl node linked to the deps.js nodes of the bundles within containing level.
     * @param tech
     * @param bundleNode
     * @param magicNode
     * @return {*}
     */
    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        var ctx = this.ctx,
            arch = ctx.arch,
            levelNode = arch.getNode(PATH.relative(this.root, this.level.dir)),
            depsTech = this.level.getTech('deps.js').getTechName(),
            bundles = arch.getChildren(levelNode)
                .filter(function(b) {
                    var n = arch.getNode(b);
                    return n instanceof exports.BundleNode && n !== this;
                }, this)
                .map(function(b) {
                    return U.getNodeTechPath(this.level, arch.getNode(b).item, depsTech);
                }, this);

        return this.setBemDeclNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode,
            'merge',
            bundles);

    },

    /**
     * Creates BemDecl node which maps to 'bem decl [cmd] [decls]'.
     * @param techName
     * @param techPath
     * @param bundleNode
     * @param magicNode
     * @param cmd Command to execute (merge or substract).
     * @param decls Declaration paths to execute command on.
     * @param force
     * @return {Node}
     */
    setBemDeclNode: function(techName, techPath, bundleNode, magicNode, cmd, decls, force) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemDeclNode.BemDeclNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: techPath,
                techName: techName,
                cmd: cmd,
                decls: decls,
                force: force
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;
    }

});
<commit_msg>- getLevels fixed to avoid putting undefined level into the result array (close #203)<commit_after>var Q = require('q'),
    INHERIT = require('inherit'),
    U = require('../util'),
    PATH = require('path'),
    FS = require('fs'),

    BlockNode = require('./block').BlockNodeName,
    fileNodes = require('./file'),
    BemCreateNode = require('./create'),
    BemBuildNode = require('./build'),
    BemDeclNode = require('./decl'),
    BorschikNode = require('./borschik'),

    registry = require('../nodesregistry'),
    LOGGER = require('../logger'),

    BundleNodeName = exports.BundleNodeName = 'BundleNode';

exports.__defineGetter__(BundleNodeName, function() {
    return registry.getNodeClass(BundleNodeName);
});

registry.decl(BundleNodeName, BlockNode, /** @lends BundleNode.prototype */ {

    nodeType: 6,

    make: function() {

        return this.ctx.arch.withLock(this.alterArch(), this);

    },

    alterArch: function() {

        var ctx = this.ctx;

        return function() {

            // create real node for page
            var arch = ctx.arch,
                bundleNode;

            if (arch.hasNode(this.path)) {
                bundleNode = arch.getNode(this.path);
            } else {
                bundleNode = new fileNodes.FileNode({
                    root: this.root,
                    path: this.path
                });
                arch.setNode(bundleNode, arch.getParents(this));
            }

            // generate targets for page files
            this.getTechs().map(function(tech) {
                var techNode = this.createTechNode(tech, bundleNode, this);
                if (techNode) {
                    this.createOptimizerNode(tech, techNode, bundleNode);
                }
            }, this);

            return Q.when(this.takeSnapshot('after alterArch BundleNode ' + this.getId()));
        };

    },

    lastModified: function() {
        return Q.resolve(0);
    },

    createTechNode: function(tech, bundleNode, magicNode) {

        var f = 'create-' + tech + '-node';
        f = typeof this[f] === 'function'? f : 'createDefaultTechNode';

        LOGGER.fdebug('Using %s() to create node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    createOptimizerNode: function(tech, sourceNode, bundleNode) {

        var f = 'create-' + tech + '-optimizer-node';
        f = typeof this[f] === 'function'? f : 'createDefaultOptimizerNode';

        LOGGER.fdebug('Using %s() to create optimizer node for tech %s', f, tech);

        return this[f].apply(this, arguments);

    },

    getBundlePath: function(tech) {
        return this.level.getPath(this.getNodePrefix(), tech);
    },

    getTechs: function() {
        return [
            'bemjson.js',
            'bemdecl.js',
            'deps.js',
            'bemhtml.js',
            'css',
            'ie.css',
            'js',
            'html'
        ];
    },

    cleanup: function() {
        var arch = this.ctx.arch;
        if (!arch.hasNode(this.path)) return;
        arch.removeTree(this.path);
    },

    getLevels: function(prefix) {
        return (this.level.getConfig().bundleBuildLevels || [])
            .concat([PATH.join(PATH.dirname(prefix), 'blocks')]);
    },

    /**
     * Checks that dependencies for specified node are met (appropriate nodes exist in the arch) or that node's file is
     * already exists on the file system.
     * @param node
     * @return {Node} Specified node if dependencies are met, FileNode if not but file does exist, null otherwise.
     */
    useFileOrBuild: function(node) {

        var deps = node.getDependencies();

        for(var i = 0, l = deps.length; i < l; i++) {
            var d = deps[i];

            if (!this.ctx.arch.hasNode(d)) {
                LOGGER.fverbose('Dependency %s is required to build %s but does not exist, checking if target already built', d, node.getId());

                if (!PATH.existsSync(node.getPath())) {
                    LOGGER.fwarn('%s will not be built because dependency file %s does not exist', node.path, d);
                    return null;
                }

                return new fileNodes.FileNode({
                    root: this.root,
                    path: node.getId()
                });
            }
        }

        return node;

    },

    /**
     * Create a bem build node, add it to the arch, add
     * dependencies to it. Then create a meta node and link
     * it to the build node.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} declPath
     * @param {String} bundleNode
     * @param {String} magicNode
     * @param {Boolean} [forked]
     */
    setBemBuildNode: function(techName, techPath, declPath, bundleNode, magicNode, forked) {

        var arch = this.ctx.arch,
            buildNode = new BemBuildNode.BemBuildNode({
                root: this.root,
                bundlesLevel: this.level,
                levels: this.getLevels(PATH.resolve(this.root, this.getNodePrefix())),
                declPath: declPath,
                techPath: techPath,
                techName: techName,
                output: this.getNodePrefix(),
                forked: forked
            }),
            metaNode = buildNode.getMetaNode();

        // Set bem build node to arch and add dependencies to it
        arch.setNode(buildNode)
            .addChildren(buildNode, buildNode.getDependencies());

        // Add file aliases to arch and link with buildNode as parents
        buildNode.getFiles().forEach(function(f) {

            if (buildNode.getId() === f) return;

            var alias = new fileNodes.FileNode({ path: f, root: this.root });
            arch.setNode(alias).addParents(buildNode, alias);

        }, this);

        bundleNode && arch.addParents(buildNode, bundleNode);
        magicNode && arch.addChildren(buildNode, magicNode);

        // Set bem build meta node to arch
        arch.setNode(metaNode)
            .addParents(metaNode, buildNode)
            .addChildren(metaNode, metaNode.getDependencies());

        return buildNode;

    },

    /**
     * Create a bem create node, add it to the arch,
     * add dependencies to it.
     *
     * @param {String} techName
     * @param {String} techPath
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setBemCreateNode: function(techName, techPath, bundleNode, magicNode, force) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemCreateNode.BemCreateNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: techPath,
                techName: techName,
                force: force
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        // Add file aliases to arch and link with node as parents
        node.getFiles && node.getFiles().forEach(function(f) {

            if (node.getId() === f) return;

            var alias = new fileNodes.FileNode({ path: f, root: this.root });
            arch.setNode(alias).addParents(node, alias);

        }, this);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    /**
     * Create file node, add it to the arch, add dependencies to it.
     *
     * @param {String} tech
     * @param {String} bundleNode
     * @param {String} magicNode
     */
    setFileNode: function(tech, bundleNode, magicNode) {

        var arch = this.ctx.arch,
            filePath = this.getBundlePath(tech);

        if (!PATH.existsSync(PATH.resolve(this.root, filePath))) return;

        var node = new fileNodes.FileNode({
            root: this.root,
            path: filePath
        });

        arch.setNode(node);

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;

    },

    createDefaultTechNode: function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('deps.js'),
            bundleNode,
            magicNode);

    },

    createDefaultOptimizerNode: function(tech, sourceNode, bundleNode) {},

    createBorschikOptimizerNode: function(tech, sourceNode, bundleNode) {

        var files = sourceNode.getFiles? sourceNode.getFiles() : [sourceNode.path];

        LOGGER.fdebug('Creating borschik nodes for %s', files);

        return files.map(function(file) {

                var node = new (registry.getNodeClass('BorschikNode'))({
                    root: this.root,
                    input: file,
                    tech: tech,
                    forked: true
                });

                this.ctx.arch
                    .setNode(node)
                    .addParents(node, bundleNode)
                    .addChildren(node, sourceNode);

                return node;

            }, this);

    },

    'create-bemjson.js-node': function(tech, bundleNode, magicNode) {
        return this.setFileNode.apply(this, arguments);
    },

    'create-bemdecl.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        return this.setBemBuildNode(
            tech,
            this.level.resolveTech(tech),
            this.getBundlePath('bemdecl.js'),
            bundleNode,
            magicNode);

    },

    'create-html-node': function(tech, bundleNode, magicNode) {

        return this.setBemCreateNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode);

    },

    'create-js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this.createBorschikOptimizerNode('js', sourceNode, bundleNode);
    },

    'create-priv.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-bemhtml.js-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-js-optimizer-node'].apply(this, arguments);
    },

    'create-css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this.createBorschikOptimizerNode('css-fast', sourceNode, bundleNode);
    },

    'create-ie.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var nodes = this['create-css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.link(this.getBundlePath('css'), nodes);
        return nodes;

    },

    'create-ie6.css-optimizer-node': function(tech, sourceNode, bundleNode) {

        var nodes = this['create-ie.css-optimizer-node'].apply(this, arguments);
        this.ctx.arch.link(this.getBundlePath('ie.css'), nodes);
        return nodes;

    },

    'create-ie7.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie8.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie6.css-optimizer-node'].apply(this, arguments);
    },

    'create-ie9.css-optimizer-node': function(tech, sourceNode, bundleNode) {
        return this['create-ie.css-optimizer-node'].apply(this, arguments);
    }

});

var MergedBundleNodeName = exports.MergedBundleNodeName = 'MergedBundleNode';

exports.__defineGetter__(MergedBundleNodeName, function() {
    return registry.getNodeClass(MergedBundleNodeName);
});

registry.decl(MergedBundleNodeName, BundleNodeName, /** @lends MergedBundleNode.prototype */ {

    make: function() {
        var path = PATH.resolve(this.root, this.path);

        if (!PATH.existsSync(path)) FS.mkdirSync(path);

        return this.__base();
    },

    /**
     * Overriden. Creates BemDecl node linked to the deps.js nodes of the bundles within containing level.
     * @param tech
     * @param bundleNode
     * @param magicNode
     * @return {*}
     */
    'create-deps.js-node': function(tech, bundleNode, magicNode) {

        var ctx = this.ctx,
            arch = ctx.arch,
            levelNode = arch.getNode(PATH.relative(this.root, this.level.dir)),
            depsTech = this.level.getTech('deps.js').getTechName(),
            bundles = arch.getChildren(levelNode)
                .filter(function(b) {
                    var n = arch.getNode(b);
                    return n instanceof exports.BundleNode && n !== this;
                }, this)
                .map(function(b) {
                    return U.getNodeTechPath(this.level, arch.getNode(b).item, depsTech);
                }, this);

        return this.setBemDeclNode(
            tech,
            this.level.resolveTech(tech),
            bundleNode,
            magicNode,
            'merge',
            bundles);

    },

    /**
     * Creates BemDecl node which maps to 'bem decl [cmd] [decls]'.
     * @param techName
     * @param techPath
     * @param bundleNode
     * @param magicNode
     * @param cmd Command to execute (merge or substract).
     * @param decls Declaration paths to execute command on.
     * @param force
     * @return {Node}
     */
    setBemDeclNode: function(techName, techPath, bundleNode, magicNode, cmd, decls, force) {

        var arch = this.ctx.arch,
            node = this.useFileOrBuild(new BemDeclNode.BemDeclNode({
                root: this.root,
                level: this.level,
                item: this.item,
                techPath: techPath,
                techName: techName,
                cmd: cmd,
                decls: decls,
                force: force
            }));

        if (!node) return;

        // Set bem create node to arch and add dependencies to it
        arch.setNode(node)
            .addChildren(node, node.getDependencies());

        bundleNode && arch.addParents(node, bundleNode);
        magicNode && arch.addChildren(node, magicNode);

        return node;
    }

});
<|endoftext|>


<commit_before>var precinct = require('precinct');
var q = require('q');
var path = require('path');
var fs = require('fs');
var cabinet = require('filing-cabinet');
var debug = require('debug')('tree');

/**
 * Recursively find all dependencies (avoiding circular) traversing the entire dependency tree
 * and returns a flat list of all unique, visited nodes
 *
 * @param {Object} options
 * @param {String} options.filename - The path of the module whose tree to traverse
 * @param {String} options.root - The directory containing all JS files
 * @param {Object} [options.visited] - Cache of visited, absolutely pathed files that should not be reprocessed.
 *                             Format is a filename -> tree as list lookup table
 * @param {Boolean} [options.isListForm=false]
 */
module.exports = function(options) {
  var filename = options.filename;
  var root = options.root;
  var visited = options.visited;
  var isListForm = options.isListForm;

  if (!filename) { throw new Error('filename not given'); }
  if (!root) { throw new Error('root directory not given'); }

  debug('given filename: ' + filename);

  filename = path.resolve(process.cwd(), filename);

  debug('resolved filename: ' + filename);

  visited = visited || {};

  debug('visited: ', visited);

  if (!fs.existsSync(filename)) {
    debug('file ' + filename + ' does not exist');
    return isListForm ? [] : {};
  }

  if (visited[filename]) {
    debug('already visited: ' + filename);

    return visited[filename];
  }

  var tree;
  var results = traverse({
    filename: filename,
    root: root,
    visited: visited,
    isListForm: isListForm
  });

  debug('traversal complete', results);

  if (isListForm) {
    debug('list form of results requested');

    tree = removeDups(results);
    debug('removed dups from the resulting list');

  } else {
    debug('object form of results requested');

    tree = {};
    tree[filename] = results;
  }

  debug('final tree', tree);
  return tree;
};

/**
 * Executes a post-order depth first search on the dependency tree and returns a
 * list of absolute file paths. The order of files in the list will be the
 * proper concatenation order for bundling.
 *
 * In other words, for any file in the list, all of that file's dependencies (direct or indirect) will appear at
 * lower indeces in the list. The root (entry point) file will therefore appear
 * last.
 *
 * The list will not contain duplicates.
 *
 * Params are those of module.exports
 */
module.exports.toList = function(options) {

  // Can't pass args since visited is optional and positions will be off
  return module.exports({
    filename: options.filename,
    root: options.root,
    visited: options.visited,
    isListForm: true
  });
};

/**
 * Returns the list of dependencies for the given filename
 * Protected for testing
 * @param  {String} filename
 * @return {String[]}
 */
module.exports._getDependencies = function(filename) {
  var dependencies;

  try {
    dependencies = precinct.paperwork(filename, {
      includeCore: false
    });
  } catch (e) {
    dependencies = [];
  }

  return dependencies;
};

/**
 * @param  {Object} options
 * @param  {String} options.filename
 * @param  {String} options.root
 * @param  {Object} options.visited
 * @param  {Boolean} [options.isListForm=false] - Whether or not to collect the tree in a list form
 * @param  {Boolean} [options.config] - Path to a requirejs config for AMD apps
 * @return {Object|String[]}
 */
function traverse(options) {
  var filename = options.filename;
  var root = options.root;
  var visited = options.visited;
  var isListForm = options.isListForm;
  var config = options.config;

  isListForm = !!isListForm;

  var subTree = isListForm ? [] : {};

  debug('traversing ' + filename);

  if (visited[filename]) {
    debug('already visited');
    return visited[filename];
  }

  var dependencies = module.exports._getDependencies(filename);

  debug('extracted ' + dependencies.length + ' dependencies: ', dependencies);

  if (dependencies.length) {
    debug('avoiding loaders');
    dependencies = avoidLoaders(dependencies);

    dependencies = dependencies
    .map(function(dep) {
      var options = {
        partial: dep,
        filename: filename,
        directory: root,
        config: {}
      };

      debug('cabinet lookup with options', options);

      return cabinet(options);
    })
    .filter(function(dep) {
      return fs.existsSync(dep);
    });
  }

  // Prevents cycles by eagerly marking the current file as read
  // so that any dependent dependencies exit
  visited[filename] = isListForm ? [] : {};

  dependencies.forEach(function(d) {
    var options = {
      filename: d,
      root: root,
      visited: visited,
      config: config
    };

    if (isListForm) {
      options.isListForm = isListForm;
      subTree = subTree.concat(traverse(options));
    } else {
      subTree[d] = traverse(options);
    }
  });

  if (isListForm) {
    // Prevents redundancy about each memoized step
    subTree = removeDups(subTree);
    subTree.push(filename);
    visited[filename] = visited[filename].concat(subTree);

  } else {
    visited[filename] = subTree;
  }

  return subTree;
}

/**
 * Returns a list of unique items from the array
 *
 * If only we had es6 Set.
 *
 * @param  {String[]} list
 * @return {String[]}
 */
function removeDups(list) {
  var cache = {};
  var unique = [];

  list.forEach(function(item) {
    if (!cache[item]) {
      unique.push(item);
      cache[item] = true;
    }
  });

  return unique;
}

/**
 * Returns a list of dependencies that do not include requirejs loaders (like hogan, text, and css)
 * @param  {String[]} dependencies
 * @return {String[]}
 */
function avoidLoaders(dependencies) {
  var avoided = [
    'hgn!',
    'css!',
    'txt!'
  ];
  var pattern = new RegExp(avoided.join('|'));

  return dependencies.filter(function(dep) {
    return !pattern.test(dep);
  });
}
<commit_msg>Inherit the extension for amd results<commit_after>var precinct = require('precinct');
var path = require('path');
var fs = require('fs');
var cabinet = require('filing-cabinet');
var debug = require('debug')('tree');

/**
 * Recursively find all dependencies (avoiding circular) traversing the entire dependency tree
 * and returns a flat list of all unique, visited nodes
 *
 * @param {Object} options
 * @param {String} options.filename - The path of the module whose tree to traverse
 * @param {String} options.root - The directory containing all JS files
 * @param {Object} [options.visited] - Cache of visited, absolutely pathed files that should not be reprocessed.
 *                             Format is a filename -> tree as list lookup table
 * @param {Boolean} [options.isListForm=false]
 */
module.exports = function(options) {
  var filename = options.filename;
  var root = options.root;
  var visited = options.visited;
  var isListForm = options.isListForm;

  if (!filename) { throw new Error('filename not given'); }
  if (!root) { throw new Error('root directory not given'); }

  debug('given filename: ' + filename);

  filename = path.resolve(process.cwd(), filename);

  debug('resolved filename: ' + filename);

  visited = visited || {};

  debug('visited: ', visited);

  if (!fs.existsSync(filename)) {
    debug('file ' + filename + ' does not exist');
    return isListForm ? [] : {};
  }

  if (visited[filename]) {
    debug('already visited: ' + filename);

    return visited[filename];
  }

  var tree;
  var results = traverse({
    filename: filename,
    root: root,
    visited: visited,
    isListForm: isListForm
  });

  debug('traversal complete', results);

  if (isListForm) {
    debug('list form of results requested');

    tree = removeDups(results);
    debug('removed dups from the resulting list');

  } else {
    debug('object form of results requested');

    tree = {};
    tree[filename] = results;
  }

  debug('final tree', tree);
  return tree;
};

/**
 * Executes a post-order depth first search on the dependency tree and returns a
 * list of absolute file paths. The order of files in the list will be the
 * proper concatenation order for bundling.
 *
 * In other words, for any file in the list, all of that file's dependencies (direct or indirect) will appear at
 * lower indeces in the list. The root (entry point) file will therefore appear
 * last.
 *
 * The list will not contain duplicates.
 *
 * Params are those of module.exports
 */
module.exports.toList = function(options) {

  // Can't pass args since visited is optional and positions will be off
  return module.exports({
    filename: options.filename,
    root: options.root,
    visited: options.visited,
    isListForm: true
  });
};

/**
 * Returns the list of dependencies for the given filename
 * Protected for testing
 * @param  {String} filename
 * @return {String[]}
 */
module.exports._getDependencies = function(filename) {
  var dependencies;

  try {
    dependencies = precinct.paperwork(filename, {
      includeCore: false
    });
  } catch (e) {
    dependencies = [];
  }

  return dependencies;
};

/**
 * @param  {Object} options
 * @param  {String} options.filename
 * @param  {String} options.root
 * @param  {Object} options.visited
 * @param  {Boolean} [options.isListForm=false] - Whether or not to collect the tree in a list form
 * @param  {Boolean} [options.config] - Path to a requirejs config for AMD apps
 * @return {Object|String[]}
 */
function traverse(options) {
  var filename = options.filename;
  var root = options.root;
  var visited = options.visited;
  var isListForm = options.isListForm;
  var config = options.config;

  isListForm = !!isListForm;

  var subTree = isListForm ? [] : {};

  debug('traversing ' + filename);

  if (visited[filename]) {
    debug('already visited');
    return visited[filename];
  }

  var dependencies = module.exports._getDependencies(filename);

  debug('extracted ' + dependencies.length + ' dependencies: ', dependencies);

  if (dependencies.length) {
    debug('avoiding loaders');
    dependencies = avoidLoaders(dependencies);

    dependencies = dependencies
    .map(function(dep) {
      var options = {
        partial: dep,
        filename: filename,
        directory: root,
        config: {}
      };

      debug('cabinet lookup with options', options);
      var result = cabinet(options);
      debug('cabinet result ' + result);

      if (!path.extname(result)) {
        debug('extensionless result');
        result += path.extname(filename);
        debug('after inheriting extension: ' + result);
      }

      return result;
    })
    .filter(function(dep) {
      debug('filtering out files that don\'t exist');
      return fs.existsSync(dep);
    });
  }

  // Prevents cycles by eagerly marking the current file as read
  // so that any dependent dependencies exit
  visited[filename] = isListForm ? [] : {};

  dependencies.forEach(function(d) {
    var options = {
      filename: d,
      root: root,
      visited: visited,
      config: config
    };

    if (isListForm) {
      options.isListForm = isListForm;
      subTree = subTree.concat(traverse(options));
    } else {
      subTree[d] = traverse(options);
    }
  });

  if (isListForm) {
    // Prevents redundancy about each memoized step
    subTree = removeDups(subTree);
    subTree.push(filename);
    visited[filename] = visited[filename].concat(subTree);

  } else {
    visited[filename] = subTree;
  }

  return subTree;
}

/**
 * Returns a list of unique items from the array
 *
 * If only we had es6 Set.
 *
 * @param  {String[]} list
 * @return {String[]}
 */
function removeDups(list) {
  var cache = {};
  var unique = [];

  list.forEach(function(item) {
    if (!cache[item]) {
      unique.push(item);
      cache[item] = true;
    }
  });

  return unique;
}

/**
 * Returns a list of dependencies that do not include requirejs loaders (like hogan, text, and css)
 * @param  {String[]} dependencies
 * @return {String[]}
 */
function avoidLoaders(dependencies) {
  var avoided = [
    'hgn!',
    'css!',
    'txt!'
  ];
  var pattern = new RegExp(avoided.join('|'));

  return dependencies.filter(function(dep) {
    return !pattern.test(dep);
  });
}
<|endoftext|>


<commit_before>const Slack = require('slack-node')
const slack = new Slack()

module.exports = function notify ({
  channel,
  text,
  webhook
}) {
  return new Promise((resolve, reject) => {
    slack.setWebhook(webhook)
    slack.webhook({channel, text}, (err, response) => {
      if (err) reject(err)
      else resolve(response)
    })
  })
}
<commit_msg>refactor(logger): Never reject when Slack webhook fails<commit_after>const Slack = require('slack-node')
const slack = new Slack()

module.exports = function notify ({
  channel,
  text,
  webhook
}) {
  return new Promise((resolve, reject) => {
    try {
      slack.setWebhook(webhook)
      slack.webhook({channel, text}, (err, response) => {
        if (err) onError(err)
        else resolve(response)
      })
    } catch (err) {
      onError(err)
    }

    function onError (err) {
      console.log(text)
      console.error('Error posting to Slack webhook')
      console.error(err)
      resolve(err) // Always resolve to avoid logging to cause failure
    }
  })
}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '4.0.3'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  // get the autoreply setting
  var autoreply = (opts || {}).autoreply;

  // initialise the metadata
  var localMeta = {};

  // create the signaller
  var signaller = mbus('', (opts || {}).logger);

  // initialise the id
  var id = signaller.id = (opts || {}).id || uuid();

  // initialise the attributes
  var attributes = signaller.attributes = {
    browser: detect.browser,
    browserVersion: detect.browserVersion,
    id: id,
    agent: 'signaller@' + metadata.version
  };

  // create the peers map
  var peers = signaller.peers = getable({});

  // create the outbound message queue
  var queue = require('pull-pushable')();

  var processor;
  var announceTimer = 0;
  var readyState = RS_DISCONNECTED;

  function announceOnReconnect() {
    signaller.announce();
  }

  function connect() {
    // initiate the messenger
    readyState = RS_CONNECTING;
    messenger(function(err, source, sink) {
      if (err) {
        return signaller('error', err);
      }

      // flag as connected
      readyState = RS_CONNECTED;

      // pass messages to the processor
      pull(
        source,
        // monitor disconnection
        pull.through(function(null, function() {
          console.log('ended');

          // trigger the disconnected event
          signaller('disconnected');
        })),
        pull.drain(function(data) {
          // process the message
          console.log('need to process the message: ', data);
        })
      )

      // pass the queue to the sink
      pull(queue, sink);

      // trigger the connected event
      signaller('connected');
    });
  }

  function createDataLine(args) {
    return args.map(prepareArg).join('|');
  }

  function createMetadata() {
    return extend({}, localMeta, { id: signaller.id });
  }

  function extractProp(name) {
    return messenger[name];
  }

  function prepareArg(arg) {
    if (typeof arg == 'object' && (! (arg instanceof String))) {
      return JSON.stringify(arg);
    }
    else if (typeof arg == 'function') {
      return null;
    }

    return arg;
  }

  /**
    ### signaller#send(message, data*)

    Use the send function to send a message to other peers in the current
    signalling scope (if announced in a room this will be a room, otherwise
    broadcast to all peers connected to the signalling server).

  **/
  var send = signaller.send = function() {
    // iterate over the arguments and stringify as required
    // var metadata = { id: signaller.id };
    var args = [].slice.call(arguments);

    // inject the metadata
    args.splice(1, 0, createMetadata());

    // queue the dataline for sending
    queue.push(createDataLine(args));
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data, sender) {

    function sendAnnounce() {
      (sender || send)('/announce', attributes);
      signaller('local:announce', attributes);
    }

    clearTimeout(announceTimer);

    // update internal attributes
    extend(attributes, data, { id: signaller.id });

    // if we are already connected, then ensure we announce on reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', announceOnReconnect);
      signaller.on('connected', announceOnReconnect);
    }

    // send the attributes over the network
    return announceTimer = setTimeout(function() {
      if (readyState !== RS_CONNECTED) {
        return signaller.once('connected', sendAnnounce);
      }

      sendAnnounce();
    }, (opts || {}).announceDelay || 10);
  };

  /**
    ### isMaster(targetId)

    A simple function that indicates whether the local signaller is the master
    for it's relationship with peer signaller indicated by `targetId`.  Roles
    are determined at the point at which signalling peers discover each other,
    and are simply worked out by whichever peer has the lowest signaller id
    when lexigraphically sorted.

    For example, if we have two signaller peers that have discovered each
    others with the following ids:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6`

    They would be assigned roles:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6` (master)

  **/
  signaller.isMaster = function(targetId) {
    var peer = peers.get(targetId);

    return peer && peer.roleIdx !== 0;
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    send('/leave', { id: id });

    // stop announcing on reconnect
    signaller.removeListener('connected', announceOnReconnect);

    // end our current queue
    queue.end();

    // create a new queue to buffer new messages
    queue = pushable();

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  /**
    ### metadata(data?)

    Get (pass no data) or set the metadata that is passed through with each
    request sent by the signaller.

    __NOTE:__ Regardless of what is passed to this function, metadata
    generated by the signaller will **always** include the id of the signaller
    and this cannot be modified.
  **/
  signaller.metadata = function(data) {
    if (arguments.length === 0) {
      return extend({}, localMeta);
    }

    localMeta = extend({}, data);
  };

  /**
    ### to(targetId)

    Use the `to` function to send a message to the specified target peer.
    A large parge of negotiating a WebRTC peer connection involves direct
    communication between two parties which must be done by the signalling
    server.  The `to` function provides a simple way to provide a logical
    communication channel between the two parties:

    ```js
    var send = signaller.to('e95fa05b-9062-45c6-bfa2-5055bf6625f4').send;

    // create an offer on a local peer connection
    pc.createOffer(
      function(desc) {
        // set the local description using the offer sdp
        // if this occurs successfully send this to our peer
        pc.setLocalDescription(
          desc,
          function() {
            send('/sdp', desc);
          },
          handleFail
        );
      },
      handleFail
    );
    ```

  **/
  signaller.to = function(targetId) {
    // create a sender that will prepend messages with /to|targetId|
    var sender = function() {
      // get the peer (yes when send is called to make sure it hasn't left)
      var peer = signaller.peers.get(targetId);
      var args;

      if (! peer) {
        throw new Error('Unknown peer: ' + targetId);
      }

      // if the peer is inactive, then abort
      if (peer.inactive) {
        return;
      }

      args = [
        '/to',
        targetId
      ].concat([].slice.call(arguments));

      // inject metadata
      args.splice(3, 0, createMetadata());
      queue.push(createDataLine(args));
    };

    return {
      announce: function(data) {
        return signaller.announce(data, sender);
      },

      send: sender,
    };
  };

  // initialise opts defaults
  opts = defaults({}, opts, require('./defaults'));

  // set the autoreply flag
  signaller.autoreply = autoreply === undefined || autoreply;

  // create the processor
  signaller.process = processor = require('./processor')(signaller, opts);

  connect();
  return signaller;
};
<commit_msg>Fix the stupid<commit_after>/* jshint node: true */
'use strict';

var detect = require('rtc-core/detect');
var defaults = require('cog/defaults');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');

// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;

// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
  version: '4.0.3'
};

/**
  # rtc-signaller

  The `rtc-signaller` module provides a transportless signalling
  mechanism for WebRTC.

  ## Purpose

  <<< docs/purpose.md

  ## Getting Started

  While the signaller is capable of communicating by a number of different
  messengers (i.e. anything that can send and receive messages over a wire)
  it comes with support for understanding how to connect to an
  [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.

  The following code sample demonstrates how:

  <<< examples/getting-started.js

  <<< docs/events.md

  <<< docs/signalflow-diagrams.md

  ## Reference

  The `rtc-signaller` module is designed to be used primarily in a functional
  way and when called it creates a new signaller that will enable
  you to communicate with other peers via your messaging network.

  ```js
  // create a signaller from something that knows how to send messages
  var signaller = require('rtc-signaller')(messenger);
  ```

  As demonstrated in the getting started guide, you can also pass through
  a string value instead of a messenger instance if you simply want to
  connect to an existing `rtc-switchboard` instance.

**/
module.exports = function(messenger, opts) {
  // get the autoreply setting
  var autoreply = (opts || {}).autoreply;

  // initialise the metadata
  var localMeta = {};

  // create the signaller
  var signaller = mbus('', (opts || {}).logger);

  // initialise the id
  var id = signaller.id = (opts || {}).id || uuid();

  // initialise the attributes
  var attributes = signaller.attributes = {
    browser: detect.browser,
    browserVersion: detect.browserVersion,
    id: id,
    agent: 'signaller@' + metadata.version
  };

  // create the peers map
  var peers = signaller.peers = getable({});

  // create the outbound message queue
  var queue = require('pull-pushable')();

  var processor;
  var announceTimer = 0;
  var readyState = RS_DISCONNECTED;

  function announceOnReconnect() {
    signaller.announce();
  }

  function connect() {
    // initiate the messenger
    readyState = RS_CONNECTING;
    messenger(function(err, source, sink) {
      if (err) {
        return signaller('error', err);
      }

      // flag as connected
      readyState = RS_CONNECTED;

      // pass messages to the processor
      pull(
        source,
        // monitor disconnection
        pull.through(null, function() {
          console.log('ended');

          // trigger the disconnected event
          signaller('disconnected');
        }),
        pull.drain(function(data) {
          // process the message
          console.log('need to process the message: ', data);
        })
      )

      // pass the queue to the sink
      pull(queue, sink);

      // trigger the connected event
      signaller('connected');
    });
  }

  function createDataLine(args) {
    return args.map(prepareArg).join('|');
  }

  function createMetadata() {
    return extend({}, localMeta, { id: signaller.id });
  }

  function extractProp(name) {
    return messenger[name];
  }

  function prepareArg(arg) {
    if (typeof arg == 'object' && (! (arg instanceof String))) {
      return JSON.stringify(arg);
    }
    else if (typeof arg == 'function') {
      return null;
    }

    return arg;
  }

  /**
    ### signaller#send(message, data*)

    Use the send function to send a message to other peers in the current
    signalling scope (if announced in a room this will be a room, otherwise
    broadcast to all peers connected to the signalling server).

  **/
  var send = signaller.send = function() {
    // iterate over the arguments and stringify as required
    // var metadata = { id: signaller.id };
    var args = [].slice.call(arguments);

    // inject the metadata
    args.splice(1, 0, createMetadata());

    // queue the dataline for sending
    queue.push(createDataLine(args));
  };

  /**
    ### announce(data?)

    The `announce` function of the signaller will pass an `/announce` message
    through the messenger network.  When no additional data is supplied to
    this function then only the id of the signaller is sent to all active
    members of the messenging network.

    #### Joining Rooms

    To join a room using an announce call you simply provide the name of the
    room you wish to join as part of the data block that you annouce, for
    example:

    ```js
    signaller.announce({ room: 'testroom' });
    ```

    Signalling servers (such as
    [rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
    place your peer connection into a room with other peers that have also
    announced in this room.

    Once you have joined a room, the server will only deliver messages that
    you `send` to other peers within that room.

    #### Providing Additional Announce Data

    There may be instances where you wish to send additional data as part of
    your announce message in your application.  For instance, maybe you want
    to send an alias or nick as part of your announce message rather than just
    use the signaller's generated id.

    If for instance you were writing a simple chat application you could join
    the `webrtc` room and tell everyone your name with the following announce
    call:

    ```js
    signaller.announce({
      room: 'webrtc',
      nick: 'Damon'
    });
    ```

    #### Announcing Updates

    The signaller is written to distinguish between initial peer announcements
    and peer data updates (see the docs on the announce handler below). As
    such it is ok to provide any data updates using the announce method also.

    For instance, I could send a status update as an announce message to flag
    that I am going offline:

    ```js
    signaller.announce({ status: 'offline' });
    ```

  **/
  signaller.announce = function(data, sender) {

    function sendAnnounce() {
      (sender || send)('/announce', attributes);
      signaller('local:announce', attributes);
    }

    clearTimeout(announceTimer);

    // update internal attributes
    extend(attributes, data, { id: signaller.id });

    // if we are already connected, then ensure we announce on reconnect
    if (readyState === RS_CONNECTED) {
      // always announce on reconnect
      signaller.removeListener('connected', announceOnReconnect);
      signaller.on('connected', announceOnReconnect);
    }

    // send the attributes over the network
    return announceTimer = setTimeout(function() {
      if (readyState !== RS_CONNECTED) {
        return signaller.once('connected', sendAnnounce);
      }

      sendAnnounce();
    }, (opts || {}).announceDelay || 10);
  };

  /**
    ### isMaster(targetId)

    A simple function that indicates whether the local signaller is the master
    for it's relationship with peer signaller indicated by `targetId`.  Roles
    are determined at the point at which signalling peers discover each other,
    and are simply worked out by whichever peer has the lowest signaller id
    when lexigraphically sorted.

    For example, if we have two signaller peers that have discovered each
    others with the following ids:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6`

    They would be assigned roles:

    - `b11f4fd0-feb5-447c-80c8-c51d8c3cced2`
    - `8a07f82e-49a5-4b9b-a02e-43d911382be6` (master)

  **/
  signaller.isMaster = function(targetId) {
    var peer = peers.get(targetId);

    return peer && peer.roleIdx !== 0;
  };

  /**
    ### leave()

    Tell the signalling server we are leaving.  Calling this function is
    usually not required though as the signalling server should issue correct
    `/leave` messages when it detects a disconnect event.

  **/
  signaller.leave = signaller.close = function() {
    // send the leave signal
    send('/leave', { id: id });

    // stop announcing on reconnect
    signaller.removeListener('connected', announceOnReconnect);

    // end our current queue
    queue.end();

    // create a new queue to buffer new messages
    queue = pushable();

    // set connected to false
    readyState = RS_DISCONNECTED;
  };

  /**
    ### metadata(data?)

    Get (pass no data) or set the metadata that is passed through with each
    request sent by the signaller.

    __NOTE:__ Regardless of what is passed to this function, metadata
    generated by the signaller will **always** include the id of the signaller
    and this cannot be modified.
  **/
  signaller.metadata = function(data) {
    if (arguments.length === 0) {
      return extend({}, localMeta);
    }

    localMeta = extend({}, data);
  };

  /**
    ### to(targetId)

    Use the `to` function to send a message to the specified target peer.
    A large parge of negotiating a WebRTC peer connection involves direct
    communication between two parties which must be done by the signalling
    server.  The `to` function provides a simple way to provide a logical
    communication channel between the two parties:

    ```js
    var send = signaller.to('e95fa05b-9062-45c6-bfa2-5055bf6625f4').send;

    // create an offer on a local peer connection
    pc.createOffer(
      function(desc) {
        // set the local description using the offer sdp
        // if this occurs successfully send this to our peer
        pc.setLocalDescription(
          desc,
          function() {
            send('/sdp', desc);
          },
          handleFail
        );
      },
      handleFail
    );
    ```

  **/
  signaller.to = function(targetId) {
    // create a sender that will prepend messages with /to|targetId|
    var sender = function() {
      // get the peer (yes when send is called to make sure it hasn't left)
      var peer = signaller.peers.get(targetId);
      var args;

      if (! peer) {
        throw new Error('Unknown peer: ' + targetId);
      }

      // if the peer is inactive, then abort
      if (peer.inactive) {
        return;
      }

      args = [
        '/to',
        targetId
      ].concat([].slice.call(arguments));

      // inject metadata
      args.splice(3, 0, createMetadata());
      queue.push(createDataLine(args));
    };

    return {
      announce: function(data) {
        return signaller.announce(data, sender);
      },

      send: sender,
    };
  };

  // initialise opts defaults
  opts = defaults({}, opts, require('./defaults'));

  // set the autoreply flag
  signaller.autoreply = autoreply === undefined || autoreply;

  // create the processor
  signaller.process = processor = require('./processor')(signaller, opts);

  connect();
  return signaller;
};
<|endoftext|>


<commit_before>const DatastoreTrailpack = require('trailpack/datastore')
const Waterline = require('waterline')
const lib = require('./lib')

/**
 * Waterline Trailpack
 *
 * Allow the trails application to interface with the Waterline ORM.
 */
module.exports = class WaterlineTrailpack extends DatastoreTrailpack {

  /**
   * Validate the database config, and api.model definitions
   */
  validate () {
    return Promise.all([
      lib.Validator.validateDatabaseConfig(this.app.config.database),
      //lib.Validator.validateModels(this.app.api.models)
    ])
  }

  /**
   * Merge configuration into models, load Waterline collections.
   */
  configure () {
    this.wl = new Waterline()
  }

  /**
   * Initialize Waterline. This will compile the schema and connect to the
   * database.
   */
  initialize () {
    this.models = lib.Transformer.transformModels(this.app)
    this.adapters = lib.Transformer.transformAdapters(this.app)
    this.connections = lib.Transformer.transformConnections(this.app)

    Object.values(this.models).map(model => {
      this.wl.loadCollection(Waterline.Collection.extend(model))
    })

    const wlConfig = { adapters: this.adapters, connections: this.connections }
    return new Promise((resolve, reject) => {
      this.wl.initialize(wlConfig, (err, orm) => {
        if (err) return reject(err)

        this.orm = orm
        this.app.orm = lib.Transformer.transformWaterlineOrm(this.orm)
        resolve()
      })
    })
  }

  unload () {
    return new Promise((resolve, reject) => {
      this.wl.teardown(resolve)
    })
  }

  constructor (app) {
    super(app, {
      config: require('./config'),
      pkg: require('./package'),
      api: require('./api')
    })
  }
}
<commit_msg>[fix] validate correct config obj<commit_after>const DatastoreTrailpack = require('trailpack/datastore')
const Waterline = require('waterline')
const lib = require('./lib')

/**
 * Waterline Trailpack
 *
 * Allow the trails application to interface with the Waterline ORM.
 */
module.exports = class WaterlineTrailpack extends DatastoreTrailpack {

  /**
   * Validate the database config, and api.model definitions
   */
  validate () {
    return Promise.all([
      lib.Validator.validateDatabaseConfig(this.app.config.stores),
      //lib.Validator.validateModels(this.app.api.models)
    ])
  }

  /**
   * Merge configuration into models, load Waterline collections.
   */
  configure () {
    this.wl = new Waterline()
  }

  /**
   * Initialize Waterline. This will compile the schema and connect to the
   * database.
   */
  initialize () {
    this.models = lib.Transformer.transformModels(this.app)
    this.adapters = lib.Transformer.transformAdapters(this.app)
    this.connections = lib.Transformer.transformConnections(this.app)

    Object.values(this.models).map(model => {
      this.wl.loadCollection(Waterline.Collection.extend(model))
    })

    const wlConfig = { adapters: this.adapters, connections: this.connections }
    return new Promise((resolve, reject) => {
      this.wl.initialize(wlConfig, (err, orm) => {
        if (err) return reject(err)

        this.orm = orm
        this.app.orm = lib.Transformer.transformWaterlineOrm(this.orm)
        resolve()
      })
    })
  }

  unload () {
    return new Promise((resolve, reject) => {
      this.wl.teardown(resolve)
    })
  }

  constructor (app) {
    super(app, {
      config: require('./config'),
      pkg: require('./package'),
      api: require('./api')
    })
  }
}
<|endoftext|>