<commit_before>/**
 * Data Server
 * @module lib/data-server
 */

var path = require('path'),
    crypto = require('crypto'),
    _ = require('lodash'),
    async = require('async'),
    moment = require('moment-timezone'),
    jsonUtils = require('./json'),
    tmpl = require('./templates'),
    redisClient, app, rivers, DATE_FORMAT = 'YYYY/MM/DD HH:mm:ss',
    CONFIG;

function getRiver(name) {
    return _.find(rivers, function(river) {
        return river.name == name;
    });
}

function renderDataToCsv(json, res) {
    var csvLines = '';
    csvLines += json.headers.join(',') + '\n';
    _.each(json.data, function(point) {
        csvLines += point.join(',') + '\n';
    });
    res.setHeader('Content-Type', 'text');
    res.end(csvLines);
}

function renderPage(name, data, format, res, callback) {
    if (format == 'json') {
        delete data.tmpl;
        jsonUtils.render(data, res, callback);
    } else if (format == 'html') {
        tmpl.renderHtml(name, data, res);
    } else if (format == 'csv') {
        renderDataToCsv(data, res);
    } else {
        renderError(
            400,
            new Error('Format ' + format + ' is unsupported!'), 'html', res
        );
    }
}

function renderError(code, error, format, res, callback) {
    if (format == 'json') {
        jsonUtils.renderErrors(code, [error], res, callback);
    } else if (format == 'html') {
        tmpl.renderHtml('error', {
            message: error.message,
            title: 'Error!'
        }, res);
    }
}

function handleIndex(req, res) {
    var ext = req.params.ext || 'json',
        data;
    data = _.map(rivers, function(river) {
        var riverData = _.extend({
            urls: {
                keys: CONFIG.baseurl + '/' + river.name + '/keys.json',
                meta: CONFIG.baseurl + '/' + river.name + '/meta.json'
            }
        }, river.config);
        return riverData;
    });
    redisClient.getTemporalStreamCount(function(err, count) {
        renderPage('index', {
            rivers: data,
            tmpl: {
                title: 'River View',
                totalStreams: count
            }
        }, ext, res, req.query.callback);
    });
}

function handleLogRequest(req, res) {
    redisClient.getLogs(function(error, logs) {
        if (error) return renderError(503, error, 'html', res, req.query.callback);
        renderPage('activity', {
            logs: logs,
            title: 'River Stream Activity Log'
        }, 'html', res, req.query.callback);
    });
}

function handleRiverMetaDataRequest(req, res) {
    var riverName = req.params.river,
        ext = req.params.ext || 'json',
        river = getRiver(riverName),
        data;
    if (! river) {
        return handle404(req, res);
    }
    data = _.extend({}, getRiver(riverName).config);
    // The "source" might be a URL, or there might be a "soda" config with an
    // object defining the SODA request.
    if (! data.sources) {
        data.sources = [];
    }
    if (data.soda) {
        data.sources = data.sources.concat(_.map(data.soda, function(sodaSpec) {
            return JSON.stringify(sodaSpec);
        }));
    }
    data = _.extend({
        urls: {
            keys: CONFIG.baseurl + '/' + riverName + '/keys.json',
            meta: CONFIG.baseurl + '/' + riverName + '/meta.json'
        }
    }, data);

    data.tmpl = {};
    // Add email hash for gravatar
    data.tmpl.emailHash = crypto.createHash('md5').update(data.email).digest("hex");
    data.tmpl.title = riverName + ' metadata';
    data.tmpl.poweredBy = river.config.poweredBy;

    data.tmpl.intervalName = getIntervalName(data);
    data.tmpl.intervalString = getIntervalString(data);

    renderPage('riverMeta', data, ext, res, req.query.callback);
}

function getIntervalName(riverConfig) {
    return ((riverConfig.hasOwnProperty('cronInterval')) ? "Cron Interval:" : "Collection Interval");
}

function getIntervalString(riverConfig) {
    return ((riverConfig.hasOwnProperty('cronInterval'))
             ? ((typeof riverConfig.cronInterval === 'string') ? "" + riverConfig.cronInterval + ""
                                                       : "

" + riverConfig.cronInterval.join("

") + "

")
             : riverConfig.interval);
}

function handleRiverStreamsRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        includeDetails = req.query.includeDetails,
        ext = req.params.ext || 'json';

    if (! river) {
        return handle404(req, res);
    }
    river.getKeys(function(error, keys) {
        var propertyFetchers = {},
            urls = {
                keys: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/keys.json',
                meta: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/meta.json',
                streams: {}
            };

        if (error) return renderError(503, error, ext, res, req.query.callback);

        _.each(keys, function(key) {
            urls.streams[key] = {
                data: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/' + encodeURIComponent(key) + '/data.json',
                meta: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/' + encodeURIComponent(key) + '/meta.json'
            };
        });
        if (ext == 'json' && includeDetails) {
            _.each(keys, function(key) {
                propertyFetchers[key] = function(callback) {
                    river.getMetaData(key, function(err, meta) {
                        if (err) return callback(err);
                        callback(null, meta);
                    });
                };
            });
            async.parallel(propertyFetchers, function(err, meta) {
                if (err) return renderError(503, err, ext, res, req.query.callback);
                renderPage('keys', {
                    name: riverName,
                    keys: meta,
                    urls: urls,
                    tmpl: {
                        title: riverName + ' keys',
                        poweredBy: river.config.poweredBy
                    }
                }, ext, res, req.query.callback);
            });
        } else {
            renderPage('keys', {
                name: riverName,
                keys: keys,
                urls: urls,
                tmpl: {
                    title: riverName + ' keys',
                    poweredBy: river.config.poweredBy
                }
            }, ext, res, req.query.callback);
        }
    });
}

function aggregateTemporalData(periodString, columns, data) {
    var headers = ['datetime', 'count'];
    var dataOut = [];
    var dateIndex = columns.indexOf('datetime');
    var aggregationDuration = moment.duration(
        parseInt(periodString.split(/\s+/).shift()),
        periodString.split(/\s+/).pop()
    );
    var goUntil = moment(data[0][dateIndex], DATE_FORMAT).add(aggregationDuration);
    var count = 0;

    _.each(data, function(point) {
        var time = moment(point[dateIndex], DATE_FORMAT);
        if (time < goUntil) {
            count++;
        } else {
            dataOut.push([goUntil.format(DATE_FORMAT), count]);
            goUntil = goUntil.add(aggregationDuration);
            count = 1;
        }
    });

    return {
        headers: headers,
        data: dataOut
    };
}

function augmentWithNavigationDetails(payload) {
    var out = _.extend({}, payload),
        baseurl = CONFIG.baseurl + '/' + payload.name + '/' + payload.id + '/data.json',
        since = payload.meta.since.timestamp,
        until = payload.meta.until.timestamp,
        payloadDuration = until - since,
        leftBound = since,
        rightBound = until,
        nextBound = rightBound + payloadDuration,
        prevBound = leftBound - payloadDuration,
        durationString = moment.duration(payloadDuration, 'seconds').humanize();

    if (since !== until) {
        out.urls = {
            prev: encodeURI(baseurl + '?since=' + prevBound + '&until=' + leftBound) + '&snapto=until',
            next: encodeURI(baseurl + '?since=' + rightBound + '&until=' + nextBound) + '&snapto=since'
        };
    }

    out.meta.duration = durationString;

    return out;
}

function handleTemporalDataRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        id = req.params.id,
        ext = req.params.ext || 'json',
        query = req.query;

    if (! river) {
        return handle404(req, res);
    }

    // Can't have all three of these query params at once.
    if (query.since !== undefined
        && query.until !== undefined
        && query.limit !== undefined) {
        return handleError(
            400,
            'Cannot specify "since" and "until" and "limit" simultaneously.',
            req, res
        );
    }
    // Can't have a limit larger than a certain value.
    if (query.limit && query.limit > CONFIG.maxDataPointsPerRequest) {
        return handleError(
            400,
            'Cannot handle data limit larger than ' + CONFIG.maxDataPointsPerRequest,
            req, res
        );
    }

    // For HTML requests, we'll enforce a maximum data point limit for display.
    if (ext == 'html' && ! query.limit) {
        query.limit = CONFIG.defaults.html.maxPoints;
    }

    river.getTemporalData(id, query, function(error, data) {
        var dataOut = [],
            payload,
            columns = ['datetime'].concat(river.config.fields),
            templateName = river.config.type + '-data',
            aggregated,
            threeMonthsAgo = moment().subtract(3, 'months').unix(),
            firstPoint, lastPoint;

        if (error) return renderError(503, error, ext, res, query.callback);

        _.each(data, function(point) {
            var timeString = moment(new Date(parseInt(point.timestamp) * 1000))
                .tz(river.config.timezone).format(DATE_FORMAT);
            dataOut.push([timeString].concat(point.data));
            if (! firstPoint) {
                firstPoint = {
                    timestamp: point.timestamp, timestring: timeString
                };
            }
            lastPoint = {
                timestamp: point.timestamp, timestring: timeString
            };
        });

        // If user filtered by field, only include field in columns.
        if (query.field) {
            columns = ['datetime', query.field];
        }

        // Include in the response some meta data that captures the actual since
        // and until values of the query (meaning the first and last data point)
        payload = {
            name: riverName,
            type: river.config.type,
            timezone: river.config.timezone,
            id: id,
            headers: columns,
            data: dataOut,
            meta: {
                since: firstPoint,
                until: lastPoint,
                count: dataOut.length
            }
        };

        payload = augmentWithNavigationDetails(payload);

        if (ext == 'html') {
            river.getFirstAndLastUpdatedTimes(id, function(err, first, last) {
                if (err) return renderError(503, err, ext, res, query.callback);
                river.getDataCount(id, function(err, dataCount) {
                    var htmlRenderOpts;
                    if (err) return renderError(503, err, ext, res, query.callback);
                    htmlRenderOpts = {
                        name: riverName,
                        type: river.config.type,
                        id: id,
                        headers: columns,
                        dataLimit: query.limit,
                        data: dataOut,
                        timezone: last.format('zz'),
                        threeMonthsAgo: threeMonthsAgo,
                        tmpl: {
                            dataCount: dataCount,
                            splitCharts: river.config.fields.length > 2,
                            title: riverName + ' data',
                            firstData: first.format('YYYY/MM/DD HH:mm:ss'),
                            lastData: last.format('YYYY/MM/DD HH:mm:ss'),
                            lastDataLabel: last.fromNow(),
                            dataJson: JSON.stringify(dataOut),
                            poweredBy: river.config.poweredBy
                        }
                    };
                    if (query.aggregate) {
                        aggregated = aggregateTemporalData(query.aggregate, columns, dataOut);
                        htmlRenderOpts.data = aggregated.data;
                        htmlRenderOpts.headers = aggregated.headers;
                        htmlRenderOpts.aggregated_by = query.aggregate;
                    }
                    renderPage(templateName, htmlRenderOpts, ext, res, query.callback);
                });
            });
        } else {
            if (query.aggregate) {
                aggregated = aggregateTemporalData(query.aggregate, columns, dataOut);
                payload.data = aggregated.data;
                payload.headers = aggregated.headers;
                payload.aggregated_by = query.aggregate;
            }
            renderPage(templateName, payload, ext, res, query.callback);
        }
    });
}

function handleStreamMetaDataRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        id = req.params.id,
        ext = req.params.ext || 'json';
    if (! river) {
        return handle404(req, res);
    }
    river.getMetaData(id, function(error, meta) {
        renderPage('meta', {
            name: riverName,
            id: id,
            metadata: meta,
            tmpl: {
                title: riverName + ' metadata'
            }
        }, ext, res, req.query.callback);
    });
}

function handle404(req, res) {
    var message = '404: Not found: ' + req.url;
    handleError(404, message, req, res);
}

function handleError(code, message, req, res) {
    res.status(code);
    // respond with html page
    if (req.accepts('html')) {
        renderError(code, new Error(message), 'html', res);
    } else if (req.accepts('json')) {
        renderError(code, new Error(message), 'json', res, req.query.callback);
    } else {
        // default to plain-text. send()
        res.type('txt').send(message);
    }

}

/**
 * Starts up the data service that handles all HTTP traffic to River View. This
 * includes the River View web UI as well as all JSON/CSV/etc data requests.
 * @param opts {object}
 * @param opts.app {object} Express js application
 * @param opts.rivers {array} List of Rivers
 * @param opts.config {object} Application configuration
 * @param opts.redisClient {RedisClient} Redis client.
 */
function startDataService(opts) {
    app = opts.app;
    redisClient = opts.redisClient;
    rivers = opts.rivers;
    CONFIG = opts.config;

    tmpl.compile(path.join(__dirname, '..', 'site', 'templates'), CONFIG.baseurl);

    app.use('/index\.:ext?', handleIndex);
    app.use('/activity/?', handleLogRequest);
    app.use('/:river/meta\.:ext?', handleRiverMetaDataRequest);
    app.use('/:river/keys\.:ext?', handleRiverStreamsRequest);
    app.use('/:river/:id/data\.:ext?', handleTemporalDataRequest);
    app.use('/:river/:id/meta\.:ext?', handleStreamMetaDataRequest);
    app.use(/^\/$/, function(req, res) {
        res.redirect('/index.html');
    });
    app.use(handle404);

    app.listen(CONFIG.port, function(err) {
        if (err) throw err;
        console.log(
            '  ************************************************************\n' +
            '  * River View Data Server started on %s', CONFIG.baseurl + '\n' +
            '  ************************************************************\n'
        );
    });
}

module.exports = startDataService;
<commit_msg>Fixes #142<commit_after>/**
 * Data Server
 * @module lib/data-server
 */

var path = require('path'),
    crypto = require('crypto'),
    _ = require('lodash'),
    async = require('async'),
    moment = require('moment-timezone'),
    jsonUtils = require('./json'),
    tmpl = require('./templates'),
    redisClient, app, rivers, DATE_FORMAT = 'YYYY/MM/DD HH:mm:ss',
    CONFIG;

function getRiver(name) {
    return _.find(rivers, function(river) {
        return river.name == name;
    });
}

function renderDataToCsv(json, res) {
    var csvLines = '';
    csvLines += json.headers.join(',') + '\n';
    _.each(json.data, function(point) {
        csvLines += point.join(',') + '\n';
    });
    res.setHeader('Content-Type', 'text');
    res.end(csvLines);
}

function renderPage(name, data, format, res, callback) {
    if (format == 'json') {
        delete data.tmpl;
        jsonUtils.render(data, res, callback);
    } else if (format == 'html') {
        tmpl.renderHtml(name, data, res);
    } else if (format == 'csv') {
        renderDataToCsv(data, res);
    } else {
        renderError(
            400,
            new Error('Format ' + format + ' is unsupported!'), 'html', res
        );
    }
}

function renderError(code, error, format, res, callback) {
    if (format == 'json') {
        jsonUtils.renderErrors(code, [error], res, callback);
    } else if (format == 'html') {
        tmpl.renderHtml('error', {
            message: error.message,
            title: 'Error!'
        }, res);
    }
}

function handleIndex(req, res) {
    var ext = req.params.ext || 'json',
        data;
    data = _.map(rivers, function(river) {
        var riverData = _.extend({
            urls: {
                keys: CONFIG.baseurl + '/' + river.name + '/keys.json',
                meta: CONFIG.baseurl + '/' + river.name + '/meta.json'
            }
        }, river.config);
        return riverData;
    });
    redisClient.getTemporalStreamCount(function(err, count) {
        renderPage('index', {
            rivers: data,
            tmpl: {
                title: 'River View',
                totalStreams: count
            }
        }, ext, res, req.query.callback);
    });
}

function handleLogRequest(req, res) {
    redisClient.getLogs(function(error, logs) {
        if (error) return renderError(503, error, 'html', res, req.query.callback);
        renderPage('activity', {
            logs: logs,
            title: 'River Stream Activity Log'
        }, 'html', res, req.query.callback);
    });
}

function handleRiverMetaDataRequest(req, res) {
    var riverName = req.params.river,
        ext = req.params.ext || 'json',
        river = getRiver(riverName),
        data;
    if (! river) {
        return handle404(req, res);
    }
    data = _.extend({}, getRiver(riverName).config);
    // The "source" might be a URL, or there might be a "soda" config with an
    // object defining the SODA request.
    if (! data.sources) {
        data.sources = [];
    }
    if (data.soda) {
        data.sources = data.sources.concat(_.map(data.soda, function(sodaSpec) {
            return JSON.stringify(sodaSpec);
        }));
    }
    data = _.extend({
        urls: {
            keys: CONFIG.baseurl + '/' + riverName + '/keys.json',
            meta: CONFIG.baseurl + '/' + riverName + '/meta.json'
        }
    }, data);

    data.tmpl = {};
    // Add email hash for gravatar
    data.tmpl.emailHash = crypto.createHash('md5').update(data.email).digest("hex");
    data.tmpl.title = riverName + ' metadata';
    data.tmpl.poweredBy = river.config.poweredBy;

    data.tmpl.intervalName = getIntervalName(data);
    data.tmpl.intervalString = getIntervalString(data);

    renderPage('riverMeta', data, ext, res, req.query.callback);
}

function getIntervalName(riverConfig) {
    return ((riverConfig.hasOwnProperty('cronInterval')) ? "Cron Interval:" : "Collection Interval");
}

function getIntervalString(riverConfig) {
    return ((riverConfig.hasOwnProperty('cronInterval'))
             ? ((typeof riverConfig.cronInterval === 'string') ? "" + riverConfig.cronInterval + ""
                                                       : "

" + riverConfig.cronInterval.join("

") + "

")
             : riverConfig.interval);
}

function handleRiverStreamsRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        includeDetails = req.query.includeDetails,
        ext = req.params.ext || 'json';

    if (! river) {
        return handle404(req, res);
    }
    river.getKeys(function(error, keys) {
        var propertyFetchers = {},
            urls = {
                keys: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/keys.json',
                meta: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/meta.json',
                streams: {}
            };

        if (error) return renderError(503, error, ext, res, req.query.callback);

        _.each(keys, function(key) {
            urls.streams[key] = {
                data: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/' + encodeURIComponent(key) + '/data.json',
                meta: CONFIG.baseurl + '/' + encodeURIComponent(riverName) + '/' + encodeURIComponent(key) + '/meta.json'
            };
        });
        if (ext == 'json' && includeDetails) {
            _.each(keys, function(key) {
                propertyFetchers[key] = function(callback) {
                    river.getMetaData(key, function(err, meta) {
                        if (err) return callback(err);
                        callback(null, meta);
                    });
                };
            });
            async.parallel(propertyFetchers, function(err, meta) {
                if (err) return renderError(503, err, ext, res, req.query.callback);
                renderPage('keys', {
                    name: riverName,
                    keys: meta,
                    urls: urls,
                    tmpl: {
                        title: riverName + ' keys',
                        poweredBy: river.config.poweredBy
                    }
                }, ext, res, req.query.callback);
            });
        } else {
            renderPage('keys', {
                name: riverName,
                keys: keys,
                urls: urls,
                tmpl: {
                    title: riverName + ' keys',
                    poweredBy: river.config.poweredBy
                }
            }, ext, res, req.query.callback);
        }
    });
}

function aggregateTemporalData(periodString, columns, data) {
    var headers = ['datetime', 'count'];
    var dataOut = [];
    var dateIndex = columns.indexOf('datetime');
    var aggregationDuration = moment.duration(
        parseInt(periodString.split(/\s+/).shift()),
        periodString.split(/\s+/).pop()
    );
    var goUntil = moment(data[0][dateIndex], DATE_FORMAT).add(aggregationDuration);
    var count = 0;

    _.each(data, function(point) {
        var time = moment(point[dateIndex], DATE_FORMAT);
        if (time < goUntil) {
            count++;
        } else {
            dataOut.push([goUntil.format(DATE_FORMAT), count]);
            goUntil = goUntil.add(aggregationDuration);
            count = 1;
        }
    });

    return {
        headers: headers,
        data: dataOut
    };
}

function augmentWithNavigationDetails(payload) {
    var out = _.extend({}, payload),
        baseurl = CONFIG.baseurl + '/' + payload.name + '/' + payload.id + '/data.json',
        since = payload.meta.since.timestamp,
        until = payload.meta.until.timestamp,
        payloadDuration = until - since,
        leftBound = since,
        rightBound = until,
        nextBound = rightBound + payloadDuration,
        prevBound = leftBound - payloadDuration,
        durationString = moment.duration(payloadDuration, 'seconds').humanize();

    if (since !== until) {
        out.urls = {
            prev: encodeURI(baseurl + '?since=' + prevBound + '&until=' + leftBound) + '&snapto=until',
            next: encodeURI(baseurl + '?since=' + rightBound + '&until=' + nextBound) + '&snapto=since'
        };
    }

    out.meta.duration = durationString;

    return out;
}

function handleTemporalDataRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        id = req.params.id,
        ext = req.params.ext || 'json',
        query = req.query;

    if (! river) {
        return handle404(req, res);
    }

    // Can't have all three of these query params at once.
    if (query.since !== undefined
        && query.until !== undefined
        && query.limit !== undefined) {
        return handleError(
            400,
            'Cannot specify "since" and "until" and "limit" simultaneously.',
            req, res
        );
    }
    // Can't have a limit larger than a certain value.
    if (query.limit && query.limit > CONFIG.maxDataPointsPerRequest) {
        return handleError(
            400,
            'Cannot handle data limit larger than ' + CONFIG.maxDataPointsPerRequest,
            req, res
        );
    }

    // For HTML requests, we'll enforce a maximum data point limit for display.
    if (ext == 'html' && ! query.limit) {
        query.limit = CONFIG.defaults.html.maxPoints;
    }

    river.getTemporalData(id, query, function(error, data) {
        var dataOut = [],
            payload,
            columns = ['datetime'].concat(river.config.fields),
            templateName = river.config.type + '-data',
            aggregated,
            threeMonthsAgo = moment().subtract(3, 'months').unix(),
            firstPoint, lastPoint;

        if (error) return renderError(503, error, ext, res, query.callback);

        _.each(data, function(point) {
            var timeString = moment(new Date(parseInt(point.timestamp) * 1000))
                .tz(river.config.timezone).format(DATE_FORMAT);
            dataOut.push([timeString].concat(point.data));
            if (! firstPoint) {
                firstPoint = {
                    timestamp: point.timestamp, timestring: timeString
                };
            }
            lastPoint = {
                timestamp: point.timestamp, timestring: timeString
            };
        });

        // If user filtered by field, only include field in columns.
        if (query.field) {
            columns = ['datetime', query.field];
        }

        // Include in the response some meta data that captures the actual since
        // and until values of the query (meaning the first and last data point)
        payload = {
            name: riverName,
            type: river.config.type,
            timezone: river.config.timezone,
            id: id,
            headers: columns,
            data: dataOut,
            meta: {
                since: firstPoint,
                until: lastPoint,
                count: dataOut.length
            }
        };

        if (firstPoint && lastPoint) {
            payload = augmentWithNavigationDetails(payload);
        }

        if (ext == 'html') {
            river.getFirstAndLastUpdatedTimes(id, function(err, first, last) {
                if (err) return renderError(503, err, ext, res, query.callback);
                river.getDataCount(id, function(err, dataCount) {
                    var htmlRenderOpts;
                    if (err) return renderError(503, err, ext, res, query.callback);
                    htmlRenderOpts = {
                        name: riverName,
                        type: river.config.type,
                        id: id,
                        headers: columns,
                        dataLimit: query.limit,
                        data: dataOut,
                        timezone: last.format('zz'),
                        threeMonthsAgo: threeMonthsAgo,
                        tmpl: {
                            dataCount: dataCount,
                            splitCharts: river.config.fields.length > 2,
                            title: riverName + ' data',
                            firstData: first.format('YYYY/MM/DD HH:mm:ss'),
                            lastData: last.format('YYYY/MM/DD HH:mm:ss'),
                            lastDataLabel: last.fromNow(),
                            dataJson: JSON.stringify(dataOut),
                            poweredBy: river.config.poweredBy
                        }
                    };
                    if (query.aggregate) {
                        aggregated = aggregateTemporalData(query.aggregate, columns, dataOut);
                        htmlRenderOpts.data = aggregated.data;
                        htmlRenderOpts.headers = aggregated.headers;
                        htmlRenderOpts.aggregated_by = query.aggregate;
                    }
                    renderPage(templateName, htmlRenderOpts, ext, res, query.callback);
                });
            });
        } else {
            if (query.aggregate) {
                aggregated = aggregateTemporalData(query.aggregate, columns, dataOut);
                payload.data = aggregated.data;
                payload.headers = aggregated.headers;
                payload.aggregated_by = query.aggregate;
            }
            renderPage(templateName, payload, ext, res, query.callback);
        }
    });
}

function handleStreamMetaDataRequest(req, res) {
    var riverName = req.params.river,
        river = getRiver(riverName),
        id = req.params.id,
        ext = req.params.ext || 'json';
    if (! river) {
        return handle404(req, res);
    }
    river.getMetaData(id, function(error, meta) {
        renderPage('meta', {
            name: riverName,
            id: id,
            metadata: meta,
            tmpl: {
                title: riverName + ' metadata'
            }
        }, ext, res, req.query.callback);
    });
}

function handle404(req, res) {
    var message = '404: Not found: ' + req.url;
    handleError(404, message, req, res);
}

function handleError(code, message, req, res) {
    res.status(code);
    // respond with html page
    if (req.accepts('html')) {
        renderError(code, new Error(message), 'html', res);
    } else if (req.accepts('json')) {
        renderError(code, new Error(message), 'json', res, req.query.callback);
    } else {
        // default to plain-text. send()
        res.type('txt').send(message);
    }

}

/**
 * Starts up the data service that handles all HTTP traffic to River View. This
 * includes the River View web UI as well as all JSON/CSV/etc data requests.
 * @param opts {object}
 * @param opts.app {object} Express js application
 * @param opts.rivers {array} List of Rivers
 * @param opts.config {object} Application configuration
 * @param opts.redisClient {RedisClient} Redis client.
 */
function startDataService(opts) {
    app = opts.app;
    redisClient = opts.redisClient;
    rivers = opts.rivers;
    CONFIG = opts.config;

    tmpl.compile(path.join(__dirname, '..', 'site', 'templates'), CONFIG.baseurl);

    app.use('/index\.:ext?', handleIndex);
    app.use('/activity/?', handleLogRequest);
    app.use('/:river/meta\.:ext?', handleRiverMetaDataRequest);
    app.use('/:river/keys\.:ext?', handleRiverStreamsRequest);
    app.use('/:river/:id/data\.:ext?', handleTemporalDataRequest);
    app.use('/:river/:id/meta\.:ext?', handleStreamMetaDataRequest);
    app.use(/^\/$/, function(req, res) {
        res.redirect('/index.html');
    });
    app.use(handle404);

    app.listen(CONFIG.port, function(err) {
        if (err) throw err;
        console.log(
            '  ************************************************************\n' +
            '  * River View Data Server started on %s', CONFIG.baseurl + '\n' +
            '  ************************************************************\n'
        );
    });
}

module.exports = startDataService;
<|endoftext|>


<commit_before>dojo.provide("EnumSlider.widget.EnumSlider");
dojo.require("dijit.form.Slider");

mendix.widget.declare('EnumSlider.widget.EnumSlider', {
	addons       : [mendix.addon._Contextable],
    inputargs: {
		name : '',
		sliderWidth  : 300,
		sliderHeight  : 50,
		direction : 'horizontal',
		onchangemf : '',
		includeEmpty : false,
		emptyCaption : '',
		enumExceptions : ''
    },

	//IMPLEMENTATION
	isInactive : false,
	hasChanged : false,
	context : null,
	slideEnum : null,
	currentNr : 0,
	slider : null,
	exceptions: null,

	getSlideEnum : function(context) {
		this.context = context;
		var trackClass = context.getTrackClass ? context.getTrackClass() : context.trackClass; //MWE: getTrackClass() does not exist anymore in 3.0
		if (trackClass == '')
			return;

		var meta = mx.metadata.getMetaEntity({ className: trackClass });
		this.slideEnum = [];
		if (this.enumExceptions != '')
			this.exceptions = this.enumExceptions.split(",");

		if(meta && meta.getAttributeClass(this.name) == 'Enum') {
			this.slideEnum = meta.getEnumMap(this.name);
			var spliceList = [];
			if (this.exceptions)
				for (var j = 0; j < this.exceptions.length; j++) {
					var exception = this.exceptions[j].replace(/^ /gi, '');
					for (var i = 0; i < this.slideEnum.length; i++) {
						if (this.slideEnum[i].key == exception)
							spliceList.push(i);
					}
				}
			for (var i = 0; i < spliceList.length; i++) {
				this.slideEnum.splice(spliceList[i]-i, 1);
			}

			if (this.includeEmpty == true) {
				var emptyValue = {
					'key': '',
					'caption': this.emptyCaption
				}
				this.slideEnum.unshift(emptyValue);
			}
		}
	},

	renderSlide : function (mxobject) {
		var enumArray = [];
		var longestStr = '';
		if (this.slideEnum && this.slideEnum.length > 0) {
			dojo.empty(this.domNode);
			var currentValue = mxobject.getAttribute(this.name);
			for (var i = 0; i < this.slideEnum.length; i++) {
				var strSize = mendix.dom.getStringSize(this.slideEnum[i].caption);
				if (this.slideEnum[i].key == currentValue)
					this.currentNr = i;

				if (strSize > longestStr)
					longestStr = strSize;
				enumArray.push(this.slideEnum[i].caption);
			}
			var enumcount = this.slideEnum.length;
			if (this.direction == "horizontal") {
				var sliderRuleLabels = new dijit.form.HorizontalRuleLabels({
					container: 'bottomDecoration',
					labels: enumArray,
					style: 'cursor: pointer',
					onMouseUp : dojo.hitch(this, function (e) {
						var value = e.target.innerHTML;
						if (value.indexOf("<") > -1)
							value = dojo.trim(value.substring(0, value.indexOf("<") || value.length));

						if (value != "") {
							for (var i = 0; i < this.slideEnum.length; i++) {
								if (this.slideEnum[i].caption == value) {
									this.currentNr = i;
									this.slider.attr("value", i);
									return;
								}
							}
						}
					})

				}, mendix.dom.div());

				var sliderRule = new dijit.form.HorizontalRule({
					count: enumcount,
					container: 'bottomDecoration',
					style: 'width: 100%; height: 5px;'
				}, mendix.dom.div());

				this.slider = new dijit.form.HorizontalSlider({
					name: "slider_widget",
					value: this.currentNr,
					minimum: 0,
					maximum: enumcount-1,
					intermediateChanges: true,
					discreteValues: enumcount,
					style: "width:"+this.sliderWidth+"px; height: 30px;",
					onMouseUp: dojo.hitch(this, this.execclick),
					onBlur: dojo.hitch(this, this.execclick),
					onChange: dojo.hitch(this, function(value) {
						this.hasChanged = true;
						this.onChange();
					})
				});

				this.slider.addChild(sliderRule);
				this.slider.addChild(sliderRuleLabels);
				sliderRuleLabels.startup();
				sliderRule.startup();
				this.slider.startup();
				dojo.style(this.slider.progressBar, "background", "none");
				this.domNode.appendChild(this.slider.domNode);
				dojo.style(this.slider.containerNode, 'textAlign', 'left');
			} else {
				// Vertical slider is rendered bottom up, so reversing the enum so it still starts at the top.
				enumArray = enumArray.reverse();
				this.slideEnum = this.slideEnum.reverse();
				var flipped = enumArray.length - (this.currentNr+1);

				var sliderRuleLabels = new dijit.form.VerticalRuleLabels({
					container: 'rightDecoration',
					style : 'cursor: pointer',
					labels: enumArray,
					onMouseUp : dojo.hitch(this, function (e) {
						var value = e.target.innerHTML;
						if (value.indexOf("<") > -1)
							value = dojo.trim(value.substring(0, value.indexOf("<") || value.length));

						if (value != "") {
							for (var i = 0; i < this.slideEnum.length; i++) {
								if (this.slideEnum[i].caption == value) {
									this.currentNr = i;
									this.slider.attr("value", i);
									return;
								}
							}
						}
					})
				}, mendix.dom.div());

				var sliderRule = new dijit.form.VerticalRule({
					count: enumcount,
					container: 'rightDecoration',
					style: 'width: 5px;'
				}, mendix.dom.div());

				this.slider = new dijit.form.VerticalSlider({
					name: "slider_widget",
					value: flipped,
					minimum: 0,
					maximum: enumcount-1,
					intermediateChanges: true,
					discreteValues: enumcount,
					style: "height: "+this.sliderHeight+"px;",
					onMouseUp: dojo.hitch(this, this.execclick),
					onBlur: dojo.hitch(this, this.execclick),
					onChange: dojo.hitch(this, function(value) {
						this.hasChanged = true;
						this.onChange();
					})
				});

				this.slider.addChild(sliderRule);
				this.slider.addChild(sliderRuleLabels);
				sliderRuleLabels.startup();
				sliderRule.startup();
				this.slider.startup();
				dojo.style(this.slider.progressBar, "background", "none");
				dojo.style(this.domNode, 'height', (this.sliderHeight+50)+"px"); // IE fix.
				this.domNode.appendChild(this.slider.domNode);
			}
		}
		this.slider.attr('disabled', this.isInactive);
	},

	execclick : function() {
		if (this.hasChanged == true){
			this.hasChanged = false;
			if (this.onchangemf != '' && this.context && this.context.getTrackId()) {
				var context = mx.ui.newContext();
				context.setContext(this.context.getTrackEntity(), this.context.getTrackId());
				mx.xas.action({
					actionname	: this.onchangemf,
					context		: context,
					callback	: function() {
						// ok
					},
					error		: function() {
						// error
					}
				});
				mx.ui.destroyContext(context);
			}
		}
	},

	postCreate : function(){
		//housekeeping
		dojo.empty(this.domNode);
		dojo.style(this.domNode, {
			"height" : this.sliderHeight+"px",
			"width" : this.sliderWidth+"px"
		});

		this.exceptions = [];

		this.initContext();
		this.actRendered();
	},

	_setDisabledAttr : function(value) {
		this.isInactive = !!value;
		if (this.slider)
			this.slider.attr('disabled', !!value);
	},

	_getValueAttr : function () {
		if (this.slideEnum && this.slider)
			return this.slideEnum[this.slider.attr('value')].key;
		else
			return '';
	},

	onChange : function () {
	},

	_setValueAttr : function(value) {
		if (this.slideEnum && this.slideEnum.length > 0 && this.slider.attr("value") != value)
			for (var i = 0; i < this.slideEnum.length; i++)
				if (this.slideEnum[i].key == value) {
					this.currentNr = i;
					if (this.slider)
						this.slider.attr("value", i);
				}
	},

	applyContext : function(context, callback){
		this.getSlideEnum(context);
		if (context && context.getActiveGUID())
			mx.processor.getObject(context.getActiveGUID(), dojo.hitch(this, this.renderSlide));
		else
			logger.warn(this.id + ".applyContext received empty context");
		callback && callback();
	},

	uninitialize : function(){
		this.slider && this.slider.destroyRecursive();
	}
});<commit_msg>Add sort of spliceList in order of slideEnum indexes<commit_after>dojo.provide("EnumSlider.widget.EnumSlider");
dojo.require("dijit.form.Slider");

