<commit_before>var _ = require('underscore'),
    Q = require('q'),
    QFS = require('q-fs'),
    FS = require('fs'),
    INHERIT = require('inherit'),
    PATH = require('../path'),
    CP = require('child_process'),
    VM = require('vm'),
    LOGGER = require('../logger'),
    BEM = require('../coa').api,
    createLevel = require('../level').createLevel,
    Context = require('../context').Context,
    U = require('../util'),
    registry = require('../nodesregistry'),

    FileNode = require('./file'),
    GeneratedFileNode = require('./file').GeneratedFileNodeName,

    BemBuildNodeName = exports.BemBuildNodeName = 'BemBuildNode';

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

registry.decl(BemBuildNodeName, GeneratedFileNode, {

    nodeType: 7,

    __constructor: function(o) {

        this.bundlesLevel = o.bundlesLevel;
        this.levelsPaths = o.levels;
        this.levels = o.levels.map(createLevel);
        this.declPath = o.declPath;
        this.techName = o.techName;
        this.output = o.output;
        this.forked = typeof o.forked === 'undefined'? true : !!o.forked;

        // NOTE: Every time we need new tech object so we use createTech().
        // If we use getTech(), we'll overwrite context of the same tech
        // object that is prone to collisions
        this.tech = this.bundlesLevel.createTech(o.techName, o.techPath);
        this.techPath = this.tech.getTechPath();
        this.tech.setContext(new Context(this.bundlesLevel, { level: this.levels }));

        this.__base(U.extend({ path: this.__self.createId(o) }, o));

    },

    isValid: function() {
        var _this = this;

        return Q.when(this.__base(), function(valid){
            if (!valid) return false;

            var meta = _this.getMetaNode();
            if (!_this.ctx.arch.hasNode(meta)) return false;

            return Q.all([
                    _this.readMeta(),
                    _this.lastModified(),
                    meta.lastModified()
                ])
                .spread(function(meta, nodeLastModified, metaLastModified) {

                    // expired if <tech>.meta.js is invalid
                    if (meta === null) return false;

                    // expired if .<tech>.meta.js is newer than .<tech>
                    if (metaLastModified > nodeLastModified) return false;

                    // Possible values: promised, callback, sync
                    var strategy = process.env.BEM_IO_STRATEGY_BUILD_IS_VALID || process.env.BEM_IO_STRATEGY;

                    ['promised', 'callback', 'sync'].indexOf(strategy) !== -1 || (strategy = 'callback');
                    LOGGER.fverbose('Using %s strategy in BemBuildNode.isValid()', strategy);

                    if (strategy === 'promised') {

                        // Promised build result validity check
                        return (function() {

                            var valid = true;
                            return Q.all(meta.map(function(m) {

                                return QFS.lastModified(PATH.resolve(_this.root, m))
                                    .then(function(d) {
                                        if (d > nodeLastModified) valid = false;
                                    })
                                    .fail(function() {
                                        valid = false;
                                    });

                            }))
                            .then(function() {
                                return valid;
                            });

                        })();

                    }

                    else if (strategy === 'callback') {

                        // Async build result validity check
                        return (function() {

                            var d = Q.defer(),
                                total = meta.length,
                                count = 0;

                            if (total === 0) d.resolve(true);

                            meta.forEach(function(path) {

                                FS.stat(PATH.resolve(_this.root, path), function(err, stat) {

                                    count++;

                                    if (err || stat.mtime.getTime() > nodeLastModified) d.resolve(false);
                                    if (count < total) return;
                                    d.resolve(true);

                                });

                            });

                            return d.promise;

                        })();

                    }

                    // Sync build result validity check
                    // See https://github.com/bem/bem-tools/issues/157
                    for (var i = 0, l = meta.length; i < l; i++) {
                        if (FS.statSync(PATH.resolve(_this.root, meta[i])).mtime.getTime() > nodeLastModified) return false;
                    }

                    return true;

                });
        });
    },

    make: function() {
        var opts = {
            outputLevel: this.bundlesLevel.dir,
            level: this.levelsPaths,
            declaration: PATH.resolve(this.root, this.declPath),
            tech: this.techPath,
            outputName: PATH.resolve(this.root, this.output)
        };

        this.log('bem.build(forked=%j, %s)', this.forked, JSON.stringify(opts, null, 4));

        if (!this.forked) {
            opts.level = this.levels;
            return BEM.build(opts);
        }

        // TODO: generalize forking of bem commands
        var _this = this,
            d = Q.defer(),
            worker = CP.fork(PATH.join(__dirname, 'workers', 'bembuild.js'), null, { env: process.env }),
            handler = function(m) {
                (m.code !== 0)? d.reject(m.msg) : d.resolve();
            };

        worker.on('exit', function(code) {
            LOGGER.fdebug("Exit of bembuild worker for node '%s' with code %s", _this.output, code);
            handler({ code: code });
        });

        worker.on('message', function(m) {
            LOGGER.fdebug("Message from bembuild worker for node '%s': %j", _this.output, m);
            handler(m);
        });

        worker.send(opts);

        return d.promise;
    },

    readMeta: function() {
        return this._readMeta(this.getMetaNode().getPath());
    },

    _readMeta: function(path) {

        path = PATH.resolve(this.root, path);
        var _this = this,
            relativize = getPathRelativizer(this.root);

        return U.readFile(path)
            .then(function(c) {

                return VM.runInThisContext(c).map(function(f) {
                    return relativize(PATH.resolve(PATH.dirname(path), f));
                }, _this);

            })
            .fail(function() {
                return null;
            });

    },

    getMetaNode: function() {

        var node = this.metaNode || (this.metaNode = new exports.BemBuildMetaNode({
            root: this.root,
            bundlesLevel: this.bundlesLevel,
            levels: this.levelsPaths,
            declPath: this.declPath,
            techPath: this.techPath,
            techName: this.techName,
            output: this.output,
            forked: this.forked
        }));

        node.buildNode = this;

        return node;

    },

    /**
     * Get files minimum mtime in milliseconds or -1 in case of any file doesn't exist.
     *
     * @return {Promise * Number}
     */
    lastModified: function() {

        return Q.all(this.tech
            .getPaths(PATH.resolve(this.root, this.output), this.tech.getBuildSuffixes())
            .map(function(path) {

                return QFS.lastModified(path)
                    .fail(function() {
                        return -1;
                    });

            }))
            .spread(Math.min);

    },

    /**
     * clean() implementation.
     * @return {Promise * Undefined}
     */
    clean: function() {

        var _this = this;
        return Q.all(this.tech
            .getPaths(PATH.resolve(this.root, this.output), this.tech.getBuildSuffixes())
            .map(function(path) {

                return QFS.remove(path)
                    .then(function() {
                        LOGGER.fverbose('[-] Removed %j', path);
                    })
                    .fail(function() {});

            }))
            .then(function() {

                return U.removePath(_this.getPath())
                    .then(function() {
                        LOGGER.fverbose('[-] Removed %j', _this.getId());
                    })
                    .fail(function() {});

            });

    },

    getFiles: function() {
        return this.tech.getPaths(this.output, this.tech.getBuildSuffixes());
    },

    getDependencies: function() {

        var deps = this.tech.getDependencies().map(function(d) {
            return this.bundlesLevel.getPath(this.output, d);
        }, this);

        deps.push(this.declPath);
        return deps;

    }

}, {

    createId: function(o) {

        return o.bundlesLevel
            .createTech(o.techName, o.techPath)
            .getPath(o.output);

    }

});

var BemBuildMetaNodeName = exports.BemBuildMetaNodeName = 'BemBuildMetaNode';

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

