<commit_before>'use strict';

const config = require('../config');
const db = require('./redis-clients').users;
const fs = require('fs');
const Primus = require('primus');
const primusemitter = require('primus-emitter');
const primusrooms = require('primus-rooms');
const rooms = require('./rooms').rooms;
const uglify = require('uglify-js');
const utils = require('./utils');

const banDuration = utils.banDuration;
const isFunction = utils.isFunction;
const isString = utils.isString;
const sparks = Object.create(null); // Sparks of all rooms
let primus;
let sessionstore;

/**
 * Expose a function to set up Primus.
 */

module.exports = function(options) {
  sessionstore = options.sessionstore;

  // Create Primus instance
  primus = new Primus(options.server, {
    authorization: authorize,
    plugin: {
      emitter: primusemitter,
      rooms: primusrooms
    },
    rooms: { wildcard: false },
    transformer: 'websockets'
  });

  // Remove unneeded middleware
  ['cors', 'no-cache', 'primus.js', 'spec', 'x-xss'].forEach(function(
    middleware
  ) {
    primus.remove(middleware);
  });

  // Add cookieParser middleware
  primus.use('cookies', options.parser, 0);

  primus.on('connection', connection);
  primus.on('joinroom', joinRoom);
  primus.on('log', function(type) {
    if (type === 'error') {
      const err = arguments[1];
      console.error(err.stack || err.message);
    }
  });

  // Minify and store the client-side library in the public directory
  const library = uglify.minify(primus.library());
  fs.writeFileSync(__dirname + '/../public/js/primus.min.js', library.code);

  return { primus: primus, sparks: sparks };
};

/**
 * Authorization handler.
 */

function authorize(req, authorized) {
  const cookies = req.signedCookies;
  if (!cookies['connect.sid']) {
    const err = new Error('connect.sid cookie not transmitted');
    console.error(err.message);
    return authorized(err);
  }
  sessionstore.get(cookies['connect.sid'], function(err, session) {
    if (err || !session) {
      err = err || new Error('session not found');
      console.error(err.message);
      return authorized(err);
    }
    db.exists(['ban:' + req.forwarded.ip], function(err, exists) {
      if (err) {
        console.error(err.message);
        return authorized(err);
      }
      if (exists) {
        return authorized(new Error('banned IP address'));
      }
      req.user = session.user;
      authorized();
    });
  });
}

/**
 * Handle `connection` event.
 */

function connection(spark) {
  const nickname = spark.request.cookies.nickname;
  const user = spark.request.user;
  const room = spark.query.room;

  spark.send(
    'overview',
    config.rooms.reduce(function(data, room) {
      data[room] = rooms[room].totusers;
      return data;
    }, {})
  );

  if (!~config.rooms.indexOf(room)) return;

  if (user) {
    if (sparks[user]) {
      // User already in a room
      spark.send('alreadyinaroom');
    } else {
      spark.nickname = user;
      spark.join(room, function() {
        rooms[room].addUser(spark, true);
      });
    }
  } else {
    if (isString(nickname)) {
      rooms[room].join(spark, nickname);
    } else {
      spark.send('nickname');
    }

    spark.on('nickname', function(nickname) {
      if (isString(nickname)) {
        rooms[room].join(spark, nickname);
      }
    });
  }
}

/**
 * Handle `joinroom` event.
 */

function joinRoom(room, spark) {
  room = rooms[room];
  spark.on('ban', function(who, why, duration, callback) {
    if (
      isString(who) &&
      isString(why) &&
      isString(duration) &&
      isFunction(callback)
    ) {
      room.onKick(who, why, spark.nickname, banDuration(duration), callback);
    }
  });
  spark.on('chatmsg', function(msg, to) {
    if (isString(msg)) {
      room.onChatMessage(msg, spark, to);
    }
  });
  spark.on('guess', function(guess) {
    if (isString(guess)) {
      room.onGuess(spark, guess);
    }
  });
  spark.on('ignore', function(who, callback) {
    if (isString(who) && isFunction(callback)) {
      room.onIgnore(who, spark.nickname, callback);
    }
  });
  spark.on('kick', function(who, why, callback) {
    if (isString(who) && isString(why) && isFunction(callback)) {
      room.onKick(who, why, spark.nickname, callback);
    }
  });
  spark.on('leaveallrooms', function() {
    room.removeUser(spark.nickname);
  });
  spark.on('unban', function(ip, callback) {
    if (isString(ip) && isFunction(callback)) {
      utils.unban(ip, spark, callback);
    }
  });
  spark.on('unignore', function(who) {
    if (isString(who)) {
      room.onUnignore(who, spark.nickname);
    }
  });
}
<commit_msg>set the `maxLength` option to 1024<commit_after>'use strict';

const config = require('../config');
const db = require('./redis-clients').users;
const fs = require('fs');
const Primus = require('primus');
const primusemitter = require('primus-emitter');
const primusrooms = require('primus-rooms');
const rooms = require('./rooms').rooms;
const uglify = require('uglify-js');
const utils = require('./utils');

const banDuration = utils.banDuration;
const isFunction = utils.isFunction;
const isString = utils.isString;
const sparks = Object.create(null); // Sparks of all rooms
let primus;
let sessionstore;

/**
 * Expose a function to set up Primus.
 */

module.exports = function(options) {
  sessionstore = options.sessionstore;

  // Create Primus instance
  primus = new Primus(options.server, {
    authorization: authorize,
    maxLength: 1024,
    plugin: {
      emitter: primusemitter,
      rooms: primusrooms
    },
    rooms: { wildcard: false },
    transformer: 'websockets'
  });

  // Remove unneeded middleware
  ['cors', 'no-cache', 'primus.js', 'spec', 'x-xss'].forEach(function(
    middleware
  ) {
    primus.remove(middleware);
  });

  // Add cookieParser middleware
  primus.use('cookies', options.parser, 0);

  primus.on('connection', connection);
  primus.on('joinroom', joinRoom);
  primus.on('log', function(type) {
    if (type === 'error') {
      const err = arguments[1];
      console.error(err.stack || err.message);
    }
  });

  // Minify and store the client-side library in the public directory
  const library = uglify.minify(primus.library());
  fs.writeFileSync(__dirname + '/../public/js/primus.min.js', library.code);

  return { primus: primus, sparks: sparks };
};

/**
 * Authorization handler.
 */

function authorize(req, authorized) {
  const cookies = req.signedCookies;
  if (!cookies['connect.sid']) {
    const err = new Error('connect.sid cookie not transmitted');
    console.error(err.message);
    return authorized(err);
  }
  sessionstore.get(cookies['connect.sid'], function(err, session) {
    if (err || !session) {
      err = err || new Error('session not found');
      console.error(err.message);
      return authorized(err);
    }
    db.exists(['ban:' + req.forwarded.ip], function(err, exists) {
      if (err) {
        console.error(err.message);
        return authorized(err);
      }
      if (exists) {
        return authorized(new Error('banned IP address'));
      }
      req.user = session.user;
      authorized();
    });
  });
}

/**
 * Handle `connection` event.
 */

function connection(spark) {
  const nickname = spark.request.cookies.nickname;
  const user = spark.request.user;
  const room = spark.query.room;

  spark.send(
    'overview',
    config.rooms.reduce(function(data, room) {
      data[room] = rooms[room].totusers;
      return data;
    }, {})
  );

  if (!~config.rooms.indexOf(room)) return;

  if (user) {
    if (sparks[user]) {
      // User already in a room
      spark.send('alreadyinaroom');
    } else {
      spark.nickname = user;
      spark.join(room, function() {
        rooms[room].addUser(spark, true);
      });
    }
  } else {
    if (isString(nickname)) {
      rooms[room].join(spark, nickname);
    } else {
      spark.send('nickname');
    }

    spark.on('nickname', function(nickname) {
      if (isString(nickname)) {
        rooms[room].join(spark, nickname);
      }
    });
  }
}

/**
 * Handle `joinroom` event.
 */

function joinRoom(room, spark) {
  room = rooms[room];
  spark.on('ban', function(who, why, duration, callback) {
    if (
      isString(who) &&
      isString(why) &&
      isString(duration) &&
      isFunction(callback)
    ) {
      room.onKick(who, why, spark.nickname, banDuration(duration), callback);
    }
  });
  spark.on('chatmsg', function(msg, to) {
    if (isString(msg)) {
      room.onChatMessage(msg, spark, to);
    }
  });
  spark.on('guess', function(guess) {
    if (isString(guess)) {
      room.onGuess(spark, guess);
    }
  });
  spark.on('ignore', function(who, callback) {
    if (isString(who) && isFunction(callback)) {
      room.onIgnore(who, spark.nickname, callback);
    }
  });
  spark.on('kick', function(who, why, callback) {
    if (isString(who) && isString(why) && isFunction(callback)) {
      room.onKick(who, why, spark.nickname, callback);
    }
  });
  spark.on('leaveallrooms', function() {
    room.removeUser(spark.nickname);
  });
  spark.on('unban', function(ip, callback) {
    if (isString(ip) && isFunction(callback)) {
      utils.unban(ip, spark, callback);
    }
  });
  spark.on('unignore', function(who) {
    if (isString(who)) {
      room.onUnignore(who, spark.nickname);
    }
  });
}
<|endoftext|>


<commit_before>var url = require('url');
var gzip = require('zlib').gzip;
var wrabbit = require('wrabbit');
var File = require('./file');
var Cache = require('./cache');
var Composition = require('./composition');
var moduleCache = Cache('modules');

// the index client
var client;

// index document http headers
var resHeaders = {
    'Cache-Control': 'public, max-age=31536000',
    'Vary': 'Accept-Encoding',
    'Content-Type': 'text/html; charset=utf-8',
    'Content-Encoding': 'gzip',
    'Server': 'JCES'
};

/**
 * Send the index document to the client.
 *
 * @private
 * @param {object} The http response object.
 * @param {boolean} Remove session if true.
 */