mendix.widget.declare('EnumSlider.widget.EnumSlider', {
	addons       : [mendix.addon._Contextable],
    inputargs: {
		name : '',
		sliderWidth  : 300,
		sliderHeight  : 50,
		direction : 'horizontal',
		onchangemf : '',
		includeEmpty : false,
		emptyCaption : '',
		enumExceptions : ''
    },

	//IMPLEMENTATION
	isInactive : false,
	hasChanged : false,
	context : null,
	slideEnum : null,
	currentNr : 0,
	slider : null,
	exceptions: null,

	getSlideEnum : function(context) {
		this.context = context;
		var trackClass = context.getTrackClass ? context.getTrackClass() : context.trackClass; //MWE: getTrackClass() does not exist anymore in 3.0
		if (trackClass == '')
			return;

		var meta = mx.metadata.getMetaEntity({ className: trackClass });
		this.slideEnum = [];
		if (this.enumExceptions != '')
			this.exceptions = this.enumExceptions.split(",");

		if(meta && meta.getAttributeClass(this.name) == 'Enum') {
			this.slideEnum = meta.getEnumMap(this.name);
			var spliceList = [];
			if (this.exceptions) {
				for (var j = 0; j < this.exceptions.length; j++) {
					var exception = this.exceptions[j].replace(/^ /gi, '');
					for (var i = 0; i < this.slideEnum.length; i++) {
						if (this.slideEnum[i].key == exception)
							spliceList.push(i);
					}
				}
				// Sort slideEnum in order of slideEnum indexes
				spliceList.sort(fucntion(a,b){return a-b});
			}
			for (var i = 0; i < spliceList.length; i++) {
				this.slideEnum.splice(spliceList[i]-i, 1);
			}

			if (this.includeEmpty == true) {
				var emptyValue = {
					'key': '',
					'caption': this.emptyCaption
				}
				this.slideEnum.unshift(emptyValue);
			}
		}
	},

	renderSlide : function (mxobject) {
		var enumArray = [];
		var longestStr = '';
		if (this.slideEnum && this.slideEnum.length > 0) {
			dojo.empty(this.domNode);
			var currentValue = mxobject.getAttribute(this.name);
			for (var i = 0; i < this.slideEnum.length; i++) {
				var strSize = mendix.dom.getStringSize(this.slideEnum[i].caption);
				if (this.slideEnum[i].key == currentValue)
					this.currentNr = i;

				if (strSize > longestStr)
					longestStr = strSize;
				enumArray.push(this.slideEnum[i].caption);
			}
			var enumcount = this.slideEnum.length;
			if (this.direction == "horizontal") {
				var sliderRuleLabels = new dijit.form.HorizontalRuleLabels({
					container: 'bottomDecoration',
					labels: enumArray,
					style: 'cursor: pointer',
					onMouseUp : dojo.hitch(this, function (e) {
						var value = e.target.innerHTML;
						if (value.indexOf("<") > -1)
							value = dojo.trim(value.substring(0, value.indexOf("<") || value.length));

						if (value != "") {
							for (var i = 0; i < this.slideEnum.length; i++) {
								if (this.slideEnum[i].caption == value) {
									this.currentNr = i;
									this.slider.attr("value", i);
									return;
								}
							}
						}
					})

				}, mendix.dom.div());

				var sliderRule = new dijit.form.HorizontalRule({
					count: enumcount,
					container: 'bottomDecoration',
					style: 'width: 100%; height: 5px;'
				}, mendix.dom.div());

				this.slider = new dijit.form.HorizontalSlider({
					name: "slider_widget",
					value: this.currentNr,
					minimum: 0,
					maximum: enumcount-1,
					intermediateChanges: true,
					discreteValues: enumcount,
					style: "width:"+this.sliderWidth+"px; height: 30px;",
					onMouseUp: dojo.hitch(this, this.execclick),
					onBlur: dojo.hitch(this, this.execclick),
					onChange: dojo.hitch(this, function(value) {
						this.hasChanged = true;
						this.onChange();
					})
				});

				this.slider.addChild(sliderRule);
				this.slider.addChild(sliderRuleLabels);
				sliderRuleLabels.startup();
				sliderRule.startup();
				this.slider.startup();
				dojo.style(this.slider.progressBar, "background", "none");
				this.domNode.appendChild(this.slider.domNode);
				dojo.style(this.slider.containerNode, 'textAlign', 'left');
			} else {
				// Vertical slider is rendered bottom up, so reversing the enum so it still starts at the top.
				enumArray = enumArray.reverse();
				this.slideEnum = this.slideEnum.reverse();
				var flipped = enumArray.length - (this.currentNr+1);

				var sliderRuleLabels = new dijit.form.VerticalRuleLabels({
					container: 'rightDecoration',
					style : 'cursor: pointer',
					labels: enumArray,
					onMouseUp : dojo.hitch(this, function (e) {
						var value = e.target.innerHTML;
						if (value.indexOf("<") > -1)
							value = dojo.trim(value.substring(0, value.indexOf("<") || value.length));

						if (value != "") {
							for (var i = 0; i < this.slideEnum.length; i++) {
								if (this.slideEnum[i].caption == value) {
									this.currentNr = i;
									this.slider.attr("value", i);
									return;
								}
							}
						}
					})
				}, mendix.dom.div());

				var sliderRule = new dijit.form.VerticalRule({
					count: enumcount,
					container: 'rightDecoration',
					style: 'width: 5px;'
				}, mendix.dom.div());

				this.slider = new dijit.form.VerticalSlider({
					name: "slider_widget",
					value: flipped,
					minimum: 0,
					maximum: enumcount-1,
					intermediateChanges: true,
					discreteValues: enumcount,
					style: "height: "+this.sliderHeight+"px;",
					onMouseUp: dojo.hitch(this, this.execclick),
					onBlur: dojo.hitch(this, this.execclick),
					onChange: dojo.hitch(this, function(value) {
						this.hasChanged = true;
						this.onChange();
					})
				});

				this.slider.addChild(sliderRule);
				this.slider.addChild(sliderRuleLabels);
				sliderRuleLabels.startup();
				sliderRule.startup();
				this.slider.startup();
				dojo.style(this.slider.progressBar, "background", "none");
				dojo.style(this.domNode, 'height', (this.sliderHeight+50)+"px"); // IE fix.
				this.domNode.appendChild(this.slider.domNode);
			}
		}
		this.slider.attr('disabled', this.isInactive);
	},

	execclick : function() {
		if (this.hasChanged == true){
			this.hasChanged = false;
			if (this.onchangemf != '' && this.context && this.context.getTrackId()) {
				var context = mx.ui.newContext();
				context.setContext(this.context.getTrackEntity(), this.context.getTrackId());
				mx.xas.action({
					actionname	: this.onchangemf,
					context		: context,
					callback	: function() {
						// ok
					},
					error		: function() {
						// error
					}
				});
				mx.ui.destroyContext(context);
			}
		}
	},

	postCreate : function(){
		//housekeeping
		dojo.empty(this.domNode);
		dojo.style(this.domNode, {
			"height" : this.sliderHeight+"px",
			"width" : this.sliderWidth+"px"
		});

		this.exceptions = [];

		this.initContext();
		this.actRendered();
	},

	_setDisabledAttr : function(value) {
		this.isInactive = !!value;
		if (this.slider)
			this.slider.attr('disabled', !!value);
	},

	_getValueAttr : function () {
		if (this.slideEnum && this.slider)
			return this.slideEnum[this.slider.attr('value')].key;
		else
			return '';
	},

	onChange : function () {
	},

	_setValueAttr : function(value) {
		if (this.slideEnum && this.slideEnum.length > 0 && this.slider.attr("value") != value)
			for (var i = 0; i < this.slideEnum.length; i++)
				if (this.slideEnum[i].key == value) {
					this.currentNr = i;
					if (this.slider)
						this.slider.attr("value", i);
				}
	},

	applyContext : function(context, callback){
		this.getSlideEnum(context);
		if (context && context.getActiveGUID())
			mx.processor.getObject(context.getActiveGUID(), dojo.hitch(this, this.renderSlide));
		else
			logger.warn(this.id + ".applyContext received empty context");
		callback && callback();
	},

	uninitialize : function(){
		this.slider && this.slider.destroyRecursive();
	}
});<|endoftext|>


