<commit_before>var fs = require('fs');
var url = require('url');
var path = require('path');
var request = require('request');
var crypto = require('crypto');

command = Bones.Command.extend();

command.description = 'export project';
command.usage = '<project> <export>';

command.options['format'] = {
    'title': 'format=[format]',
    'description': 'Export format (png|pdf|svg|mbtiles).'
};

command.options['bbox'] = {
    'title': 'bbox=[bbox]',
    'description': 'Comma separated coordinates of bounding box to export.'
};

command.options['minzoom'] = {
    'title': 'minzoom=[zoom]',
    'description': 'MBTiles: minimum zoom level to export.'
};

command.options['maxzoom'] = {
    'title': 'maxzoom=[zoom]',
    'description': 'MBTiles: maximum zoom level to export.'
};

command.options['width'] = {
    'title': 'width=[width]',
    'description': 'Image: image width in pixels.',
    'default': 400
};

command.options['height'] = {
    'title': 'height=[height]',
    'description': 'Image: image height in pixels.',
    'default': 400
};

command.options['url'] = {
    'title': 'url=[url]',
    'description': 'URL to PUT updates to.'
};

command.options['log'] = {
    'title': 'log',
    'description': 'Write crash logs to destination directory.'
};

command.prototype.initialize = function(plugin, callback) {
    var opts = plugin.config;
    if (process.env.tilemillConfig)
        _(opts).extend(JSON.parse(process.env.tilemillConfig));
    opts.files = path.resolve(opts.files);
    opts.project = plugin.argv._[1];
    opts.filepath = path.resolve(plugin.argv._[2]);
    callback = callback || function() {};
    this.opts = opts;

    // Write crash log
    if (opts.log) {
        process.on('uncaughtException', function(err) {
            fs.writeFileSync(opts.filepath + '.crashlog', err.stack || err.toString());
            process.exit(1);
        });
    }

    // Validation.
    if (!opts.project || !opts.filepath) return plugin.help();
    if (!path.existsSync(path.dirname(opts.filepath)))
        return this.error(new Error('Export path does not exist: ' + path.dirname(opts.filepath)));

    // Format.
    if (!opts.format) opts.format = path.extname(opts.filepath).split('.').pop();
    if (!_(['pdf', 'svg', 'png', 'mbtiles', 'upload']).include(opts.format))
        return this.error(new Error('Invalid format: ' + opts.format));

    // Convert string params into numbers.
    if (!_(opts.bbox).isUndefined())
        opts.bbox = _(opts.bbox.split(',')).map(parseFloat);
    if (!_(opts.minzoom).isUndefined())
        opts.minzoom = parseInt(opts.minzoom, 10);
    if (!_(opts.maxzoom).isUndefined())
        opts.maxzoom = parseInt(opts.maxzoom, 10);
    if (!_(opts.width).isUndefined())
        opts.width = parseInt(opts.width, 10);
    if (!_(opts.height).isUndefined())
        opts.height = parseInt(opts.height, 10);

    // Rename the output filepath using a random hash if file already exists.
    if (path.existsSync(opts.filepath) && opts.format !== 'upload') {
        var hash = crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var ext = path.extname(opts.filepath);
        opts.filepath = opts.filepath.replace(ext, '_' + hash + ext);

        // Update filename in TileMill.
        this.put({ filename: path.basename(opts.filepath), status: 'processing' });
    }

    // Set process title.
    process.title = 'tm-' + path.basename(opts.filepath);

    // Catch SIGINT.
    process.on('SIGINT', function () {
        console.log('Got SIGINT. Run "kill ' + process.pid + '" to terminate.');
    });
    process.on('SIGUSR1', process.exit);

    // Load project, localize and call export function.
    (new models.Project({id: plugin.argv._[1] })).fetch({
        success: function(model, resp) {
            model.localize(resp, function(err) {
                if (err) return this.error(err);
                model.mml = _(model.mml).extend({
                    name: model.mml.name || model.id,
                    version: model.mml.version || '1.0.0',
                    minzoom: !_(opts.minzoom).isUndefined() ? opts.minzoom : model.get('minzoom'),
                    maxzoom: !_(opts.maxzoom).isUndefined() ? opts.maxzoom : model.get('maxzoom'),
                    bounds: !_(opts.bbox).isUndefined() ? opts.bbox : model.get('bounds')
                });

                // Unset map center for now given that it may be outside
                // overridden bounds provided.
                if (model.mml.center) delete model.mml.center;

                this[opts.format](model, callback);
            }.bind(this));
        }.bind(this),
        error: function(model, resp) {
            this.error(resp);
        }.bind(this)
    });
};

command.prototype.error = function(err) {
    this.put({
        status: 'error',
        error: err.toString(),
        updated: +new Date()
    });
    console.error(err.toString());
};

command.prototype.put = function(data, callback) {
    callback = callback || function() {};
    if (!this.opts.url) return callback();
    request.put({
        uri: this.opts.url,
        headers: {
            'Host': url.parse(this.opts.url).host,
            'Cookie': 'bones.token=token'
        },
        json: _(data).extend({'bones.token': 'token'})
    }, callback);
};

command.prototype.png =
command.prototype.svg =
command.prototype.pdf = function(project, callback) {
    var mapnik = require('mapnik');
    var sm = new (require('sphericalmercator'))();
    var map = new mapnik.Map(this.opts.width, this.opts.height);

    map.fromStringSync(project.xml, {
        strict: false,
        base: path.join(this.opts.files, 'project', project.id) + '/'
    });
    map.bufferSize = this.opts.bufferSize;
    map.extent = sm.convert(project.mml.bounds, '900913');
    try {
        map.renderFileSync(this.opts.filepath, { format: this.opts.format });
        this.put({
            status: 'complete',
            progress: 1,
            updated: +new Date()
        }, process.exit);
    } catch(err) {
        this.error(err);
    }
};

command.prototype.mbtiles = function (project, callback) {
    var tilelive = require('tilelive');
    require('mbtiles').registerProtocols(tilelive);
    require('tilelive-mapnik').registerProtocols(tilelive);

    var uri = {
        protocol: 'mapnik:',
        slashes: true,
        xml: project.xml,
        mml: project.mml,
        pathname: path.join(this.opts.files, 'project', project.id, project.id + '.xml'),
        query: { bufferSize: this.opts.bufferSize }
    };
    tilelive.load(uri, function(err, source) {
        if (err) throw err;
        tilelive.load('mbtiles://' + this.opts.filepath, function(err, sink) {
            if (err) throw err;
            sink.startWriting(function(err) {
                if (err) throw err;
                sink.putInfo(project.mml, function(err) {
                    if (err) throw err;
                    var copy = tilelive.copy({
                        source: source,
                        sink: sink,
                        bbox: project.mml.bounds,
                        minZoom: project.mml.minzoom,
                        maxZoom: project.mml.maxzoom,
                        concurrency: 100,
                        tiles: true,
                        grids: !!project.mml.interactivity
                    });

                    var timeout = setInterval(function progress() {
                        var progress = (copy.copied + copy.failed) / copy.total;
                        var remaining = Math.floor((Date.now() - copy.started) * (1 / progress));
                        this.put({
                            status: progress < 1 ? 'processing' : 'complete',
                            progress: progress,
                            remaining: remaining,
                            updated: +new Date(),
                        });
                    }.bind(this), 5000);
                    copy.on('warning', function(err) {
                        console.log(err);
                    }.bind(this));
                    copy.on('finished', function() {
                        clearTimeout(timeout);
                        this.put({
                            status: 'complete',
                            progress: 1,
                            updated: +new Date()
                        }, process.exit);
                    }.bind(this));
                    copy.on('error', function(err) {
                        clearTimeout(timeout);
                        this.error(err);
                    }.bind(this));
                }.bind(this));
            }.bind(this));
        }.bind(this));
    }.bind(this));
};

command.prototype.upload = function (project, callback) {
    var hash = crypto.createHash('md5')
        .update(+new Date + '')
        .digest('hex')
        .substring(0, 6);
    var policyEndpoint = url.format({
        protocol: 'http:',
        host: this.opts.mapboxHost || 'tiles.mapbox.com',
        pathname: '/v2/'+ hash + '/upload.json'
    });

    request.get({
        uri: policyEndpoint,
        headers: {
            'Host': url.parse(policyEndpoint).host,
        }
    }, _(function(err, resp, body) {
        if (err) return this.error(err);
        if (resp.statusCode !== 200) return this.error(
            new Error('MapBox Hosting is not available. Status '
                      + resp.statusCode + '.'));

        try {
            var uploadArgs = JSON.parse(body);
        } catch (e) {
            this.error(new Error('Failed to parse policy from MapBox Hosting.'));
        }

        var bucket = uploadArgs.bucket;
        delete uploadArgs.bucket;
        delete uploadArgs.filename;

        var filename = path.basename(this.opts.filepath);
        // Write multipart body post to a temporary file. Fix this once piping
        // works better.
        var tmpPath = '/tmp/' + filename + '.post';
        var dest = fs.createWriteStream(tmpPath);

        var body = '';
        _(uploadArgs).each(function(arg, key) {
            body += '--frontier\r\n';
            body += 'Content-Disposition: form-data; name="' + key + '"\r\n',
            body += '\r\n' + arg + '\r\n';
        });

        body += '--frontier\r\n';
        body += 'Content-Disposition: form-data; name="file"; filename="' +
            path.basename(this.opts.filepath) + '"\r\n'
        body += 'Content-Type: application/octet-stream\r\n\r\n';
        dest.write(body, 'ascii');

        var source = fs.createReadStream(this.opts.filepath);
        source.on('end', function() {
            dest.end('\r\n--frontier--', 'ascii');
        });
        source.pipe(dest, {end: false});

        dest.on('close', _(function() {
            // Temporary file has been closed. Time to upload.
            fs.stat(tmpPath, _(function(err, stat) {
                if (err) return this.error(err);
                var options = {
                    uri: 'http://' + bucket + '.s3.amazonaws.com/',
                    headers: {
                        'Content-Type': 'multipart/form-data; boundary=frontier',
                        'Content-Length': stat.size,
                        'X_FILE_NAME': filename
                    }
                };
                var source = fs.createReadStream(tmpPath);
                var dest = request.post(options, _(function(err, resp) {
                    if (err) return this.error(err);
                    fs.unlink(tmpPath, _(function(err) {
                        if (err) return this.error(err);
                        this.put({
                            status: 'complete',
                            progress: 1,
                            url: resp.headers.location.split('?')[0],
                            updated: +new Date()
                        }, process.exit);
                    }).bind(this));
                }).bind(this));

                // Start the upload!
                source.pipe(dest);

            }).bind(this));
        }).bind(this));
    }).bind(this));
};
<commit_msg>Hash in boundry.<commit_after>var fs = require('fs');
var url = require('url');
var path = require('path');
var request = require('request');
var crypto = require('crypto');