exports.client = function (stream) {

    var req = stream.req;
    var res = stream.res;

    stream.headers = resHeaders;

    // remove sid
    if (req._sidInvalid) {
        resHeaders['Set-Cookie'] = 'sid=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';
    } else if (resHeaders['Set-Cookie']) {
        delete resHeaders['Set-Cookie'];
    }

    if (client) {
        return stream.end(200, client);
    }

    // get the engine client and core modules paths with fingerprints
    var loadContext = {
        module: engine.operation_id,
        base: engine.root + 'lib/client/',
        scripts: [
            'engine.js',
            'transform.js',
            'instance.js',
            'flow.js',
            'socket.js',
            'stream.js',
            'utils.js',
            'resource.js'
        ]
    };

    // load logger module in dev mode
    if (!engine.production) {
        loadContext.scripts.splice(-2, 0, 'logs.js');
    }

    File.prepare(loadContext, function (err, files) {

        // send the error in the browser
        if (err) {
            stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
            return stream.end(500, err.toString());
        }

        // get script files array
        files = files.scripts;

        var script = '';

        // load single files for better debugging
        // var i = length - 1, url, cleanPath; i >= 0; --i
        for (var i = (files.length - 1); i >= 0; --i) {
            script += '';
        }

        // compress client
        gzip(
            '<!DOCTYPE html>' +
                '<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">' +
                '<link rel="icon" href="/favicon.ico"/>' +
                script +
            '',
            function (err, data) {

                // send the error in the browser
                if (err) {
                    engine.log('E', err);
                    stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
                    return stream.end(500, err.toString());
                }

                client = data;
                resHeaders['Content-Length'] = client.length;

                // send client
                stream.end(200, client);
            }
        );
    });
};

/**
 * Handle public files.
 *
 * @public
 * @param {object} The stream object.
 */
exports.public = function (stream) {

    // TODO stream file
    File.get({
        path: stream.pathname,
        wrap: stream.query.w,
        base: engine.paths.app_public

    }, function (err, file) {

        if (err) {
            stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
            stream.end(404, 'File "' + stream.pathname + '" not found.');
            return;
        }

        stream.headers = file.http;
        stream.end(200, file.data);
    });
};

/**
 * Proxy an external script and append the engine wrapping code.
 *
 * @public
 * @param {object} The link object.
 */
exports.wrap = function (stream) {
    var url = stream.path.join('/');
    wrabbit.stream(url, stream.query.w === "1" ? "E('" + url + "', function (require, module, exports, global, engine) {" : "", stream.res);
};

/**
 * Load an module instance over websoockets.
 *
 * @public
 * @param {object} The link object.
 */
exports.composition = function (stream) {
    var self = this;
    var role = stream.socket.session[engine.session_role];

    // receive data
    stream.data(function (err, instance) {

        // load entrypoint module instance with hostname
        if (!instance) {

            instance = stream.socket.upgradeReq.headers.host.split(':')[0].replace(/\./g, '_');

            // get the public version of the entrypoint module instance
            if (!role) {
                instance += '.pub';
            }
        }

        // tell cache to remove instance on composition change
        var related = {instances: {}};
        related.instances[instance] = true;

        // get composition
        Composition(instance, related, role, function (err, composition) {

            if (err) {
                return stream.write(err.toString());
            }

            if (!composition.client || !composition.client.module) {
                return stream.write('Incomplete client composition!');
            }

            // write composition to client
            stream.write(null, composition.client);
        });
    });
};

/**
 * Get the client side module and engine scripts.
 *
 * @public
 * @param {object} The link object.
 */