registry.decl(BemBuildMetaNodeName, BemBuildNodeName, {

    /**
     * Overriden.
     *
     * Constructs new BemBuildMetaNode instance and changes the path property with `.bem/cache` prefix.
     *
     * @param {Object} o
     */
    __constructor: function(o) {
        this.__base(o);
        this.path = PATH.join('.bem', 'cache', this.path);
    },

    isValid: function() {

        var ctx = this.ctx;

        // expired in case of clean or other methods
        if (ctx.method && ctx.method != 'make') return false;

        var decl = U.readDecl(PATH.resolve(this.root, this.declPath)),
            tech = this.tech,
            relativize = getPathRelativizer(this.root),
            prefixes = tech.getBuildPrefixes(tech.transformBuildDecl(decl), this.levels),
            filteredPaths = tech.filterPrefixes(prefixes, tech.getSuffixes())
                .invoke('map', relativize),
            savedPaths = this.readMeta();

        ctx.prefixes = prefixes;
        ctx.filteredPaths = filteredPaths;
        ctx.savedPaths = savedPaths;

        // expired if build is forced; must return after filling the ctx with promises
        if (ctx.force) return false;

        var _this = this;
        return Q.all([filteredPaths, savedPaths])
            .spread(function(filteredPaths, savedPaths) {
                if (savedPaths === null) return false;

                var diff = [].concat(_.difference(savedPaths, filteredPaths),
                    _.difference(filteredPaths, savedPaths));

                LOGGER.fdebug('*** isValid(%j)=%j', _this.getId(), !diff.length);
                LOGGER.fdebug('*** diff=%j', diff);
                LOGGER.fdebug('*** savedPaths=%j', savedPaths);
                LOGGER.fdebug('*** filteredPaths=%j', filteredPaths);

                return !diff.length;
            });
    },

    make: function() {
        var _this = this,
            ctx = this.ctx,
            tech = this.tech,
            arch = ctx.arch,
            relativize = getPathRelativizer(this.root),
            allPaths = ctx.prefixes.then(function(prefixes) {
                return tech.getPaths(prefixes, tech.getCreateSuffixes()).map(relativize);
            });

        return Q.all([allPaths, ctx.filteredPaths, ctx.savedPaths])
            .spread(function(allPaths, filteredPaths, savedPaths) {

                return arch.withLock(_this.alterArch(allPaths, filteredPaths, savedPaths), _this)
                    .then(function() {

                        // write file list to .meta.js
                        var relativize = getPathRelativizer(PATH.dirname(_this.path)),
                            paths = filteredPaths.map(function(f) {
                                return relativize(f);
                            });

                        U.mkdirs(PATH.dirname(_this.getPath()));
                        return QFS.write(_this.getPath(), '(' + JSON.stringify(paths, null, 4) + ')');

                    });

            });
    },

    alterArch: function(allPaths, filteredPaths, savedPaths) {

        return function() {

            var ctx = this.ctx,
                arch = ctx.arch,
                buildNodeId = this.buildNode.getId();

            // find difference with array read from file and filteredPaths
            var obsolete = _.difference(savedPaths, filteredPaths);

            // clean obsolete dependencies from arch
            obsolete.forEach(function(p) {
                if (!arch.hasNode(p)) return;

                // remove link with p
                arch.unlink(p, buildNodeId);

                // when p has no other dependent nodes, remove it from arch
                if (!arch.getParents(p).length) arch.removeNode(p);
            });

            // create nodes for all existent paths to blocks files: FileNode(path)
            filteredPaths.forEach(function(p) {
                arch.hasNode(p) || arch.addNode(new FileNode.FileNode({
                    root: this.root,
                    path: p
                }));
                // link created nodes to BemBuildNode corresponding to this node
                ctx.plan.link(p, buildNodeId);
            }, this);

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

    },

    readMeta: function() {
        return this._readMeta(this.getPath());
    },

    /**
     * Get file mtime in milliseconds or -1 in case of file doesn't exist.
     *
     * @return {Promise * Number}
     */
    lastModified: function() {

        // TODO: should really use link to GeneratedFileNode.prototype.lastModified() here
        return QFS.lastModified(this.getPath())
            .fail(function() {
                return -1;
            });

    },

    /**
     * clean() implementation.
     * @return {Promise * Undefined}
     */
    clean: function() {

        // TODO: should really use link to GeneratedFileNode.prototype.clean() here
        var _this = this;
        return QFS.remove(this.getPath())
            .then(function() {
                LOGGER.fverbose('[-] Removed %j', _this.getId());
            })
            .fail(function() {});

    },

    getDependencies: function() {
        return [this.declPath];
    }

}, {

    createId: function(o) {
        return this.__base(o) + '.meta.js';
    }

});

function getPathRelativizer(from) {
    return function(p) {
        return PATH.relative(from, p);
    }
}
<commit_msg>Use proper (build) suffixes in MetaNode (close #320)<commit_after>var _ = require('underscore'),
    Q = require('q'),
    QFS = require('q-fs'),
    FS = require('fs'),
    INHERIT = require('inherit'),
    PATH = require('../path'),
    CP = require('child_process'),
    VM = require('vm'),
    LOGGER = require('../logger'),
    BEM = require('../coa').api,
    createLevel = require('../level').createLevel,
    Context = require('../context').Context,
    U = require('../util'),
    registry = require('../nodesregistry'),

    FileNode = require('./file'),
    GeneratedFileNode = require('./file').GeneratedFileNodeName,

    BemBuildNodeName = exports.BemBuildNodeName = 'BemBuildNode';

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

registry.decl(BemBuildNodeName, GeneratedFileNode, {

    nodeType: 7,

    __constructor: function(o) {

        this.bundlesLevel = o.bundlesLevel;
        this.levelsPaths = o.levels;
        this.levels = o.levels.map(createLevel);
        this.declPath = o.declPath;
        this.techName = o.techName;
        this.output = o.output;
        this.forked = typeof o.forked === 'undefined'? true : !!o.forked;

        // NOTE: Every time we need new tech object so we use createTech().
        // If we use getTech(), we'll overwrite context of the same tech
        // object that is prone to collisions
        this.tech = this.bundlesLevel.createTech(o.techName, o.techPath);
        this.techPath = this.tech.getTechPath();
        this.tech.setContext(new Context(this.bundlesLevel, { level: this.levels }));

        this.__base(U.extend({ path: this.__self.createId(o) }, o));

    },

    isValid: function() {
        var _this = this;

        return Q.when(this.__base(), function(valid){
            if (!valid) return false;

            var meta = _this.getMetaNode();
            if (!_this.ctx.arch.hasNode(meta)) return false;

            return Q.all([
                    _this.readMeta(),
                    _this.lastModified(),
                    meta.lastModified()
                ])
                .spread(function(meta, nodeLastModified, metaLastModified) {

                    // expired if <tech>.meta.js is invalid
                    if (meta === null) return false;

                    // expired if .<tech>.meta.js is newer than .<tech>
                    if (metaLastModified > nodeLastModified) return false;

                    // Possible values: promised, callback, sync
                    var strategy = process.env.BEM_IO_STRATEGY_BUILD_IS_VALID || process.env.BEM_IO_STRATEGY;

                    ['promised', 'callback', 'sync'].indexOf(strategy) !== -1 || (strategy = 'callback');
                    LOGGER.fverbose('Using %s strategy in BemBuildNode.isValid()', strategy);

                    if (strategy === 'promised') {

                        // Promised build result validity check
                        return (function() {

                            var valid = true;
                            return Q.all(meta.map(function(m) {

                                return QFS.lastModified(PATH.resolve(_this.root, m))
                                    .then(function(d) {
                                        if (d > nodeLastModified) valid = false;
                                    })
                                    .fail(function() {
                                        valid = false;
                                    });

                            }))
                            .then(function() {
                                return valid;
                            });

                        })();

                    }

                    else if (strategy === 'callback') {

                        // Async build result validity check
                        return (function() {

                            var d = Q.defer(),
                                total = meta.length,
                                count = 0;

                            if (total === 0) d.resolve(true);

                            meta.forEach(function(path) {

                                FS.stat(PATH.resolve(_this.root, path), function(err, stat) {

                                    count++;

                                    if (err || stat.mtime.getTime() > nodeLastModified) d.resolve(false);
                                    if (count < total) return;
                                    d.resolve(true);

                                });

                            });

                            return d.promise;

                        })();

                    }

                    // Sync build result validity check
                    // See https://github.com/bem/bem-tools/issues/157
                    for (var i = 0, l = meta.length; i < l; i++) {
                        if (FS.statSync(PATH.resolve(_this.root, meta[i])).mtime.getTime() > nodeLastModified) return false;
                    }

                    return true;

                });
        });
    },

    make: function() {
        var opts = {
            outputLevel: this.bundlesLevel.dir,
            level: this.levelsPaths,
            declaration: PATH.resolve(this.root, this.declPath),
            tech: this.techPath,
            outputName: PATH.resolve(this.root, this.output)
        };

        this.log('bem.build(forked=%j, %s)', this.forked, JSON.stringify(opts, null, 4));

        if (!this.forked) {
            opts.level = this.levels;
            return BEM.build(opts);
        }

        // TODO: generalize forking of bem commands
        var _this = this,
            d = Q.defer(),
            worker = CP.fork(PATH.join(__dirname, 'workers', 'bembuild.js'), null, { env: process.env }),
            handler = function(m) {
                (m.code !== 0)? d.reject(m.msg) : d.resolve();
            };

        worker.on('exit', function(code) {
            LOGGER.fdebug("Exit of bembuild worker for node '%s' with code %s", _this.output, code);
            handler({ code: code });
        });

        worker.on('message', function(m) {
            LOGGER.fdebug("Message from bembuild worker for node '%s': %j", _this.output, m);
            handler(m);
        });

        worker.send(opts);

        return d.promise;
    },

    readMeta: function() {
        return this._readMeta(this.getMetaNode().getPath());
    },

    _readMeta: function(path) {

        path = PATH.resolve(this.root, path);
        var _this = this,
            relativize = getPathRelativizer(this.root);

        return U.readFile(path)
            .then(function(c) {

                return VM.runInThisContext(c).map(function(f) {
                    return relativize(PATH.resolve(PATH.dirname(path), f));
                }, _this);

            })
            .fail(function() {
                return null;
            });

    },

    getMetaNode: function() {

        var node = this.metaNode || (this.metaNode = new exports.BemBuildMetaNode({
            root: this.root,
            bundlesLevel: this.bundlesLevel,
            levels: this.levelsPaths,
            declPath: this.declPath,
            techPath: this.techPath,
            techName: this.techName,
            output: this.output,
            forked: this.forked
        }));

        node.buildNode = this;

        return node;

    },

    /**
     * Get files minimum mtime in milliseconds or -1 in case of any file doesn't exist.
     *
     * @return {Promise * Number}
     */
    lastModified: function() {

        return Q.all(this.tech
            .getPaths(PATH.resolve(this.root, this.output), this.tech.getBuildSuffixes())
            .map(function(path) {

                return QFS.lastModified(path)
                    .fail(function() {
                        return -1;
                    });

            }))
            .spread(Math.min);

    },

    /**
     * clean() implementation.
     * @return {Promise * Undefined}
     */
    clean: function() {

        var _this = this;
        return Q.all(this.tech
            .getPaths(PATH.resolve(this.root, this.output), this.tech.getBuildSuffixes())
            .map(function(path) {

                return QFS.remove(path)
                    .then(function() {
                        LOGGER.fverbose('[-] Removed %j', path);
                    })
                    .fail(function() {});

            }))
            .then(function() {

                return U.removePath(_this.getPath())
                    .then(function() {
                        LOGGER.fverbose('[-] Removed %j', _this.getId());
                    })
                    .fail(function() {});

            });

    },

    getFiles: function() {
        return this.tech.getPaths(this.output, this.tech.getBuildSuffixes());
    },

    getDependencies: function() {

        var deps = this.tech.getDependencies().map(function(d) {
            return this.bundlesLevel.getPath(this.output, d);
        }, this);

        deps.push(this.declPath);
        return deps;

    }

}, {

    createId: function(o) {

        return o.bundlesLevel
            .createTech(o.techName, o.techPath)
            .getPath(o.output);

    }

});

var BemBuildMetaNodeName = exports.BemBuildMetaNodeName = 'BemBuildMetaNode';

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

registry.decl(BemBuildMetaNodeName, BemBuildNodeName, {

    /**
     * Overriden.
     *
     * Constructs new BemBuildMetaNode instance and changes the path property with `.bem/cache` prefix.
     *
     * @param {Object} o
     */
    __constructor: function(o) {
        this.__base(o);
        this.path = PATH.join('.bem', 'cache', this.path);
    },

    isValid: function() {

        var ctx = this.ctx;

        // expired in case of clean or other methods
        if (ctx.method && ctx.method != 'make') return false;

        var decl = U.readDecl(PATH.resolve(this.root, this.declPath)),
            tech = this.tech,
            relativize = getPathRelativizer(this.root),
            prefixes = tech.getBuildPrefixes(tech.transformBuildDecl(decl), this.levels),
            filteredPaths = tech.filterPrefixes(prefixes, tech.getBuildSuffixes())
                .invoke('map', relativize),
            savedPaths = this.readMeta();

        ctx.prefixes = prefixes;
        ctx.filteredPaths = filteredPaths;
        ctx.savedPaths = savedPaths;

        // expired if build is forced; must return after filling the ctx with promises
        if (ctx.force) return false;

        var _this = this;
        return Q.all([filteredPaths, savedPaths])
            .spread(function(filteredPaths, savedPaths) {
                if (savedPaths === null) return false;

                var diff = [].concat(_.difference(savedPaths, filteredPaths),
                    _.difference(filteredPaths, savedPaths));

                LOGGER.fdebug('*** isValid(%j)=%j', _this.getId(), !diff.length);
                LOGGER.fdebug('*** diff=%j', diff);
                LOGGER.fdebug('*** savedPaths=%j', savedPaths);
                LOGGER.fdebug('*** filteredPaths=%j', filteredPaths);

                return !diff.length;
            });
    },

    make: function() {
        var _this = this,
            ctx = this.ctx,
            tech = this.tech,
            arch = ctx.arch,
            relativize = getPathRelativizer(this.root),
            allPaths = ctx.prefixes.then(function(prefixes) {
                return tech.getPaths(prefixes, tech.getBuildSuffixes()).map(relativize);
            });

        return Q.all([allPaths, ctx.filteredPaths, ctx.savedPaths])
            .spread(function(allPaths, filteredPaths, savedPaths) {

                return arch.withLock(_this.alterArch(allPaths, filteredPaths, savedPaths), _this)
                    .then(function() {

                        // write file list to .meta.js
                        var relativize = getPathRelativizer(PATH.dirname(_this.path)),
                            paths = filteredPaths.map(function(f) {
                                return relativize(f);
                            });

                        U.mkdirs(PATH.dirname(_this.getPath()));
                        return QFS.write(_this.getPath(), '(' + JSON.stringify(paths, null, 4) + ')');

                    });

            });
    },

    alterArch: function(allPaths, filteredPaths, savedPaths) {

        return function() {

            var ctx = this.ctx,
                arch = ctx.arch,
                buildNodeId = this.buildNode.getId();

            // find difference with array read from file and filteredPaths
            var obsolete = _.difference(savedPaths, filteredPaths);

            // clean obsolete dependencies from arch
            obsolete.forEach(function(p) {
                if (!arch.hasNode(p)) return;

                // remove link with p
                arch.unlink(p, buildNodeId);

                // when p has no other dependent nodes, remove it from arch
                if (!arch.getParents(p).length) arch.removeNode(p);
            });

            // create nodes for all existent paths to blocks files: FileNode(path)
            filteredPaths.forEach(function(p) {
                arch.hasNode(p) || arch.addNode(new FileNode.FileNode({
                    root: this.root,
                    path: p
                }));
                // link created nodes to BemBuildNode corresponding to this node
                ctx.plan.link(p, buildNodeId);
            }, this);

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

    },

    readMeta: function() {
        return this._readMeta(this.getPath());
    },

    /**
     * Get file mtime in milliseconds or -1 in case of file doesn't exist.
     *
     * @return {Promise * Number}
     */
    lastModified: function() {

        // TODO: should really use link to GeneratedFileNode.prototype.lastModified() here
        return QFS.lastModified(this.getPath())
            .fail(function() {
                return -1;
            });

    },

    /**
     * clean() implementation.
     * @return {Promise * Undefined}
     */
    clean: function() {

        // TODO: should really use link to GeneratedFileNode.prototype.clean() here
        var _this = this;
        return QFS.remove(this.getPath())
            .then(function() {
                LOGGER.fverbose('[-] Removed %j', _this.getId());
            })
            .fail(function() {});

    },

    getDependencies: function() {
        return [this.declPath];
    }

}, {

    createId: function(o) {
        return this.__base(o) + '.meta.js';
    }

});

function getPathRelativizer(from) {
    return function(p) {
        return PATH.relative(from, p);
    }
}
<|endoftext|>


<commit_before>const Slate = require('slate');

function onBackspace(event, data, state, opts) {
    const { startBlock, startOffset,
        isCollapsed, endBlock } = state;

    // If a cursor is collapsed at the start of the block, do nothing
    if (startOffset === 0 && isCollapsed) {
        event.preventDefault();
        return state;
    }

    // If "normal" deletion, we continue
    if (startBlock === endBlock) {
        return;
    }

    // If cursor is between multiple blocks,
    // we clear the content of the cells
    event.preventDefault();

    const { blocks, focusBlock } = state;
    const transform = blocks.reduce(
        (tr, block) => {
            if (block.type !== opts.typeCell) {
                return transform;
            }

            const cellRange = Slate.Selection.create()
                .collapseToStartOf(block)
                .moveToOffsets(0, block.length);

            return tr.deleteAtRange(cellRange);
        },
        state.transform()
    );

    // Clear selected cells
    return transform
        .collapseToStartOf(focusBlock)
        .apply();
}

module.exports = onBackspace;
<commit_msg>review: use moveToRangeOf<commit_after>const Slate = require('slate');

function onBackspace(event, data, state, opts) {
    const { startBlock, startOffset,
        isCollapsed, endBlock } = state;

    // If a cursor is collapsed at the start of the block, do nothing
    if (startOffset === 0 && isCollapsed) {
        event.preventDefault();
        return state;
    }

    // If "normal" deletion, we continue
    if (startBlock === endBlock) {
        return;
    }

    // If cursor is between multiple blocks,
    // we clear the content of the cells
    event.preventDefault();

    const { blocks, focusBlock } = state;
    const transform = blocks.reduce(
        (tr, block) => {
            if (block.type !== opts.typeCell) {
                return transform;
            }

            const cellRange = Slate.Selection.create()
                .moveToRangeOf(block);

            return tr.deleteAtRange(cellRange);
        },
        state.transform()
    );

    // Clear selected cells
    return transform
        .collapseToStartOf(focusBlock)
        .apply();
}

module.exports = onBackspace;
<|endoftext|>


<commit_before>var url = require("url");
var mime = require("mime");
var fresh = require("fresh");

var renderer = require("./page_renderer.js");
var asset_bundler = require("./asset_bundler.js");

var module_utils = require("./utils/module_utils.js");
var path_utils = require("./utils/path_utils.js");
var object_utils = require("./utils/object_utils.js");
var connect_utils = require("connect").utils;

module.exports = {

	cacheStore: null,

	cacheSettings: {},

	setCacheExpiryHeaders: function(header, cache_settings) {
		var cache_control = ( cache_settings.directives && cache_settings.directives.slice(0) ) || [];

		var max_age = cache_settings["max_age"] || 0;
		cache_control.push("max-age=" + max_age);

		var expiry_milliseconds = new Date().getTime() + (parseInt(max_age, 10) * 1000);
		header["Expires"] = new Date(expiry_milliseconds).toUTCString();
		header["Cache-Control"] = cache_control.join(", ");
	},

	setContentType: function(header, file_extension, preset) {
		if (preset) {
			header["Content-Type"] = preset;
		}	else {
			var content_type = mime.lookup(file_extension);
			var charset = mime.charsets.lookup(content_type);

			header["Content-Type"] = content_type + (charset ? "; charset=" + charset.toLowerCase() : "");
		}
	},

	sendResponse: function(response, status_code, headers, body){
		var encoding = (headers["Content-Type"].split("=")[1] === "utf-8") ? "utf8" : "binary";

		response.statusCode = parseInt(status_code, 10);

		if (headers) {
			for (var name in headers) {
				if (headers.hasOwnProperty(name)) {
					response.setHeader(name, headers[name]);
				}
			}
		}

		response.end(new Buffer(body, encoding));
	},

	getStatusPage: function(response, status_code, file_extension, header, request_options) {
		var self = this;
		var status_code_path = "/" + status_code;

		// reset the content type to error page's content type
		self.setContentType(header, file_extension);

		self.cacheStore.get(status_code_path, file_extension, object_utils.cacheObj(null, header), request_options, function(err, cache_obj){
			if(!err){
				return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
			} else {
				renderer.render(status_code_path, file_extension, null, request_options, function(rendered_obj) {

					var body_length = (rendered_obj.body && rendered_obj.body.length || 0);
					header["Content-Length"] = body_length;

					if (body_length) {
						self.cacheStore.update(status_code_path, file_extension, object_utils.cacheObj(rendered_obj.body, header), request_options, function(err){
							if (err) {
								// we do nothing here.
								// maybe log the failure?
							}

							return self.sendResponse(response, status_code, header, rendered_obj.body);
						});
					} else {
						return self.sendResponse(response, status_code, header, "");
					}
				});
			}
		});
	},

	prepareRenderedResponse: function(response, request_basename, file_extension, rendered_obj, request_options) {
		var self = this;

		var rendered_options = rendered_obj.options || {};
		var header = rendered_options.header || {};
		var cache_settings = rendered_options.cache || self.cacheSettings;
		var status_code = parseInt(rendered_options.status, 10) || 200;

		self.setContentType(header, file_extension, header["Content-Type"]);

		self.setCacheExpiryHeaders(header, cache_settings);

		// Check for log messages and print them on the console.
		if (rendered_options.log) {
			console.log(rendered_options.log.message);
		}

		if (status_code === 200 && rendered_obj.body.length) {
			self.cacheStore.update(request_basename, file_extension, object_utils.cacheObj(rendered_obj.body, header), request_options, function(err, cache_obj) {
				if (err) {
					console.log("[Error in Cache] " + err);
					return self.getStatusPage(response, 500, ".html", header, request_options);
				}

				return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
			});
		} else {
			return self.getStatusPage(response, status_code, ".html", header, request_options);
		}
	},

	prepareCachedResponse: function(response, request_basename, file_extension, rendered_obj, request_options) {
		var self = this;

		var rendered_options = rendered_obj.options || {};
		var header = rendered_options.header || {};
		var cache_settings = rendered_options.cache || self.cacheSettings;
		var status_code = parseInt(rendered_options.status, 10) || 200;

		self.setContentType(header, file_extension, header["Content-Type"]);

		self.setCacheExpiryHeaders(header, cache_settings);

		self.cacheStore.get(request_basename, file_extension, object_utils.cacheObj(null, header), request_options, function(err, cache_obj) {
			return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
		});
	},

	validatePublicCache: function(request, response, stat, callback) {
		var self = this;
		var headers;

		if (connect_utils.conditionalGET(request)) {
			if (stat) {
				headers = { "etag": connect_utils.etag(stat), "last-modified": stat.mtime };
			} else {
				headers = { "etag": response.getHeader("etag"), "last-modified": response.getHeader("last-modified") };
			}

			// check if the cache is still fresh
			if (fresh(request.headers, headers)) {
				return connect_utils.notModified(response);
			}	else {
				return callback();
			}
		} else {
			return callback();
		}
	},

	handleBundleRequest: function(request, response, request_basename, file_extension, request_options) {
		var self = this;

		asset_bundler.getBundle(request_basename, file_extension, request_options, function(err, bundled_obj) {
			if (err) {
				return self.getStatusPage(response, 404, ".html", {}, request_options);
			}

			var bundled_options = bundled_obj.options || {};
			var header = bundled_options.header || {};
			var cache_settings = bundled_options.cache || self.cacheSettings;
			var status_code = parseInt(bundled_options.status, 10) || 200;

			self.setContentType(header, file_extension);
			self.setCacheExpiryHeaders(header, cache_settings);

			// Check for log messages and print them on the console.
			if (bundled_options.log) {
				console.log(bundled_options.log.message);
			}

			if (bundled_obj.modified) {
				return self.sendResponse(response, status_code, header, bundled_obj.body);
			} else {
				response.setHeader("ETag", header["ETag"]);
				response.setHeader("Last-Modified", header["Last-Modified"]);

				// If public cache is not modified serve a 304 response.
				return self.validatePublicCache(request, response, null, function() {
					// This callback runs only if the public cache is invalid.
					return self.sendResponse(response, status_code, header, bundled_obj.body);
				});
			}
		});
	},

	handlePageRequest: function(request, response, request_basename, file_extension, request_options) {
		var self = this;

		self.cacheStore.stat(request_basename, file_extension, request_options, function(err, stat) {
			var last_modified = (stat && stat.mtime) || null;

			renderer.render(request_basename, file_extension, last_modified, request_options, function(rendered_obj) {
				if (rendered_obj.modified) {
					return self.prepareRenderedResponse(response, request_basename, file_extension, rendered_obj, request_options);
				} else {
					// If public cache is not modified serve a 304 response.
					return self.validatePublicCache(request, response, stat, function() {
						// This callback runs only if the public cache is invalid.
						return self.prepareCachedResponse(response, request_basename, file_extension, rendered_obj, request_options);
					});
				}
			});
		});
	},

	handle: function(request, response, next) {
		var self = this;

		var parsed_url = url.parse(request.url, true);
    var request_path = parsed_url.pathname.replace(/\.\.\/|\/$/g, "");

		var file_extension = path_utils.getExtension(request_path, (request.accept && request.accept.types));
		var request_basename = path_utils.getBasename(request_path, file_extension);

		var request_options = {
			"query": parsed_url.query,
			"host": request.headers.host,
			"xhr": (request.headers.http_x_requested_with && request.headers.http_x_requested_with === "xmlhttprequest"),
			"authorization": request.headers.authorization,
			"header": request.headers,
			"cookies": request.cookies
		};

		// check if the given request is an asset bundler request
		if (asset_bundler.isBundlePath(request_basename, file_extension)) {
			return self.handleBundleRequest(request, response, request_basename, file_extension, request_options);
		} else {
			return self.handlePageRequest(request, response, request_basename, file_extension, request_options);
		}
	},

	setup: function(config){
		var self = this;

		self.cacheSettings = (config.server && config.server.cache) || {};

		renderer.setup(config);

		asset_bundler.setup(config);

		self.cacheStore = module_utils.requireAndSetup(config.plugins.cache_store, config);

		return function(req, res, next){
			return self.handle(req, res, next);
		};
	}

};
<commit_msg>Only encode html responses as UTF-8<commit_after>var url = require("url");
var mime = require("mime");
var fresh = require("fresh");

var renderer = require("./page_renderer.js");
var asset_bundler = require("./asset_bundler.js");

var module_utils = require("./utils/module_utils.js");
var path_utils = require("./utils/path_utils.js");
var object_utils = require("./utils/object_utils.js");
var connect_utils = require("connect").utils;

module.exports = {

	cacheStore: null,

	cacheSettings: {},

	setCacheExpiryHeaders: function(header, cache_settings) {
		var cache_control = ( cache_settings.directives && cache_settings.directives.slice(0) ) || [];

		var max_age = cache_settings["max_age"] || 0;
		cache_control.push("max-age=" + max_age);

		var expiry_milliseconds = new Date().getTime() + (parseInt(max_age, 10) * 1000);
		header["Expires"] = new Date(expiry_milliseconds).toUTCString();
		header["Cache-Control"] = cache_control.join(", ");
	},

	setContentType: function(header, file_extension, preset) {
		if (preset) {
			header["Content-Type"] = preset;
		}	else {
			var content_type = mime.lookup(file_extension);
			var charset = mime.charsets.lookup(content_type);

			header["Content-Type"] = content_type + (charset ? "; charset=" + charset.toLowerCase() : "");
		}
	},

	sendResponse: function(response, status_code, headers, body){
		var encoding = (headers["Content-Type"].split(";")[0] === "text/html") ? "utf8" : "binary";

		response.statusCode = parseInt(status_code, 10);

		if (headers) {
			for (var name in headers) {
				if (headers.hasOwnProperty(name)) {
					response.setHeader(name, headers[name]);
				}
			}
		}

		response.end(body, encoding);
	},

	getStatusPage: function(response, status_code, file_extension, header, request_options) {
		var self = this;
		var status_code_path = "/" + status_code;

		// reset the content type to error page's content type
		self.setContentType(header, file_extension);

		self.cacheStore.get(status_code_path, file_extension, object_utils.cacheObj(null, header), request_options, function(err, cache_obj){
			if(!err){
				return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
			} else {
				renderer.render(status_code_path, file_extension, null, request_options, function(rendered_obj) {

					var body_length = (rendered_obj.body && rendered_obj.body.length || 0);
					header["Content-Length"] = body_length;

					if (body_length) {
						self.cacheStore.update(status_code_path, file_extension, object_utils.cacheObj(rendered_obj.body, header), request_options, function(err){
							if (err) {
								// we do nothing here.
								// maybe log the failure?
							}

							return self.sendResponse(response, status_code, header, rendered_obj.body);
						});
					} else {
						return self.sendResponse(response, status_code, header, "");
					}
				});
			}
		});
	},

	prepareRenderedResponse: function(response, request_basename, file_extension, rendered_obj, request_options) {
		var self = this;

		var rendered_options = rendered_obj.options || {};
		var header = rendered_options.header || {};
		var cache_settings = rendered_options.cache || self.cacheSettings;
		var status_code = parseInt(rendered_options.status, 10) || 200;

		self.setContentType(header, file_extension, header["Content-Type"]);

		self.setCacheExpiryHeaders(header, cache_settings);

		// Check for log messages and print them on the console.
		if (rendered_options.log) {
			console.log(rendered_options.log.message);
		}

		if (status_code === 200 && rendered_obj.body.length) {
			self.cacheStore.update(request_basename, file_extension, object_utils.cacheObj(rendered_obj.body, header), request_options, function(err, cache_obj) {
				if (err) {
					console.log("[Error in Cache] " + err);
					return self.getStatusPage(response, 500, ".html", header, request_options);
				}

				return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
			});
		} else {
			return self.getStatusPage(response, status_code, ".html", header, request_options);
		}
	},

	prepareCachedResponse: function(response, request_basename, file_extension, rendered_obj, request_options) {
		var self = this;

		var rendered_options = rendered_obj.options || {};
		var header = rendered_options.header || {};
		var cache_settings = rendered_options.cache || self.cacheSettings;
		var status_code = parseInt(rendered_options.status, 10) || 200;

		self.setContentType(header, file_extension, header["Content-Type"]);

		self.setCacheExpiryHeaders(header, cache_settings);

		self.cacheStore.get(request_basename, file_extension, object_utils.cacheObj(null, header), request_options, function(err, cache_obj) {
			return self.sendResponse(response, status_code, cache_obj.options.header, cache_obj.body);
		});
	},

	validatePublicCache: function(request, response, stat, callback) {
		var self = this;
		var headers;

		if (connect_utils.conditionalGET(request)) {
			if (stat) {
				headers = { "etag": connect_utils.etag(stat), "last-modified": stat.mtime };
			} else {
				headers = { "etag": response.getHeader("etag"), "last-modified": response.getHeader("last-modified") };
			}

			// check if the cache is still fresh
			if (fresh(request.headers, headers)) {
				return connect_utils.notModified(response);
			}	else {
				return callback();
			}
		} else {
			return callback();
		}
	},

	handleBundleRequest: function(request, response, request_basename, file_extension, request_options) {
		var self = this;

		asset_bundler.getBundle(request_basename, file_extension, request_options, function(err, bundled_obj) {
			if (err) {
				return self.getStatusPage(response, 404, ".html", {}, request_options);
			}

			var bundled_options = bundled_obj.options || {};
			var header = bundled_options.header || {};
			var cache_settings = bundled_options.cache || self.cacheSettings;
			var status_code = parseInt(bundled_options.status, 10) || 200;

			self.setContentType(header, file_extension);
			self.setCacheExpiryHeaders(header, cache_settings);

			// Check for log messages and print them on the console.
			if (bundled_options.log) {
				console.log(bundled_options.log.message);
			}

			if (bundled_obj.modified) {
				return self.sendResponse(response, status_code, header, bundled_obj.body);
			} else {
				response.setHeader("ETag", header["ETag"]);
				response.setHeader("Last-Modified", header["Last-Modified"]);

				// If public cache is not modified serve a 304 response.
				return self.validatePublicCache(request, response, null, function() {
					// This callback runs only if the public cache is invalid.
					return self.sendResponse(response, status_code, header, bundled_obj.body);
				});
			}
		});
	},

	handlePageRequest: function(request, response, request_basename, file_extension, request_options) {
		var self = this;

		self.cacheStore.stat(request_basename, file_extension, request_options, function(err, stat) {
			var last_modified = (stat && stat.mtime) || null;

			renderer.render(request_basename, file_extension, last_modified, request_options, function(rendered_obj) {
				if (rendered_obj.modified) {
					return self.prepareRenderedResponse(response, request_basename, file_extension, rendered_obj, request_options);
				} else {
					// If public cache is not modified serve a 304 response.
					return self.validatePublicCache(request, response, stat, function() {
						// This callback runs only if the public cache is invalid.
						return self.prepareCachedResponse(response, request_basename, file_extension, rendered_obj, request_options);
					});
				}
			});
		});
	},

	handle: function(request, response, next) {
		var self = this;

		var parsed_url = url.parse(request.url, true);
    var request_path = parsed_url.pathname.replace(/\.\.\/|\/$/g, "");

		var file_extension = path_utils.getExtension(request_path, (request.accept && request.accept.types));
		var request_basename = path_utils.getBasename(request_path, file_extension);

		var request_options = {
			"query": parsed_url.query,
			"host": request.headers.host,
			"xhr": (request.headers.http_x_requested_with && request.headers.http_x_requested_with === "xmlhttprequest"),
			"authorization": request.headers.authorization,
			"header": request.headers,
			"cookies": request.cookies
		};

		// check if the given request is an asset bundler request
		if (asset_bundler.isBundlePath(request_basename, file_extension)) {
			return self.handleBundleRequest(request, response, request_basename, file_extension, request_options);
		} else {
			return self.handlePageRequest(request, response, request_basename, file_extension, request_options);
		}
	},

	setup: function(config){
		var self = this;

		self.cacheSettings = (config.server && config.server.cache) || {};

		renderer.setup(config);

		asset_bundler.setup(config);

		self.cacheStore = module_utils.requireAndSetup(config.plugins.cache_store, config);

		return function(req, res, next){
			return self.handle(req, res, next);
		};
	}

};
<|endoftext|>


<commit_before>var Parser = require('./parser.js'),
    scheme = require('./scheme.js');

module.exports = parseLispExpr;

function strconcat (a, b) {return a+b;}

var parseSpecialChar =  Parser.oneOfChars('&|-<>?=!$~+*\\/');

var parseAtom =
  Parser.alphaChar
  .or(parseSpecialChar)
  .followedBy(
    Parser.many(
      parseSpecialChar
      .or(Parser.alphaChar)
      .or(Parser.numericChar)))
  .reduce(strconcat, '')
  .map(function (str) {return new scheme.SchemeAtom(str); });

var parseString =
  Parser.matchStr('"').squash()
  .followedBy(
    Parser.many(
      Parser.noneOfChars('\\"')
      .or(Parser.matchStr('\\"').andReturn('"'))
      .or(Parser.matchStr('\\n').andReturn('\n'))
      .or(Parser.matchStr('\\t').andReturn('\t'))
      .or(Parser.matchStr('\\r').andReturn('\r'))
    )
  )
  .followedBy(Parser.matchStr('"').squash())
  .reduce(strconcat, '')
  .map(function (str) {return new scheme.SchemeString(str); } );

var parseTrue =
  Parser.matchStr('#t')
  .andReturn(new scheme.SchemeBool(true));

var parseFalse =
  Parser.matchStr('#f')
  .andReturn(new scheme.SchemeBool(false));

var parseBool = parseTrue.or(parseFalse);

var parseNum =
  Parser.numericChar
  .followedBy(Parser.many(Parser.numericChar))
  .followedBy(
    Parser.maybe(
      Parser.matchStr('.')
      .followedBy(Parser.many(Parser.numericChar))
    )
  )
  .reduce(strconcat, '')
  .map(parseFloat)
  .map(function (num) {return new scheme.SchemeNum(num); });

var parseWhiteSpace = Parser.many(Parser.oneOfChars(' \n\r\t'));

var parseNullList =
  Parser.matchStr('()')
  .andReturn(new scheme.SchemeList([]));

var parseListStart =
  Parser.matchStr("(")
  .followedBy(Parser.maybe(parseWhiteSpace))
  .squash()
  .followedBy(Parser.sepBy(parseWhiteSpace,
    new Parser(function (str) {
      return parseExpr.parse(str);
    })
  ))

var parseList =
  parseListStart
  .followedBy(Parser.maybe(parseWhiteSpace).squash())
  .followedBy(Parser.matchStr(')').squash())
  .then(function(res) { return [new scheme.SchemeList(res)]; });

var parseDottedList =
  parseListStart
  .followedBy(parseWhiteSpace.squash())
  .followedBy(Parser.matchStr('.').squash())
  .followedBy(parseWhiteSpace.squash())
  .followedBy(new Parser(function (str) {
    return parseExpr.parse(str);
  }))
  .followedBy(Parser.maybe(parseWhiteSpace).squash())
  .followedBy(Parser.matchStr(')').squash())
  .then(function(res) { return [new scheme.SchemeDottedList(res.slice(0, -1), res[res.length-1])]; });

var parseQuotedExpr =
  Parser.matchStr('\'')
  .squash()
  .followedBy(new Parser(function (str) {
    return parseExpr.parse(str);
  }))
  .then(function (res) {
    return [new scheme.SchemeList([new scheme.SchemeAtom('quoted'), res[0]])];
  });

var parseExpr = parseAtom
  .or(parseString)
  .or(parseBool)
  .or(parseNum)
  .or(parseNullList)
  .or(parseList)
  .or(parseDottedList)
  .or(parseQuotedExpr);

function parseLispExpr (str) {
  return parseExpr.parse(str);
}

parseLispExpr.ParseError = Parser.ParseError;
<commit_msg>Remove trailing space<commit_after>var Parser = require('./parser.js'),
    scheme = require('./scheme.js');

module.exports = parseLispExpr;

function strconcat (a, b) {return a+b;}

var parseSpecialChar =  Parser.oneOfChars('&|-<>?=!$~+*\\/');

var parseAtom =
  Parser.alphaChar
  .or(parseSpecialChar)
  .followedBy(
    Parser.many(
      parseSpecialChar
      .or(Parser.alphaChar)
      .or(Parser.numericChar)))
  .reduce(strconcat, '')
  .map(function (str) {return new scheme.SchemeAtom(str); });

var parseString =
  Parser.matchStr('"').squash()
  .followedBy(
    Parser.many(
      Parser.noneOfChars('\\"')
      .or(Parser.matchStr('\\"').andReturn('"'))
      .or(Parser.matchStr('\\n').andReturn('\n'))
      .or(Parser.matchStr('\\t').andReturn('\t'))
      .or(Parser.matchStr('\\r').andReturn('\r'))
    )
  )
  .followedBy(Parser.matchStr('"').squash())
  .reduce(strconcat, '')
  .map(function (str) {return new scheme.SchemeString(str); } );

var parseTrue =
  Parser.matchStr('#t')
  .andReturn(new scheme.SchemeBool(true));

var parseFalse =
  Parser.matchStr('#f')
  .andReturn(new scheme.SchemeBool(false));

var parseBool = parseTrue.or(parseFalse);

var parseNum =
  Parser.numericChar
  .followedBy(Parser.many(Parser.numericChar))
  .followedBy(
    Parser.maybe(
      Parser.matchStr('.')
      .followedBy(Parser.many(Parser.numericChar))
    )
  )
  .reduce(strconcat, '')
  .map(parseFloat)
  .map(function (num) {return new scheme.SchemeNum(num); });

var parseWhiteSpace = Parser.many(Parser.oneOfChars(' \n\r\t'));

var parseNullList =
  Parser.matchStr('()')
  .andReturn(new scheme.SchemeList([]));

var parseListStart =
  Parser.matchStr("(")
  .followedBy(Parser.maybe(parseWhiteSpace))
  .squash()
  .followedBy(Parser.sepBy(parseWhiteSpace,
    new Parser(function (str) {
      return parseExpr.parse(str);
    })
  ))

var parseList =
  parseListStart
  .followedBy(Parser.maybe(parseWhiteSpace).squash())
  .followedBy(Parser.matchStr(')').squash())
  .then(function(res) { return [new scheme.SchemeList(res)]; });

var parseDottedList =
  parseListStart
  .followedBy(parseWhiteSpace.squash())
  .followedBy(Parser.matchStr('.').squash())
  .followedBy(parseWhiteSpace.squash())
  .followedBy(new Parser(function (str) {
    return parseExpr.parse(str);
  }))
  .followedBy(Parser.maybe(parseWhiteSpace).squash())
  .followedBy(Parser.matchStr(')').squash())
  .then(function(res) { return [new scheme.SchemeDottedList(res.slice(0, -1), res[res.length-1])]; });

var parseQuotedExpr =
  Parser.matchStr('\'')
  .squash()
  .followedBy(new Parser(function (str) {
    return parseExpr.parse(str);
  }))
  .then(function (res) {
    return [new scheme.SchemeList([new scheme.SchemeAtom('quoted'), res[0]])];
  });

var parseExpr = parseAtom
  .or(parseString)
  .or(parseBool)
  .or(parseNum)
  .or(parseNullList)
  .or(parseList)
  .or(parseDottedList)
  .or(parseQuotedExpr);

function parseLispExpr (str) {
  return parseExpr.parse(str);
}

parseLispExpr.ParseError = Parser.ParseError;
<|endoftext|>


<commit_before>'use strict';

var asynEach = require('async-foreach').forEach,
    chalk = require('chalk'),
    fs = require('graceful-fs'),
    globby = require('globby'),
    path = require('path'),
    pkg = require('../package.json'),
    PDF = require('pdfkit'),
    Q = require('q'),
    sizeOf = require('image-size'),
    utils = require('./utils'),
    Website = require('./website');

function getDate() {

    var d = new Date(),
        date =
        d.getUTCFullYear() + '-' +
        (d.getUTCMonth() + 1) + '-' +
        d.getUTCDate();
    return date;
}

function getFiles(outputPath, size) {
    return globby.sync([path.join(outputPath, '**', '*' + size + '.png')]);
}

/**
 * Write text to PDF object
 * @author Steven Britton
 * @date   2016-04-25
 * @param  {Object}   pdf        pdf-kit object
 * @param  {String}   title      title header for field
 * @param  {String}   field      field that's part of the cotent collection
 * @param  {String}   fieldColor font color for the field parameter
 */
function writeTextPDF(pdf, title, field, fieldColor){

    fieldColor = fieldColor || 'black';

    pdf.fontSize(30)
        .fillColor('black')
        .text(title);

    pdf.fontSize(20)
        .fillColor(fieldColor)
        .text(field);

    pdf.moveDown();
}

function createPDF(pdfShooter, arrImages, dimensions, domainName, outputPath, viewport, done) {

    utils.log.verbose('createPDF', arguments);

    var date = getDate(),
        credit,
        page,
        pdfPath = path.resolve(outputPath, utils.urlToDir(domainName) + '-' + viewport + '-' + date + '.pdf'),
        tempURL,
        writeStream;

    utils.log.verbose('pdfPath', pdfPath);

    credit = 'Siteshooter v' + pkg.version + ' - https://www.devopsgroup.io';

    var pdf = new PDF({
        layout: 'portrait',
        size: [dimensions.width, sizeOf(arrImages[0]).height],
        margins: {
            top: 72,
            left: 60,
            right: 72,
            bottom: 20
        }
    });

    writeStream = fs.createWriteStream(pdfPath);

    pdf.pipe(writeStream);

    pdf.pipe(fs.createWriteStream(pdfPath));

    arrImages.forEach(function(png, i) {

        page = png.replace('screenshots/', '');

        // remove from last slash and remaining chars (e.g., /1600.png)
        page = page.substr(0, page.lastIndexOf('/'));

        // get page information stored in website collection
        page = pdfShooter.website.contentCollection.pages.filter(function(item, indx, arr) {

            // remove protocol and last slash
            tempURL = item.loc
                .replace(/^(http|https):\/\//, '')
                .replace(/\/$/, '');

            return (tempURL === page);
        });

        // add site-level information to cover page
        if (i === 0) {

            writeTextPDF(pdf, 'Date', date);

            writeTextPDF(pdf, 'Website', page[0].loc, 'blue');

            writeTextPDF(pdf, 'Viewport', viewport + ' (' + dimensions.width + 'x' + dimensions.height + ')');

            writeTextPDF(pdf, 'Number of webpages', arrImages.length);

            // add credit
            pdf.fontSize(20)
                .fillColor('black')
                .text(credit, 600, 1195);

        }

        // check page object returned from content collection
        if (page[0] !== undefined) {

            pdf.addPage({
                size: [sizeOf(png).width, sizeOf(png).height],
                width: sizeOf(png).width,
                height: sizeOf(png).height,
                margins: {
                    top: 72,
                    left: 60,
                    right: 72,
                    bottom: 72
                }
            });

            // add page specific details
            writeTextPDF(pdf, 'URL', page[0].loc, 'blue');

            writeTextPDF(pdf, 'Meta Title', page[0].meta.title);

            writeTextPDF(pdf, 'Meta Description', page[0].meta.description);

        }

        pdf.addPage({
            size: [sizeOf(png).width, sizeOf(png).height],
            width: sizeOf(png).width,
            height: sizeOf(png).height
        });

        pdf.image(png, 0, 0);
    });

    pdf.end();

    writeStream.on('finish', function() {

        done();

    });

}

/**
 * PDFShooter Object
 * @author Steven Britton
 * @date   2016-04-14
 * @param  {Object}  options [description]
 */
function PDFShooter(parameters) {

    utils.log.verbose('PDFShooter: constructor', parameters);

    this.options = parameters;
    this.website = new Website(this.options);

}

PDFShooter.prototype.start = function() {

    console.log(chalk.yellow.bold('\n ⤷ Generating PDFs'));

    var deferred = Q.defer(),
        domainName = this.options.domain.name,
        outputPath = this.options.paths.output,
        pdfs = [],
        pdfShooter = this;

    // load array of viewports and their respective .png files
    pdfs = this.options.viewports.map(function(item, i) {
        return {
            'viewport': item.viewport,
            'dimensions': { 'width': item.width, 'height': item.height },
            'files': getFiles(outputPath, item.width)
        };
    });

    pdfShooter.website.get()

    .then(function() {

        // create individual pdfs based on viewport
        return asynEach(pdfs, function(item, i) {

            var done = this.async();

            // make sure we have some screenshots
            if (item.files.length > 0) {
                createPDF(pdfShooter, item.files, item.dimensions, domainName, outputPath, item.viewport, function() {
                    done();
                });
            } else {

                console.log(chalk.red.bold('   ✗ '), chalk.red('Screenshots for viewport '), chalk.red.bold(item.viewport), chalk.red(' do not exist.'));

                done();
            }

        }, function(success, array) {
            if (success) {
                deferred.resolve(array);

            } else {
                deferred.reject(success);
            }
        });

    });

    return deferred.promise;
};

module.exports = PDFShooter;
<commit_msg>feat(pdf-shooter): add Google Analytics version to cover page<commit_after>'use strict';

var asynEach = require('async-foreach').forEach,
    chalk = require('chalk'),
    fs = require('graceful-fs'),
    globby = require('globby'),
    path = require('path'),
    pkg = require('../package.json'),
    PDF = require('pdfkit'),
    Q = require('q'),
    sizeOf = require('image-size'),
    utils = require('./utils'),
    Website = require('./website');

function getDate() {

    var d = new Date(),
        date =
        d.getUTCFullYear() + '-' +
        (d.getUTCMonth() + 1) + '-' +
        d.getUTCDate();
    return date;
}

/**
 * Gets all files based on viewport size
 * @author Steven Britton
 * @date   2016-04-26
 * @param  {String}   outputPath Path to generated screenshots
 * @param  {String}   size       Viewport size
 * @return {Array}               Array of paths to images
 */
function getFiles(outputPath, size) {
    return globby.sync([path.join(outputPath, '**', '*' + size + '.png')]);
}

/**
 * Write text to PDF object
 * @author Steven Britton
 * @date   2016-04-25
 * @param  {Object}   pdf        pdf-kit object
 * @param  {String}   title      title header for field
 * @param  {String}   field      field that's part of the cotent collection
 * @param  {String}   fieldColor font color for the field parameter
 */
function writeTextPDF(pdf, title, field, fieldColor){

    fieldColor = fieldColor || 'black';

    pdf.fontSize(30)
        .fillColor('black')
        .text(title);

    pdf.fontSize(20)
        .fillColor(fieldColor)
        .text(field);

    pdf.moveDown();
}

function createPDF(pdfShooter, arrImages, dimensions, domainName, outputPath, viewport, done) {

    utils.log.verbose('createPDF', arguments);

    var date = getDate(),
        credit,
        page,
        pdfPath = path.resolve(outputPath, utils.urlToDir(domainName) + '-' + viewport + '-' + date + '.pdf'),
        tempURL,
        writeStream;

    utils.log.verbose('pdfPath', pdfPath);

    credit = 'Siteshooter v' + pkg.version + ' - https://www.devopsgroup.io';

    var pdf = new PDF({
        layout: 'portrait',
        size: [dimensions.width, sizeOf(arrImages[0]).height],
        margins: {
            top: 72,
            left: 60,
            right: 72,
            bottom: 20
        }
    });

    writeStream = fs.createWriteStream(pdfPath);

    pdf.pipe(writeStream);

    pdf.pipe(fs.createWriteStream(pdfPath));

    arrImages.forEach(function(png, i) {

        page = png.replace('screenshots/', '');

        // remove from last slash and remaining chars (e.g., /1600.png)
        page = page.substr(0, page.lastIndexOf('/'));

        // get page information stored in website collection
        page = pdfShooter.website.contentCollection.pages.filter(function(item, indx, arr) {

            // remove protocol and last slash
            tempURL = item.loc
                .replace(/^(http|https):\/\//, '')
                .replace(/\/$/, '');

            return (tempURL === page);
        });

        // add site-level information to cover page
        if (i === 0) {

            writeTextPDF(pdf, 'Date', date);

            writeTextPDF(pdf, 'Website', page[0].loc, 'blue');

            writeTextPDF(pdf, 'Viewport', viewport + ' (' + dimensions.width + 'x' + dimensions.height + ')');

            writeTextPDF(pdf, 'Google Analytics Version', page[0].meta.gaVersion);

            writeTextPDF(pdf, 'Number of webpages', arrImages.length);

            // add credit
            pdf.fontSize(20)
                .fillColor('black')
                .text(credit, 600, 1195);

        }

        // check page object returned from content collection
        if (page[0] !== undefined) {

            pdf.addPage({
                size: [sizeOf(png).width, sizeOf(png).height],
                width: sizeOf(png).width,
                height: sizeOf(png).height,
                margins: {
                    top: 72,
                    left: 60,
                    right: 72,
                    bottom: 72
                }
            });

            // add page specific details
            writeTextPDF(pdf, 'URL', page[0].loc, 'blue');

            writeTextPDF(pdf, 'Meta Title', page[0].meta.title);

            writeTextPDF(pdf, 'Meta Description', page[0].meta.description);

        }

        pdf.addPage({
            size: [sizeOf(png).width, sizeOf(png).height],
            width: sizeOf(png).width,
            height: sizeOf(png).height
        });

        pdf.image(png, 0, 0);
    });

    pdf.end();

    writeStream.on('finish', function() {

        done();

    });

}

/**
 * PDFShooter Object
 * @author Steven Britton
 * @date   2016-04-14
 * @param  {Object}  options [description]
 */
function PDFShooter(parameters) {

    utils.log.verbose('PDFShooter: constructor', parameters);

    this.options = parameters;
    this.website = new Website(this.options);

}

PDFShooter.prototype.start = function() {

    console.log(chalk.yellow.bold('\n ⤷ Generating PDFs'));

    var deferred = Q.defer(),
        domainName = this.options.domain.name,
        outputPath = this.options.paths.output,
        pdfs = [],
        pdfShooter = this;

    // load array of viewports and their respective .png files
    pdfs = this.options.viewports.map(function(item, i) {
        return {
            'viewport': item.viewport,
            'dimensions': { 'width': item.width, 'height': item.height },
            'files': getFiles(outputPath, item.width)
        };
    });

    pdfShooter.website.get()

    .then(function() {

        // create individual pdfs based on viewport
        return asynEach(pdfs, function(item, i) {

            var done = this.async();

            // make sure we have some screenshots
            if (item.files.length > 0) {
                createPDF(pdfShooter, item.files, item.dimensions, domainName, outputPath, item.viewport, function() {
                    done();
                });
            } else {

                console.log(chalk.red.bold('   ✗ '), chalk.red('Screenshots for viewport '), chalk.red.bold(item.viewport), chalk.red(' do not exist.'));

                done();
            }

        }, function(success, array) {
            if (success) {
                deferred.resolve(array);

            } else {
                deferred.reject(success);
            }
        });

    });

    return deferred.promise;
};

module.exports = PDFShooter;
<|endoftext|>


<commit_before>define(function(require) {
    "use strict";

    var UuidHelper = require("../helpers/uuid-helper");

    var _key = {};
    var _getAttributes = UuidHelper.generateUuid();

    /*
        @class View

        @classdesc View class.
    */

    /*
        @constructor View

        Constructs a View instance. Note that this does not add the View to AmpedJS's internal records.

        @param [htmlElement|string] containerElement        The desired container HTML element or selector.
    */
    var View = function(containerElement) {
        //Private instance members
        var attributes = {
            dataBindings: [],
            containerElement: (function (containerElement) {
                if (containerElement instanceof HTMLElement) {
                    return containerElement;
                }
                else {
                    return document.querySelector(containerElement);
                }
            })(containerElement),
            elements: []
        };

        //Public instance members
        //(None)

        //Special instance method for private member access
        this[_getAttributes] = function(key) {
            if (key === _key) {
                return attributes;
            }
        };

        //Return instance
        return this;
    };

    View.prototype = (function(prototype) {
        //Private static members
        //(None)

        //Public prototype members
        /*
            @function getContainerElement

            Retrieves the View's container element.

            @return {HTMLElement}
        */
        prototype.getContainerElement = function() {
            return this[_getAttributes](_key).containerElement;
        };

        /*
            @function getElements

            Retrieves the View's Elements.

            @return {Element[]}
        */
        prototype.getElements = function() {
            return this[_getAttributes](_key).elements;
        };

        /*
            @function addElement

            Adds the specified Element to the View.
        */
        prototype.addElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            if (elements.indexOf(element) === -1) {
                var isChild = (function(parent, child) {
                    var node = child.parentNode;

                    while (node !== null) {
                        if (node === parent) {
                            return true;
                        }

                        node = node.parentNode;
                    }
                    return false;
                })(this[_getAttributes](_key).containerElement, element.getElement());

                if (isChild) {
                    elements.push(element);
                }
            }
        };

        /*
            @function removeElement

            Removes the specified Element from the View.
        */
        prototype.removeElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            var index = elements.indexOf(element);

            if (index !== -1) {
                elements.splice(index, 1);
            }
        };

        /*
            @function hasElement

            Checks whether the specified Element exists in the View.
        */
        prototype.hasElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            return elements.indexOf(element) !== -1;
        };

        /*
            @function refresh

            Refreshes the View (and its Elements) with the specified Model instance.

            @param [{object}] instance      The desired model instance.
        */
        prototype.refresh = function(instance) {
            var attributes = this[_getAttributes](_key);
            var elements = attributes.elements;

            for (var i = 0, length = elements.length; i < length; i++) {
                elements[i].refresh(instance);
            }
        };

        return prototype;
    })(View.prototype || {});

    return View;
});
<commit_msg>Core.View.View: implemented trigger(), observe() and unobserve() convenience methods.<commit_after>define(function(require) {
    "use strict";

    var Event = require("../models/event");
    var EventHelper = require("../helpers/event-helper");
    var UuidHelper = require("../helpers/uuid-helper");

    var _key = {};
    var _getAttributes = UuidHelper.generateUuid();

    /*
        @class View

        @classdesc View class.
    */

    /*
        @constructor View

        Constructs a View instance. Note that this does not add the View to AmpedJS's internal records.

        @param [htmlElement|string] containerElement        The desired container HTML element or selector.
    */
    var View = function(containerElement) {
        //Private instance members
        var attributes = {
            dataBindings: [],
            containerElement: (function (containerElement) {
                if (containerElement instanceof HTMLElement) {
                    return containerElement;
                }
                else {
                    return document.querySelector(containerElement);
                }
            })(containerElement),
            elements: []
        };

        //Public instance members
        //(None)

        //Special instance method for private member access
        this[_getAttributes] = function(key) {
            if (key === _key) {
                return attributes;
            }
        };

        //Return instance
        return this;
    };

    View.prototype = (function(prototype) {
        //Private static members
        //(None)

        //Public prototype members
        /*
            @function getContainerElement

            Retrieves the View's container element.

            @return {HTMLElement}
        */
        prototype.getContainerElement = function() {
            return this[_getAttributes](_key).containerElement;
        };

        /*
            @function getElements

            Retrieves the View's Elements.

            @return {Element[]}
        */
        prototype.getElements = function() {
            return this[_getAttributes](_key).elements;
        };

        /*
            @function addElement

            Adds the specified Element to the View.
        */
        prototype.addElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            if (elements.indexOf(element) === -1) {
                var isChild = (function(parent, child) {
                    var node = child.parentNode;

                    while (node !== null) {
                        if (node === parent) {
                            return true;
                        }

                        node = node.parentNode;
                    }
                    return false;
                })(this[_getAttributes](_key).containerElement, element.getElement());

                if (isChild) {
                    elements.push(element);
                }
            }
        };

        /*
            @function removeElement

            Removes the specified Element from the View.
        */
        prototype.removeElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            var index = elements.indexOf(element);

            if (index !== -1) {
                elements.splice(index, 1);
            }
        };

        /*
            @function hasElement

            Checks whether the specified Element exists in the View.
        */
        prototype.hasElement = function(element) {
            var elements = this[_getAttributes](_key).elements;

            return elements.indexOf(element) !== -1;
        };

        /*
            @function refresh

            Refreshes the View (and its Elements) with the specified Model instance.

            @param [{object}] instance      The desired model instance.
        */
        prototype.refresh = function(instance) {
            var attributes = this[_getAttributes](_key);
            var elements = attributes.elements;

            for (var i = 0, length = elements.length; i < length; i++) {
                elements[i].refresh(instance);
            }
        };

        /*
            @function trigger

            Triggers an event on the item.

            @param {string} event       The desired event name
            @param [{object}] args      Additional arguments for the event
        */
        prototype.trigger = function(event, args) {
            EventHelper.trigger(new Event(this, event, args));
        };

        /*
            @function observe

            Observes the item for the specified event such that the specified event listener is called
            when the aforementioned event fires.

            @param {string} event                   The desired event to be observed
            @param {function} eventListener         The desired event listener
        */
        prototype.observe = function(event, eventListener) {
            EventHelper.observe(this, event, eventListener);
        };

        /*
            @function unobserve

            Stops observing the item for the specified and specified event listener.

            @param {string} event                   The desired event to stop observing
            @param {function} eventListener         The desired event listener
        */
        prototype.unobserve = function(event, eventListener) {
            EventHelper.unobserve(this, event, eventListener);
        };

        return prototype;
    })(View.prototype || {});

    return View;
});
<|endoftext|>