command = Bones.Command.extend();

command.description = 'export project';
command.usage = '<project> <export>';

command.options['format'] = {
    'title': 'format=[format]',
    'description': 'Export format (png|pdf|svg|mbtiles).'
};

command.options['bbox'] = {
    'title': 'bbox=[bbox]',
    'description': 'Comma separated coordinates of bounding box to export.'
};

command.options['minzoom'] = {
    'title': 'minzoom=[zoom]',
    'description': 'MBTiles: minimum zoom level to export.'
};

command.options['maxzoom'] = {
    'title': 'maxzoom=[zoom]',
    'description': 'MBTiles: maximum zoom level to export.'
};

command.options['width'] = {
    'title': 'width=[width]',
    'description': 'Image: image width in pixels.',
    'default': 400
};

command.options['height'] = {
    'title': 'height=[height]',
    'description': 'Image: image height in pixels.',
    'default': 400
};

command.options['url'] = {
    'title': 'url=[url]',
    'description': 'URL to PUT updates to.'
};

command.options['log'] = {
    'title': 'log',
    'description': 'Write crash logs to destination directory.'
};

command.prototype.initialize = function(plugin, callback) {
    var opts = plugin.config;
    if (process.env.tilemillConfig)
        _(opts).extend(JSON.parse(process.env.tilemillConfig));
    opts.files = path.resolve(opts.files);
    opts.project = plugin.argv._[1];
    opts.filepath = path.resolve(plugin.argv._[2]);
    callback = callback || function() {};
    this.opts = opts;

    // Write crash log
    if (opts.log) {
        process.on('uncaughtException', function(err) {
            fs.writeFileSync(opts.filepath + '.crashlog', err.stack || err.toString());
            process.exit(1);
        });
    }

    // Validation.
    if (!opts.project || !opts.filepath) return plugin.help();
    if (!path.existsSync(path.dirname(opts.filepath)))
        return this.error(new Error('Export path does not exist: ' + path.dirname(opts.filepath)));

    // Format.
    if (!opts.format) opts.format = path.extname(opts.filepath).split('.').pop();
    if (!_(['pdf', 'svg', 'png', 'mbtiles', 'upload']).include(opts.format))
        return this.error(new Error('Invalid format: ' + opts.format));

    // Convert string params into numbers.
    if (!_(opts.bbox).isUndefined())
        opts.bbox = _(opts.bbox.split(',')).map(parseFloat);
    if (!_(opts.minzoom).isUndefined())
        opts.minzoom = parseInt(opts.minzoom, 10);
    if (!_(opts.maxzoom).isUndefined())
        opts.maxzoom = parseInt(opts.maxzoom, 10);
    if (!_(opts.width).isUndefined())
        opts.width = parseInt(opts.width, 10);
    if (!_(opts.height).isUndefined())
        opts.height = parseInt(opts.height, 10);

    // Rename the output filepath using a random hash if file already exists.
    if (path.existsSync(opts.filepath) && opts.format !== 'upload') {
        var hash = crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var ext = path.extname(opts.filepath);
        opts.filepath = opts.filepath.replace(ext, '_' + hash + ext);

        // Update filename in TileMill.
        this.put({ filename: path.basename(opts.filepath), status: 'processing' });
    }

    // Set process title.
    process.title = 'tm-' + path.basename(opts.filepath);

    // Catch SIGINT.
    process.on('SIGINT', function () {
        console.log('Got SIGINT. Run "kill ' + process.pid + '" to terminate.');
    });
    process.on('SIGUSR1', process.exit);

    // Load project, localize and call export function.
    (new models.Project({id: plugin.argv._[1] })).fetch({
        success: function(model, resp) {
            model.localize(resp, function(err) {
                if (err) return this.error(err);
                model.mml = _(model.mml).extend({
                    name: model.mml.name || model.id,
                    version: model.mml.version || '1.0.0',
                    minzoom: !_(opts.minzoom).isUndefined() ? opts.minzoom : model.get('minzoom'),
                    maxzoom: !_(opts.maxzoom).isUndefined() ? opts.maxzoom : model.get('maxzoom'),
                    bounds: !_(opts.bbox).isUndefined() ? opts.bbox : model.get('bounds')
                });

                // Unset map center for now given that it may be outside
                // overridden bounds provided.
                if (model.mml.center) delete model.mml.center;

                this[opts.format](model, callback);
            }.bind(this));
        }.bind(this),
        error: function(model, resp) {
            this.error(resp);
        }.bind(this)
    });
};

command.prototype.error = function(err) {
    this.put({
        status: 'error',
        error: err.toString(),
        updated: +new Date()
    });
    console.error(err.toString());
};

command.prototype.put = function(data, callback) {
    callback = callback || function() {};
    if (!this.opts.url) return callback();
    request.put({
        uri: this.opts.url,
        headers: {
            'Host': url.parse(this.opts.url).host,
            'Cookie': 'bones.token=token'
        },
        json: _(data).extend({'bones.token': 'token'})
    }, callback);
};

command.prototype.png =
command.prototype.svg =
command.prototype.pdf = function(project, callback) {
    var mapnik = require('mapnik');
    var sm = new (require('sphericalmercator'))();
    var map = new mapnik.Map(this.opts.width, this.opts.height);

    map.fromStringSync(project.xml, {
        strict: false,
        base: path.join(this.opts.files, 'project', project.id) + '/'
    });
    map.bufferSize = this.opts.bufferSize;
    map.extent = sm.convert(project.mml.bounds, '900913');
    try {
        map.renderFileSync(this.opts.filepath, { format: this.opts.format });
        this.put({
            status: 'complete',
            progress: 1,
            updated: +new Date()
        }, process.exit);
    } catch(err) {
        this.error(err);
    }
};

command.prototype.mbtiles = function (project, callback) {
    var tilelive = require('tilelive');
    require('mbtiles').registerProtocols(tilelive);
    require('tilelive-mapnik').registerProtocols(tilelive);

    var uri = {
        protocol: 'mapnik:',
        slashes: true,
        xml: project.xml,
        mml: project.mml,
        pathname: path.join(this.opts.files, 'project', project.id, project.id + '.xml'),
        query: { bufferSize: this.opts.bufferSize }
    };
    tilelive.load(uri, function(err, source) {
        if (err) throw err;
        tilelive.load('mbtiles://' + this.opts.filepath, function(err, sink) {
            if (err) throw err;
            sink.startWriting(function(err) {
                if (err) throw err;
                sink.putInfo(project.mml, function(err) {
                    if (err) throw err;
                    var copy = tilelive.copy({
                        source: source,
                        sink: sink,
                        bbox: project.mml.bounds,
                        minZoom: project.mml.minzoom,
                        maxZoom: project.mml.maxzoom,
                        concurrency: 100,
                        tiles: true,
                        grids: !!project.mml.interactivity
                    });

                    var timeout = setInterval(function progress() {
                        var progress = (copy.copied + copy.failed) / copy.total;
                        var remaining = Math.floor((Date.now() - copy.started) * (1 / progress));
                        this.put({
                            status: progress < 1 ? 'processing' : 'complete',
                            progress: progress,
                            remaining: remaining,
                            updated: +new Date(),
                        });
                    }.bind(this), 5000);
                    copy.on('warning', function(err) {
                        console.log(err);
                    }.bind(this));
                    copy.on('finished', function() {
                        clearTimeout(timeout);
                        this.put({
                            status: 'complete',
                            progress: 1,
                            updated: +new Date()
                        }, process.exit);
                    }.bind(this));
                    copy.on('error', function(err) {
                        clearTimeout(timeout);
                        this.error(err);
                    }.bind(this));
                }.bind(this));
            }.bind(this));
        }.bind(this));
    }.bind(this));
};

command.prototype.upload = function (project, callback) {
    var hash = crypto.createHash('md5')
        .update(+new Date + '')
        .digest('hex')
        .substring(0, 6);
    var policyEndpoint = url.format({
        protocol: 'http:',
        host: this.opts.mapboxHost || 'tiles.mapbox.com',
        pathname: '/v2/'+ hash + '/upload.json'
    });

    request.get({
        uri: policyEndpoint,
        headers: {
            'Host': url.parse(policyEndpoint).host,
        }
    }, _(function(err, resp, body) {
        if (err) return this.error(err);
        if (resp.statusCode !== 200) return this.error(
            new Error('MapBox Hosting is not available. Status '
                      + resp.statusCode + '.'));

        try {
            var uploadArgs = JSON.parse(body);
        } catch (e) {
            this.error(new Error('Failed to parse policy from MapBox Hosting.'));
        }

        var bucket = uploadArgs.bucket;
        delete uploadArgs.bucket;
        delete uploadArgs.filename;

        var filename = path.basename(this.opts.filepath);
        // Write multipart body post to a temporary file. Fix this once piping
        // works better.
        var tmpPath = '/tmp/' + filename + '.post';
        var dest = fs.createWriteStream(tmpPath);

        var boundry = '----TileMill' + crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);

        var body = '';
        _(uploadArgs).each(function(arg, key) {
            body += '--' + boundry + '\r\n';
            body += 'Content-Disposition: form-data; name="' + key + '"\r\n',
            body += '\r\n' + arg + '\r\n';
        });

        body += '--' + boundry + '\r\n';
        body += 'Content-Disposition: form-data; name="file"; filename="' +
            path.basename(this.opts.filepath) + '"\r\n'
        body += 'Content-Type: application/octet-stream\r\n\r\n';
        dest.write(body, 'ascii');

        var source = fs.createReadStream(this.opts.filepath);
        source.on('end', function() {
            dest.end('\r\n--' + boundry + '--', 'ascii');
        });
        source.pipe(dest, {end: false});

        dest.on('close', _(function() {
            // Temporary file has been closed. Time to upload.
            fs.stat(tmpPath, _(function(err, stat) {
                if (err) return this.error(err);
                var options = {
                    uri: 'http://' + bucket + '.s3.amazonaws.com/',
                    headers: {
                        'Content-Type': 'multipart/form-data; boundary=' + boundry,
                        'Content-Length': stat.size,
                        'X_FILE_NAME': filename
                    }
                };
                var source = fs.createReadStream(tmpPath);
                var dest = request.post(options, _(function(err, resp) {
                    if (err) return this.error(err);
                    fs.unlink(tmpPath, _(function(err) {
                        if (err) return this.error(err);
                        this.put({
                            status: 'complete',
                            progress: 1,
                            url: resp.headers.location.split('?')[0],
                            updated: +new Date()
                        }, process.exit);
                    }).bind(this));
                }).bind(this));

                // Start the upload!
                source.pipe(dest);

            }).bind(this));
        }).bind(this));
    }).bind(this));
};
<|endoftext|>