exports.file = function (stream) {

    // the module name must be almost alphanumeric
    if (stream.pathname.replace(/[^a-z0-9\/\.\-_@\|]|\.\.\//gi, '') !== stream.pathname) {
        stream.headers = {'content-type': 'text/plain'};
        return stream.end(400, 'Incorrect data in module request URL');
    }

    // create file descriptor form request
    var file;
    if (!(file = createModuleFileDescriptor(stream))) {
        return stream.end(404, 'File not found. TODO: look in public folder for requested file.');
    }

    // TODO stream file
    File.get(file, function (err, script) {

        if (err) {
            stream.headers = {'content-type': 'text/plain'};
            return stream.end(404, err.toString());
        }

        stream.headers = script.http;
        stream.end(200, script.data);
    });
};

/**
 * Get a HTML snippet.
 *
 * @public
 * @param {object} The link object.
 */
exports.markup = function (stream) {
    var self = this;

    stream.data(function (err, path) {

        var file = {
            path: path,
            base: engine.paths.app_markup,
            noCompression: true
        };

        // get markup form a module
        if (path[0] !== '/') {

            // create file descriptor form request
            if (
                !(file = createModuleFileDescriptor({
                    pathname: path,
                    path: path.split('/')
                }))
            ) {
                stream.headers = {'content-type': 'text/plain'};
                return stream.end(404, 'File not found.');
            }
        }

        // send html snippet to client
        File.get(file, function (err, file) {

            if (err || !file && !file.data) {
                err = err|| 'HTML snippet not found.';
                return stream.write(err);
            }

            stream.write(err, [file.ext, file.data.toString('utf8')]);
        });
    });
};

function createModuleFileDescriptor (request) {

    // create file descriptor
    var file = {
        ext:  request.pathname,
        path: request.path.slice(1).join('/'),
        wrap: request.query.w === '1' ? true : false,
        module: request.path[0]
    };

    // handle core engine files
    if (file.module === engine.operation_id) {
        file.base = engine.root + 'lib/client/';
        return file;
    }

    // get module package
    var modulePackage;
    if (!(modulePackage = moduleCache.get(file.module))) {
        return;
    }

    // check if module package has a base and client resources
    if (
        !modulePackage._base ||
        !modulePackage.composition ||
        !modulePackage.composition.client ||
        (
            !modulePackage.composition.client.module &&
            !modulePackage.composition.client.styles &&
            !modulePackage.composition.client.markup
        )
    ) {
        return;
    }

    // set base on file descriptor
    file.base = modulePackage._base;

    // check if requested file is in the module client resources
    var i, l, path;

    // script
    if (modulePackage.composition.client.module) {
        for (i = 0, l = modulePackage.composition.client.module.length; i < l; ++i) {
            path = modulePackage.composition.client.module[i];
            if ((path.indexOf('(') === 0 ? path.substr(3) : path) === file.ext) {
                return file;
            }
        }
    }

    // styles
    if (modulePackage.composition.client.styles) {
        for (i = 0, l = modulePackage.composition.client.styles.length; i < l; ++i) {
            if (modulePackage.composition.client.styles[i] === file.ext) {
                return file;
            }
        }
    }

    //markup
    if (modulePackage.composition.client.markup) {
        for (i = 0, l = modulePackage.composition.client.markup.length; i < l; ++i) {
            if (modulePackage.composition.client.markup[i] === file.ext) {
                return file;
            }
        }
    }
}
<commit_msg>better errors in client composition loader<commit_after>var url = require('url');
var gzip = require('zlib').gzip;
var wrabbit = require('wrabbit');
var File = require('./file');
var Cache = require('./cache');
var Composition = require('./composition');
var moduleCache = Cache('modules');

// the index client
var client;

// index document http headers
var resHeaders = {
    'Cache-Control': 'public, max-age=31536000',
    'Vary': 'Accept-Encoding',
    'Content-Type': 'text/html; charset=utf-8',
    'Content-Encoding': 'gzip',
    'Server': 'JCES'
};

/**
 * Send the index document to the client.
 *
 * @private
 * @param {object} The http response object.
 * @param {boolean} Remove session if true.
 */
exports.client = function (stream) {

    var req = stream.req;
    var res = stream.res;

    stream.headers = resHeaders;

    // remove sid
    if (req._sidInvalid) {
        resHeaders['Set-Cookie'] = 'sid=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT';
    } else if (resHeaders['Set-Cookie']) {
        delete resHeaders['Set-Cookie'];
    }

    if (client) {
        return stream.end(200, client);
    }

    // get the engine client and core modules paths with fingerprints
    var loadContext = {
        module: engine.operation_id,
        base: engine.root + 'lib/client/',
        scripts: [
            'engine.js',
            'transform.js',
            'instance.js',
            'flow.js',
            'socket.js',
            'stream.js',
            'utils.js',
            'resource.js'
        ]
    };

    // load logger module in dev mode
    if (!engine.production) {
        loadContext.scripts.splice(-2, 0, 'logs.js');
    }

    File.prepare(loadContext, function (err, files) {

        // send the error in the browser
        if (err) {
            stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
            return stream.end(500, err.toString());
        }

        // get script files array
        files = files.scripts;

        var script = '';

        // load single files for better debugging
        // var i = length - 1, url, cleanPath; i >= 0; --i
        for (var i = (files.length - 1); i >= 0; --i) {
            script += '';
        }

        // compress client
        gzip(
            '<!DOCTYPE html>' +
                '<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">' +
                '<link rel="icon" href="/favicon.ico"/>' +
                script +
            '',
            function (err, data) {

                // send the error in the browser
                if (err) {
                    engine.log('E', err);
                    stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
                    return stream.end(500, err.toString());
                }

                client = data;
                resHeaders['Content-Length'] = client.length;

                // send client
                stream.end(200, client);
            }
        );
    });
};

/**
 * Handle public files.
 *
 * @public
 * @param {object} The stream object.
 */
exports.public = function (stream) {

    // TODO stream file
    File.get({
        path: stream.pathname,
        wrap: stream.query.w,
        base: engine.paths.app_public

    }, function (err, file) {

        if (err) {
            stream.headers = {'Content-Type': 'text/plain; charset=utf-8'};
            stream.end(404, 'File "' + stream.pathname + '" not found.');
            return;
        }

        stream.headers = file.http;
        stream.end(200, file.data);
    });
};

/**
 * Proxy an external script and append the engine wrapping code.
 *
 * @public
 * @param {object} The link object.
 */
exports.wrap = function (stream) {
    var url = stream.path.join('/');
    wrabbit.stream(url, stream.query.w === "1" ? "E('" + url + "', function (require, module, exports, global, engine) {" : "", stream.res);
};

/**
 * Load an module instance over websoockets.
 *
 * @public
 * @param {object} The link object.
 */
exports.composition = function (stream) {
    var self = this;
    var role = stream.socket.session[engine.session_role];

    // receive data
    stream.data(function (err, instance) {

        // load entrypoint module instance with hostname
        if (!instance) {

            instance = stream.socket.upgradeReq.headers.host.split(':')[0].replace(/\./g, '_');

            // get the public version of the entrypoint module instance
            if (!role) {
                instance += '.pub';
            }
        }

        // tell cache to remove instance on composition change
        var related = {instances: {}};
        related.instances[instance] = true;

        // get composition
        Composition(instance, related, role, function (err, composition) {

            if (err) {
                return stream.write(err.toString());
            }

            if (!composition.client) {
                return stream.write(
                    engine.log('E', 'Static: Composition "' + composition.name + '" has no client config.')
                );
            }

            if (!composition.client.module) {
                return stream.write(
                    engine.log('E', 'Static: Composition "' + composition.name + '" has no client module.')
                );
            }

            // write composition to client
            stream.write(null, composition.client);
        });
    });
};

/**
 * Get the client side module and engine scripts.
 *
 * @public
 * @param {object} The link object.
 */
exports.file = function (stream) {

    // the module name must be almost alphanumeric
    if (stream.pathname.replace(/[^a-z0-9\/\.\-_@\|]|\.\.\//gi, '') !== stream.pathname) {
        stream.headers = {'content-type': 'text/plain'};
        return stream.end(400, 'Incorrect data in module request URL');
    }

    // create file descriptor form request
    var file;
    if (!(file = createModuleFileDescriptor(stream))) {
        return stream.end(404, 'File not found. TODO: look in public folder for requested file.');
    }

    // TODO stream file
    File.get(file, function (err, script) {

        if (err) {
            stream.headers = {'content-type': 'text/plain'};
            return stream.end(404, err.toString());
        }

        stream.headers = script.http;
        stream.end(200, script.data);
    });
};

/**
 * Get a HTML snippet.
 *
 * @public
 * @param {object} The link object.
 */
exports.markup = function (stream) {
    var self = this;

    stream.data(function (err, path) {

        var file = {
            path: path,
            base: engine.paths.app_markup,
            noCompression: true
        };

        // get markup form a module
        if (path[0] !== '/') {

            // create file descriptor form request
            if (
                !(file = createModuleFileDescriptor({
                    pathname: path,
                    path: path.split('/')
                }))
            ) {
                stream.headers = {'content-type': 'text/plain'};
                return stream.end(404, 'File not found.');
            }
        }

        // send html snippet to client
        File.get(file, function (err, file) {

            if (err || !file && !file.data) {
                err = err|| 'HTML snippet not found.';
                return stream.write(err);
            }

            stream.write(err, [file.ext, file.data.toString('utf8')]);
        });
    });
};

function createModuleFileDescriptor (request) {

    // create file descriptor
    var file = {
        ext:  request.pathname,
        path: request.path.slice(1).join('/'),
        wrap: request.query.w === '1' ? true : false,
        module: request.path[0]
    };

    // handle core engine files
    if (file.module === engine.operation_id) {
        file.base = engine.root + 'lib/client/';
        return file;
    }

    // get module package
    var modulePackage;
    if (!(modulePackage = moduleCache.get(file.module))) {
        return;
    }

    // check if module package has a base and client resources
    if (
        !modulePackage._base ||
        !modulePackage.composition ||
        !modulePackage.composition.client ||
        (
            !modulePackage.composition.client.module &&
            !modulePackage.composition.client.styles &&
            !modulePackage.composition.client.markup
        )
    ) {
        return;
    }

    // set base on file descriptor
    file.base = modulePackage._base;

    // check if requested file is in the module client resources
    var i, l, path;

    // script
    if (modulePackage.composition.client.module) {
        for (i = 0, l = modulePackage.composition.client.module.length; i < l; ++i) {
            path = modulePackage.composition.client.module[i];
            if ((path.indexOf('(') === 0 ? path.substr(3) : path) === file.ext) {
                return file;
            }
        }
    }

    // styles
    if (modulePackage.composition.client.styles) {
        for (i = 0, l = modulePackage.composition.client.styles.length; i < l; ++i) {
            if (modulePackage.composition.client.styles[i] === file.ext) {
                return file;
            }
        }
    }

    //markup
    if (modulePackage.composition.client.markup) {
        for (i = 0, l = modulePackage.composition.client.markup.length; i < l; ++i) {
            if (modulePackage.composition.client.markup[i] === file.ext) {
                return file;
            }
        }
    }
}
<|endoftext|>


<commit_before>'use strict';

/*
 * Class for abstracting out a status message for whatever kalabox is doing
 * right now in a granular specific way so cli and gui users will have better
 * feedback.
 */

/*
 * Implementing this as a class, so that once we have multiple app states in
 * memory, each can their own status message as well as core.
 */

// NPM modules.
var _ = require('lodash');

// Node modules.
var events = require('events');
var util = require('util');

// Kalabox modules.
var Promise = require('./promise.js');

/*
 * Constructor.
 */
function Status() {
  if (this instanceof Status) {
    // Default message to null.
    this.message = null;
    // Init promise chain.
    this.promiseChain = Promise.resolve();
    // Init promise chain depth.
    this.promiseChainDepth = 0;
    // Call event emitter's constructor.
    events.EventEmitter.call(this);
  } else {
    return new Status();
  }
}
/*
 * Inherit from event emitter.
 */
util.inherits(Status, events.EventEmitter);

/*
 * Update kalabox status message.
 */
Status.prototype.update = function() {
  var self = this;
  // Convert arguments to an array.
  var args = _.toArray(arguments);
  // Use util format so place holders can be used in strings.
  var message = util.format.apply(null, args);
  // Increment number of messages in queue.
  self.promiseChainDepth += 1;
  // Add to the promise chain.
  self.promiseChain = self.promiseChain.then(function() {
    // Emit update message.
    return Promise.try(function() {
      self.emit('update', {
        message: message
      });
    })
    .then(function() {
      // Calculate how long to delay based on queued messages.
      var delay = self.promiseChainDepth < 5 ?
        1000 :
        1000 / self.promiseChainDepth;
      // Delay to smooth things out.
      return Promise.delay(delay);
    })
    // Smooth out updates with a delay before the next update.
    .finally(function() {
      // Decrement number of messages in queue.
      self.promiseChainDepth -= 1;
    })
    // Ignore errors.
    .catch(function() {});
  });
};

/*
 * Temporary fix to have more granular feedback in the GUI. Logging
 * data is fed into this function.
 */
Status.prototype.fromLog = function(data) {

  // Update status when pulling an image.
  var images = data.match(/Pulling from (.*)/);
  if (images) {
    this.update('Pulling image %s.', images[1]);
  }

  // Update status based on some logging data.
  if (data.match(/Downloading .*boot2docker.iso/)) {
    this.update('Downloading ISO.');
  } else if (data.match(/Creating VirtualBox VM/)) {
    this.update('Creating VM.');
  } else if (data.match(/Starting the VM/)) {
    this.update('Starting VM.');
  } else if (data.match(/Provisioning with boot2docker/)) {
    this.update('Provisioning Docker.');
  }

};

/*
 * End status message to clear it.
 */
Status.prototype.end = function() {
  this.update();
};

/*
 * Export constructor.
 */
module.exports = Status;
<commit_msg>Only smoothing out kbox.status updates for the GUI.<commit_after>'use strict';

/*
 * Class for abstracting out a status message for whatever kalabox is doing
 * right now in a granular specific way so cli and gui users will have better
 * feedback.
 */

/*
 * Implementing this as a class, so that once we have multiple app states in
 * memory, each can their own status message as well as core.
 */

// NPM modules.
var _ = require('lodash');

// Node modules.
var events = require('events');
var util = require('util');
var deps = require('./core/deps.js');

// Kalabox modules.
var Promise = require('./promise.js');

/*
 * Constructor.
 */
function Status() {
  if (this instanceof Status) {
    // Default message to null.
    this.message = null;
    // Init promise chain.
    this.promiseChain = Promise.resolve();
    // Init promise chain depth.
    this.promiseChainDepth = 0;
    // Call event emitter's constructor.
    events.EventEmitter.call(this);
  } else {
    return new Status();
  }
}
/*
 * Inherit from event emitter.
 */
util.inherits(Status, events.EventEmitter);

/*
 * Update kalabox status message.
 */
Status.prototype.update = function() {
  var self = this;
  // Convert arguments to an array.
  var args = _.toArray(arguments);
  // Use util format so place holders can be used in strings.
  var message = util.format.apply(null, args);
  // Increment number of messages in queue.
  self.promiseChainDepth += 1;
  // Add to the promise chain.
  self.promiseChain = self.promiseChain.then(function() {
    // Emit update message.
    return Promise.try(function() {
      self.emit('update', {
        message: message
      });
    })
    .then(function() {
      if (deps.get('mode') === 'gui') {
        // Calculate how long to delay based on queued messages.
        var delay = self.promiseChainDepth < 5 ?
          1000 :
          1000 / self.promiseChainDepth;
        // Delay to smooth things out.
        return Promise.delay(delay);
      }
    })
    // Smooth out updates with a delay before the next update.
    .finally(function() {
      // Decrement number of messages in queue.
      self.promiseChainDepth -= 1;
    })
    // Ignore errors.
    .catch(function() {});
  });
};

/*
 * Temporary fix to have more granular feedback in the GUI. Logging
 * data is fed into this function.
 */
Status.prototype.fromLog = function(data) {

  // Update status when pulling an image.
  var images = data.match(/Pulling from (.*)/);
  if (images) {
    this.update('Pulling image %s.', images[1]);
  }

  // Update status based on some logging data.
  if (data.match(/Downloading .*boot2docker.iso/)) {
    this.update('Downloading ISO.');
  } else if (data.match(/Creating VirtualBox VM/)) {
    this.update('Creating VM.');
  } else if (data.match(/Starting the VM/)) {
    this.update('Starting VM.');
  } else if (data.match(/Provisioning with boot2docker/)) {
    this.update('Provisioning Docker.');
  }

};

/*
 * End status message to clear it.
 */
Status.prototype.end = function() {
  this.update();
};

/*
 * Export constructor.
 */
module.exports = Status;
<|endoftext|>


<commit_before>var Trello = require('node-trello'),
     async = require('async'),
    moment = require('moment');

var api;
var boards;

var tick = function() { process.stdout.write('.') };

var passed_date = new Date(2013, 4-1, 24);

var createStatus = function(api_key, api_token, new_boards, callback) {
  api = new Trello(api_key, api_token);
  boards = new_boards;

  async.waterfall([
    getBoards,
    appendListAndCardInfos,
    appendComments,
    callback
  ]);
}

var getBoards = function(callback) {
    callback(null, boards);
}

var appendListAndCardInfos = function(data, callback) {
  var data2 = [];
  async.forEach(
    data,
    function(item, callback2) {
      api.get('/1/board/' + item.board_id + '/lists/all', {'cards':'all'}, function(err, response) {
        tick();
        console.log('Got response for board ' + item.board_id);
        console.log('Found ' + response.length + ' lists');
        if(err) throw err;
        for (var j=0; j < response.length; j++) {
          var list_name = response[j].name;
          var list_id = response[j].id;
          if(response[j].cards)
            console.log('Found ' + response[j].cards.length + " cards for list " + list_name);
          for (var i = 0; response[j].cards && i < response[j].cards.length; i++) {
            var card = response[j].cards[i];
            var card_name = card.name;
            var n = card_name.match(/\((\d)\)/);
            var estimate = n ? n[1] : 0;
            //var idMembers = card.idMembers;
            data2.push({
              //board_id: item.board_id,
              card_id: card.id,
              list_name : list_name,
              //list_id : list_id,
              card_name : card_name,
              //idMembers : idMembers
              url: card.url,
              labels: card.labels
            });
          };
        }
        callback2(null);
      });
    },
    function() {
      callback(null, data2);
    }
  );
}

var appendComments = function(data, callback) {
  console.log('\nappendComments passed ' + passed_date)
  var data2 = [];
  async.forEach(
    data,
    function(card, callback2) {
      api.get('/1/cards/' + card.card_id + '/actions', {filter:'commentCard'}, function(err, comments) {
        tick();
        comments.forEach( function (comment) {
          var comment_date = new Date(comment.date);
          if(passed_date.getTime() < comment_date.getTime()) {
            card.owner = comment.memberCreator.fullName;
            if(!card.comments) card.comments = [];
            card.comments.push( { 'text' : comment.data.text, 'date' : comment_date } );
          }
        });

        if(card.comments) {
          data2.push(card);
        }
        callback2(null);
      });
    },
    function() {
      callback(null, data2);
    }
  );
}

exports.createStatus = module.exports.createStatus = createStatus;

<commit_msg>just open tickets and boards<commit_after>var Trello = require('node-trello'),
     async = require('async'),
    moment = require('moment');

var api;
var boards;

var tick = function() { process.stdout.write('.') };

var passed_date = new Date(2013, 4-1, 24);

var createStatus = function(api_key, api_token, new_boards, callback) {
  api = new Trello(api_key, api_token);
  boards = new_boards;

  async.waterfall([
    getBoards,
    appendListAndCardInfos,
    appendComments,
    callback
  ]);
}

var getBoards = function(callback) {
    callback(null, boards);
}

var appendListAndCardInfos = function(data, callback) {
  var data2 = [];
  async.forEach(
    data,
    function(item, callback2) {
      api.get('/1/board/' + item.board_id + '/lists/open', {'cards':'open'}, function(err, response) {
        tick();
        console.log('Got response for board ' + item.board_id);
        console.log('Found ' + response.length + ' lists');
        if(err) throw err;
        for (var j=0; j < response.length; j++) {
          var list_name = response[j].name;
          var list_id = response[j].id;
          if(response[j].cards)
            console.log('Found ' + response[j].cards.length + " cards for list " + list_name);
          for (var i = 0; response[j].cards && i < response[j].cards.length; i++) {
            var card = response[j].cards[i];
            var card_name = card.name;
            var n = card_name.match(/\((\d)\)/);
            var estimate = n ? n[1] : 0;
            data2.push({
              card_id: card.id,
              list_name : list_name,
              card_name : card_name,
              //id_members : card.idMembers,
              url: card.url,
              labels: card.labels
            });
          };
        }
        callback2(null);
      });
    },
    function() {
      callback(null, data2);
    }
  );
}

var appendComments = function(data, callback) {
  console.log('\nappendComments passed ' + passed_date)
  var data2 = [];
  async.forEach(
    data,
    function(card, callback2) {
      api.get('/1/cards/' + card.card_id + '/actions', {filter:'commentCard'}, function(err, comments) {
        tick();
        comments.forEach( function (comment) {
          var comment_date = new Date(comment.date);
          if(passed_date.getTime() < comment_date.getTime()) {
            card.owner = comment.memberCreator.fullName;
            if(!card.comments) card.comments = [];
            card.comments.push( { 'text' : comment.data.text, 'date' : comment_date } );
          }
        });

        if(card.comments) {
          data2.push(card);
        }
        callback2(null);
      });
    },
    function() {
      callback(null, data2);
    }
  );
}

exports.createStatus = module.exports.createStatus = createStatus;

<|endoftext|>


<commit_before>var db = require('ezseed-database').db
var spawn = require('child_process').spawn
var logger = require('ezseed-logger')('stream')
var fs = require('fs')
var debug = require('debug')('ezseed:stream')
var config = require('./config')

//sizes: https://www.ffmpeg.org/ffmpeg-utils.html
const FFMPEG = [
  '-preset', 'ultrafast',
  '-maxrate', '2500k',
  '-bufsize', '5000k',
  '-pix_fmt', 'yuv420p',
]

const FFMPEG_IOS = [
  '-vcodec', 'libx264',
  '-s', 'vga', //640x480
  '-segment_list_type', 'm3u8',
  '-map', '0:v',
  '-map', '0:a:0',
  '-c:a', 'libfaac', //audio codec
  '-ar', '44100', //audio bitrate
  '-ac', '2', //stereo
  '-f', 'hls',
  '-pix_fmt', 'yuv420p',
  '-crf', '35',
  '-framerate', '12',
  '-g', '24',
  '-hls_time', '10', //hls time in sec
  '-hls_list_size', '6', //max list size
  '-hls_wrap', '18', //wrap after 18
  '-start_number', '1',
]

const FFMPEG_ANDROID = [
  '-vcodec', 'libx264',
  '-vb', '250k',
  '-s', 'vga', //640x480
  '-profile:v baseline',
  '-keyint_min', '150',
  '-pix_fmt', 'yuv420p',
  '-deinterlace',
  '-acodec', 'libmp3lame',
  '-f', 'mp4',
  '-movflags',
  'frag_keyframe+empty_moov+faststart',
  '-crf', '35',
  '-framerate', '12',
  '-g', '24'
]

const FFMPEG_DESKTOP = [
  '-vcodec', 'libx264',
  '-acodec', 'libfaac',
  // '-framerate', '30',
  // '-g', '60',
  '-crf', '18',
  '-vb', '128k',
  '-ac', '2',
  '-ar', '44100',
  '-f', 'flv'
]

var spawner = function(res, arguments) {

  debug('ffmpeg ', arguments.join(' '))

  var ff = spawn('ffmpeg', arguments, {cwd: config.tmp, stdio: ['ignore', 'pipe', 'pipe']})

  ff.stdout.pipe(res)

  ff.stderr.on('data', function(d) {
    debug(d.toString())
  })

  ff.stdout.on('close', function() {
    res.end()
  })

  res.on('close', function() {
    ff.kill()
    res.end()
  })

  res.on('eroor', function() {
    ff.kill()
    res.end()
  })

  ff.on('exit', function(code) {
    debug('ffmpeg closed with code', code)
    res.end()
  })
}

var stream = {
  ios: function(res, file) {
    res.type('m3u8')
    spawner(res,
        ['-i', file.path]
        .concat(FFMPEG_IOS)
        .concat(FFMPEG)
        .concat(['pipe:1'])
    )
  },
  android: function(res, file) {
    res.type('mp4')
    spawner(res,
      ['-i', file.path]
      .concat(FFMPEG_ANDROID)
      .concat(FFMPEG)
      .concat(['pipe:1'])
    )
  },
  desktop: function(res, file) {
    res.type('flv')
    spawner(res,
      ['-i', file.path]
      .concat(FFMPEG_DESKTOP)
      .concat(FFMPEG)
      .concat(['pipe:1'])
    )
  }
}

module.exports = function(req, res) {

  db.files.get(req.params.id, function(err, file) {
    if(err) {
      logger.error('File download - ', err.message)
      return res.status(500).send({error: err.message}).end()
    }

    if(!file || !fs.existsSync(file.path)) {
      return res.status(404).send({error: 'File not found'}).end()
    }

    debug('streaming file', req.params.os)

    if(req.params.os) {
      req.params.os = req.params.os.toLowerCase()
    }

    //http://hgoebl.github.io/mobile-detect.js/doc/MobileDetect.html#os
    switch(req.params.os) {
      case 'ios':
      case 'safari':
        return stream.ios(res, file)
        break;
      case 'androidos':
        return stream.android(res, file)
        break;
      //desktop
      default:
        return stream.desktop(res, file)
        break;
    }

  })

}
<commit_msg>fix(stream): Audio codec libfaac not found, replacing with aac<commit_after>var db = require('ezseed-database').db
var spawn = require('child_process').spawn
var logger = require('ezseed-logger')('stream')
var fs = require('fs')
var debug = require('debug')('ezseed:stream')
var config = require('./config')

//sizes: https://www.ffmpeg.org/ffmpeg-utils.html
const FFMPEG = [
  '-preset', 'ultrafast',
  '-maxrate', '2500k',
  '-bufsize', '5000k',
  '-pix_fmt', 'yuv420p',
]

const FFMPEG_IOS = [
  '-vcodec', 'libx264',
  '-s', 'vga', //640x480
  '-segment_list_type', 'm3u8',
  '-map', '0:v',
  '-map', '0:a:0',
  '-c:a', 'aac', //audio codec
  '-ar', '44100', //audio bitrate
  '-ac', '2', //stereo
  '-f', 'hls',
  '-pix_fmt', 'yuv420p',
  '-crf', '35',
  '-framerate', '12',
  '-g', '24',
  '-hls_time', '10', //hls time in sec
  '-hls_list_size', '6', //max list size
  '-hls_wrap', '18', //wrap after 18
  '-start_number', '1',
]

const FFMPEG_ANDROID = [
  '-vcodec', 'libx264',
  '-vb', '250k',
  '-s', 'vga', //640x480
  '-profile:v baseline',
  '-keyint_min', '150',
  '-pix_fmt', 'yuv420p',
  '-deinterlace',
  '-acodec', 'libmp3lame',
  '-f', 'mp4',
  '-movflags',
  'frag_keyframe+empty_moov+faststart',
  '-crf', '35',
  '-framerate', '12',
  '-g', '24'
]

const FFMPEG_DESKTOP = [
  '-vcodec', 'libx264',
  '-acodec', 'aac',
  // '-framerate', '30',
  // '-g', '60',
  '-crf', '18',
  '-vb', '128k',
  '-ac', '2',
  '-ar', '44100',
  '-f', 'flv'
]

var spawner = function(res, arguments) {

  debug('ffmpeg ', arguments.join(' '))

  var ff = spawn('ffmpeg', arguments, {cwd: config.tmp, stdio: ['ignore', 'pipe', 'pipe']})

  ff.stdout.pipe(res)

  ff.stderr.on('data', function(d) {
    debug(d.toString())
  })

  ff.stdout.on('close', function() {
    res.end()
  })

  res.on('close', function() {
    ff.kill()
    res.end()
  })

  res.on('eroor', function() {
    ff.kill()
    res.end()
  })

  ff.on('exit', function(code) {
    debug('ffmpeg closed with code', code)
    res.end()
  })
}

var stream = {
  ios: function(res, file) {
    res.type('m3u8')
    spawner(res,
        ['-i', file.path]
        .concat(FFMPEG_IOS)
        .concat(FFMPEG)
        .concat(['pipe:1'])
    )
  },
  android: function(res, file) {
    res.type('mp4')
    spawner(res,
      ['-i', file.path]
      .concat(FFMPEG_ANDROID)
      .concat(FFMPEG)
      .concat(['pipe:1'])
    )
  },
  desktop: function(res, file) {
    res.type('flv')
    spawner(res,
      ['-i', file.path]
      .concat(FFMPEG_DESKTOP)
      .concat(FFMPEG)
      .concat(['pipe:1'])
    )
  }
}

module.exports = function(req, res) {

  db.files.get(req.params.id, function(err, file) {
    if(err) {
      logger.error('File download - ', err.message)
      return res.status(500).send({error: err.message}).end()
    }

    if(!file || !fs.existsSync(file.path)) {
      return res.status(404).send({error: 'File not found'}).end()
    }

    debug('streaming file', req.params.os)

    if(req.params.os) {
      req.params.os = req.params.os.toLowerCase()
    }

    //http://hgoebl.github.io/mobile-detect.js/doc/MobileDetect.html#os
    switch(req.params.os) {
      case 'ios':
      case 'safari':
        return stream.ios(res, file)
        break;
      case 'androidos':
        return stream.android(res, file)
        break;
      //desktop
      default:
        return stream.desktop(res, file)
        break;
    }

  })

}
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var Duplex = require('stream').Duplex;
var utils = require('../lib/utils');

exports.Stream = Stream;

var MAX_HTTP_PAYLOAD_SIZE = 16383; // TODO: this is repeated in multiple files

function Stream(log) {
  var self = this;

  Duplex.call(this);

  this.upstream = new Duplex({ objectMode: true });
  this._read = this.upstream._read = function noop() {};
  this.upstream._write = function(frame, encoding, done) {
    self.emit('receiving', frame);
    done();
  };

  this._log = log || require('./utils').nolog;

  this._initializeState();

  this._initializeFlowControl();
}

Stream.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Stream } });