<commit_before>import React, {Component} from 'react'
import Bugsnag from '@bugsnag/react-native'
import BugsnagPluginReactNavigation from '@bugsnag/plugin-react-navigation';
import {NavigationContainer} from '@react-navigation/native';
import * as Scenarios from './Scenarios'
import {
  View,
  Text,
  TextInput,
  Button,
  StyleSheet,
  NativeModules
} from 'react-native'

let getDefaultConfiguration = () => { return {
    apiKey: '12312312312312312312312312312312',
    endpoint: 'http://bs-local.com:9339',
    autoTrackSessions: false
  }
}

let getManualModeConfiguration = (apiKey) => { return {
  apiKey: apiKey,
  endpoints: {
    notify: 'https://notify.bugsnag.com',
    sessions: 'https://sessions.bugsnag.com'
  },
  autoTrackSessions: false
}
}

const defaultJsConfig = () => ({
  plugins: [new BugsnagPluginReactNavigation()]
})

export default class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      currentScenario: '',
      scenarioMetaData: '',
      manualApiKey: null,
      scenario: null
    }
    console.log(`Available scenarios:\n  ${Object.keys(Scenarios).join('\n  ')}`)
  }

  setScenario = newScenario => {
    this.setState(() => ({ currentScenario: newScenario }))
  }

  setScenarioMetaData = newScenarioMetaData => {
    this.setState(() => ({ scenarioMetaData: newScenarioMetaData }))
  }

  setManualApiKey = newApiKey => {
    this.setState(() => ({ manualApiKey: newApiKey }))
  }

  startScenario = () => {
    console.log(`Running scenario: ${this.state.currentScenario}`)
    console.log(`  with MetaData: ${this.state.scenarioMetaData}`)
    let scenarioName = this.state.currentScenario
    let scenarioMetaData = this.state.scenarioMetaData
    let configuration = getDefaultConfiguration()
    let jsConfig = defaultJsConfig()
    let scenario = new Scenarios[scenarioName](configuration, scenarioMetaData, jsConfig)
    console.log(`  with config: ${JSON.stringify(configuration)} (native) and ${JSON.stringify(jsConfig)} (js)`)
    NativeModules.BugsnagTestInterface.startBugsnag(configuration)
      .then(() => {
        Bugsnag.start(jsConfig)
        this.setState({ scenario: scenario })
        scenario.run()
      })
  }

  startBugsnag = () => {
    console.log(`Starting Bugsnag for scenario: ${this.state.currentScenario}`)
    console.log(`  with MetaData: ${this.state.scenarioMetaData}`)
    let scenarioName = this.state.currentScenario
    let scenarioMetaData = this.state.scenarioMetaData
    let configuration
    if (this.state.manualApiKey) {
      configuration = getManualModeConfiguration(this.state.manualApiKey)
    }
    else {
      configuration = getDefaultConfiguration()
    }
    let jsConfig = defaultJsConfig()
    let scenario = new Scenarios[scenarioName](configuration, scenarioMetaData, jsConfig)
    console.log(`  with config: ${JSON.stringify(configuration)} (native) and ${JSON.stringify(jsConfig)} (js)`)
    NativeModules.BugsnagTestInterface.startBugsnag(configuration, () => {
      Bugsnag.start(jsConfig)
      this.setState({ scenario })
    })
  }

  waiting () {
    return (
      <View style={styles.container}>
        <View style={styles.child}>
          <Text>React-native end-to-end test app</Text>
          <TextInput style={styles.textInput}
            placeholder='Scenario Name'
            accessibilityLabel='scenarioInput'
            onChangeText={this.setScenario} />
          <TextInput style={styles.textInput}
            placeholder='Scenario Metadata'
            accessibilityLabel='scenarioMetaDataInput'
            onChangeText={this.setScenarioMetaData} />
          <Button style={styles.clickyButton}
            accessibilityLabel='startScenarioButton'
            title='Start scenario'
            onPress={this.startScenario}/>
          <Button style={styles.clickyButton}
            accessibilityLabel='startBugsnagButton'
            title='Start Bugsnag'
            onPress={this.startBugsnag}/>
        </View>
      </View>
    )
  }

  ready () {
    const BugsnagNavigationContainer = Bugsnag.getPlugin('reactNavigation').createNavigationContainer(NavigationContainer);
    return (
      <BugsnagNavigationContainer>
        {this.state.scenario.view()}
      </BugsnagNavigationContainer>
    );
  }

  render () {
    return this.state.scenario
      ? this.ready()
      : this.waiting()
  }
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
    flex: 1,
    backgroundColor: '#eaefea',
    alignItems: 'center',
    justifyContent: 'center',
    paddingTop: '15%'
  },
  child: {
    flex: 1
  },
  textInput: {
    backgroundColor: '#fff',
    borderWidth: 0.5,
    borderColor: '#000',
    borderRadius: 4,
    margin: 5,
    padding: 5
  },
  clickyButton: {
    backgroundColor: '#acbcef',
    borderWidth: 0.5,
    borderColor: '#000',
    borderRadius: 4,
    margin: 5,
    padding: 5
  }
})
<commit_msg>Tests/RN-nav: Ensure startBugsnag also uses promises<commit_after>import React, {Component} from 'react'
import Bugsnag from '@bugsnag/react-native'
import BugsnagPluginReactNavigation from '@bugsnag/plugin-react-navigation';
import {NavigationContainer} from '@react-navigation/native';
import * as Scenarios from './Scenarios'
import {
  View,
  Text,
  TextInput,
  Button,
  StyleSheet,
  NativeModules
} from 'react-native'