<commit_before>var fs = require('fs');
var url = require('url');
var path = require('path');
var request = require('request');
var crypto = require('crypto');
var Step = require('step');

command = Bones.Command.extend();

command.description = 'export project';
command.usage = '<project> <export>';

command.options['format'] = {
    'title': 'format=[format]',
    'description': 'Export format (png|pdf|svg|mbtiles).'
};

command.options['bbox'] = {
    'title': 'bbox=[bbox]',
    'description': 'Comma separated coordinates of bounding box to export.'
};

command.options['minzoom'] = {
    'title': 'minzoom=[zoom]',
    'description': 'MBTiles: minimum zoom level to export.'
};

command.options['maxzoom'] = {
    'title': 'maxzoom=[zoom]',
    'description': 'MBTiles: maximum zoom level to export.'
};

command.options['width'] = {
    'title': 'width=[width]',
    'description': 'Image: image width in pixels.',
    'default': 400
};

command.options['height'] = {
    'title': 'height=[height]',
    'description': 'Image: image height in pixels.',
    'default': 400
};

command.options['url'] = {
    'title': 'url=[url]',
    'description': 'URL to PUT updates to.'
};

command.options['log'] = {
    'title': 'log',
    'description': 'Write crash logs to destination directory.'
};

command.prototype.initialize = function(plugin, callback) {
    var opts = plugin.config;
    if (process.env.tilemillConfig)
        _(opts).extend(JSON.parse(process.env.tilemillConfig));
    opts.files = path.resolve(opts.files);
    opts.project = plugin.argv._[1];
    opts.filepath = path.resolve(plugin.argv._[2]);
    callback = callback || function() {};
    this.opts = opts;

    // Write crash log
    if (opts.log) {
        process.on('uncaughtException', function(err) {
            fs.writeFileSync(opts.filepath + '.crashlog', err.stack || err.toString());
            process.exit(1);
        });
    }

    // Validation.
    if (!opts.project || !opts.filepath) return plugin.help();
    if (!path.existsSync(path.dirname(opts.filepath)))
        return this.error(new Error('Export path does not exist: ' + path.dirname(opts.filepath)));

    // Format.
    if (!opts.format) opts.format = path.extname(opts.filepath).split('.').pop();
    if (!_(['pdf', 'svg', 'png', 'mbtiles', 'upload']).include(opts.format))
        return this.error(new Error('Invalid format: ' + opts.format));

    // Convert string params into numbers.
    if (!_(opts.bbox).isUndefined())
        opts.bbox = _(opts.bbox.split(',')).map(parseFloat);
    if (!_(opts.minzoom).isUndefined())
        opts.minzoom = parseInt(opts.minzoom, 10);
    if (!_(opts.maxzoom).isUndefined())
        opts.maxzoom = parseInt(opts.maxzoom, 10);
    if (!_(opts.width).isUndefined())
        opts.width = parseInt(opts.width, 10);
    if (!_(opts.height).isUndefined())
        opts.height = parseInt(opts.height, 10);

    // Rename the output filepath using a random hash if file already exists.
    if (path.existsSync(opts.filepath) && opts.format !== 'upload') {
        var hash = crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var ext = path.extname(opts.filepath);
        opts.filepath = opts.filepath.replace(ext, '_' + hash + ext);

        // Update filename in TileMill.
        this.put({ filename: path.basename(opts.filepath), status: 'processing' });
    }

    // Set process title.
    process.title = 'tm-' + path.basename(opts.filepath);

    // Catch SIGINT.
    process.on('SIGINT', function () {
        console.log('Got SIGINT. Run "kill ' + process.pid + '" to terminate.');
    });
    process.on('SIGUSR1', process.exit);

    // Upload format does not require loaded project.
    if (opts.format === 'upload') return this[opts.format](callback);

    // Load project, localize and call export function.
    var cmd = this;
    var model = new models.Project({id:opts.project});
    Step(function() {
        Bones.utils.fetch({model:model}, this);
    }, function(err) {
        if (err) throw err;
        model.localize(model.toJSON(), this);
    }, function(err) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });

        model.mml = _(model.mml).extend({
            name: model.mml.name || model.id,
            version: model.mml.version || '1.0.0',
            minzoom: !_(opts.minzoom).isUndefined() ? opts.minzoom : model.get('minzoom'),
            maxzoom: !_(opts.maxzoom).isUndefined() ? opts.maxzoom : model.get('maxzoom'),
            bounds: !_(opts.bbox).isUndefined() ? opts.bbox : model.get('bounds')
        });

        // Unset map center if outside bounds.
        var validCenter = (function(center, bounds, minzoom, maxzoom) {
            if (center[0] < bounds[0] ||
                center[0] > bounds[2] ||
                center[1] < bounds[1] ||
                center[1] > bounds[3]) return false;
            if (center[2] < minzoom) return false;
            if (center[2] > maxzoom) return false;
            return true;
        })(model.mml.center, model.mml.bounds, model.mml.minzoom, model.mml.maxzoom);
        if (!validCenter) delete model.mml.center;

        cmd[opts.format](model, callback);
    });
};

command.prototype.error = function(err, callback) {
    this.put({
        status: 'error',
        error: err.toString(),
        updated: +new Date()
    }, callback);
};

command.prototype.remaining = function(progress, started) {
    return Math.floor(
        (Date.now() - started) * (1 / progress) -
        (Date.now() - started)
    );
};

command.prototype.put = function(data, callback) {
    callback = callback || function() {};
    data.status == 'error' ?
        console.log(JSON.stringify(data)) :
        console.error(JSON.stringify(data));

    if (!this.opts.url) return callback();
    request.put({
        uri: this.opts.url,
        headers: {
            'Host': url.parse(this.opts.url).host,
            'Cookie': 'bones.token=token'
        },
        json: _(data).extend({'bones.token': 'token'})
    }, callback);
};

command.prototype.png =
command.prototype.svg =
command.prototype.pdf = function(project, callback) {
    var mapnik = require('mapnik');
    var sm = new (require('sphericalmercator'))();
    var map = new mapnik.Map(this.opts.width, this.opts.height);

    map.fromStringSync(project.xml, {
        strict: false,
        base: path.join(this.opts.files, 'project', project.id) + '/'
    });
    map.bufferSize = this.opts.bufferSize;
    map.extent = sm.convert(project.mml.bounds, '900913');
    try {
        map.renderFileSync(this.opts.filepath, { format: this.opts.format });
        this.put({
            status: 'complete',
            progress: 1,
            updated: +new Date()
        }, process.exit);
    } catch(err) {
        this.error(err);
    }
};

command.prototype.mbtiles = function (project, callback) {
    var cmd = this;
    var tilelive = require('tilelive');
    require('mbtiles').registerProtocols(tilelive);
    require('tilelive-mapnik').registerProtocols(tilelive);

    var uri = {
        protocol: 'mapnik:',
        slashes: true,
        xml: project.xml,
        mml: project.mml,
        pathname: path.join(this.opts.files, 'project', project.id, project.id + '.xml'),
        query: { bufferSize: this.opts.bufferSize }
    };
    var source;
    var sink;

    Step(function() {
        tilelive.load(uri, this);
    }, function(err, s) {
        if (err) throw err;
        source = s;
        tilelive.load('mbtiles://' + cmd.opts.filepath, this);
    }, function(err, s) {
        if (err) throw err;
        sink = s;
        sink.startWriting(this);
    }, function(err) {
        if (err) throw err;
        sink.putInfo(project.mml, this);
    }, function(err) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });

        var copy = tilelive.copy({
            source: source,
            sink: sink,
            bbox: project.mml.bounds,
            minZoom: project.mml.minzoom,
            maxZoom: project.mml.maxzoom,
            concurrency: 100,
            tiles: true,
            grids: !!project.mml.interactivity
        });

        var timeout = setInterval(function progress() {
            var progress = (copy.copied + copy.failed) / copy.total;
            cmd.put({
                status: progress < 1 ? 'processing' : 'complete',
                progress: progress,
                remaining: cmd.remaining(progress, copy.started),
                updated: +new Date(),
            });
        }, 5000);

        copy.on('warning', function(err) {
            console.log(err);
        });
        copy.on('finished', function() {
            clearTimeout(timeout);
            cmd.put({
                status: 'complete',
                progress: 1,
                updated: +new Date()
            }, process.exit);
        });
        copy.on('error', function(err) {
            clearTimeout(timeout);
            cmd.error(err, function() {
                process.exit(1);
            });
        });
    });
};