// Managing the stream
// -------------------

Stream.prototype._send = function _send(frame) {
  this.upstream.push(frame);
  this.emit('sending', frame);
};

Stream.prototype.promise = function promise(headers) {
  this._send({
    type: 'PUSH_PROMISE',
    headers: headers
  });
};

Stream.prototype.open = function open(headers, priority) {
  this._send({
    type: 'HEADERS',
    priority: priority,
    headers: headers
  });
};

Stream.prototype.close = function close() {
  this._send({
    type: 'RST_STREAM'
  });
};

Stream.prototype.reset = function reset(error) {
  this._send({
    type: 'RST_STREAM',
    error: error
  });
};

// [Stream States](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#StreamStates)
// ----------------
//
//                           +--------+
//                     PP    |        |    PP
//                  ,--------|  idle  |--------.
//                 /         |        |         \
//                v          +--------+          v
//         +----------+          |           +----------+
//         |          |          | H         |          |
//     ,---| reserved |          |           | reserved |---.
//     |   | (local)  |          v           | (remote) |   |
//     |   +----------+      +--------+      +----------+   |
//     |      |          ES  |        |  ES          |      |
//     |      | H    ,-------|  open  |-------.      | H    |
//     |      |     /        |        |        \     |      |
//     |      v    v         +--------+         v    v      |
//     |   +----------+          |           +----------+   |
//     |   |   half   |          |           |   half   |   |
//     |   |  closed  |          | R         |  closed  |   |
//     |   | (remote) |          |           | (local)  |   |
//     |   +----------+          |           +----------+   |
//     |        |                v                 |        |
//     |        |  ES / R    +--------+  ES / R    |        |
//     |        `----------->|        |<-----------'        |
//     |  R                  | closed |                  R  |
//     `-------------------->|        |<--------------------'
//                           +--------+