<commit_before>var _ = require('lodash')
var Datauri = require('datauri')
var fs = require('fs')
var Glob = require('glob').sync
var mkdirp = require('mkdirp')
var path = require('path')
var perser = require('html-to-json')
var Svgo = require('svgo')

function Svgpack (src, options) {
  this.config = {
    dirs: [],
    baseJson: '',
    src: src || [],
  }

  this.options = {
    name: 'svgpack',
    prefix: 'icon',
    dist: './svgpack',
    templates: {
      sprite: path.resolve(path.dirname('/../templates/svg/sprite.svg')),
      css: path.resolve(path.dirname('/../templates/css/svgpack.css')),
      html: path.resolve(path.dirname('/../templates/html/default.html')),
    },
    svgoOptions: {},
  }

  options.templates = Object.assign(this.options.templates, options.templates)
  options = Object.assign(this.options, options)

  this.config.dirs = [
    this.options.dist,
    this.options.dist + '/data',
    this.options.dist + '/svg',
  ]

  this.config.baseJson = path.resolve(options.dist + '/' + options.name + '.json')
}

Svgpack.prototype.init = function () {
  this.config.dirs.forEach(function (dir) {
    return mkdirp.sync(dir)
  })
  return optimaize(this.config.src, this.options)
}

function optimaize (src, options) {
  var files = new Glob(src)
  var svgo = new Svgo(options.svgoOptions)
  files.forEach(function (file, i) {
    var filename = path.basename(file)
    var input = path.resolve(path.dirname(file), filename)
    var content = fs.readFileSync(input, 'utf8')
    return svgo.optimize(content, function (result) {
      return fs.writeFileSync(options.dist + '/svg/' + filename, result.data)
    })
  })
  return createSvgToJson(options)
}

function createSvgToJson (options) {
  var files = new Glob(options.dist + '/svg/*.svg')
  var datauri = new Datauri()
  files.forEach(function (file, i) {
    var iconName = path.basename(file, '.svg')
    var filename = path.basename(file)
    var input = path.resolve(path.dirname(file), filename)
    var output = path.resolve(options.dist + '/data', iconName) + '.json'
    var content = fs.readFileSync(input, 'utf8')
    var promise = perser.parse(content, {
      'name': function ($root) {
        return iconName
      },
      'viewBox': function ($root) {
        var svg = $root.find('svg')
        return svg[0].attribs.viewbox
      },
      'path': ['path', {
        'd': function ($path) {
          return $path.attr('d')
        },
        'stroke': function ($path) {
          return $path.attr('stroke')
        },
        'fill': function ($path) {
          return $path.attr('fill')
        },
      }],
      'base64': function ($root) {
        return datauri.format(filename, content).content
      },
    })
    return promise.done(function (result) {
      fs.writeFileSync(output, JSON.stringify(result, null, '  '))
      if (i >= files.length - 1) {
        var mergeInput = new Glob(path.resolve(options.dist + '/data/*.json'))
        return dataMerge(mergeInput, options)
      }
    })
  })
}

function dataMerge (files, options) {
  var contents = []
  files.forEach(function (file, i) {
    var dir = path.dirname(file)
    var filename = path.basename(file)
    var content = fs.readFileSync(dir + '/' + filename, 'utf8')
    contents.push(content)
  })
  var props = '{ "icons": [' + contents + '] }'
  var result = path.resolve(options.dist + '/' + options.name + '.json')
  return (
    fs.writeFileSync(result, props),
    createSvgSprite(require(result), options)
  )
}

function createSvgSprite (data, options) {
  var input = fs.readFileSync(options.templates.sprite, 'utf8')
  var output = path.resolve(options.dist + '/' + options.name + '-sprite.svg')
  var content = _.template(input)
  data.config = options
  var result = content(data).replace(/\r?\n|\s\s\s\s|\s\s/g, '')
  return (
    fs.writeFileSync(output, result),
    createCss(options)
  )
}

function createCss (options) {
  var input = fs.readFileSync(options.templates.css, 'utf8')
  var outut = options.dist + '/' + options.name + '.css'
  var content = _.template(input)
  var result = content(options)
  return (
    fs.writeFileSync(outut, result),
    createHtml(options)
  )
}

function createHtml (options) {
  var data = require(path.resolve(options.dist + '/' + options.name + '.json'))
  var filePath = options.dist + '/' + options.name
  var cssInject = fs.readFileSync(filePath + '.css', 'utf8')
  var svgInject = fs.readFileSync(filePath + '-sprite.svg', 'utf8')
  var input = fs.readFileSync(options.templates.html, 'utf8')
  var output = options.dist + '/' + 'index.html'
  var content = _.template(input)
  data.options = options
  data.svgInject = svgInject
  data.cssInject = cssInject
  var result = content(data)
  return fs.writeFileSync(output, result)
}

module.exports = Svgpack
<commit_msg>fix options dist and template path<commit_after>var _ = require('lodash')
var Datauri = require('datauri')
var fs = require('fs')
var Glob = require('glob').sync
var mkdirp = require('mkdirp')
var path = require('path')
var perser = require('html-to-json')
var Svgo = require('svgo')

function Svgpack (src, options) {
  this.config = {
    dirs: [],
    baseJson: '',
    src: src || [],
  }

  this.options = {
    name: 'svgpack',
    prefix: 'icon',
    dist: process.cwd() + '/svgpack',
    templates: {
      sprite: path.join(__dirname, '/../templates/svg/sprite.svg'),
      css: path.join(__dirname, '/../templates/css/svgpack.css'),
      html: path.join(__dirname, '/../templates/html/default.html'),
    },
    svgoOptions: {},
  }

  options.templates = Object.assign(this.options.templates, options.templates)
  options = Object.assign(this.options, options)

  this.config.dirs = [
    this.options.dist,
    this.options.dist + '/data',
    this.options.dist + '/svg',
  ]

  this.config.baseJson = path.resolve(options.dist + '/' + options.name + '.json')
}

Svgpack.prototype.init = function () {
  this.config.dirs.forEach(function (dir) {
    return mkdirp.sync(dir)
  })
  return optimaize(this.config.src, this.options)
}

function optimaize (src, options) {
  var files = new Glob(src)
  var svgo = new Svgo(options.svgoOptions)
  files.forEach(function (file, i) {
    var filename = path.basename(file)
    var input = path.resolve(path.dirname(file), filename)
    var content = fs.readFileSync(input, 'utf8')
    return svgo.optimize(content, function (result) {
      return fs.writeFileSync(options.dist + '/svg/' + filename, result.data)
    })
  })
  return createSvgToJson(options)
}

function createSvgToJson (options) {
  var files = new Glob(options.dist + '/svg/*.svg')
  var datauri = new Datauri()
  files.forEach(function (file, i) {
    var iconName = path.basename(file, '.svg')
    var filename = path.basename(file)
    var input = path.resolve(path.dirname(file), filename)
    var output = path.resolve(options.dist + '/data', iconName) + '.json'
    var content = fs.readFileSync(input, 'utf8')
    var promise = perser.parse(content, {
      'name': function ($root) {
        return iconName
      },
      'viewBox': function ($root) {
        var svg = $root.find('svg')
        return svg[0].attribs.viewbox
      },
      'path': ['path', {
        'd': function ($path) {
          return $path.attr('d')
        },
        'stroke': function ($path) {
          return $path.attr('stroke')
        },
        'fill': function ($path) {
          return $path.attr('fill')
        },
      }],
      'base64': function ($root) {
        return datauri.format(filename, content).content
      },
    })
    return promise.done(function (result) {
      fs.writeFileSync(output, JSON.stringify(result, null, '  '))
      if (i >= files.length - 1) {
        var mergeInput = new Glob(path.resolve(options.dist + '/data/*.json'))
        return dataMerge(mergeInput, options)
      }
    })
  })
}

function dataMerge (files, options) {
  var contents = []
  files.forEach(function (file, i) {
    var dir = path.dirname(file)
    var filename = path.basename(file)
    var content = fs.readFileSync(dir + '/' + filename, 'utf8')
    contents.push(content)
  })
  var props = '{ "icons": [' + contents + '] }'
  var result = path.resolve(options.dist + '/' + options.name + '.json')
  return (
    fs.writeFileSync(result, props),
    createSvgSprite(require(result), options)
  )
}

function createSvgSprite (data, options) {
  var input = fs.readFileSync(options.templates.sprite, 'utf8')
  var output = path.resolve(options.dist + '/' + options.name + '-sprite.svg')
  var content = _.template(input)
  data.config = options
  var result = content(data).replace(/\r?\n|\s\s\s\s|\s\s/g, '')
  return (
    fs.writeFileSync(output, result),
    createCss(options)
  )
}

function createCss (options) {
  var input = fs.readFileSync(options.templates.css, 'utf8')
  var outut = options.dist + '/' + options.name + '.css'
  var content = _.template(input)
  var result = content(options)
  return (
    fs.writeFileSync(outut, result),
    createHtml(options)
  )
}

function createHtml (options) {
  var data = require(path.resolve(options.dist + '/' + options.name + '.json'))
  var filePath = options.dist + '/' + options.name
  var cssInject = fs.readFileSync(filePath + '.css', 'utf8')
  var svgInject = fs.readFileSync(filePath + '-sprite.svg', 'utf8')
  var input = fs.readFileSync(options.templates.html, 'utf8')
  var output = options.dist + '/' + 'index.html'
  var content = _.template(input)
  data.options = options
  data.svgInject = svgInject
  data.cssInject = cssInject
  var result = content(data)
  return fs.writeFileSync(output, result)
}