command.prototype.upload = function (callback) {
    var cmd = this;
    var key;
    var bucket;
    var mapURL = '';
    var freeURL = '';
    var modelURL = '';
    var hash = crypto.createHash('md5')
        .update(+new Date + '')
        .digest('hex')
        .substring(0, 6);
    var policyEndpoint = url.format({
        protocol: 'http:',
        host: this.opts.mapboxHost || 'api.tiles.mapbox.com',
        pathname: '/v2/'+ hash + '/upload.json'
    });

    // Set URLs for account uploads.
    if (this.opts.syncAccount && this.opts.syncAccessToken) {
        mapURL = _('<%=base%>/<%=account%>/map/<%=handle%>')
            .template({
                base: this.opts.syncURL,
                account: this.opts.syncAccount,
                handle: this.opts.project
            });
        modelURL = _('<%=base%>/api/Map/<%=account%>.<%=handle%>?access_token=<%=token%>')
            .template({
                base: this.opts.syncURL,
                account: this.opts.syncAccount,
                handle: this.opts.project,
                token: this.opts.syncAccessToken
            });
    }

    Step(function() {
        request.get({
            uri: policyEndpoint,
            headers: { 'Host': url.parse(policyEndpoint).host }
        }, this);
    }, function(err, resp, body) {
        if (err) throw err;
        if (resp.statusCode !== 200)
            throw new Error('MapBox Hosting is not available. Status ' + resp.statusCode + '.');

        // Let Step catch thrown errors here.
        uploadArgs = JSON.parse(body);
        key = uploadArgs.key;
        bucket = uploadArgs.bucket;
        delete uploadArgs.bucket;
        delete uploadArgs.filename;

        var stat = fs.statSync(cmd.opts.filepath);
        var boundary = '----TileMill' + crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var filename = path.basename(cmd.opts.filepath);
        var multipartBody = new Buffer(_(uploadArgs).map(function(value, key) {
            return '--' + boundary + '\r\n'
                + 'Content-Disposition: form-data; name="' + key + '"\r\n'
                + '\r\n' + value + '\r\n';
            })
            .concat(['--' + boundary + '\r\n'
                + 'Content-Disposition: form-data; name="file"; filename="' + filename + '"\r\n'
                + 'Content-Type: application/octet-stream\r\n\r\n'])
            .join(''));
        var terminate = new Buffer('\r\n--' + boundary + '--', 'ascii');

        var dest = http.request({
            host: bucket + '.s3.amazonaws.com',
            path: '/',
            method: 'POST',
            headers: {
                'Content-Type': 'multipart/form-data; boundary=' + boundary,
                'Content-Length': stat.size + multipartBody.length + terminate.length,
                'X_FILE_NAME': filename
            }
        }, function(resp) {
            if (resp.statusCode !== 303)
                return this(new Error('S3 is not available. Status ' + resp.statusCode + '.'));

            freeURL = resp.headers.location.split('?')[0];
            this();
        }.bind(this))

        // Write multipart values from memory.
        dest.write(multipartBody, 'ascii');

        // Set up read for MBTiles file and start the upload.
        var bytesWritten = 0;
        var started = Date.now();
        var updated = Date.now();
        fs.createReadStream(cmd.opts.filepath)
            .on('data', function(chunk) {
                bytesWritten += chunk.length;

                if (Date.now() < updated + 5000) return;

                var progress = bytesWritten / stat.size;
                updated = Date.now();
                cmd.put({
                    status: 'complete',
                    progress: progress,
                    status: progress < 1 ? 'processing' : 'complete',
                    remaining: cmd.remaining(progress, started),
                    updated: updated
                });
            })
            .on('end', function() {
                dest.write(terminate);
                dest.end();
            })
            .pipe(dest, {end: false});
    }, function(err) {
        if (err) throw err;
        if (!modelURL) return this(); // Free

        request.get(modelURL, this);
    }, function(err, res, body) {
        if (err) throw err;
        if (!modelURL) return this(); // Free

        // Let Step catch thrown errors here.
        var model = _(res.statusCode === 404 ? {} : JSON.parse(body)).extend({
            id: cmd.opts.syncAccount + '.' + cmd.opts.project,
            _type: 'tileset',
            created: +new Date,
            status: 'pending',
            url: 'http://' + bucket + '.s3.amazonaws.com/' + key
        });
        request.put({ url:modelURL, json:model }, this);
    }, function(err, res, body) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });
        if (modelURL && res.statusCode !== 200)
            return cmd.error('Map publish failed: ' + res.statusCode, function() {
                process.exit(1);
            });
        cmd.put({
            status: 'complete',
            progress: 1,
            url: modelURL ? mapURL : freeURL,
            updated: +new Date()
        }, process.exit);
    });
};
<commit_msg>Fix: Switch console.log/error.<commit_after>var fs = require('fs');
var url = require('url');
var path = require('path');
var request = require('request');
var crypto = require('crypto');
var Step = require('step');

command = Bones.Command.extend();

command.description = 'export project';
command.usage = '<project> <export>';

command.options['format'] = {
    'title': 'format=[format]',
    'description': 'Export format (png|pdf|svg|mbtiles).'
};

command.options['bbox'] = {
    'title': 'bbox=[bbox]',
    'description': 'Comma separated coordinates of bounding box to export.'
};

command.options['minzoom'] = {
    'title': 'minzoom=[zoom]',
    'description': 'MBTiles: minimum zoom level to export.'
};

command.options['maxzoom'] = {
    'title': 'maxzoom=[zoom]',
    'description': 'MBTiles: maximum zoom level to export.'
};

command.options['width'] = {
    'title': 'width=[width]',
    'description': 'Image: image width in pixels.',
    'default': 400
};

command.options['height'] = {
    'title': 'height=[height]',
    'description': 'Image: image height in pixels.',
    'default': 400
};

command.options['url'] = {
    'title': 'url=[url]',
    'description': 'URL to PUT updates to.'
};

command.options['log'] = {
    'title': 'log',
    'description': 'Write crash logs to destination directory.'
};

command.prototype.initialize = function(plugin, callback) {
    var opts = plugin.config;
    if (process.env.tilemillConfig)
        _(opts).extend(JSON.parse(process.env.tilemillConfig));
    opts.files = path.resolve(opts.files);
    opts.project = plugin.argv._[1];
    opts.filepath = path.resolve(plugin.argv._[2]);
    callback = callback || function() {};
    this.opts = opts;

    // Write crash log
    if (opts.log) {
        process.on('uncaughtException', function(err) {
            fs.writeFileSync(opts.filepath + '.crashlog', err.stack || err.toString());
            process.exit(1);
        });
    }

    // Validation.
    if (!opts.project || !opts.filepath) return plugin.help();
    if (!path.existsSync(path.dirname(opts.filepath)))
        return this.error(new Error('Export path does not exist: ' + path.dirname(opts.filepath)));

    // Format.
    if (!opts.format) opts.format = path.extname(opts.filepath).split('.').pop();
    if (!_(['pdf', 'svg', 'png', 'mbtiles', 'upload']).include(opts.format))
        return this.error(new Error('Invalid format: ' + opts.format));

    // Convert string params into numbers.
    if (!_(opts.bbox).isUndefined())
        opts.bbox = _(opts.bbox.split(',')).map(parseFloat);
    if (!_(opts.minzoom).isUndefined())
        opts.minzoom = parseInt(opts.minzoom, 10);
    if (!_(opts.maxzoom).isUndefined())
        opts.maxzoom = parseInt(opts.maxzoom, 10);
    if (!_(opts.width).isUndefined())
        opts.width = parseInt(opts.width, 10);
    if (!_(opts.height).isUndefined())
        opts.height = parseInt(opts.height, 10);

    // Rename the output filepath using a random hash if file already exists.
    if (path.existsSync(opts.filepath) && opts.format !== 'upload') {
        var hash = crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var ext = path.extname(opts.filepath);
        opts.filepath = opts.filepath.replace(ext, '_' + hash + ext);

        // Update filename in TileMill.
        this.put({ filename: path.basename(opts.filepath), status: 'processing' });
    }

    // Set process title.
    process.title = 'tm-' + path.basename(opts.filepath);

    // Catch SIGINT.
    process.on('SIGINT', function () {
        console.log('Got SIGINT. Run "kill ' + process.pid + '" to terminate.');
    });
    process.on('SIGUSR1', process.exit);

    // Upload format does not require loaded project.
    if (opts.format === 'upload') return this[opts.format](callback);

    // Load project, localize and call export function.
    var cmd = this;
    var model = new models.Project({id:opts.project});
    Step(function() {
        Bones.utils.fetch({model:model}, this);
    }, function(err) {
        if (err) throw err;
        model.localize(model.toJSON(), this);
    }, function(err) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });

        model.mml = _(model.mml).extend({
            name: model.mml.name || model.id,
            version: model.mml.version || '1.0.0',
            minzoom: !_(opts.minzoom).isUndefined() ? opts.minzoom : model.get('minzoom'),
            maxzoom: !_(opts.maxzoom).isUndefined() ? opts.maxzoom : model.get('maxzoom'),
            bounds: !_(opts.bbox).isUndefined() ? opts.bbox : model.get('bounds')
        });

        // Unset map center if outside bounds.
        var validCenter = (function(center, bounds, minzoom, maxzoom) {
            if (center[0] < bounds[0] ||
                center[0] > bounds[2] ||
                center[1] < bounds[1] ||
                center[1] > bounds[3]) return false;
            if (center[2] < minzoom) return false;
            if (center[2] > maxzoom) return false;
            return true;
        })(model.mml.center, model.mml.bounds, model.mml.minzoom, model.mml.maxzoom);
        if (!validCenter) delete model.mml.center;

        cmd[opts.format](model, callback);
    });
};

command.prototype.error = function(err, callback) {
    this.put({
        status: 'error',
        error: err.toString(),
        updated: +new Date()
    }, callback);
};

command.prototype.remaining = function(progress, started) {
    return Math.floor(
        (Date.now() - started) * (1 / progress) -
        (Date.now() - started)
    );
};

command.prototype.put = function(data, callback) {
    callback = callback || function() {};
    data.status == 'error' ?
        console.error(JSON.stringify(data)) :
        console.log(JSON.stringify(data));

    if (!this.opts.url) return callback();
    request.put({
        uri: this.opts.url,
        headers: {
            'Host': url.parse(this.opts.url).host,
            'Cookie': 'bones.token=token'
        },
        json: _(data).extend({'bones.token': 'token'})
    }, callback);
};

command.prototype.png =
command.prototype.svg =
command.prototype.pdf = function(project, callback) {
    var mapnik = require('mapnik');
    var sm = new (require('sphericalmercator'))();
    var map = new mapnik.Map(this.opts.width, this.opts.height);

    map.fromStringSync(project.xml, {
        strict: false,
        base: path.join(this.opts.files, 'project', project.id) + '/'
    });
    map.bufferSize = this.opts.bufferSize;
    map.extent = sm.convert(project.mml.bounds, '900913');
    try {
        map.renderFileSync(this.opts.filepath, { format: this.opts.format });
        this.put({
            status: 'complete',
            progress: 1,
            updated: +new Date()
        }, process.exit);
    } catch(err) {
        this.error(err);
    }
};