Stream.prototype._initializeState = function _initializeState() {
  this.state = 'IDLE';
  this.on('sending', this._transition.bind(this, true));
  this.on('receiving', this._transition.bind(this, false));
};

Stream.prototype._setState = function transition(state) {
  if (this.state !== state) {
    this._log.debug({ state: state }, 'State transition');
    this.emit('state', state);
    this.state = state;
  }
};

// `_transition` is called every time there's an incoming or outgoing frame. It manages state
// transitions, and detects stream errors.
Stream.prototype._transition = function transition(sending, frame) {
  var receiving = !sending;
  var error = false;

  switch (this.state) {
    // All streams start in the "idle" state. In this state, no frames have been exchanged.
    //
    // * Sending or receiving a HEADERS frame causes the stream to become "open".
    // * Sending a PUSH_PROMISE frame marks the associated stream for later use. The stream state
    //   for the reserved stream transitions to "reserved (local)".
    // * Receiving a PUSH_PROMISE frame marks the associated stream as reserved by the remote peer.
    //   The state of the stream becomes "reserved (remote)".
    case 'IDLE':
      if (frame.type === 'HEADERS') {
        this._setState('OPEN');
      } else if (frame.type === 'PUSH_PROMISE') {
        this._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE');
      } else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
        error = true;
      }
      break;

    // A stream in the "reserved (local)" state is one that has been promised by sending a
    // PUSH_PROMISE frame.
    //
    // * The endpoint can send a HEADERS frame. This causes the stream to open in a "half closed
    //   (remote)" state.
    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
    //   releases the stream reservation.
    // * An endpoint MUST NOT send any other type of frame in this state.
    case 'RESERVED_LOCAL':
      if (sending && frame.type === 'HEADERS') {
        this._setState('HALF_CLOSED_REMOTE');
      } else if ( sending && frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
        error = true;
      }
      break;

    // A stream in the "reserved (remote)" state has been reserved by a remote peer.
    //
    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
    //   releases the stream reservation.
    // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
    // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
    case 'RESERVED_REMOTE':
      if (frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } else if (receiving && frame.type === 'HEADERS') {
        this._setState('HALF_CLOSED_LOCAL');
      } else {
        error = true;
      }
      break;

    // The "open" state is where both peers can send frames. In this state, sending peers observe
    // advertised stream level flow control limits.
    //
    // * From this state either endpoint can send a frame with a END_STREAM flag set, which causes
    //   the stream to transition into one of the "half closed" states: an endpoint sending a
    //   END_STREAM flag causes the stream state to become "half closed (local)"; an endpoint
    //   receiving a END_STREAM flag causes the stream state to become "half closed (remote)".
    // * Either endpoint can send a RST_STREAM frame from this state, causing it to transition
    //   immediately to "closed".
    case 'OPEN':
      if (frame.flags && frame.flags.END_STREAM) {
        this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
      } else if (frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } else if (frame.type !== 'DATA') { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
        error = true;
      }
      break;

    // A stream that is "half closed (local)" cannot be used for sending frames.
    //
    // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
    //   flag is received, or when either peer sends a RST_STREAM frame.
    case 'HALF_CLOSED_LOCAL':
      if (frame.type === 'RST_STREAM' || (receiving && frame.flags && frame.flags.END_STREAM)) {
        this._setState('CLOSED');
      } else if (sending) { // TODO: what is valid here?
        error = true;
      }
      break;

    // A stream that is "half closed (remote)" is no longer being used by the peer to send frames.
    // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
    // if it performs flow control.
    //
    // * If an endpoint receives additional frames for a stream that is in this state it MUST
    //   respond with a stream error of type STREAM_CLOSED.
    // * A stream can transition from this state to "closed" by sending a frame that contains a
    //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
    case 'HALF_CLOSED_REMOTE':
      if (frame.type === 'RST_STREAM' || (sending && frame.flags && frame.flags.END_STREAM)) {
        this._setState('CLOSED');
      } else if (receiving) { //  // TODO: what is valid here?
        error = true;
      }
      break;

    // The "closed" state is the terminal state.
    //
    // * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame
    //   after receiving a RST_STREAM or a frame containing a END_STREAM flag on that stream MUST
    //   treat that as a stream error of type STREAM_CLOSED.
    // * If this state is reached as a result of sending a RST_STREAM frame, the peer that receives
    //   the RST_STREAM might have already sent - or enqueued for sending - frames on the stream
    //   that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that
    //   it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
    //   to limit the period over which it ignores frames and treat frames that arrive after this
    //   time as being in error.
    // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
    //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
    //   can be used to close any of those streams.
    case 'CLOSED':
      error = true;
      break;
  }
  // TODO: DATA frame handling. Sending is allowed in HALF_CLOSED_REMOTE and OPEN?

  if (error) {
    if (sending) {
      this._log.error({ frame: frame, state: this.state }, 'Stream error: sending illegal frame.');
      throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
    } else {
      this._log.error({ frame: frame, state: this.state }, 'Stream error: received illegal frame.');
      this.emit('error', 'PROTOCOL_ERROR');
    }
  }
};