module.exports = Svgpack
<|endoftext|>


<commit_before>Event.observe(document, 'dom:loaded', function() {
    new Autocomplete('search_field', {
        serviceUrl: '/search',
        minChars: 1,
        maxHeight: 400,
        width: 'auto',
        deferRequestBy: 200,
        onSelect: function(value, data) {window.location.href = data[0];}
    });

    $('search_field').focus();
});
<commit_msg>Pressing enter in the search field will redirect to the first result<commit_after>Event.observe(document, 'dom:loaded', function() {
    new Autocomplete('search_field', {
        serviceUrl: '/search',
        minChars: 1,
        maxHeight: 400,
        width: 'auto',
        deferRequestBy: 200,
        onSelect: function(value, data) {window.location.href = data[0];}
    });

    $('search_field').observe('keypress', function(e) {
        if (e.keyCode == Event.KEY_RETURN) {
            ac = Autocomplete.getInstance('search_field');
            if (ac.data.size() > 0) {
                window.location.href = ac.data[0][0];
            }
        }
    });

    $('search_field').focus();
});
<|endoftext|>


<commit_before>/*
 * Author: Olaf Delgado-Friedrichs (odf@github.com)
 * Copyright (c) 2010 ANU
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * A jQuery plugin that implements pulldown menues for text fields.
 */

/*jslint white: false, browser: true */
/*global jQuery */

(function($) {
  var handlers = {
    field: {
      mousedown: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));
	pulldown.toggle();
      },
      focus: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));

	//TODO do this in a cleaner way:
	$('select.predefined').css({ display: 'none' });

	pulldown.each(function () {
	  $(this).css({ display: 'block', left: field.position().left });
	});
      },
      blur: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));
	setTimeout(function() {
	  if ($("*:focus").attr('id') !== pulldown.attr('id')) {
	    pulldown.css({ display: 'none' });
	  }
	}, 100);
      },
      keyup: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));
	setTimeout(function() {
	  pulldown.css({ display: 'none' });
	}, 100);
      }
    },
    pulldown: {
      blur: function () {
	$(this).css({ display: 'none' });
      },
      click: function () {
        var pulldown = $(this),
	    field    = $(pulldown.data('field'));
	setTimeout(function() {
	  field.val(pulldown.val()).trigger('keyup');
	}, 100);
      }
    }
  };

  $.fn.extend({
    addPulldown: function (pulldown) {
      return this.each(function () {
	$(this).data('pulldown', pulldown.get(0)).bind(handlers.field);
	pulldown.data('field', this).bind(handlers.pulldown);
      });
    }
  });
}(jQuery));
<commit_msg>Choices plugin: cleaner way to remove clutter under IE; some doco added.<commit_after>/*
 * Author: Olaf Delgado-Friedrichs (odf@github.com)
 * Copyright (c) 2010 ANU
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * A jQuery plugin that implements pulldown menues for text fields.
 *
 * The 'setTimeout' calls, explicit manipulation of the 'display'
 * property and other strange quirks of the code were necessary in
 * order to make things work properly in Internet Explorer.
 *
 * Usage example:
 *
 *     $('input:text[data-pulldown-id]').each(function () {
 *       $(this).addPulldown($('#' + $(this).attr('data-pulldown-id')));
 *     }
 */

/*jslint white: false, browser: true */
/*global jQuery */

(function($) {
  var handlers = {
    field: {
      mousedown: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));
	pulldown.toggle();
      },
      focus: function () {
	var field    = $(this),
	    pulldown = $(field.data('pulldown'));

	$(all_pulldowns).each(function () {
	  $(this).css({ display: 'none' });
	});
	pulldown.each(function () {
	  $(this).css({ display: 'block', left: field.position().left });
	});
      },
      blur: function () {
       var field    = $(this),
           pulldown = $(field.data('pulldown'));
       setTimeout(function() {
         if ($("*:focus").attr('id') !== pulldown.attr('id')) {
           pulldown.css({ display: 'none' });
         }
       }, 100);
      },
      keyup: function () {
       var field    = $(this),
           pulldown = $(field.data('pulldown'));
       setTimeout(function() {
         pulldown.css({ display: 'none' });
       }, 100);
      }
    },
    pulldown: {
      blur: function () {
       $(this).css({ display: 'none' });
      },
      click: function () {
        var pulldown = $(this),
	    field    = $(pulldown.data('field'));
	setTimeout(function() {
	  field.val(pulldown.val()).trigger('keyup');
	}, 100);
      }
    }
  },
  all_pulldowns = [];

  $.fn.extend({
    addPulldown: function (pulldown) {
      return this.each(function () {
	all_pulldowns.push(pulldown);
	$(this).data('pulldown', pulldown.get(0)).bind(handlers.field);
	pulldown.data('field', this).bind(handlers.pulldown);
      });
    }
  });
}(jQuery));
<|endoftext|>


<commit_before>var SI = {
	// Public methods

	start: function() {
		console.debug(chrome.i18n.getMessage("checking_for_initial_session"));

		getSessionId().then(
			function(sessionId) {
				console.debug(chrome.i18n.getMessage("existing_session_found"));
				return this._recordSessionId(sessionId);
			}.bind(this),
			function() {
				console.info(chrome.i18n.getMessage("no_session_found"));
			}.bind(this)
		).then(
			function() {
				chrome.cookies.onChanged.addListener(this._onCookieChange.bind(this));
				console.info(chrome.i18n.getMessage("cookie_monitoring_enabled"));
			}.bind(this)
		);
	},

	// State

	_sessionId: null,

	// Private methods

	_onCookieChange: function(changeInfo) {
		if (!changeInfo.removed) {
			var cookie = changeInfo.cookie;
			if (cookie.domain === ".amazon.com" && cookie.name === "session-id") {
				this._recordSessionId(cookie.value);
			}
		}
	},

	_recordSessionId(sessionId) {
		if (sessionId !== this._sessionId) {
			if (this._sessionId) {
				console.debug(chrome.i18n.getMessage("new_session_found"));
			}

			this._sessionId = sessionId;
			return disableBrowsingHistory();
		} else {
			return Promise.resolve();
		}
	}
};

function postForm(url, formData) {
	return new Promise(function(resolve, reject) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState === XMLHttpRequest.DONE) {
				if (request.status === 200) {
					resolve();
				} else {
					reject();
				}
			}
		};

		request.open("POST", url);
		request.send(formData);
	});
}

function disableBrowsingHistory() {
	console.debug(chrome.i18n.getMessage("disabling_browsing_history"));

	var amazonEndpoint = "https://www.amazon.com/gp/mobile/ybh/handlers/click-stream.html";

	var formData = new FormData();
	formData.append("action", "disable");

	return postForm(amazonEndpoint, formData).then(
		function() {
			console.info(chrome.i18n.getMessage("disable_browsing_success"));
		}.bind(this),
		function() {
			console.error(chrome.i18n.getMessage("disable_browsing_error"));
		}.bind(this)
	);
}

function getSessionId() {
	return new Promise(function(resolve, reject) {
		chrome.cookies.get({
			url: "http://www.amazon.com",
			name: "session-id"
		}, function(cookie) {
			if (cookie) {
				resolve(cookie.value);
			} else {
				reject(chrome.i18n.getMessage("cookie_retrieval_error"));
			}
		});
	});
}
<commit_msg>Fix regression introduced in previous commit.<commit_after>var SI = {
	// Public methods

	start: function() {
		console.debug(chrome.i18n.getMessage("checking_for_initial_session"));

		getSessionId().then(
			function(sessionId) {
				console.debug(chrome.i18n.getMessage("existing_session_found"));
				return this._recordSessionId(sessionId);
			}.bind(this),
			function() {
				console.info(chrome.i18n.getMessage("no_session_found"));
			}.bind(this)
		).then(
			function() {
				chrome.cookies.onChanged.addListener(this._onCookieChange.bind(this));
				console.info(chrome.i18n.getMessage("cookie_monitoring_enabled"));
			}.bind(this)
		);
	},

	// State

	_sessionId: null,

	// Private methods

	_onCookieChange: function(changeInfo) {
		if (!changeInfo.removed) {
			var cookie = changeInfo.cookie;
			if (cookie.domain === ".amazon.com" && cookie.name === "session-id") {
				this._recordSessionId(cookie.value);
			}
		}
	},

	_recordSessionId(sessionId) {
		if (sessionId !== this._sessionId) {
			if (this._sessionId) {
				console.debug(chrome.i18n.getMessage("new_session_found"));
			}

			this._sessionId = sessionId;
			return disableBrowsingHistory();
		} else {
			return Promise.resolve();
		}
	}
};

function postForm(url, formData) {
	return new Promise(function(resolve, reject) {
		var request = new XMLHttpRequest();

		request.onreadystatechange = function() {
			if (request.readyState === XMLHttpRequest.DONE) {
				if (request.status === 200) {
					resolve();
				} else {
					reject();
				}
			}
		};

		request.open("POST", url);
		request.send(formData);
	});
}

function disableBrowsingHistory() {
	console.debug(chrome.i18n.getMessage("disabling_browsing_history"));

	var amazonEndpoint = "https://www.amazon.com/gp/mobile/ybh/handlers/click-stream.html";

	var formData = new FormData();
	formData.append("action", "disable");

	return postForm(amazonEndpoint, formData).then(
		function() {
			console.info(chrome.i18n.getMessage("disable_browsing_success"));
		}.bind(this),
		function() {
			console.error(chrome.i18n.getMessage("disable_browsing_error"));
		}.bind(this)
	);
}

function getSessionId() {
	return new Promise(function(resolve, reject) {
		chrome.cookies.get({
			url: "http://www.amazon.com",
			name: "session-id"
		}, function(cookie) {
			if (cookie) {
				resolve(cookie.value);
			} else {
				reject(new Error(chrome.i18n.getMessage("cookie_retrieval_error")));
			}
		});
	});
}
<|endoftext|>


<commit_before>// Description:
//   Script per fare la lista dei piatti da ordinare al TuttoBene
//
// Commands:
//   hubot menu [<menu>] - Mostra il menù. Se viene indicato anche <menu>, ne salva uno nuovo
//   hubot ordine - Lista dei piatti ordinati oggi
//   hubot email - Lista dei piatti ordinati oggi, formattata per inviare direttamente l'email
//   hubot cancella ordine - Reinizializza l'ordine corrente
//   hubot per me <ordine> [+ <ordine>] - Aggiunge <ordine> all'ordine dell'utente. Con "+ <ordine>" inserisce 2 ordini.
//   hubot per me niente - Cancella il proprio ordine
//   TB - hubot ha sempre voglia di TuttoBene!
//
// Notes:
//   <optional notes required for the script>
//
// Author:
//   nolith, tommyblue