command.prototype.mbtiles = function (project, callback) {
    var cmd = this;
    var tilelive = require('tilelive');
    require('mbtiles').registerProtocols(tilelive);
    require('tilelive-mapnik').registerProtocols(tilelive);

    var uri = {
        protocol: 'mapnik:',
        slashes: true,
        xml: project.xml,
        mml: project.mml,
        pathname: path.join(this.opts.files, 'project', project.id, project.id + '.xml'),
        query: { bufferSize: this.opts.bufferSize }
    };
    var source;
    var sink;

    Step(function() {
        tilelive.load(uri, this);
    }, function(err, s) {
        if (err) throw err;
        source = s;
        tilelive.load('mbtiles://' + cmd.opts.filepath, this);
    }, function(err, s) {
        if (err) throw err;
        sink = s;
        sink.startWriting(this);
    }, function(err) {
        if (err) throw err;
        sink.putInfo(project.mml, this);
    }, function(err) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });

        var copy = tilelive.copy({
            source: source,
            sink: sink,
            bbox: project.mml.bounds,
            minZoom: project.mml.minzoom,
            maxZoom: project.mml.maxzoom,
            concurrency: 100,
            tiles: true,
            grids: !!project.mml.interactivity
        });

        var timeout = setInterval(function progress() {
            var progress = (copy.copied + copy.failed) / copy.total;
            cmd.put({
                status: progress < 1 ? 'processing' : 'complete',
                progress: progress,
                remaining: cmd.remaining(progress, copy.started),
                updated: +new Date(),
            });
        }, 5000);

        copy.on('warning', function(err) {
            console.log(err);
        });
        copy.on('finished', function() {
            clearTimeout(timeout);
            cmd.put({
                status: 'complete',
                progress: 1,
                updated: +new Date()
            }, process.exit);
        });
        copy.on('error', function(err) {
            clearTimeout(timeout);
            cmd.error(err, function() {
                process.exit(1);
            });
        });
    });
};

command.prototype.upload = function (callback) {
    var cmd = this;
    var key;
    var bucket;
    var mapURL = '';
    var freeURL = '';
    var modelURL = '';
    var hash = crypto.createHash('md5')
        .update(+new Date + '')
        .digest('hex')
        .substring(0, 6);
    var policyEndpoint = url.format({
        protocol: 'http:',
        host: this.opts.mapboxHost || 'api.tiles.mapbox.com',
        pathname: '/v2/'+ hash + '/upload.json'
    });

    // Set URLs for account uploads.
    if (this.opts.syncAccount && this.opts.syncAccessToken) {
        mapURL = _('<%=base%>/<%=account%>/map/<%=handle%>')
            .template({
                base: this.opts.syncURL,
                account: this.opts.syncAccount,
                handle: this.opts.project
            });
        modelURL = _('<%=base%>/api/Map/<%=account%>.<%=handle%>?access_token=<%=token%>')
            .template({
                base: this.opts.syncURL,
                account: this.opts.syncAccount,
                handle: this.opts.project,
                token: this.opts.syncAccessToken
            });
    }

    Step(function() {
        request.get({
            uri: policyEndpoint,
            headers: { 'Host': url.parse(policyEndpoint).host }
        }, this);
    }, function(err, resp, body) {
        if (err) throw err;
        if (resp.statusCode !== 200)
            throw new Error('MapBox Hosting is not available. Status ' + resp.statusCode + '.');

        // Let Step catch thrown errors here.
        uploadArgs = JSON.parse(body);
        key = uploadArgs.key;
        bucket = uploadArgs.bucket;
        delete uploadArgs.bucket;
        delete uploadArgs.filename;

        var stat = fs.statSync(cmd.opts.filepath);
        var boundary = '----TileMill' + crypto.createHash('md5')
            .update(+new Date + '')
            .digest('hex')
            .substring(0, 6);
        var filename = path.basename(cmd.opts.filepath);
        var multipartBody = new Buffer(_(uploadArgs).map(function(value, key) {
            return '--' + boundary + '\r\n'
                + 'Content-Disposition: form-data; name="' + key + '"\r\n'
                + '\r\n' + value + '\r\n';
            })
            .concat(['--' + boundary + '\r\n'
                + 'Content-Disposition: form-data; name="file"; filename="' + filename + '"\r\n'
                + 'Content-Type: application/octet-stream\r\n\r\n'])
            .join(''));
        var terminate = new Buffer('\r\n--' + boundary + '--', 'ascii');

        var dest = http.request({
            host: bucket + '.s3.amazonaws.com',
            path: '/',
            method: 'POST',
            headers: {
                'Content-Type': 'multipart/form-data; boundary=' + boundary,
                'Content-Length': stat.size + multipartBody.length + terminate.length,
                'X_FILE_NAME': filename
            }
        }, function(resp) {
            if (resp.statusCode !== 303)
                return this(new Error('S3 is not available. Status ' + resp.statusCode + '.'));

            freeURL = resp.headers.location.split('?')[0];
            this();
        }.bind(this))

        // Write multipart values from memory.
        dest.write(multipartBody, 'ascii');

        // Set up read for MBTiles file and start the upload.
        var bytesWritten = 0;
        var started = Date.now();
        var updated = Date.now();
        fs.createReadStream(cmd.opts.filepath)
            .on('data', function(chunk) {
                bytesWritten += chunk.length;

                if (Date.now() < updated + 5000) return;

                var progress = bytesWritten / stat.size;
                updated = Date.now();
                cmd.put({
                    status: 'complete',
                    progress: progress,
                    status: progress < 1 ? 'processing' : 'complete',
                    remaining: cmd.remaining(progress, started),
                    updated: updated
                });
            })
            .on('end', function() {
                dest.write(terminate);
                dest.end();
            })
            .pipe(dest, {end: false});
    }, function(err) {
        if (err) throw err;
        if (!modelURL) return this(); // Free

        request.get(modelURL, this);
    }, function(err, res, body) {
        if (err) throw err;
        if (!modelURL) return this(); // Free

        // Let Step catch thrown errors here.
        var model = _(res.statusCode === 404 ? {} : JSON.parse(body)).extend({
            id: cmd.opts.syncAccount + '.' + cmd.opts.project,
            _type: 'tileset',
            created: +new Date,
            status: 'pending',
            url: 'http://' + bucket + '.s3.amazonaws.com/' + key
        });
        request.put({ url:modelURL, json:model }, this);
    }, function(err, res, body) {
        if (err) return cmd.error(err, function() {
            process.exit(1);
        });
        if (modelURL && res.statusCode !== 200)
            return cmd.error('Map publish failed: ' + res.statusCode, function() {
                process.exit(1);
            });
        cmd.put({
            status: 'complete',
            progress: 1,
            url: modelURL ? mapURL : freeURL,
            updated: +new Date()
        }, process.exit);
    });
};
<|endoftext|>


<commit_before>const fs = require('fs');
const async = require('async');
const path = require('path');
const proc = require('child_process');
const request = require('request');
const chalk = require('chalk');
const rssParser = require('rss-parser');
const urlParser = require('url');
const YAML = require('yamljs');

const logger = require('../lib/logger');
const datafire = require('../index');

const SPEC_FORMATS = ['raml', 'wadl', 'swagger_1', 'api_blueprint', 'io_docs', 'google'];

const PACKAGE_PREFIX = process.env.DATAFIRE_REGISTRY_DIR ?
  process.env.DATAFIRE_REGISTRY_DIR + '/integrations' : '@datafire'

const RSS_SCHEMA = {
  type: 'object',
  properties: {
    feed: {
      type: 'object',
      properties: {
        link: {type: 'string'},
        title: {type: 'string'},
        feedUrl: {type: 'string'},
        entries: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              id: {type: 'string'},
              link: {type: 'string'},
              title: {type: 'string'},
              pubDate: {type: 'string'},
              author: {type: 'string'},
              content: {type: 'string'},
              contentSnippet: {type: 'string'},
            }
          }
        }
      }
    }
  }
}

module.exports = (args, callback) => {
  fs.mkdir(args.directory, (err) => {
    if (err && err.code !== 'EEXIST') return callback(err);
    let specFormat = SPEC_FORMATS.filter(f => args[f])[0];
    if (args.openapi) {
      integrateOpenAPI(args.directory, args.name, args.openapi, args.patch, callback);
    } else if (specFormat) {
      integrateSpec(args.directory, args.name, specFormat, args[specFormat], callback);
    } else if (args.rss) {
      integrateRSS(args.directory, args.name, args.rss, callback);
    } else {
      let packageNames = args.integrations.map(i => PACKAGE_PREFIX + '/' + i);
      proc.exec('npm install --save ' + packageNames.join(' '), (err, stdout, stderr) => {
        if (err) return callback(err);
        callback();
      });
    }
  })
}

const addIntegration = (directory, name, type, spec, callback) => {
  name = name || getNameFromHost(spec.host);
  let filename = path.join(directory, name + (type === 'rss' ? RSS_SUFFIX : OPENAPI_SUFFIX));
  logger.log('Writing integration ' + name + ' to ' + filename.replace(process.cwd(), '.'));
  spec.info['x-datafire-name'] = name;
  fs.writeFile(filename, JSON.stringify(spec, null, 2), e => {
    callback(e, spec);
  });
}

const getLocalSpec = (name) => {
  return NATIVE_INTEGRATIONS.filter(fname => fname.startsWith(name + '.'))[0];
}

const integrateFile = (dir, name, callback) => {
  let filename = getLocalSpec(name);
  let type = filename.indexOf('.rss.') === -1 ? 'openapi' : 'rss';
  if (!filename) return callback(new Error("Integration " + name + " not found"));
  fs.readFile(path.join(NATIVE_INTEGRATIONS_DIR, filename), 'utf8', (err, data) => {
    if (err) return callback(err);
    addIntegration(dir, name, type, JSON.parse(data), callback);
  });
  spec.info['x-datafire'] = {name, type};
  fs.mkdir(dir, err => {
    if (err && err.code !== 'EEXIST') return callback(err);
    fs.writeFile(filename, JSON.stringify(spec, null, 2), e => {
      if (e) return callback(e);
      logger.log('Created integration ' + name + ' in ' + filename.replace(process.cwd(), '.'));
      callback(null, spec);
    });
  })
}