// [Flow control](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#rfc.section.6.9)
// --------------

// Flow control in HTTP/2.0 is implemented using a window kept by each sender on every stream.
// The flow control window is a simple integer value that indicates how many bytes of data the
// sender is permitted to transmit. Two flow control windows are applicable; the stream flow control
// window and the connection flow control window.
Stream.prototype._initializeFlowControl = function _initializeFlowControl() {
  this.window = INITIAL_WINDOW_SIZE;
  this.on('receiving', function(frame) {
    if (frame.type === 'WINDOW_UPDATE') {
      this._updateWindow(frame);
    }
  });
};

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow
// control windows that it maintains by the difference between the new value and the old value.
Stream.prototype.setInitialWindowSize = function setInitialWindowSize(initialWindowSize) {
  this.window = this.window - this._initialWindowSize + initialWindowSize;
  this._initialWindowSize = initialWindowSize;
};

// Flow control can be disabled for all streams on the connection using the
// SETTINGS_FLOW_CONTROL_OPTIONS setting.
Stream.prototype.disableFlowControl = function disableFlowControl() {
  this.window = Infinity;
};

// A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
// specified in the frame.
//
// Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
// END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
// flag set is ignored.
Stream.prototype._updateWindow = function _received(frame) {
  if (frame.flags.END_FLOW_CONTROL) {
    this.disableFlowControl();
  } else {
    this.window += frame.window_size;
  }
  this.emit('window_update');
};

// After sending a flow controlled frame, the sender reduces the space available in both windows by
// the length of the transmitted frame. For flow control calculations, the 8 byte frame header is
// not counted.
Stream.prototype._write = function _write(buffer, encoding, done) {
  var chunks = utils.cut(buffer, MAX_HTTP_PAYLOAD_SIZE);
  var sent = 0;

  while (chunks.length > 0 && chunks[0].length <= this.window) {
    var chunk = chunks.shift();
    sent += chunk.length;
    this.window -= chunk.length;
    this._send({
      type: 'DATA',
      flags: {},
      data: chunk
    });
  }

  if (chunks.length === 0) {
    done();
  } else {
    this.once('window_update', this._write.bind(this, buffer.slice(sent), encoding, done));
  }
};
<commit_msg>Small corrections for Stream<commit_after>var Duplex = require('stream').Duplex;
var utils = require('../lib/utils');

exports.Stream = Stream;

var MAX_HTTP_PAYLOAD_SIZE = 16383; // TODO: this is repeated in multiple files

function Stream(log) {
  var self = this;

  Duplex.call(this);

  this.upstream = new Duplex({ objectMode: true });
  this._read = this.upstream._read = function noop() {};
  this.upstream._write = function(frame, encoding, done) {
    self.emit('receiving', frame);
    done();
  };

  this._log = log || require('./utils').nolog;

  this._initializeState();

  this._initializeFlowControl();
}

Stream.prototype = Object.create(Duplex.prototype, { constructor: { value: Stream } });

// Managing the stream
// -------------------

Stream.prototype._send = function _send(frame) {
  this.upstream.push(frame);
  this.emit('sending', frame);
};

Stream.prototype.promise = function promise(headers) {
  this._send({
    type: 'PUSH_PROMISE',
    headers: headers
  });
};

Stream.prototype.open = function open(headers, priority) {
  this._send({
    type: 'HEADERS',
    priority: priority,
    headers: headers
  });
};

Stream.prototype.close = function close() {
  this._send({
    type: 'RST_STREAM'
  });
};

Stream.prototype.reset = function reset(error) {
  this._send({
    type: 'RST_STREAM',
    error: error
  });
};

// [Stream States](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#StreamStates)
// ----------------
//
//                           +--------+
//                     PP    |        |    PP
//                  ,--------|  idle  |--------.
//                 /         |        |         \
//                v          +--------+          v
//         +----------+          |           +----------+
//         |          |          | H         |          |
//     ,---| reserved |          |           | reserved |---.
//     |   | (local)  |          v           | (remote) |   |
//     |   +----------+      +--------+      +----------+   |
//     |      |          ES  |        |  ES          |      |
//     |      | H    ,-------|  open  |-------.      | H    |
//     |      |     /        |        |        \     |      |
//     |      v    v         +--------+         v    v      |
//     |   +----------+          |           +----------+   |
//     |   |   half   |          |           |   half   |   |
//     |   |  closed  |          | R         |  closed  |   |
//     |   | (remote) |          |           | (local)  |   |
//     |   +----------+          |           +----------+   |
//     |        |                v                 |        |
//     |        |  ES / R    +--------+  ES / R    |        |
//     |        `----------->|        |<-----------'        |
//     |  R                  | closed |                  R  |
//     `-------------------->|        |<--------------------'
//                           +--------+

Stream.prototype._initializeState = function _initializeState() {
  this.state = 'IDLE';
  this.on('sending', this._transition.bind(this, true));
  this.on('receiving', this._transition.bind(this, false));
};

Stream.prototype._setState = function transition(state) {
  if (this.state !== state) {
    this._log.debug({ state: state }, 'State transition');
    this.emit('state', state);
    this.state = state;
  }
};

// `_transition` is called every time there's an incoming or outgoing frame. It manages state
// transitions, and detects stream errors.
Stream.prototype._transition = function transition(sending, frame) {
  var receiving = !sending;
  var error = false;

  switch (this.state) {
    // All streams start in the **idle** state. In this state, no frames have been exchanged.
    //
    // * Sending or receiving a HEADERS frame causes the stream to become "open".
    // * Sending a PUSH_PROMISE frame marks the associated stream for later use. The stream state
    //   for the reserved stream transitions to "reserved (local)".
    // * Receiving a PUSH_PROMISE frame marks the associated stream as reserved by the remote peer.
    //   The state of the stream becomes "reserved (remote)".
    case 'IDLE':
      if (frame.type === 'HEADERS') {
        this._setState('OPEN');
      } else if (frame.type === 'PUSH_PROMISE') {
        this._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE');
      } else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
        error = true;
      }
      break;

    // A stream in the **reserved (local)** state is one that has been promised by sending a
    // PUSH_PROMISE frame.
    //
    // * The endpoint can send a HEADERS frame. This causes the stream to open in a "half closed
    //   (remote)" state.
    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
    //   releases the stream reservation.
    // * An endpoint MUST NOT send any other type of frame in this state.
    case 'RESERVED_LOCAL':
      if (sending && frame.type === 'HEADERS') {
        this._setState('HALF_CLOSED_REMOTE');
      } else if ( sending && frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
        error = true;
      }
      break;

    // A stream in the **reserved (remote)** state has been reserved by a remote peer.
    //
    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
    //   releases the stream reservation.
    // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
    // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
    case 'RESERVED_REMOTE':
      if (frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } else if (receiving && frame.type === 'HEADERS') {
        this._setState('HALF_CLOSED_LOCAL');
      } else {
        error = true;
      }
      break;

    // The **open** state is where both peers can send frames. In this state, sending peers observe
    // advertised stream level flow control limits.
    //
    // * From this state either endpoint can send a frame with a END_STREAM flag set, which causes
    //   the stream to transition into one of the "half closed" states: an endpoint sending a
    //   END_STREAM flag causes the stream state to become "half closed (local)"; an endpoint
    //   receiving a END_STREAM flag causes the stream state to become "half closed (remote)".
    // * Either endpoint can send a RST_STREAM frame from this state, causing it to transition
    //   immediately to "closed".
    case 'OPEN':
      if (frame.flags && frame.flags.END_STREAM) {
        this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
      } else if (frame.type === 'RST_STREAM') {
        this._setState('CLOSED');
      } // Anything else is OK
      break;

    // A stream that is **half closed (local)** cannot be used for sending frames.
    //
    // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
    //   flag is received, or when either peer sends a RST_STREAM frame.
    case 'HALF_CLOSED_LOCAL':
      if (frame.type === 'RST_STREAM' || (receiving && frame.flags && frame.flags.END_STREAM)) {
        this._setState('CLOSED');
      } else if (sending) { // TODO: what is valid here?
        error = true;
      }
      break;

    // A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
    // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
    // if it performs flow control.
    //
    // * If an endpoint receives additional frames for a stream that is in this state it MUST
    //   respond with a stream error of type STREAM_CLOSED.
    // * A stream can transition from this state to "closed" by sending a frame that contains a
    //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
    case 'HALF_CLOSED_REMOTE':
      if (frame.type === 'RST_STREAM' || (sending && frame.flags && frame.flags.END_STREAM)) {
        this._setState('CLOSED');
      } else if (receiving) { //  // TODO: what is valid here?
        error = true;
      }
      break;

    // The **closed** state is the terminal state.
    //
    // * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame
    //   after receiving a RST_STREAM or a frame containing a END_STREAM flag on that stream MUST
    //   treat that as a stream error of type STREAM_CLOSED.
    // * If this state is reached as a result of sending a RST_STREAM frame, the peer that receives
    //   the RST_STREAM might have already sent - or enqueued for sending - frames on the stream
    //   that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that
    //   it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
    //   to limit the period over which it ignores frames and treat frames that arrive after this
    //   time as being in error.
    // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
    //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
    //   can be used to close any of those streams.
    case 'CLOSED':
      error = true;
      break;
  }
  // TODO: DATA frame handling. Sending is allowed in HALF_CLOSED_REMOTE and OPEN?

  if (error) {
    if (sending) {
      this._log.error({ frame: frame, state: this.state }, 'Stream error: sending illegal frame.');
      throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
    } else {
      this._log.error({ frame: frame, state: this.state }, 'Stream error: received illegal frame.');
      this.emit('error', 'PROTOCOL_ERROR');
    }
  }
};

// [Flow control](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#rfc.section.6.9)
// --------------