let getDefaultConfiguration = () => { return {
    apiKey: '12312312312312312312312312312312',
    endpoint: 'http://bs-local.com:9339',
    autoTrackSessions: false
  }
}

let getManualModeConfiguration = (apiKey) => { return {
  apiKey: apiKey,
  endpoints: {
    notify: 'https://notify.bugsnag.com',
    sessions: 'https://sessions.bugsnag.com'
  },
  autoTrackSessions: false
}
}

const defaultJsConfig = () => ({
  plugins: [new BugsnagPluginReactNavigation()]
})

export default class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      currentScenario: '',
      scenarioMetaData: '',
      manualApiKey: null,
      scenario: null
    }
    console.log(`Available scenarios:\n  ${Object.keys(Scenarios).join('\n  ')}`)
  }

  setScenario = newScenario => {
    this.setState(() => ({ currentScenario: newScenario }))
  }

  setScenarioMetaData = newScenarioMetaData => {
    this.setState(() => ({ scenarioMetaData: newScenarioMetaData }))
  }

  setManualApiKey = newApiKey => {
    this.setState(() => ({ manualApiKey: newApiKey }))
  }

  startScenario = () => {
    console.log(`Running scenario: ${this.state.currentScenario}`)
    console.log(`  with MetaData: ${this.state.scenarioMetaData}`)
    let scenarioName = this.state.currentScenario
    let scenarioMetaData = this.state.scenarioMetaData
    let configuration = getDefaultConfiguration()
    let jsConfig = defaultJsConfig()
    let scenario = new Scenarios[scenarioName](configuration, scenarioMetaData, jsConfig)
    console.log(`  with config: ${JSON.stringify(configuration)} (native) and ${JSON.stringify(jsConfig)} (js)`)
    NativeModules.BugsnagTestInterface.startBugsnag(configuration)
      .then(() => {
        Bugsnag.start(jsConfig)
        this.setState({ scenario: scenario })
        scenario.run()
      })
  }

  startBugsnag = () => {
    console.log(`Starting Bugsnag for scenario: ${this.state.currentScenario}`)
    console.log(`  with MetaData: ${this.state.scenarioMetaData}`)
    let scenarioName = this.state.currentScenario
    let scenarioMetaData = this.state.scenarioMetaData
    let configuration
    if (this.state.manualApiKey) {
      configuration = getManualModeConfiguration(this.state.manualApiKey)
    }
    else {
      configuration = getDefaultConfiguration()
    }
    let jsConfig = defaultJsConfig()
    let scenario = new Scenarios[scenarioName](configuration, scenarioMetaData, jsConfig)
    console.log(`  with config: ${JSON.stringify(configuration)} (native) and ${JSON.stringify(jsConfig)} (js)`)
    NativeModules.BugsnagTestInterface.startBugsnag(configuration)
    .then(() => {
      Bugsnag.start(jsConfig)
      this.setState({ scenario: scenario })
    })
  }

  waiting () {
    return (
      <View style={styles.container}>
        <View style={styles.child}>
          <Text>React-native end-to-end test app</Text>
          <TextInput style={styles.textInput}
            placeholder='Scenario Name'
            accessibilityLabel='scenarioInput'
            onChangeText={this.setScenario} />
          <TextInput style={styles.textInput}
            placeholder='Scenario Metadata'
            accessibilityLabel='scenarioMetaDataInput'
            onChangeText={this.setScenarioMetaData} />
          <Button style={styles.clickyButton}
            accessibilityLabel='startScenarioButton'
            title='Start scenario'
            onPress={this.startScenario}/>
          <Button style={styles.clickyButton}
            accessibilityLabel='startBugsnagButton'
            title='Start Bugsnag'
            onPress={this.startBugsnag}/>
        </View>
      </View>
    )
  }

  ready () {
    const BugsnagNavigationContainer = Bugsnag.getPlugin('reactNavigation').createNavigationContainer(NavigationContainer);
    return (
      <BugsnagNavigationContainer>
        {this.state.scenario.view()}
      </BugsnagNavigationContainer>
    );
  }

  render () {
    return this.state.scenario
      ? this.ready()
      : this.waiting()
  }
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
    flex: 1,
    backgroundColor: '#eaefea',
    alignItems: 'center',
    justifyContent: 'center',
    paddingTop: '15%'
  },
  child: {
    flex: 1
  },
  textInput: {
    backgroundColor: '#fff',
    borderWidth: 0.5,
    borderColor: '#000',
    borderRadius: 4,
    margin: 5,
    padding: 5
  },
  clickyButton: {
    backgroundColor: '#acbcef',
    borderWidth: 0.5,
    borderColor: '#000',
    borderRadius: 4,
    margin: 5,
    padding: 5
  }
})
<|endoftext|>