const TLDs = ['.com', '.org', '.net', '.gov', '.io', '.co.uk'];
const SUBDOMAINS = ['www.', 'api.', 'developer.'];
const getNameFromHost = (host) => {
  SUBDOMAINS.forEach(sub => {
    if (host.startsWith(sub)) host = host.substring(sub.length);
  })
  TLDs.forEach(tld => {
    if (host.endsWith(tld)) host = host.substring(0, host.length - tld.length);
  })
  return host.replace(/\./, '_');
}

const integrateOpenAPI = (dir, name, url, patch, callback) => {
  request.get(url, (err, resp, body) => {
    if (err) return callback(err);
    if (resp.headers['content-type'].indexOf('yaml') !== -1) {
      body = YAML.parse(body);
    } else {
      body = JSON.parse(body);
    }
    if (!body.host) return callback(new Error("Invalid swagger:" + JSON.stringify(body, null, 2)))
    if (patch) patch(body);
    addIntegration(dir, name, 'openapi', body, callback);
  })
}

const integrateRSS = (dir, name, urls, callback) => {
  if (typeof urls === 'string') {
    urls = {
      getItems: urls,
    }
  }
  let spec = {
    swagger: '2.0',
    basePath: '/',
    paths: {},
    definitions: {Feed: RSS_SCHEMA},
    info: {},
  }
  for (let operation in urls) {
    let url = urls[operation];
    let urlObj = urlParser.parse(url);
    urlObj.pathname = urlObj.pathname.replace(/%7B/g, '{').replace(/%7D/g, '}');
    spec.host = urlObj.hostname;
    spec.schemes = [urlObj.protocol.substring(0, urlObj.protocol.length - 1)];
    if (!name) {
      name = getNameFromHost(urlObj.hostname);
    }
    spec.paths[urlObj.pathname] = {
      get: {
        operationId: operation,
        description: "Retrieve the RSS feed",
        responses: {
          '200': {description: "OK", schema: {$ref: '#/definitions/Feed'}}
        },
        parameters: (urlObj.pathname.match(/\{\w+\}/g) || [])
              .map(p => p.substring(1, p.length - 1))
              .map(p => ({
                name: p,
                in: 'path',
                type: 'string',
                required: true,
              }))
      }
    }
  }
  async.parallel(Object.keys(spec.paths).map(path => {
    let op = spec.paths[path].get;
    return acb => {
      if (op.parameters.length) return acb();
      rssParser.parseURL(spec.schemes[0] + '://' + spec.host + path, (err, feed) => {
        if (err) return acb(err);
        feed = feed.feed;
        spec.paths[path].get.summary = feed.title;
        spec.paths[path].get.description = feed.description;
        acb();
      })
    }
  }), err => {
    if (err) return callback(err);
    let paths = Object.keys(spec.paths);
    if (paths.length === 1) {
      spec.info.title = spec.paths[paths[0]].summary;
      spec.info.description = spec.paths[paths[0]].description;
    }
    addIntegration(dir, name, 'rss', spec, callback);
  })
}

const integrateSpec = (dir, name, format, url, callback) => {
  let cmd = 'api-spec-converter "' + url + '" --from ' + format + ' --to swagger_2';
  proc.exec(cmd, (err, stdout) => {
    if (err) {
      logger.logError('Please install api-spec-converter');
      logger.log('npm install -g api-spec-converter');
      return callback(err);
    }
    addIntegration(dir, name, 'openapi', JSON.parse(stdout), callback);
  })
}

<commit_msg>fix integrate cmd<commit_after>const fs = require('fs');
const async = require('async');
const path = require('path');
const proc = require('child_process');
const request = require('request');
const chalk = require('chalk');
const rssParser = require('rss-parser');
const urlParser = require('url');
const YAML = require('yamljs');

const logger = require('../lib/logger');
const datafire = require('../index');

const SPEC_FORMATS = ['raml', 'wadl', 'swagger_1', 'api_blueprint', 'io_docs', 'google'];

const PACKAGE_PREFIX = process.env.DATAFIRE_REGISTRY_DIR ?
  process.env.DATAFIRE_REGISTRY_DIR + '/integrations' : '@datafire'

const RSS_SCHEMA = {
  type: 'object',
  properties: {
    feed: {
      type: 'object',
      properties: {
        link: {type: 'string'},
        title: {type: 'string'},
        feedUrl: {type: 'string'},
        entries: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              id: {type: 'string'},
              link: {type: 'string'},
              title: {type: 'string'},
              pubDate: {type: 'string'},
              author: {type: 'string'},
              content: {type: 'string'},
              contentSnippet: {type: 'string'},
            }
          }
        }
      }
    }
  }
}

module.exports = (args, callback) => {
  fs.mkdir(args.directory, (err) => {
    if (err && err.code !== 'EEXIST') return callback(err);
    let specFormat = SPEC_FORMATS.filter(f => args[f])[0];
    if (args.openapi) {
      integrateOpenAPI(args.directory, args.name, args.openapi, args.patch, callback);
    } else if (specFormat) {
      integrateSpec(args.directory, args.name, specFormat, args[specFormat], callback);
    } else if (args.rss) {
      integrateRSS(args.directory, args.name, args.rss, callback);
    } else {
      let packageNames = args.integrations.map(i => PACKAGE_PREFIX + '/' + i);
      proc.exec('npm install --save ' + packageNames.join(' '), (err, stdout, stderr) => {
        if (err) return callback(err);
        callback();
      });
    }
  })
}

const addIntegration = (directory, name, type, spec, callback) => {
  name = name || getNameFromHost(spec.host);
  let filename = path.join(directory, name, 'integration.json');
  logger.log('Writing integration ' + name + ' to ' + filename.replace(process.cwd(), '.'));
  spec.info['x-datafire'] = {name, type};
  fs.mkdir(path.join(directory, name), err => {
    if (err && err.code !== 'EEXIST') return callback(err);
    fs.writeFile(filename, JSON.stringify(spec, null, 2), e => {
      if (e) return callback(e);
      logger.log('Created integration ' + name + ' in ' + filename.replace(process.cwd(), '.'));
      callback(null, spec);
    });
  })
}

const getLocalSpec = (name) => {
  return NATIVE_INTEGRATIONS.filter(fname => fname.startsWith(name + '.'))[0];
}

const integrateFile = (dir, name, callback) => {
  let filename = getLocalSpec(name);
  let type = filename.indexOf('.rss.') === -1 ? 'openapi' : 'rss';
  if (!filename) return callback(new Error("Integration " + name + " not found"));
  fs.readFile(path.join(NATIVE_INTEGRATIONS_DIR, filename), 'utf8', (err, data) => {
    if (err) return callback(err);
    addIntegration(dir, name, type, JSON.parse(data), callback);
  });
}

const TLDs = ['.com', '.org', '.net', '.gov', '.io', '.co.uk'];
const SUBDOMAINS = ['www.', 'api.', 'developer.'];
const getNameFromHost = (host) => {
  SUBDOMAINS.forEach(sub => {
    if (host.startsWith(sub)) host = host.substring(sub.length);
  })
  TLDs.forEach(tld => {
    if (host.endsWith(tld)) host = host.substring(0, host.length - tld.length);
  })
  return host.replace(/\./, '_');
}

const integrateOpenAPI = (dir, name, url, patch, callback) => {
  request.get(url, (err, resp, body) => {
    if (err) return callback(err);
    if (resp.headers['content-type'].indexOf('yaml') !== -1) {
      body = YAML.parse(body);
    } else {
      body = JSON.parse(body);
    }
    if (!body.host) return callback(new Error("Invalid swagger:" + JSON.stringify(body, null, 2)))
    if (patch) patch(body);
    addIntegration(dir, name, 'openapi', body, callback);
  })
}

const integrateRSS = (dir, name, urls, callback) => {
  if (typeof urls === 'string') {
    urls = {
      getItems: urls,
    }
  }
  let spec = {
    swagger: '2.0',
    basePath: '/',
    paths: {},
    definitions: {Feed: RSS_SCHEMA},
    info: {},
  }
  for (let operation in urls) {
    let url = urls[operation];
    let urlObj = urlParser.parse(url);
    urlObj.pathname = urlObj.pathname.replace(/%7B/g, '{').replace(/%7D/g, '}');
    spec.host = urlObj.hostname;
    spec.schemes = [urlObj.protocol.substring(0, urlObj.protocol.length - 1)];
    if (!name) {
      name = getNameFromHost(urlObj.hostname);
    }
    spec.paths[urlObj.pathname] = {
      get: {
        operationId: operation,
        description: "Retrieve the RSS feed",
        responses: {
          '200': {description: "OK", schema: {$ref: '#/definitions/Feed'}}
        },
        parameters: (urlObj.pathname.match(/\{\w+\}/g) || [])
              .map(p => p.substring(1, p.length - 1))
              .map(p => ({
                name: p,
                in: 'path',
                type: 'string',
                required: true,
              }))
      }
    }
  }
  async.parallel(Object.keys(spec.paths).map(path => {
    let op = spec.paths[path].get;
    return acb => {
      if (op.parameters.length) return acb();
      rssParser.parseURL(spec.schemes[0] + '://' + spec.host + path, (err, feed) => {
        if (err) return acb(err);
        feed = feed.feed;
        spec.paths[path].get.summary = feed.title;
        spec.paths[path].get.description = feed.description;
        acb();
      })
    }
  }), err => {
    if (err) return callback(err);
    let paths = Object.keys(spec.paths);
    if (paths.length === 1) {
      spec.info.title = spec.paths[paths[0]].summary;
      spec.info.description = spec.paths[paths[0]].description;
    }
    addIntegration(dir, name, 'rss', spec, callback);
  })
}