module.exports = function (robot) {
  var initializeEmptyOrder = function () {
    return {
      timestamp: new Date(),
      dishes: {},
      users: {},
      idToName: {},
    };
  };

  var getOrder = function (msg) {
    var order = robot.brain.get('order') || initializeEmptyOrder();

    if (typeof order.timestamp === 'string') {
      order.timestamp = new Date(order.timestamp);
    }

    if (isNotToday(order.timestamp)) {
      if (msg !== undefined) {
        msg.emote('Cancello l\'ordine del ' + order.timestamp);
      }

      order = initializeEmptyOrder();
    }

    return order;
  };

  var isNotToday = function (dateToCheck) {
    var actualDate = new Date();
    return dateToCheck.getDate() != actualDate.getDate() ||
           dateToCheck.getMonth() != actualDate.getMonth() ||
           dateToCheck.getFullYear() != actualDate.getFullYear();
  };

  var clearUserOrder = function (order, user) {
    var dishes = order.users[user.id];
    order.idToName[user.id] = user.name;

    for (var id in dishes) {
      var dish = dishes[id];
      if (dish !== undefined && order.dishes[dish] !== undefined) {
        var idx = order.dishes[dish].indexOf(user.id);

        if (idx > -1) {
          order.dishes[dish].splice(idx, 1);
        }

        if (order.dishes[dish].length == 0) {
          delete order.dishes[dish];
        }
      }
    }

    return dishes;
  };

  var addNewOrder = function (order, dishes, user) {
    clearUserOrder(order, user);
    for (var id in dishes) {
      var dish = dishes[id];
      var sameDish = order.dishes[dish] || [];
      sameDish.push(user.id);
      order.dishes[dish] = sameDish;
    }

    order.users[user.id] = dishes;
  };

  robot.hear(/TB/, function (msg) {
    msg.send('Se ordinate al TuttoBene posso aiutarvi io!');
  });

  robot.hear(/TuttoBene/i, function (msg) {
    msg.send('Se ordinate al TuttoBene posso aiutarvi io!');
  });

  robot.respond(/per me (.*)/i, function (msg) {
    var dish = msg.match[1].trim();
    var user = msg.message.user;
    var order = getOrder(msg);

    if (dish === 'niente') {
      var oldDish = clearUserOrder(order, user);
      if (oldDish !== undefined)
        msg.reply('Ok, niente più ' + oldDish);
      else
        msg.reply('Ok, fatto!');
    } else {

      var dishes = dish.split('+');
      dishes = dishes.map(function (s) { return s.trim(); });

      addNewOrder(order, dishes, user);
      robot.brain.set('order', order);

      msg.reply('ok, ' + dishes.join(' e ') + ' per ' + user.name);
    }
  });

  robot.respond(/cancella ordine/i, function (msg) {
    robot.brain.set('order', initializeEmptyOrder());
    msg.reply('Ordine cancellato');
  });

  robot.respond(/ordine/i, function (msg) {
    var reply = ["Ecco l'ordine:"];
    var order = getOrder();

    for (var dish in order.dishes) {
      if (order.dishes.hasOwnProperty(dish)) {
        var userIds = order.dishes[dish];
        var line = [userIds.length, dish];

        var users = userIds.map(function (id) {
          var name = order.idToName[id];
          if (typeof name !== 'undefined' && name !== '') {
            return name;
          }
          return robot.brain.userForId(id).name;
        });

        line.push('[');
        line.push(users);
        line.push(']');

        reply.push(line.join(' '));
      }
    }

    msg.reply(reply.join('\n'));
  });

  robot.respond(/email/i, function (msg) {
    var order = getOrder();
    var order_date = new Date(order.timestamp);
    var formatted_date = order_date.getUTCDate() + '/' + (order_date.getUTCMonth() + 1);
    var reply = ["Ordine Develer del giorno " + formatted_date];

    for (var dish in order.dishes) {
      if (order.dishes.hasOwnProperty(dish)) {
        var userIds = order.dishes[dish];
        var line = [userIds.length, dish];
        reply.push(line.join(' '));
      }
    }

    msg.reply(reply.join('\n'));
  });

  robot.respond(/menu([\s\S]*)?/i, function (msg) {
    var menu = (msg.match[1] || '').trim();
    if (menu === '') {
      menu = robot.brain.get('menu') || '';
      if (menu === '') {
        msg.reply('Non c\'è nessun menu impostato!');
      } else {
        msg.reply('Il menu è:\n' + menu);
      }
    } else {
      robot.brain.set('menu', menu);
      msg.reply('ok, il menu è ' + menu);
    }
  });
};
<commit_msg>Ricerca anche parzialmente il nome del piatto.<commit_after>// Description:
//   Script per fare la lista dei piatti da ordinare al TuttoBene
//
// Commands:
//   hubot menu [<menu>] - Mostra il menù. Se viene indicato anche <menu>, ne salva uno nuovo
//   hubot ordine - Lista dei piatti ordinati oggi
//   hubot email - Lista dei piatti ordinati oggi, formattata per inviare direttamente l'email
//   hubot cancella ordine - Reinizializza l'ordine corrente
//   hubot per me <ordine> [+ <ordine>] - Aggiunge <ordine> all'ordine dell'utente. Con "+ <ordine>" inserisce 2 ordini.
//   hubot per me niente - Cancella il proprio ordine
//   TB - hubot ha sempre voglia di TuttoBene!
//
// Notes:
//   <optional notes required for the script>
//
// Author:
//   nolith, tommyblue

module.exports = function (robot) {
  var initializeEmptyOrder = function () {
    return {
      timestamp: new Date(),
      dishes: {},
      users: {},
      idToName: {},
    };
  };

  var getOrder = function (msg) {
    var order = robot.brain.get('order') || initializeEmptyOrder();

    if (typeof order.timestamp === 'string') {
      order.timestamp = new Date(order.timestamp);
    }

    if (isNotToday(order.timestamp)) {
      if (msg !== undefined) {
        msg.emote('Cancello l\'ordine del ' + order.timestamp);
      }

      order = initializeEmptyOrder();
    }

    return order;
  };

  var isNotToday = function (dateToCheck) {
    var actualDate = new Date();
    return dateToCheck.getDate() != actualDate.getDate() ||
           dateToCheck.getMonth() != actualDate.getMonth() ||
           dateToCheck.getFullYear() != actualDate.getFullYear();
  };

  var clearUserOrder = function (order, user) {
    var dishes = order.users[user.id];
    order.idToName[user.id] = user.name;

    for (var id in dishes) {
      var dish = dishes[id];
      if (dish !== undefined && order.dishes[dish] !== undefined) {
        var idx = order.dishes[dish].indexOf(user.id);

        if (idx > -1) {
          order.dishes[dish].splice(idx, 1);
        }

        if (order.dishes[dish].length == 0) {
          delete order.dishes[dish];
        }
      }
    }

    return dishes;
  };

  var addNewOrder = function (order, dishes, user) {
    clearUserOrder(order, user);
    for (var id in dishes) {
      var dish = dishes[id];
      var sameDish = order.dishes[dish] || [];
      sameDish.push(user.id);
      order.dishes[dish] = sameDish;
    }

    order.users[user.id] = dishes;
  };

  var countMatches = function (str1, str2) {
    var s1 = str1.split(' ');
    var s2 = str2.split(' ');

    s1 = s1.map(function (s) { return s.trim().toLowerCase(); });
    s2 = s2.map(function (s) { return s.trim().toLowerCase(); });

    var match = 0;

    for (i = 0; i < s1.length; i++) {
      for (j = 0; j < s2.length; j++) {
        if (s1[i] === s2[j])
          match++;
      }
    }

    return match;
  };

  var findDish = function (menu, dish) {
    var maxmatch = 0;
    var maxmatch_unique = false;
    var maxmatch_id = -1;

    for (var i = 0; i < menu.length; i++) {
      var matches = countMatches(menu[i], dish);
      if (matches > maxmatch) {
        maxmatch_id = i;
        maxmatch_unique = true;
        maxmatch = matches;
      }
      else if (matches === maxmatch) {
        maxmatch_unique = false;
      }
    }

    if (maxmatch_unique === true) {
      dish = menu[maxmatch_id];
    }
    else if (maxmatch > 0) {
      dish = null;
    }
    return dish;
  };

  robot.hear(/TB/, function (msg) {
    msg.send('Se ordinate al TuttoBene posso aiutarvi io!');
  });

  robot.hear(/TuttoBene/i, function (msg) {
    msg.send('Se ordinate al TuttoBene posso aiutarvi io!');
  });

  robot.respond(/per me (.*)/i, function (msg) {
    var dish = msg.match[1].trim();
    var user = msg.message.user;
    var order = getOrder(msg);

    if (dish === 'niente') {
      var oldDish = clearUserOrder(order, user);
      if (oldDish !== undefined)
        msg.reply('Ok, niente più ' + oldDish);
      else
        msg.reply('Ok, fatto!');
    } else {

      var dishes = dish.split('+');
      dishes = dishes.map(function (s) { return s.trim(); });

      // prova a fare un match fuzzy nel menu
      for (var d in dishes) {
        var newdish = findDish(robot.brain.get('menu').split('\n'), dishes[d])
        if (newdish === null) {
          msg.reply('non capisco, "' + dish +'" è ambiguo.');
          return;
        }
        else
          dishes[d] = newdish;
      }

      addNewOrder(order, dishes, user);
      robot.brain.set('order', order);

      msg.reply('ok, ' + dishes.join(' e ') + ' per ' + user.name);
    }
  });

  robot.respond(/cancella ordine/i, function (msg) {
    robot.brain.set('order', initializeEmptyOrder());
    msg.reply('Ordine cancellato');
  });

  robot.respond(/ordine/i, function (msg) {
    var reply = ["Ecco l'ordine:"];
    var order = getOrder();

    for (var dish in order.dishes) {
      if (order.dishes.hasOwnProperty(dish)) {
        var userIds = order.dishes[dish];
        var line = [userIds.length, dish];

        var users = userIds.map(function (id) {
          var name = order.idToName[id];
          if (typeof name !== 'undefined' && name !== '') {
            return name;
          }
          return robot.brain.userForId(id).name;
        });

        line.push('[');
        line.push(users);
        line.push(']');

        reply.push(line.join(' '));
      }
    }

    msg.reply(reply.join('\n'));
  });

  robot.respond(/email/i, function (msg) {
    var order = getOrder();
    var order_date = new Date(order.timestamp);
    var formatted_date = order_date.getUTCDate() + '/' + (order_date.getUTCMonth() + 1);
    var reply = ["Ordine Develer del giorno " + formatted_date];

    for (var dish in order.dishes) {
      if (order.dishes.hasOwnProperty(dish)) {
        var userIds = order.dishes[dish];
        var line = [userIds.length, dish];
        reply.push(line.join(' '));
      }
    }

    msg.reply(reply.join('\n'));
  });

  robot.respond(/menu([\s\S]*)?/i, function (msg) {
    var menu = (msg.match[1] || '').trim();
    if (menu === '') {
      menu = robot.brain.get('menu') || '';
      if (menu === '') {
        msg.reply('Non c\'è nessun menu impostato!');
      } else {
        msg.reply('Il menu è:\n' + menu);
      }
    } else {
      robot.brain.set('menu', menu);
      msg.reply('ok, il menu è ' + menu);
    }
  });
};
<|endoftext|>