// Flow control in HTTP/2.0 is implemented using a window kept by each sender on every stream.
// The flow control window is a simple integer value that indicates how many bytes of data the
// sender is permitted to transmit. Two flow control windows are applicable; the stream flow control
// window and the connection flow control window.
Stream.prototype._initializeFlowControl = function _initializeFlowControl() {
  this.window = INITIAL_WINDOW_SIZE;
  this.on('receiving', function(frame) {
    if (frame.type === 'WINDOW_UPDATE') {
      this._updateWindow(frame);
    }
  });
};

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow
// control windows that it maintains by the difference between the new value and the old value.
Stream.prototype.setInitialWindowSize = function setInitialWindowSize(initialWindowSize) {
  this.window = this.window - this._initialWindowSize + initialWindowSize;
  this._initialWindowSize = initialWindowSize;
};

// Flow control can be disabled for all streams on the connection using the
// SETTINGS_FLOW_CONTROL_OPTIONS setting.
Stream.prototype.disableFlowControl = function disableFlowControl() {
  this.window = Infinity;
};

// A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
// specified in the frame.
//
// Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
// END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
// flag set is ignored.
Stream.prototype._updateWindow = function _received(frame) {
  if (frame.flags.END_FLOW_CONTROL) {
    this.disableFlowControl();
  } else {
    this.window += frame.window_size;
  }
  this.emit('window_update');
};

// After sending a flow controlled frame, the sender reduces the space available in both windows by
// the length of the transmitted frame. For flow control calculations, the 8 byte frame header is
// not counted.
Stream.prototype._write = function _write(buffer, encoding, done) {
  var chunks = utils.cut(buffer, MAX_HTTP_PAYLOAD_SIZE);
  var sent = 0;

  while (chunks.length > 0 && chunks[0].length <= this.window) {
    var chunk = chunks.shift();
    sent += chunk.length;
    this.window -= chunk.length;
    this._send({
      type: 'DATA',
      flags: {},
      data: chunk
    });
  }

  if (chunks.length === 0) {
    done();
  } else {
    this.once('window_update', this._write.bind(this, buffer.slice(sent), encoding, done));
  }
};
<|endoftext|>


<commit_before>
function Symbol(name) {
    this.name = function () { return name; }

    this.asString = function () { return name; }

    this.asCode = function () {
        return 'symbols.symbol(' + JSON.stringify(name) + ')';
    }
}

function createSymbol(name) {
    return new Symbol(name);
}

module.exports = {
    symbol: createSymbol,
    isSymbol: function (value) { return value instanceof Symbol; }
};<commit_msg>Trying symbols as Code, WIP<commit_after>
function Symbol(name) {
    this.name = function () { return name; }

    this.asString = function () { return name; }

    this.asCode = function (compilefn, context) {
        return 'symbols.symbol(' + JSON.stringify(name) + ')';
    }
}

function createSymbol(name) {
    return new Symbol(name);
}