const integrateSpec = (dir, name, format, url, callback) => {
  let cmd = 'api-spec-converter "' + url + '" --from ' + format + ' --to swagger_2';
  proc.exec(cmd, (err, stdout) => {
    if (err) {
      logger.logError('Please install api-spec-converter');
      logger.log('npm install -g api-spec-converter');
      return callback(err);
    }
    addIntegration(dir, name, 'openapi', JSON.parse(stdout), callback);
  })
}

<|endoftext|>


<commit_before>const sf = require('snekfetch');
const cooldown = require('../functions/cooldown.js');
const discord = require('discord.js');

exports.run = async (bot,msg,args) => {
    if (cooldown(msg, 'strawpoll', 60, 'This command has a cooldown of **1 Minute**!')) {
        if (!args[0] || !args[1] || !args[2] || !args.join().includes('"')) return msg.channel.send('Please give at least 2 options and a title.');
        let title = args.join(' ').split('"')[1];
        if (args.slice(title.split(' ').length).join(' ').split(',').length < 2) return msg.channel.send('Please give at least 2 options');
        let poll = await sf.post('https://strawpoll.me/api/v2/polls').send({
            "title": title,
            "options": args.slice(title.split(' ').length).join(' ').split(','),
            "multi": false
        });
        msg.delete();
        const embed = new discord.RichEmbed()
            .setTitle('Strawpoll | \'' + title + '\'')
            .setDescription(`[Click here for the strawpoll](http://strawpoll.me/${poll.body.id})`)
            .setColor('#eacd10')
            .addField('Strawpoll created from:', msg.author.tag)
            .addField('Choices:', poll.body.options.join('\n'));

        msg.channel.send({embed})
    }

};

exports.help = {
    category: 'util',
    usage: '"title of poll" [poll options (separated with `,`)]',
    description: 'Creates a strawpoll',
    detail: 'Creates a new strawpoll with given options',
    botPerm    : ['SEND_MESSAGES', 'EMBED_LINKS'],
    authorPerm : [],
    alias      : [
        'poll'
    ]
};

<commit_msg>strawpoll example<commit_after>const sf = require('snekfetch');
const cooldown = require('../functions/cooldown.js');
const discord = require('discord.js');

exports.run = async (bot,msg,args) => {
    if (cooldown(msg, 'strawpoll', 60, 'This command has a cooldown of **1 Minute**!')) {
        if (!args[0] || !args[1] || !args[2] || !args.join().includes('"')) return msg.channel.send('Please give at least 2 options and a title.');
        let title = args.join(' ').split('"')[1];
        if (args.slice(title.split(' ').length).join(' ').split(',').length < 2) return msg.channel.send('Please give at least 2 options');
        let poll = await sf.post('https://strawpoll.me/api/v2/polls').send({
            "title": title,
            "options": args.slice(title.split(' ').length).join(' ').split(','),
            "multi": false
        });
        msg.delete();
        const embed = new discord.RichEmbed()
            .setTitle('Strawpoll | \'' + title + '\'')
            .setDescription(`[Click here for the strawpoll](http://strawpoll.me/${poll.body.id})`)
            .setColor('#eacd10')
            .addField('Strawpoll created from:', msg.author.tag)
            .addField('Choices:', poll.body.options.join('\n'));

        msg.channel.send({embed})
    }

};

exports.help = {
    category: 'util',
    usage: '"title of poll" [poll options (separated with `,`)]',
    description: 'Creates a strawpoll',
    detail: 'Creates a new strawpoll with given options',
    botPerm    : ['SEND_MESSAGES', 'EMBED_LINKS'],
    authorPerm : [],
    example    : "\"Is may the best bot ever?\" Yes,No"
    alias      : [
        'poll'
    ]
};
<|endoftext|>


<commit_before>define('collections', [], function() {
  'use strict';

  return {

    // sessions { _id: 'mongo generated unique id' }
    sessions:  new Meteor.Collection("sessions"),

    // users {
    //   _id: 'mongo generated unique id',
    // 	sessionId: 'session._id',
    // 	name: 'user name'
    // }
    users: new Meteor.Collection("users"),

    // workItems {
    //   _id: 'mongo generated unique id',
    //   sessionId: 'session._id',
    //   index: '1 based index of work items in this session',
    //   description: 'brief summary of user story'
    // }
    workItems: new Meteor.Collection("workItems"),

    // estimates {
    //   _id: 'mongo generated unique id',
    //  sessionId: 'session._id'
    //  workItemId: 'workItem._id'
    //  userId: 'user._id',
    //  value: 'estimate value'
    // }
    estimates: new Meteor.Collection("estimates")

  };

});
<commit_msg>single quotes for consistency<commit_after>define('collections', [], function() {
  'use strict';

  return {

    // sessions { _id: 'mongo generated unique id' }
    sessions:  new Meteor.Collection('sessions'),

    // users {
    //   _id: 'mongo generated unique id',
    // 	sessionId: 'session._id',
    // 	name: 'user name'
    // }
    users: new Meteor.Collection('users'),

    // workItems {
    //   _id: 'mongo generated unique id',
    //   sessionId: 'session._id',
    //   index: '1 based index of work items in this session',
    //   description: 'brief summary of user story'
    // }
    workItems: new Meteor.Collection('workItems'),

    // estimates {
    //   _id: 'mongo generated unique id',
    //  sessionId: 'session._id'
    //  workItemId: 'workItem._id'
    //  userId: 'user._id',
    //  value: 'estimate value'
    // }
    estimates: new Meteor.Collection('estimates')

  };

});
<|endoftext|>