<commit_before>
var Toolbox = {
  findOrCreate: function() {
    if ( this.element().length == 0 ) {
      $('body').appendDom([
        { tagName: 'div', id: 'toolbox', childNodes: [
          { tagName: 'div', class: 'head', childNodes: [
            { tagName: 'span', class: 'content title', innerHTML: 'Toolbox Title is loooooooooooooong' },
            { tagName: 'span', class: 'buttons', childNodes: [
              { tagName: 'img', class: 'close', src: '/images/window/close.jpg' },
              { tagName: 'img', class: 'min',   src: '/images/window/min.jpg' },
              { tagName: 'img', class: 'max',   src: '/images/window/max.jpg' }
            ]}
          ]},
          { tagName: 'div', class: 'sidebar left', innerHTML: 'Sidebar foo foo' },
          { tagName: 'div', class: 'body', childNodes: [
            { tagName: 'div', class: 'content', id: 'toolbox_content', childNodes: [
              { tagName: 'div', class: 'frame', innerHTML: 'Frame 1 (only Frame - Dashboard)'},
            ]}
          ]},
          { tagName: 'div', class: 'foot', childNodes: [
            { tagName: 'span', class: 'content status' }
          ]},
        ]}
      ]);

      this.element()
        .draggable( { handle: 'div.head' } )
        .resizable( {
          minWidth: 300, minHeight: 400,
          handles: 'e,se,s',
          resize : function(e,ui) {
            Toolbox.setSizes();
          },
        })
        .find('> div.body')
          .serialScroll({
            target: 'div.content',
            step: 1, cycle: false,
            lazy: true,
            items: 'div.content > div.frame',
            prev: 'a.prev', next: 'a.next',
            axis: 'xy',
            duration: 500
          })
        .end()
        .show();
      $('div#toolbox a.back').livequery(function() {
        $(this).click(function(ev) {
          Toolbox.pop()
        })
      });
    };
    this.setSizes();
    return this.element();
  },
  setSizes: function() {
    $('div#toolbox div.body').height(
      $('div#toolbox').height() - $('div#toolbox div.head').height() - $('div#toolbox div.foot').height()
    );
    $('div#toolbox > div.body > div.content > div.frame').height( $('div#toolbox > div.body'));
    // $('div#toolbox > div.body').scrollTo('div.frame:last',{axis:'x'});
  },
  push: function(content,options) {
    var options = jQuery.extend({
      href:       '/dashboard',
      title:      '[No Title]'
    }, options);
    this.scroller().find('> div.content')
      .appendDom([
        {tagName: 'div', class: 'frame', href: options.href, innerHTML: content }
      ]);
    if (this.last().find('> ul.linkbar > li > a.back').length == 0) {
      this.last().find('> ul.linkbar').appendDom([this.newBackButton()])
    }
    this.next();
    this.setTitle(options.title);
  },
  newBackButton: function(title) {
    return(
      { tagName: 'li', childNodes: [
        { tagName: 'a', class: 'back', href: '#', innerHTML: 'back' }
      ]}
    );
  },
  pop: function() {
    this.prev();
    setTimeout( function() {
      Toolbox.last().remove();
    }, 500);
  },
  updateFrameByHref: function(href,content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.element()
      .find('> div.body > div.content > div.frame[@href=href]')
        .html(content);
    this.setTitle(options.title);
  },
  popAndRefreshWith: function(content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.scoller().find('> div.content > div.frame:last').prev().html(content);
    this.pop();
    this.setTitle(options.title);
  },
  updateLastFrame: function(content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.last().html(content);
    this.setTitle(options.title);
  },
  setTitle: function(title) {
    return this.element().find('> div.head span.title').html(title);
  },
  setStatus: function(status) {
    return this.element().find('> div.foot span.status').html(status);
  },
  scroller: function() {
    return $('div#toolbox > div.body');
  },
  last: function() {
    return this.scroller().find('> div.content > div.frame:last');
  },
  element: function() {
    return $('div#toolbox');
  },
  next: function() {
    return this.scroller().trigger('next');
  },
  prev: function() {
    return this.scroller().trigger('prev');
  },
  close: function() {
    this.element().remove();
  }

};

jQuery.fn.useToolbox = function(options) {
  var settings = jQuery.extend({
    onStart: function() {
    }
  }, options);
  $(this).hover(
    // mark hovered divs as hovered. we can get them later by $('div.hovered').last() for positioniing the toolbox
    function() {
      $(this).parents('div.grid, div.rendering').addClass('hovered');
    }, function() {
      $(this).parents('div.grid, div.rendering').removeClass('hovered');
    }
  );
  $(this).click(function(event) {
    Toolbox.findOrCreate();
    event.preventDefault();
    $.ajax({
      url: $(this).attr('href'),
      type: 'GET',
      dataType: 'script'
    });
  });
};
<commit_msg>bind toolbox buttons<commit_after>
var Toolbox = {
  findOrCreate: function() {
    if ( this.element().length == 0 ) {
      $('body').appendDom([
        { tagName: 'div', id: 'toolbox', childNodes: [
          { tagName: 'div', class: 'head', childNodes: [
            { tagName: 'span', class: 'content title', innerHTML: 'Toolbox Title is loooooooooooooong' },
            { tagName: 'span', class: 'buttons', childNodes: [
              { tagName: 'img', class: 'close', src: '/images/window/close.jpg' },
              { tagName: 'img', class: 'min',   src: '/images/window/min.jpg' },
              { tagName: 'img', class: 'max',   src: '/images/window/max.jpg' }
            ]}
          ]},
          { tagName: 'div', class: 'sidebar left', innerHTML: 'Sidebar foo foo' },
          { tagName: 'div', class: 'body', childNodes: [
            { tagName: 'div', class: 'content', id: 'toolbox_content', childNodes: [
              { tagName: 'div', class: 'frame', innerHTML: 'Frame 1 (only Frame - Dashboard)'},
            ]}
          ]},
          { tagName: 'div', class: 'foot', childNodes: [
            { tagName: 'span', class: 'content status' }
          ]},
        ]}
      ]);

      this.element()
        .draggable( { handle: 'div.head' } )
        .resizable( {
          minWidth: 300, minHeight: 400,
          handles: 'e,se,s',
          resize : function(e,ui) {
            Toolbox.setSizes();
          },
        })
        .find('> div.body')
          .serialScroll({
            target: 'div.content',
            step: 1, cycle: false,
            lazy: true,
            items: 'div.content > div.frame',
            prev: 'a.prev', next: 'a.next',
            axis: 'xy',
            duration: 500
          })
        .end()
        .find(' > div.head > span.buttons')
          .find('> img.close').click(function() { Toolbox.close() }).end()
          .find('> img.min').click(function() { Toolbox.minimize() }).end()
          .find('> img.max').click(function() { Toolbox.maximize() }).end()
        .end()
        .show();
      $('div#toolbox a.back').livequery(function() {
        $(this).click(function(ev) {
          Toolbox.pop()
          // TODO download dashboard if not already loaded
        })
      });
    };
    this.setSizes();
    return this.element();
  },
  setSizes: function() {
    $('div#toolbox div.body').height(
      $('div#toolbox').height() - $('div#toolbox div.head').height() - $('div#toolbox div.foot').height()
    );
    $('div#toolbox > div.body > div.content > div.frame').height( $('div#toolbox > div.body'));
    // $('div#toolbox > div.body').scrollTo('div.frame:last',{axis:'x'});
  },
  push: function(content,options) {
    var options = jQuery.extend({
      href:       '/dashboard',
      title:      '[No Title]'
    }, options);
    this.scroller().find('> div.content')
      .appendDom([
        {tagName: 'div', class: 'frame', href: options.href, innerHTML: content }
      ]);
    if (this.last().find('> ul.linkbar > li > a.back').length == 0) {
      this.last().find('> ul.linkbar').appendDom([this.newBackButton()])
    }
    this.next();
    this.setTitle(options.title);
  },
  newBackButton: function(title) {
    return(
      { tagName: 'li', childNodes: [
        { tagName: 'a', class: 'back', href: '#', innerHTML: 'back' }
      ]}
    );
  },
  pop: function() {
    this.prev();
    setTimeout( function() {
      Toolbox.last().remove();
    }, 500);
  },
  updateFrameByHref: function(href,content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.element()
      .find('> div.body > div.content > div.frame[@href=href]')
        .html(content);
    this.setTitle(options.title);
  },
  popAndRefreshWith: function(content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.scoller().find('> div.content > div.frame:last').prev().html(content);
    this.pop();
    this.setTitle(options.title);
  },
  updateLastFrame: function(content,options) {
    var options = jQuery.extend({
      title:      '[No Title]'
    }, options);
    this.last().html(content);
    this.setTitle(options.title);
  },
  setTitle: function(title) {
    return this.element().find('> div.head span.title').html(title);
  },
  setStatus: function(status) {
    return this.element().find('> div.foot span.status').html(status);
  },
  scroller: function() {
    return $('div#toolbox > div.body');
  },
  last: function() {
    return this.scroller().find('> div.content > div.frame:last');
  },
  element: function() {
    return $('div#toolbox');
  },
  next: function() {
    return this.scroller().trigger('next');
  },
  prev: function() {
    return this.scroller().trigger('prev');
  },
  close: function() {
    this.element().remove();
  }

};

jQuery.fn.useToolbox = function(options) {
  var settings = jQuery.extend({
    onStart: function() {
    }
  }, options);
  $(this).hover(
    // mark hovered divs as hovered. we can get them later by $('div.hovered').last() for positioniing the toolbox
    function() {
      $(this).parents('div.grid, div.rendering').addClass('hovered');
    }, function() {
      $(this).parents('div.grid, div.rendering').removeClass('hovered');
    }
  );
  $(this).click(function(event) {
    Toolbox.findOrCreate();
    event.preventDefault();
    $.ajax({
      url: $(this).attr('href'),
      type: 'GET',
      dataType: 'script'
    });
  });
};
<|endoftext|>