module.exports = {
    symbol: createSymbol,
    isSymbol: function (value) { return value instanceof Symbol; }
};<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.4.0
(function() {
  var Teacup, doctypes, elements, merge_elements,
    __slice = [].slice,
    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  doctypes = {
    'default': '<!DOCTYPE html>',
    '5': '<!DOCTYPE html>',
    'xml': '<?xml version="1.0" encoding="utf-8" ?>',
    'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
    'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
    'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
    '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
    'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
    'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
    'ce': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ce-html-1.0-transitional.dtd">'
  };

  elements = {
    regular: 'a abbr address article aside audio b bdi bdo blockquote body button\
 canvas caption cite code colgroup datalist dd del details dfn div dl dt em\
 fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup\
 html i iframe ins kbd label legend li map mark menu meter nav noscript object\
 ol optgroup option output p pre progress q rp rt ruby s samp section\
 select small span strong sub summary sup table tbody td textarea tfoot\
 th thead time title tr u ul video',
    raw: 'script style',
    "void": 'area base br col command embed hr img input keygen link meta param\
 source track wbr',
    obsolete: 'applet acronym bgsound dir frameset noframes isindex listing\
 nextid noembed plaintext rb strike xmp big blink center font marquee multicol\
 nobr spacer tt',
    obsolete_void: 'basefont frame'
  };

  merge_elements = function() {
    var a, args, element, result, _i, _j, _len, _len1, _ref;
    args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    result = [];
    for (_i = 0, _len = args.length; _i < _len; _i++) {
      a = args[_i];
      _ref = elements[a].split(' ');
      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
        element = _ref[_j];
        if (__indexOf.call(result, element) < 0) {
          result.push(element);
        }
      }
    }
    return result;
  };

  Teacup = (function() {

    function Teacup() {
      this.htmlOut = null;
    }

    Teacup.prototype.resetBuffer = function(html) {
      var previous;
      if (html == null) {
        html = null;
      }
      previous = this.htmlOut;
      this.htmlOut = html;
      return previous;
    };

    Teacup.prototype.render = function() {
      var args, previous, result, template;
      template = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      previous = this.resetBuffer('');
      try {
        template.apply(null, args);
      } finally {
        result = this.resetBuffer(previous);
      }
      return result;
    };

    Teacup.prototype.cede = function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return this.render.apply(this, args);
    };

    Teacup.prototype.renderable = function(template) {
      var teacup;
      teacup = this;
      return function() {
        var args, result;
        args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
        if (teacup.htmlOut === null) {
          teacup.htmlOut = '';
          try {
            template.apply(this, args);
          } finally {
            result = teacup.resetBuffer();
          }
          return result;
        } else {
          return template.apply(this, args);
        }
      };
    };

    Teacup.prototype.renderAttr = function(name, value) {
      var k, v;
      if (!(value != null) || value === false) {
        return '';
      }
      if (name === 'data' && typeof value === 'object') {
        return ((function() {
          var _results;
          _results = [];
          for (k in value) {
            v = value[k];
            _results.push(this.renderAttr("data-" + k, v));
          }
          return _results;
        }).call(this)).join('');
      }
      if (value === true) {
        value = name;
      }
      return " " + name + "=" + (this.quote(this.escape(value.toString())));
    };

    Teacup.prototype.attrOrder = ['id', 'class'];

    Teacup.prototype.renderAttrs = function(obj) {
      var name, result, value, _i, _len, _ref;
      result = '';
      _ref = this.attrOrder;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        if (!(name in obj)) {
          continue;
        }
        result += this.renderAttr(name, obj[name]);
        delete obj[name];
      }
      for (name in obj) {
        value = obj[name];
        result += this.renderAttr(name, value);
      }
      return result;
    };

    Teacup.prototype.renderContents = function(contents) {
      if (!(contents != null)) {

      } else if (typeof contents === 'function') {
        return contents.call(this);
      } else {
        return this.text(contents);
      }
    };

    Teacup.prototype.isSelector = function(string) {
      var _ref;
      return string.length > 1 && ((_ref = string[0]) === '#' || _ref === '.');
    };

    Teacup.prototype.parseSelector = function(selector) {
      var classes, id, klass, token, _i, _len, _ref, _ref1;
      id = null;
      classes = [];
      _ref = selector.split('.');
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        token = _ref[_i];
        if (id) {
          classes.push(token);
        } else {
          _ref1 = token.split('#'), klass = _ref1[0], id = _ref1[1];
          if (klass !== '') {
            classes.push(token);
          }
        }
      }
      return {
        id: id,
        classes: classes
      };
    };

    Teacup.prototype.normalizeArgs = function(args) {
      var arg, attrs, classes, contents, id, index, selector, _i, _len;
      attrs = {};
      selector = null;
      contents = null;
      for (index = _i = 0, _len = args.length; _i < _len; index = ++_i) {
        arg = args[index];
        switch (typeof arg) {
          case 'string':
            if (index === 0 && this.isSelector(arg)) {
              selector = this.parseSelector(arg);
            } else {
              contents = arg;
            }
            break;
          case 'function':
          case 'number':
          case 'boolean':
            contents = arg;
            break;
          case 'object':
            attrs = arg;
            break;
          default:
            console.log("Teacup: invalid argument: " + (arg.toString()));
        }
      }
      if (selector != null) {
        id = selector.id, classes = selector.classes;
        if (id != null) {
          attrs.id = id;
        }
        if (classes != null ? classes.length : void 0) {
          attrs["class"] = classes.join(' ');
        }
      }
      return {
        attrs: attrs,
        contents: contents
      };
    };

    Teacup.prototype.tag = function() {
      var args, attrs, contents, tagName, _ref;
      tagName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      this.raw("<" + tagName + (this.renderAttrs(attrs)) + ">");
      this.renderContents(contents);
      return this.raw("</" + tagName + ">");
    };

    Teacup.prototype.rawTag = function() {
      var args, attrs, contents, tagName, _ref;
      tagName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      this.raw("<" + tagName + (this.renderAttrs(attrs)) + ">");
      this.raw(contents);
      return this.raw("</" + tagName + ">");
    };

    Teacup.prototype.selfClosingTag = function() {
      var args, attrs, contents, tag, _ref;
      tag = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      if (contents) {
        throw new Error("Teacup: <" + tag + "/> must not have content.  Attempted to nest " + content);
      }
      return this.raw("<" + tag + (this.renderAttrs(attrs)) + " />");
    };

    Teacup.prototype.coffeescript = function() {
      throw new Error('Teacup: coffeescript tag not implemented');
    };

    Teacup.prototype.comment = function(text) {
      return this.raw("<!--" + (this.escape(text)) + "-->");
    };

    Teacup.prototype.doctype = function(type) {
      if (type == null) {
        type = 5;
      }
      return this.raw(doctypes[type]);
    };

    Teacup.prototype.ie = function(condition, contents) {
      this.raw("<!--[if " + (this.escape(condition)) + "]>");
      this.renderContents(contents);
      return this.raw("<![endif]-->");
    };

    Teacup.prototype.text = function(s) {
      if (this.htmlOut == null) {
        throw new Error("Teacup: can't call a tag function outside a rendering context");
      }
      return this.htmlOut += s && this.escape(s.toString()) || '';
    };

    Teacup.prototype.raw = function(s) {
      return this.htmlOut += s;
    };

    Teacup.prototype.escape = function(text) {
      return text.toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
    };

    Teacup.prototype.quote = function(value) {
      var q;
      q = (__indexOf.call(value, '"') >= 0 ? "'" : '"');
      return q + value + q;
    };

    Teacup.prototype.tags = function() {
      var bound, method, tagName, _fn, _fn1, _fn2, _fn3, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3,
        _this = this;
      bound = {};
      _ref = 'cede coffeescript comment doctype escape ie raw render renderable script tag text'.split(' ');
      _fn = function(method) {
        return bound[method] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this[method].apply(_this, args);
        };
      };
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        method = _ref[_i];
        _fn(method);
      }
      _ref1 = merge_elements('regular', 'obsolete');
      _fn1 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.tag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        tagName = _ref1[_j];
        _fn1(tagName);
      }
      _ref2 = merge_elements('raw');
      _fn2 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.rawTag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        tagName = _ref2[_k];
        _fn2(tagName);
      }
      _ref3 = merge_elements('void', 'obsolete_void');
      _fn3 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.selfClosingTag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
        tagName = _ref3[_l];
        _fn3(tagName);
      }
      return bound;
    };

    return Teacup;

  })();

  if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
    module.exports = Teacup;
  } else {
    window.teacup = Teacup;
  }

}).call(this);
<commit_msg>Commit compiled js<commit_after>// Generated by CoffeeScript 1.4.0
(function() {
  var Teacup, doctypes, elements, merge_elements,
    __slice = [].slice,
    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  doctypes = {
    'default': '<!DOCTYPE html>',
    '5': '<!DOCTYPE html>',
    'xml': '<?xml version="1.0" encoding="utf-8" ?>',
    'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
    'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
    'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
    '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
    'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
    'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
    'ce': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "ce-html-1.0-transitional.dtd">'
  };

  elements = {
    regular: 'a abbr address article aside audio b bdi bdo blockquote body button\
 canvas caption cite code colgroup datalist dd del details dfn div dl dt em\
 fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup\
 html i iframe ins kbd label legend li map mark menu meter nav noscript object\
 ol optgroup option output p pre progress q rp rt ruby s samp section\
 select small span strong sub summary sup table tbody td textarea tfoot\
 th thead time title tr u ul video',
    raw: 'script style',
    "void": 'area base br col command embed hr img input keygen link meta param\
 source track wbr',
    obsolete: 'applet acronym bgsound dir frameset noframes isindex listing\
 nextid noembed plaintext rb strike xmp big blink center font marquee multicol\
 nobr spacer tt',
    obsolete_void: 'basefont frame'
  };

  merge_elements = function() {
    var a, args, element, result, _i, _j, _len, _len1, _ref;
    args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    result = [];
    for (_i = 0, _len = args.length; _i < _len; _i++) {
      a = args[_i];
      _ref = elements[a].split(' ');
      for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
        element = _ref[_j];
        if (__indexOf.call(result, element) < 0) {
          result.push(element);
        }
      }
    }
    return result;
  };

  Teacup = (function() {

    function Teacup() {
      this.htmlOut = null;
    }

    Teacup.prototype.resetBuffer = function(html) {
      var previous;
      if (html == null) {
        html = null;
      }
      previous = this.htmlOut;
      this.htmlOut = html;
      return previous;
    };

    Teacup.prototype.render = function() {
      var args, previous, result, template;
      template = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      previous = this.resetBuffer('');
      try {
        template.apply(null, args);
      } finally {
        result = this.resetBuffer(previous);
      }
      return result;
    };

    Teacup.prototype.cede = function() {
      var args;
      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return this.render.apply(this, args);
    };

    Teacup.prototype.renderable = function(template) {
      var teacup;
      teacup = this;
      return function() {
        var args, result;
        args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
        if (teacup.htmlOut === null) {
          teacup.htmlOut = '';
          try {
            template.apply(this, args);
          } finally {
            result = teacup.resetBuffer();
          }
          return result;
        } else {
          return template.apply(this, args);
        }
      };
    };

    Teacup.prototype.renderAttr = function(name, value) {
      var k, v;
      if ((value == null) || value === false) {
        return '';
      }
      if (name === 'data' && typeof value === 'object') {
        return ((function() {
          var _results;
          _results = [];
          for (k in value) {
            v = value[k];
            _results.push(this.renderAttr("data-" + k, v));
          }
          return _results;
        }).call(this)).join('');
      }
      if (value === true) {
        value = name;
      }
      return " " + name + "=" + (this.quote(this.escape(value.toString())));
    };

    Teacup.prototype.attrOrder = ['id', 'class'];

    Teacup.prototype.renderAttrs = function(obj) {
      var name, result, value, _i, _len, _ref;
      result = '';
      _ref = this.attrOrder;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        name = _ref[_i];
        if (!(name in obj)) {
          continue;
        }
        result += this.renderAttr(name, obj[name]);
        delete obj[name];
      }
      for (name in obj) {
        value = obj[name];
        result += this.renderAttr(name, value);
      }
      return result;
    };

    Teacup.prototype.renderContents = function(contents) {
      if (contents == null) {

      } else if (typeof contents === 'function') {
        return contents.call(this);
      } else {
        return this.text(contents);
      }
    };

    Teacup.prototype.isSelector = function(string) {
      var _ref;
      return string.length > 1 && ((_ref = string[0]) === '#' || _ref === '.');
    };

    Teacup.prototype.parseSelector = function(selector) {
      var classes, id, klass, token, _i, _len, _ref, _ref1;
      id = null;
      classes = [];
      _ref = selector.split('.');
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        token = _ref[_i];
        if (id) {
          classes.push(token);
        } else {
          _ref1 = token.split('#'), klass = _ref1[0], id = _ref1[1];
          if (klass !== '') {
            classes.push(token);
          }
        }
      }
      return {
        id: id,
        classes: classes
      };
    };

    Teacup.prototype.normalizeArgs = function(args) {
      var arg, attrs, classes, contents, id, index, selector, _i, _len;
      attrs = {};
      selector = null;
      contents = null;
      for (index = _i = 0, _len = args.length; _i < _len; index = ++_i) {
        arg = args[index];
        switch (typeof arg) {
          case 'string':
            if (index === 0 && this.isSelector(arg)) {
              selector = this.parseSelector(arg);
            } else {
              contents = arg;
            }
            break;
          case 'function':
          case 'number':
          case 'boolean':
            contents = arg;
            break;
          case 'object':
            attrs = arg;
            break;
          default:
            console.log("Teacup: invalid argument: " + (arg.toString()));
        }
      }
      if (selector != null) {
        id = selector.id, classes = selector.classes;
        if (id != null) {
          attrs.id = id;
        }
        if (classes != null ? classes.length : void 0) {
          attrs["class"] = classes.join(' ');
        }
      }
      return {
        attrs: attrs,
        contents: contents
      };
    };

    Teacup.prototype.tag = function() {
      var args, attrs, contents, tagName, _ref;
      tagName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      this.raw("<" + tagName + (this.renderAttrs(attrs)) + ">");
      this.renderContents(contents);
      return this.raw("</" + tagName + ">");
    };

    Teacup.prototype.rawTag = function() {
      var args, attrs, contents, tagName, _ref;
      tagName = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      this.raw("<" + tagName + (this.renderAttrs(attrs)) + ">");
      this.raw(contents);
      return this.raw("</" + tagName + ">");
    };

    Teacup.prototype.selfClosingTag = function() {
      var args, attrs, contents, tag, _ref;
      tag = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      _ref = this.normalizeArgs(args), attrs = _ref.attrs, contents = _ref.contents;
      if (contents) {
        throw new Error("Teacup: <" + tag + "/> must not have content.  Attempted to nest " + content);
      }
      return this.raw("<" + tag + (this.renderAttrs(attrs)) + " />");
    };

    Teacup.prototype.coffeescript = function() {
      throw new Error('Teacup: coffeescript tag not implemented');
    };

    Teacup.prototype.comment = function(text) {
      return this.raw("<!--" + (this.escape(text)) + "-->");
    };

    Teacup.prototype.doctype = function(type) {
      if (type == null) {
        type = 5;
      }
      return this.raw(doctypes[type]);
    };

    Teacup.prototype.ie = function(condition, contents) {
      this.raw("<!--[if " + (this.escape(condition)) + "]>");
      this.renderContents(contents);
      return this.raw("<![endif]-->");
    };

    Teacup.prototype.text = function(s) {
      if (this.htmlOut == null) {
        throw new Error("Teacup: can't call a tag function outside a rendering context");
      }
      return this.htmlOut += s && this.escape(s.toString()) || '';
    };

    Teacup.prototype.raw = function(s) {
      return this.htmlOut += s;
    };

    Teacup.prototype.escape = function(text) {
      return text.toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
    };

    Teacup.prototype.quote = function(value) {
      var q;
      q = (__indexOf.call(value, '"') >= 0 ? "'" : '"');
      return q + value + q;
    };

    Teacup.prototype.tags = function() {
      var bound, method, tagName, _fn, _fn1, _fn2, _fn3, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3,
        _this = this;
      bound = {};
      _ref = 'cede coffeescript comment doctype escape ie raw render renderable script tag text'.split(' ');
      _fn = function(method) {
        return bound[method] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this[method].apply(_this, args);
        };
      };
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        method = _ref[_i];
        _fn(method);
      }
      _ref1 = merge_elements('regular', 'obsolete');
      _fn1 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.tag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
        tagName = _ref1[_j];
        _fn1(tagName);
      }
      _ref2 = merge_elements('raw');
      _fn2 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.rawTag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) {
        tagName = _ref2[_k];
        _fn2(tagName);
      }
      _ref3 = merge_elements('void', 'obsolete_void');
      _fn3 = function(tagName) {
        return bound[tagName] = function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.selfClosingTag.apply(_this, [tagName].concat(__slice.call(args)));
        };
      };
      for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) {
        tagName = _ref3[_l];
        _fn3(tagName);
      }
      return bound;
    };

    return Teacup;

  })();

  if (typeof module !== "undefined" && module !== null ? module.exports : void 0) {
    module.exports = Teacup;
  } else {
    window.teacup = Teacup;
  }

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