<commit_before>define([
	'modules/Tag/src/TagSpaceView',
	'modules/Edition/src/ElementManager',
	'jquery',
	'pubsub',
], function(TagSpaceView, ElementManager, $, pubsub) {

	/**
	 * Create and display tags
	 * @param {Object} songModel
	 * @param {Array} tags      Array of object that contain at least a startBeat, a endBeat, can also contain a name
	 * @param {Array} colors    Array of colors in rgba or hexadecimal or html color
	 * @param {Boolean} isActive    Indicates if we draw it or not
	 * @param {Boolean} isEditable  Indicates if it's editable or not
	 */
	function TagManager(songModel, noteSpaceManager, tags, colors, isActive, isEditable) {
		if (!noteSpaceManager.noteSpace) {
			throw "TagManager - noteSpaceManager not well initialized";
		}

		this.songModel = songModel;
		this.noteSpaceManager = noteSpaceManager;

		this.CL_NAME = 'TagManager';
		this.CL_TYPE = 'CLICKABLE'
		this.tags = tags || [];
		this.colors = colors || ["#559", "#995", "#599", "#595"];
		this.tagSpaces = [];
		this.isActive = (typeof isActive !== "undefined") ? isActive : true;
		this.isEditable = (typeof isEditable !== "undefined") ? isEditable : false;

		this.initSubscribe();
		this.elemMng = new ElementManager();
	}
	TagManager.prototype.getType = function() {
		return this.CL_TYPE;
	};

	TagManager.prototype.getTags = function() {
		return this.tags;
	};

	TagManager.prototype.setTags = function(tags) {
		if (typeof tags === "undefined") {
			throw 'TagManager - setTags tags must be an array ' + tags;
		}
		this.tags = tags;
	};

	TagManager.prototype.getColors = function() {
		return this.colors;
	};

	TagManager.prototype.setColors = function(colors) {
		if (typeof colors === "undefined") {
			throw 'TagManager - setColors colors must be an array ' + colors;
		}
		this.colors = colors;
		//$.publish('TagManager-setColors', this);
	};

	TagManager.prototype.setActive = function(active) {
		this.isActive = !!active;
	};

	TagManager.prototype.getActive = function() {
		return this.isActive;
	};

	TagManager.prototype.onSelected = function(coords, clicked) {
		var indexTagClicked = this.inPathPosition(coords);
		// console.log(indexTagClicked);
		var self = this;
		if (this.tagSpaces[indexTagClicked].isInPathDelete(coords)) {
			//console.log('delete', indexTagClicked);
			this.tags.splice(indexTagClicked, 1);
			$.publish('ToViewer-draw', self.songModel);
		}
	};

	TagManager.prototype.isEnabled = function() {
		return true;
	};
	TagManager.prototype.enable = function() {};
	TagManager.prototype.disable = function() {};

	/**
	 * Subscribe to view events
	 */
	TagManager.prototype.initSubscribe = function() {
		var self = this;
		$.subscribe('LSViewer-drawEnd', function(el, viewer) {
			self.drawTags(viewer);
			if (self.noteSpaceManager.viewer.canvasLayer) {
				self.noteSpaceManager.viewer.canvasLayer.addElement(self);
			}
		});

	};

	TagManager.prototype.isEnabled = function() {
		return this.isActive;
	};

	TagManager.prototype.inPathPosition = function(coords) {
		for (var i = 0, c = this.tagSpaces.length; i < c; i++) {
			if (typeof this.tagSpaces[i] !== "undefined") {
				if (this.tagSpaces[i].isInPath(coords)) {
					return i;
				}
			}
		}
		return false;
	};

	TagManager.prototype.inPath = function(coords) {
		if (this.inPathPosition(coords) === false) {
			return false;
		}
		return true;
	};

	/**
	 * Function takes tags and transform them into TagSpace View that can be displayed on leadsheet,
	 * it basically transform beat position to x, y positions
	 * @param  {Object} viewer LSViewer
	 * @return {Array} array of TagSpaceViews
	 */
	TagManager.prototype.getTagAreas = function(i, viewer) {

		var tag;
		var nm = this.songModel.getComponent('notes');
		var fromIndex, toIndex;

		tag = this.tags[i];
		startEnd = nm.getIndexesStartingBetweenBeatInterval(tag.startBeat, tag.endBeat);
		fromIndex = startEnd[0];
		toIndex = startEnd[1];
		return this.elemMng.getElementsAreaFromCursor(this.noteSpaceManager.noteSpace, [fromIndex, toIndex]);
	};

	TagManager.prototype.drawTags = function(viewer) {
		if (this.isActive !== true) {
			return;
		}
		if (this.tags.length <= 0) {
			console.log('no tag');
			return;
		}

		var ctx = viewer.ctx;
		this.tagSpaces = [];
		var self = this;
		var areas;

		for (var i = 0; i < self.tags.length; i++) {
			areas = self.getTagAreas(i, viewer);
			if (areas.length === 0) {
				console.warn("area not found for " + i + "th tag");
				continue;
			}
			this.tagSpaces.push(new TagSpaceView(areas, this.tags[i].name));
		}

		viewer.drawElem(function() {

			var tagSpace;
			var saveFillColor = ctx.fillStyle;
			ctx.font = "15px Arial";

			var yDecalToggle = 3;
			var numberOfColors = self.colors.length;
			for (var i = 0; i < self.tagSpaces.length; i++) {
				ctx.globalAlpha = 0.4;
				tagSpace = self.tagSpaces[i];
				ctx.fillStyle = self.colors[i % numberOfColors]; // permute colors each time
				var numberOfTagPosition = tagSpace.position.length;
				for (var j = 0; j < numberOfTagPosition; j++) {
					//this makes shift a bit tags , useful in case they overlap
					if (i % 2) {
						tagSpace.position[j].y += yDecalToggle;
						tagSpace.position[j].h += yDecalToggle;
					} else {
						tagSpace.position[j].y -= yDecalToggle;
						tagSpace.position[j].h -= yDecalToggle;
					}
					//we paint the area
					ctx.fillRect(
						tagSpace.position[j].x,
						tagSpace.position[j].y,
						tagSpace.position[j].w,
						tagSpace.position[j].h
					);
				}
				//we write the tag name
				ctx.globalAlpha = 1;
				ctx.fillStyle = "black";
				ctx.fillText(tagSpace.name, tagSpace.position[0].x, tagSpace.position[0].y + tagSpace.position[0].h + 15);

				if (self.isEditable === true) {
					ctx.fillStyle = "#666";
					ctx.fillRect(
						tagSpace.position[numberOfTagPosition - 1].x + tagSpace.position[numberOfTagPosition - 1].w - 20,
						tagSpace.position[numberOfTagPosition - 1].y,
						20,
						20
					);
					ctx.fillStyle = "#eee";
					ctx.fillText('X', tagSpace.position[numberOfTagPosition - 1].x + tagSpace.position[numberOfTagPosition - 1].w - 20 + 6, tagSpace.position[numberOfTagPosition - 1].y + 19);
				}
			}
			ctx.fillStyle = saveFillColor;
			ctx.globalAlpha = 1;

		});

	};

	// no getYs function because it is not selectable
	return TagManager;
});<commit_msg>fixed bug in tagManager, caused by the refactoring of noteManager<commit_after>define([
	'modules/Tag/src/TagSpaceView',
	'modules/Edition/src/ElementManager',
	'jquery',
	'pubsub',
], function(TagSpaceView, ElementManager, $, pubsub) {

	/**
	 * Create and display tags
	 * @param {Object} songModel
	 * @param {Array} tags      Array of object that contain at least a startBeat, a endBeat, can also contain a name
	 * @param {Array} colors    Array of colors in rgba or hexadecimal or html color
	 * @param {Boolean} isActive    Indicates if we draw it or not
	 * @param {Boolean} isEditable  Indicates if it's editable or not
	 */
	function TagManager(songModel, noteSpaceManager, tags, colors, isActive, isEditable) {
		if (!noteSpaceManager.noteSpace) {
			throw "TagManager - noteSpaceManager not well initialized";
		}

		this.songModel = songModel;
		this.noteSpaceManager = noteSpaceManager;

		this.CL_NAME = 'TagManager';
		this.CL_TYPE = 'CLICKABLE'
		this.tags = tags || [];
		this.colors = colors || ["#559", "#995", "#599", "#595"];
		this.tagSpaces = [];
		this.isActive = (typeof isActive !== "undefined") ? isActive : true;
		this.isEditable = (typeof isEditable !== "undefined") ? isEditable : false;

		this.initSubscribe();
		this.elemMng = new ElementManager();
	}
	TagManager.prototype.getType = function() {
		return this.CL_TYPE;
	};

	TagManager.prototype.getTags = function() {
		return this.tags;
	};

	TagManager.prototype.setTags = function(tags) {
		if (typeof tags === "undefined") {
			throw 'TagManager - setTags tags must be an array ' + tags;
		}
		this.tags = tags;
	};

	TagManager.prototype.getColors = function() {
		return this.colors;
	};

	TagManager.prototype.setColors = function(colors) {
		if (typeof colors === "undefined") {
			throw 'TagManager - setColors colors must be an array ' + colors;
		}
		this.colors = colors;
		//$.publish('TagManager-setColors', this);
	};

	TagManager.prototype.setActive = function(active) {
		this.isActive = !!active;
	};

	TagManager.prototype.getActive = function() {
		return this.isActive;
	};

	TagManager.prototype.onSelected = function(coords, clicked) {
		var indexTagClicked = this.inPathPosition(coords);
		// console.log(indexTagClicked);
		var self = this;
		if (this.tagSpaces[indexTagClicked].isInPathDelete(coords)) {
			//console.log('delete', indexTagClicked);
			this.tags.splice(indexTagClicked, 1);
			$.publish('ToViewer-draw', self.songModel);
		}
	};

	TagManager.prototype.isEnabled = function() {
		return true;
	};
	TagManager.prototype.enable = function() {};
	TagManager.prototype.disable = function() {};

	/**
	 * Subscribe to view events
	 */
	TagManager.prototype.initSubscribe = function() {
		var self = this;
		$.subscribe('LSViewer-drawEnd', function(el, viewer) {
			self.drawTags(viewer);
			if (self.noteSpaceManager.viewer.canvasLayer) {
				self.noteSpaceManager.viewer.canvasLayer.addElement(self);
			}
		});

	};

	TagManager.prototype.isEnabled = function() {
		return this.isActive;
	};

	TagManager.prototype.inPathPosition = function(coords) {
		for (var i = 0, c = this.tagSpaces.length; i < c; i++) {
			if (typeof this.tagSpaces[i] !== "undefined") {
				if (this.tagSpaces[i].isInPath(coords)) {
					return i;
				}
			}
		}
		return false;
	};

	TagManager.prototype.inPath = function(coords) {
		if (this.inPathPosition(coords) === false) {
			return false;
		}
		return true;
	};

	/**
	 * Function takes tags and transform them into TagSpace View that can be displayed on leadsheet,
	 * it basically transform beat position to x, y positions
	 * @param  {Object} viewer LSViewer
	 * @return {Array} array of TagSpaceViews
	 */
	TagManager.prototype.getTagAreas = function(i, viewer) {

		var tag;
		var nm = this.songModel.getComponent('notes');
		var fromIndex, toIndex;

		tag = this.tags[i];
		startEnd = nm.getIndexesStartingBetweenBeatInterval(tag.startBeat, tag.endBeat, true);
		fromIndex = startEnd[0];
		toIndex = startEnd[1];
		return this.elemMng.getElementsAreaFromCursor(this.noteSpaceManager.noteSpace, [fromIndex, toIndex]);
	};

	TagManager.prototype.drawTags = function(viewer) {
		if (this.isActive !== true) {
			return;
		}
		if (this.tags.length <= 0) {
			console.log('no tag');
			return;
		}

		var ctx = viewer.ctx;
		this.tagSpaces = [];
		var self = this;
		var areas;

		for (var i = 0; i < self.tags.length; i++) {
			areas = self.getTagAreas(i, viewer);
			if (areas.length === 0) {
				console.warn("area not found for " + i + "th tag");
				continue;
			}
			this.tagSpaces.push(new TagSpaceView(areas, this.tags[i].name));
		}

		viewer.drawElem(function() {

			var tagSpace;
			var saveFillColor = ctx.fillStyle;
			ctx.font = "15px Arial";

			var yDecalToggle = 3;
			var numberOfColors = self.colors.length;
			for (var i = 0; i < self.tagSpaces.length; i++) {
				ctx.globalAlpha = 0.4;
				tagSpace = self.tagSpaces[i];
				ctx.fillStyle = self.colors[i % numberOfColors]; // permute colors each time
				var numberOfTagPosition = tagSpace.position.length;
				for (var j = 0; j < numberOfTagPosition; j++) {
					//this makes shift a bit tags , useful in case they overlap
					if (i % 2) {
						tagSpace.position[j].y += yDecalToggle;
						tagSpace.position[j].h += yDecalToggle;
					} else {
						tagSpace.position[j].y -= yDecalToggle;
						tagSpace.position[j].h -= yDecalToggle;
					}
					//we paint the area
					ctx.fillRect(
						tagSpace.position[j].x,
						tagSpace.position[j].y,
						tagSpace.position[j].w,
						tagSpace.position[j].h
					);
				}
				//we write the tag name
				ctx.globalAlpha = 1;
				ctx.fillStyle = "black";
				ctx.fillText(tagSpace.name, tagSpace.position[0].x, tagSpace.position[0].y + tagSpace.position[0].h + 15);

				if (self.isEditable === true) {
					ctx.fillStyle = "#666";
					ctx.fillRect(
						tagSpace.position[numberOfTagPosition - 1].x + tagSpace.position[numberOfTagPosition - 1].w - 20,
						tagSpace.position[numberOfTagPosition - 1].y,
						20,
						20
					);
					ctx.fillStyle = "#eee";
					ctx.fillText('X', tagSpace.position[numberOfTagPosition - 1].x + tagSpace.position[numberOfTagPosition - 1].w - 20 + 6, tagSpace.position[numberOfTagPosition - 1].y + 19);
				}
			}
			ctx.fillStyle = saveFillColor;
			ctx.globalAlpha = 1;

		});

	};

	// no getYs function because it is not selectable
	return TagManager;
});<|endoftext|>


<commit_before>export function nameSpacedTransformer(ns, transformer) {
  if (!transformer) {
    return nameSpacedTransformer.bind(null, ns);
  }
  return (state) => {
    const newState = transformer(state[ns]);
    // nothing has changed
    if (newState === state[ns]) {
      return state;
    }
    return { ...state, [ns]: newState };
  };
}
<commit_msg>Fix for name-spaced transformer<commit_after>export function nameSpacedTransformer(ns, transformer) {
  if (!transformer) {
    return nameSpacedTransformer.bind(null, ns);
  }
  return (state) => {
    const newState = transformer(state[ns]);

    // nothing has changed
    // noop
    if (!newState || newState === state[ns]) {
      return null;
    }

    return { ...state, [ns]: newState };
  };
}
<|endoftext|>