<commit_before>var util = require('util');
var winston = require('winston-uber');
var raven = require('uber-raven');
var ravenParsers = require('uber-raven/lib/parsers.js');
var extend = require('xtend');
var stringify = require('json-stringify-safe');
var isError = require('core-util-is').isError;

var CIRCULAR_STRING = '[Circular';

function computeErrLoc(msg) {
    var line = new Error(msg).stack.split('\n')[10];
    var errLoc = line ?
        line.replace(/^\s*at .*\/([^\:\/]*).*$/, "$1") : '';

    return errLoc;
}

function SentryLogger(options) {
    if (!(this instanceof SentryLogger)) {
        return new SentryLogger(options);
    }

    winston.Transport.call(this, options);
    this.name = 'SentryLogger';
    this.level = options.level || 'error';
    this.enabled = typeof(options.enabled) === 'boolean' ?
        options.enabled : true;
    this.dsn = options.dsn;
    this.defaultTags = options.tags || {};
    this.computeErrLoc = options.computeErrLoc || computeErrLoc;

    if (!this.defaultTags.pid) {
        this.defaultTags.pid = process.pid;
    }

    var ravenErrorHandler = typeof options.onRavenError === 'function' ?
        options.onRavenError : function (e) {
            // do not warn() on 429 errors
            if (e.message.indexOf('429') >= 0) {
                return;
            }

            winston.warn('Raven failed to upload to Sentry: ', {
                message: e.message,
                stack: e.stack,
                reason: e.reason,
                statusCode: e.statusCode
            });
        };

    if (!this.dsn && !options.ravenClient) {
        throw new Error('Cannot construct a Sentry transport without a DSN');
    }
    this.ravenClient = options.ravenClient ||
        new raven.Client(this.enabled && this.dsn);
    this.ravenClient.loggerName = 'SentryLogger';
    // Unsure how statsd could integrate with this due to ubiquity of the logger
    // and no initialization function for winston transports
    this.ravenClient.on('error', ravenErrorHandler);

    this.captureError = this.ravenClient.captureError.bind(this.ravenClient);
    this.captureMessage = this.ravenClient.captureMessage
        .bind(this.ravenClient);
    this.sentryProber = options.sentryProber || null;
    this.sentryProberCallbackImmediately = !options.sentryProberDetectFailuresBy;
    this.sentryProberDetectFailuresByCallback = options.sentryProberDetectFailuresBy === SentryLogger.detectBy.CALLBACK;
    this.sentryProberDetectFailuresByEvent = options.sentryProberDetectFailuresBy === SentryLogger.detectBy.EVENT;
    this.sentryProberDetectFailuresByEventSuccessEvent = options.sentryProberDetectFailuresByEventSuccessEvent;
    this.sentryProberDetectFailuresByEventFailureEvent = options.sentryProberDetectFailuresByEventFailureEvent;
}

SentryLogger.detectBy = {
    CALLBACK: 'callback',
    EVENT: 'event'
};

util.inherits(SentryLogger, winston.Transport);

SentryLogger.prototype.name = 'SentryLogger';

SentryLogger.prototype.log = function(level, msg, meta, callback) {
    var thunk;

    if (!meta || typeof meta !== 'object') {
        meta = {};
    }
    // store original meta because we might serialize it
    var originalMeta = meta;
    var stringMeta = stringify(meta);

    // To avoid sending circular data to sentry we check
    // whether its circular using json-stringify-safe and then
    // stringify(parse(meta)) to make it not circular
    meta = stringMeta.indexOf(CIRCULAR_STRING) !== -1 ?
        JSON.parse(stringMeta) : meta;

    var sentryArgs = {
        extra: meta,
        tags: extend(this.defaultTags, meta.tags || {})
    };

    if (meta.tags) {
        delete meta.tags;
    }

    if (!sentryArgs.tags.nodeVersion) {
        sentryArgs.tags.nodeVersion = process.version;
    }

    // if we have an error in the meta object we want to
    // serialize it in a sentry friendly way
    var metaTuple = containsError(originalMeta);
    if (metaTuple) {
        sentryArgs.extra.originalMessage = msg;

        if (metaTuple.key) {
            delete sentryArgs.extra[metaTuple.key];
        }

        msg = metaTuple.value;
    }

    // if we have a req in the meta object we want to
    // use sentry's HTTP request serializer to pretty print the
    // http Request rather then sending the raw representation
    var reqTuple = containsRequest(originalMeta);
    if (reqTuple) {
        var req = ravenParsers .parseRequest(reqTuple.value)
            ['sentry.interfaces.Http'];
        req.env = { REMOTE_ADDR: req.env.REMOTE_ADDR };
        sentryArgs['sentry.interfaces.Http'] = req;

        if (reqTuple.key) {
            delete sentryArgs.extra[reqTuple.key];
        }
    }

    var errLoc;

    if(this.sentryProber && this.sentryProberDetectFailuresByEvent) {
        this.ravenClient.once(
            this.sentryProberDetectFailuresByEventFailureEvent || 'error',
            function onProberFailure(err) {
                callback(err || new Error('Sentry Prober emitted failure event.'));
            }
        );
        this.ravenClient.once(
            this.sentryProberDetectFailuresByEventSuccessEvent || 'logged',
            function onProberSuccess(message) {
                callback(null, message || true);
            }
        );
    }

    if (isError(msg)) {
        errLoc = this.computeErrLoc(msg.message);
        msg.message = errLoc + ": " + msg.message;

        if (this.sentryProber) {
            thunk = this.captureError.bind(null, msg, sentryArgs);
            this.sentryProber.probe(thunk, this.sentryProberDetectFailuresByCallback ? callback : null);
        } else {
            this.captureError(msg, sentryArgs, callback);
        }
    } else {
        // winston adds in 9 stacks of function calls between the
        // actual .log() call location
        errLoc = this.computeErrLoc(msg);

        if (!sentryArgs.extra.stack) {
            sentryArgs.extra.stack = new Error('stack').stack;
        }

        if (this.sentryProber) {
            thunk = this.captureMessage
                .bind(null, errLoc + ": " + msg, sentryArgs);
            this.sentryProber.probe(thunk, this.sentryProberDetectFailuresByCallback ? callback : null);
        } else {
            this.captureMessage(errLoc + ": " + msg, sentryArgs, callback);
        }
    }

    if (this.sentryProber && this.sentryProberCallbackImmediately) {
        callback(null, true);
    }
};

module.exports = SentryLogger;

function containsError(obj) {
    if (isError(obj)) {
        return { key: null, value: obj };
    }

    var error = null;
    Object.keys(obj).some(function (key) {
        if (isError(obj[key])) {
            error = { key: key, value: obj[key] };
            return true;
        }
    });

    return error;
}

function containsRequest(obj) {
    var req = null;
    Object.keys(obj).some(function (key) {
        if (isRequest(obj[key])) {
            req = { key: key, value: obj[key] };
            return true;
        }
    });

    return req;
}

function isRequest(req) {
    return req &&
        typeof req.headers === 'object' &&
        typeof req.method === 'string' &&
        typeof req.url === 'string' &&
        typeof req.on === 'function';
}
<commit_msg>only warn on prober failure instead of raising an error since that's what the library used to do<commit_after>var util = require('util');
var winston = require('winston-uber');
var raven = require('uber-raven');
var ravenParsers = require('uber-raven/lib/parsers.js');
var extend = require('xtend');
var stringify = require('json-stringify-safe');
var isError = require('core-util-is').isError;

var CIRCULAR_STRING = '[Circular';

function computeErrLoc(msg) {
    var line = new Error(msg).stack.split('\n')[10];
    var errLoc = line ?
        line.replace(/^\s*at .*\/([^\:\/]*).*$/, "$1") : '';

    return errLoc;
}

function SentryLogger(options) {
    if (!(this instanceof SentryLogger)) {
        return new SentryLogger(options);
    }

    winston.Transport.call(this, options);
    this.name = 'SentryLogger';
    this.level = options.level || 'error';
    this.enabled = typeof(options.enabled) === 'boolean' ?
        options.enabled : true;
    this.dsn = options.dsn;
    this.defaultTags = options.tags || {};
    this.computeErrLoc = options.computeErrLoc || computeErrLoc;

    if (!this.defaultTags.pid) {
        this.defaultTags.pid = process.pid;
    }

    var ravenErrorHandler = typeof options.onRavenError === 'function' ?
        options.onRavenError : function (e) {
            // do not warn() on 429 errors
            if (e.message.indexOf('429') >= 0) {
                return;
            }

            winston.warn('Raven failed to upload to Sentry: ', {
                message: e.message,
                stack: e.stack,
                reason: e.reason,
                statusCode: e.statusCode
            });
        };

    if (!this.dsn && !options.ravenClient) {
        throw new Error('Cannot construct a Sentry transport without a DSN');
    }
    this.ravenClient = options.ravenClient ||
        new raven.Client(this.enabled && this.dsn);
    this.ravenClient.loggerName = 'SentryLogger';
    // Unsure how statsd could integrate with this due to ubiquity of the logger
    // and no initialization function for winston transports
    this.ravenClient.on('error', ravenErrorHandler);

    this.captureError = this.ravenClient.captureError.bind(this.ravenClient);
    this.captureMessage = this.ravenClient.captureMessage
        .bind(this.ravenClient);
    this.sentryProber = options.sentryProber || null;
    this.sentryProberCallbackImmediately = !options.sentryProberDetectFailuresBy;
    this.sentryProberDetectFailuresByCallback = options.sentryProberDetectFailuresBy === SentryLogger.detectBy.CALLBACK;
    this.sentryProberDetectFailuresByEvent = options.sentryProberDetectFailuresBy === SentryLogger.detectBy.EVENT;
    this.sentryProberDetectFailuresByEventSuccessEvent = options.sentryProberDetectFailuresByEventSuccessEvent;
    this.sentryProberDetectFailuresByEventFailureEvent = options.sentryProberDetectFailuresByEventFailureEvent;
}

SentryLogger.detectBy = {
    CALLBACK: 'callback',
    EVENT: 'event'
};

util.inherits(SentryLogger, winston.Transport);

SentryLogger.prototype.name = 'SentryLogger';

SentryLogger.prototype.log = function(level, msg, meta, callback) {
    var thunk;

    if (!meta || typeof meta !== 'object') {
        meta = {};
    }
    // store original meta because we might serialize it
    var originalMeta = meta;
    var stringMeta = stringify(meta);

    // To avoid sending circular data to sentry we check
    // whether its circular using json-stringify-safe and then
    // stringify(parse(meta)) to make it not circular
    meta = stringMeta.indexOf(CIRCULAR_STRING) !== -1 ?
        JSON.parse(stringMeta) : meta;

    var sentryArgs = {
        extra: meta,
        tags: extend(this.defaultTags, meta.tags || {})
    };

    if (meta.tags) {
        delete meta.tags;
    }

    if (!sentryArgs.tags.nodeVersion) {
        sentryArgs.tags.nodeVersion = process.version;
    }

    // if we have an error in the meta object we want to
    // serialize it in a sentry friendly way
    var metaTuple = containsError(originalMeta);
    if (metaTuple) {
        sentryArgs.extra.originalMessage = msg;

        if (metaTuple.key) {
            delete sentryArgs.extra[metaTuple.key];
        }

        msg = metaTuple.value;
    }

    // if we have a req in the meta object we want to
    // use sentry's HTTP request serializer to pretty print the
    // http Request rather then sending the raw representation
    var reqTuple = containsRequest(originalMeta);
    if (reqTuple) {
        var req = ravenParsers .parseRequest(reqTuple.value)
            ['sentry.interfaces.Http'];
        req.env = { REMOTE_ADDR: req.env.REMOTE_ADDR };
        sentryArgs['sentry.interfaces.Http'] = req;

        if (reqTuple.key) {
            delete sentryArgs.extra[reqTuple.key];
        }
    }

    var errLoc;

    if(this.sentryProber && this.sentryProberDetectFailuresByEvent) {
        this.ravenClient.once(
            this.sentryProberDetectFailuresByEventFailureEvent || 'error',
            function onProberFailure() {
                // We swallow this and callback with a success case
                // because sentry failures are warned by ravenErrorHandler instead.
                // Theoretically this is "incorrect" wrt the "detectFailuresByEvent" api,
                // but since this library swallowed errors for so long when you supplied a sentryProber,
                // we should continue to only warn and not raise an error.
                // callback(err || new Error('Sentry Prober emitted failure event.'));
                callback(null, true);
            }
        );
        this.ravenClient.once(
            this.sentryProberDetectFailuresByEventSuccessEvent || 'logged',
            function onProberSuccess(message) {
                callback(null, message || true);
            }
        );
    }

    if (isError(msg)) {
        errLoc = this.computeErrLoc(msg.message);
        msg.message = errLoc + ": " + msg.message;

        if (this.sentryProber) {
            thunk = this.captureError.bind(null, msg, sentryArgs);
            this.sentryProber.probe(thunk, this.sentryProberDetectFailuresByCallback ? callback : null);
        } else {
            this.captureError(msg, sentryArgs, callback);
        }
    } else {
        // winston adds in 9 stacks of function calls between the
        // actual .log() call location
        errLoc = this.computeErrLoc(msg);

        if (!sentryArgs.extra.stack) {
            sentryArgs.extra.stack = new Error('stack').stack;
        }

        if (this.sentryProber) {
            thunk = this.captureMessage
                .bind(null, errLoc + ": " + msg, sentryArgs);
            this.sentryProber.probe(thunk, this.sentryProberDetectFailuresByCallback ? callback : null);
        } else {
            this.captureMessage(errLoc + ": " + msg, sentryArgs, callback);
        }
    }

    if (this.sentryProber && this.sentryProberCallbackImmediately) {
        callback(null, true);
    }
};

module.exports = SentryLogger;

function containsError(obj) {
    if (isError(obj)) {
        return { key: null, value: obj };
    }

    var error = null;
    Object.keys(obj).some(function (key) {
        if (isError(obj[key])) {
            error = { key: key, value: obj[key] };
            return true;
        }
    });

    return error;
}

function containsRequest(obj) {
    var req = null;
    Object.keys(obj).some(function (key) {
        if (isRequest(obj[key])) {
            req = { key: key, value: obj[key] };
            return true;
        }
    });

    return req;
}

function isRequest(req) {
    return req &&
        typeof req.headers === 'object' &&
        typeof req.method === 'string' &&
        typeof req.url === 'string' &&
        typeof req.on === 'function';
}
<|endoftext|>


<commit_before>'use strict';

var dom = require('./src/dom');
var ngdoc = require('./src/ngdoc');
var reader = require('./src/reader');

var fs = require('fs');
var vfs = require('vinyl-fs');
var through2 = require('through2');
var extend = require('extend');
var _ = require('lodash');
var gutil = require('gulp-util');
var File = gutil.File;
var PluginError = gutil.PluginError;
var path = require('canonical-path');
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var merge = require('merge-stream');

var setup;
var fakeDest = '_FAKE_DEST_';
var templates = path.resolve(__dirname, 'src/templates');
var bowerComponents = path.resolve(__dirname, 'bower_components');

function copyTemplates() {
  return function () {
    return vfs.src(['**/*', '!**/*.tmpl'], {cwd: templates});
  };
}

function streamFile(src, dir, dest, name) {
  return function () {
    return vfs.src(src).pipe(through2.obj(function (file, enc, callback) {
      name = name === undefined ? path.normalize(file.path).split('/').pop() : name;
      this.push(new File({
        base: dest,
        cwd: dest,
        path: path.join(dest, dir, name),
        contents: file.contents
      }));
      callback(null);
    }));
  };
}

function sections(sects) {
  return merge(_.map(sects, function (data, key) {
    if (data instanceof String) {
      data = {glob: data};
    }
    if (!data.hasOwnProperty('glob')) {
      throw new PluginError('gulp-ngdocs', 'Invalid sections, please refer to the documentation.');
    }
    if (!data.hasOwnProperty('title')) {
      data.title = key;
    }
    if (!data.hasOwnProperty('api')) {
      data.api = true;
    }
    var glob = data.glob;
    var opts = data.globOpts;
    setup.sections[key] = data.title;
    setup.apis[key] = data.api;
    return vfs.src(glob, opts)
      .pipe(through2.obj(
        function (file, enc, cb) {
          file.section = key;
          this.push(file);
          cb(null);
        }
      ));
    }));
}

function processDoc(opts) {

  var options = extend({
    startPage: '/api',
    scripts: [],
    styles: [],
    title: 'API Documentation',
    html5Mode: true,
    editExample: true,
    navTemplate: false,
    navContent: '',
    navTemplateData: {}
  }, opts);

  setup = {sections: {}, pages: [], apis: {}};

  if (options.scripts && !(options.scripts instanceof Array)) {
    options.scripts = [options.scripts];
  }
  if (options.styles && !(options.styles instanceof Array)) {
    options.styles = [options.styles];
  }
  var defaultSection = 'api';
  var defaultScripts = [
    path.join(bowerComponents, 'angular/angular.min.js'),
    path.join(bowerComponents, 'angular/angular.min.js.map'),
    path.join(bowerComponents, 'angular-animate/angular-animate.min.js'),
    path.join(bowerComponents, 'angular-animate/angular-animate.min.js.map'),
    path.join(bowerComponents, 'marked/lib/marked.js'),
    path.join(bowerComponents, 'google-code-prettify/src/prettify.js')
  ];

  function writeSetup() {
    var options = setup.__options,
        content, data = {
          scripts: options.scripts,
          styles: options.styles,
          sections: _.keys(setup.sections).join('|'),
          discussions: options.discussions,
          analytics: options.analytics,
          navContent: options.navContent,
          title: options.title,
          image: options.image,
          titleLink: options.titleLink,
          imageLink: options.imageLink,
          bestMatch: options.bestMatch,
          deferLoad: !!options.deferLoad
        };

    // create index.html
    content = fs.readFileSync(path.resolve(templates, 'index.tmpl'), 'utf8');
    content = _.template(content, data);
    docsStream.push(new File({
      base: fakeDest,
      cwd: fakeDest,
      path: path.join(fakeDest, 'index.html'),
      contents: new Buffer(content, 'utf8')
    }));
    // create setup file
    setup.html5Mode = options.html5Mode;
    setup.editExample = options.editExample;
    setup.startPage = options.startPage;
    setup.discussions = options.discussions;
    setup.scripts = _.map(options.scripts, function(url) { return path.basename(url); });
    docsStream.push(new File({
      base: fakeDest,
      cwd: fakeDest,
      path: setup.__file,
      contents: new Buffer('NG_DOCS=' + JSON.stringify(setup, null, 2) + ';', 'utf8')
    }));
  }

  function transformFunction (file, enc, callback) {
    if (file.isNull()) {
      callback(null);
      return; // ignore
    }
    if (file.isStream()) {
      callback(new gutil.PluginError('gulp-ngdocs', 'Streaming not supported'));
      return;
    }
    if (file.contents) {
      if (!merged) {
        merged = merge(fstreams.map(function (f) {
          var s = f();
          s.on('data', function (file) {
            docsStream.push(file);
          });
          return s;
        }));

        merged.on('end', function () {
          mergedEnded = true;
          if (docsStreamEndCb) {
            docsStreamEndCb(null);
          }
        });
      }
      var content = decoder.write(file.contents);
      if (!file.section) {
        file.section = defaultSection;
      }
      reader.process(content, file.path, file.section, options);
    }
    callback(null);
  }

  function flushFunction (cb) {
    if (merged) {
      docsStreamEndCb = cb;
      ngdoc.merge(reader.docs);
      reader.docs.forEach(function(doc){
        // this hack is here because on OSX angular.module and angular.Module map to the same file.
        var id = doc.id.replace('angular.Module', 'angular.IModule').replace(':', '.'),
            file = path.join(fakeDest, 'partials', doc.section, id + '.html'),
            dir = path.join(fakeDest, 'partials', doc.section);
        docsStream.push(new File({
          base: fakeDest,
          cwd: fakeDest,
          path: file,
          contents: new Buffer(doc.html(), 'utf8')
        }));
      });

      ngdoc.checkBrokenLinks(reader.docs, setup.apis, options);

      setup.pages = _.union(setup.pages, ngdoc.metadata(reader.docs));

      writeSetup(this);

      if (mergedEnded) {
        docsStreamEndCb(null);
        docsStreamEndCb = false;
      }
    } else {
      cb(null);
    }
  }

  var fstreams = [];
  var docsStream = through2.obj(transformFunction, flushFunction);
  var merged = false;
  var mergedEnded = false;
  var docsStreamEndCb = false;
  if (options.navTemplate) {
    options.navContent = _.template(
      fs.readFileSync(options.navTemplate, 'utf8'),
      options.navTemplateData);
  }

  if (options.image) {
    if (!/^((https?:)?\/\/|\.\.\/)/.test(options.image)) {
      fstreams.push(streamFile(options.image, 'img', fakeDest));
      options.image = 'img/' + path.basename(options.image);
    }
  }

  var scriptNames = [];
  options.scripts = _.map(options.scripts, function (file) {
    var fileName = path.normalize(file).split('/').pop();
    scriptNames.push(fileName);
    if (/^((https?:)?\/\/)/.test(file)) {
      return file;
    } else {
      fstreams.push(streamFile(file, 'js', fakeDest));
      return path.join('js', fileName);
    }
  });

  _.forEach(defaultScripts, function (script, i) {
    var fileName = path.normalize(script).split('/').pop();
    if (scriptNames.indexOf(fileName) === -1) {
      fstreams.push(streamFile(script, 'js', fakeDest));
      options.scripts.splice(i, 0, path.join('js', fileName));
    }
  });

  //Filter the maps
  options.scripts = _.filter(options.scripts, function (file) {
    return !(/^.*\.map$/.test(file));
  });

  options.styles = _.map(options.styles, function(file) {
    if (/^((https?:)?\/\/)/.test(file)) {
      return file;
    } else {
      fstreams.push(streamFile(file, 'css', fakeDest));
      return 'css/' + path.normalize(file).split('/').pop();
    }
  });

  fstreams.push(copyTemplates(templates));
  setup.__file = path.join(fakeDest, 'js/docs-setup.js');
  setup.__options = options;

  reader.docs = [];
  if (Object.keys(setup.sections).length === 0) {
    setup.sections[defaultSection] = options.title;
    setup.apis[defaultSection] = true;
  }

  return docsStream;
}

module.exports = {
  process: processDoc,
  sections: sections
};<commit_msg>Allow gulp.watch to continue running.<commit_after>'use strict';

var dom = require('./src/dom');
var ngdoc = require('./src/ngdoc');
var reader = require('./src/reader');

var fs = require('fs');
var vfs = require('vinyl-fs');
var through2 = require('through2');
var extend = require('extend');
var _ = require('lodash');
var gutil = require('gulp-util');
var File = gutil.File;
var PluginError = gutil.PluginError;
var path = require('canonical-path');
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var merge = require('merge-stream');

var setup;
var fakeDest = '_FAKE_DEST_';
var templates = path.resolve(__dirname, 'src/templates');
var bowerComponents = path.resolve(__dirname, 'bower_components');

function copyTemplates() {
  return function () {
    return vfs.src(['**/*', '!**/*.tmpl'], {cwd: templates});
  };
}

function streamFile(src, dir, dest, name) {
  return function () {
    return vfs.src(src).pipe(through2.obj(function (file, enc, callback) {
      name = name === undefined ? path.normalize(file.path).split('/').pop() : name;
      this.push(new File({
        base: dest,
        cwd: dest,
        path: path.join(dest, dir, name),
        contents: file.contents
      }));
      callback(null);
    }));
  };
}

function sections(sects) {
  return merge(_.map(sects, function (data, key) {
    if (data instanceof String) {
      data = {glob: data};
    }
    if (!data.hasOwnProperty('glob')) {
      throw new PluginError('gulp-ngdocs', 'Invalid sections, please refer to the documentation.');
    }
    if (!data.hasOwnProperty('title')) {
      data.title = key;
    }
    if (!data.hasOwnProperty('api')) {
      data.api = true;
    }
    var glob = data.glob;
    var opts = data.globOpts;
    setup.sections[key] = data.title;
    setup.apis[key] = data.api;
    return vfs.src(glob, opts)
      .pipe(through2.obj(
        function (file, enc, cb) {
          file.section = key;
          this.push(file);
          cb(null);
        }
      ));
    }));
}

function processDoc(opts) {

  var options = extend({
    startPage: '/api',
    scripts: [],
    styles: [],
    title: 'API Documentation',
    html5Mode: true,
    editExample: true,
    navTemplate: false,
    navContent: '',
    navTemplateData: {}
  }, opts);

  setup = {sections: {}, pages: [], apis: {}};

  if (options.scripts && !(options.scripts instanceof Array)) {
    options.scripts = [options.scripts];
  }
  if (options.styles && !(options.styles instanceof Array)) {
    options.styles = [options.styles];
  }
  var defaultSection = 'api';
  var defaultScripts = [
    path.join(bowerComponents, 'angular/angular.min.js'),
    path.join(bowerComponents, 'angular/angular.min.js.map'),
    path.join(bowerComponents, 'angular-animate/angular-animate.min.js'),
    path.join(bowerComponents, 'angular-animate/angular-animate.min.js.map'),
    path.join(bowerComponents, 'marked/lib/marked.js'),
    path.join(bowerComponents, 'google-code-prettify/src/prettify.js')
  ];

  function writeSetup() {
    var options = setup.__options,
        content, data = {
          scripts: options.scripts,
          styles: options.styles,
          sections: _.keys(setup.sections).join('|'),
          discussions: options.discussions,
          analytics: options.analytics,
          navContent: options.navContent,
          title: options.title,
          image: options.image,
          titleLink: options.titleLink,
          imageLink: options.imageLink,
          bestMatch: options.bestMatch,
          deferLoad: !!options.deferLoad
        };

    // create index.html
    content = fs.readFileSync(path.resolve(templates, 'index.tmpl'), 'utf8');
    content = _.template(content, data);
    docsStream.push(new File({
      base: fakeDest,
      cwd: fakeDest,
      path: path.join(fakeDest, 'index.html'),
      contents: new Buffer(content, 'utf8')
    }));
    // create setup file
    setup.html5Mode = options.html5Mode;
    setup.editExample = options.editExample;
    setup.startPage = options.startPage;
    setup.discussions = options.discussions;
    setup.scripts = _.map(options.scripts, function(url) { return path.basename(url); });
    docsStream.push(new File({
      base: fakeDest,
      cwd: fakeDest,
      path: setup.__file,
      contents: new Buffer('NG_DOCS=' + JSON.stringify(setup, null, 2) + ';', 'utf8')
    }));
  }

  function transformFunction (file, enc, callback) {
    if (file.isNull()) {
      callback(null);
      return; // ignore
    }
    if (file.isStream()) {
      callback(new gutil.PluginError('gulp-ngdocs', 'Streaming not supported'));
      return;
    }
    if (file.contents) {
      if (!merged) {
        merged = merge(fstreams.map(function (f) {
          var s = f();
          s.on('data', function (file) {
            docsStream.push(file);
          });
          return s;
        }));

        merged.on('end', function () {
          mergedEnded = true;
          if (docsStreamEndCb) {
            docsStreamEndCb(null);
          }
        });
      }
      var content = decoder.write(file.contents);
      if (!file.section) {
        file.section = defaultSection;
      }
      reader.process(content, file.path, file.section, options);
    }
    callback(null);
  }

  function flushFunction (cb) {
    if (merged) {
      docsStreamEndCb = cb;
      // IMPORTANT: Allow gulp.watch to continue running.
      try{
        ngdoc.merge(reader.docs);
        reader.docs.forEach(function(doc){
          // this hack is here because on OSX angular.module and angular.Module map to the same file.
          var id = doc.id.replace('angular.Module', 'angular.IModule').replace(':', '.'),
              file = path.join(fakeDest, 'partials', doc.section, id + '.html'),
              dir = path.join(fakeDest, 'partials', doc.section);
          docsStream.push(new File({
            base: fakeDest,
            cwd: fakeDest,
            path: file,
            contents: new Buffer(doc.html(), 'utf8')
          }));
        });

        ngdoc.checkBrokenLinks(reader.docs, setup.apis, options);

        setup.pages = _.union(setup.pages, ngdoc.metadata(reader.docs));
      } catch (flushError) {
        console.log(flushError);
      }
      writeSetup(this);

      if (mergedEnded) {
        docsStreamEndCb(null);
        docsStreamEndCb = false;
      }
    } else {
      cb(null);
    }
  }

  var fstreams = [];
  var docsStream = through2.obj(transformFunction, flushFunction);
  var merged = false;
  var mergedEnded = false;
  var docsStreamEndCb = false;
  if (options.navTemplate) {
    options.navContent = _.template(
      fs.readFileSync(options.navTemplate, 'utf8'),
      options.navTemplateData);
  }

  if (options.image) {
    if (!/^((https?:)?\/\/|\.\.\/)/.test(options.image)) {
      fstreams.push(streamFile(options.image, 'img', fakeDest));
      options.image = 'img/' + path.basename(options.image);
    }
  }

  var scriptNames = [];
  options.scripts = _.map(options.scripts, function (file) {
    var fileName = path.normalize(file).split('/').pop();
    scriptNames.push(fileName);
    if (/^((https?:)?\/\/)/.test(file)) {
      return file;
    } else {
      fstreams.push(streamFile(file, 'js', fakeDest));
      return path.join('js', fileName);
    }
  });

  _.forEach(defaultScripts, function (script, i) {
    var fileName = path.normalize(script).split('/').pop();
    if (scriptNames.indexOf(fileName) === -1) {
      fstreams.push(streamFile(script, 'js', fakeDest));
      options.scripts.splice(i, 0, path.join('js', fileName));
    }
  });

  //Filter the maps
  options.scripts = _.filter(options.scripts, function (file) {
    return !(/^.*\.map$/.test(file));
  });

  options.styles = _.map(options.styles, function(file) {
    if (/^((https?:)?\/\/)/.test(file)) {
      return file;
    } else {
      fstreams.push(streamFile(file, 'css', fakeDest));
      return 'css/' + path.normalize(file).split('/').pop();
    }
  });

  fstreams.push(copyTemplates(templates));
  setup.__file = path.join(fakeDest, 'js/docs-setup.js');
  setup.__options = options;

  reader.docs = [];
  if (Object.keys(setup.sections).length === 0) {
    setup.sections[defaultSection] = options.title;
    setup.apis[defaultSection] = true;
  }

  return docsStream;
}

module.exports = {
  process: processDoc,
  sections: sections
};
<|endoftext|>


<commit_before>/**
 * Module to track defined routing and their naming
 * This will build a route name table by traversing the routes hierarchy, starting from routes/index
 * The result will be stored as singleton object, routeTable
 *
 * This pattern is used in:
 * https://github.com/alexmingoia/koa-router
 * https://github.com/alubbe/named-routes
 *
 * Sadly, none of them can be used for our modular architecture in express.js
 * 1. var router = require('/path/to/this/router')(app);
 * 2. var router = require('/path/to/this/router');
 *
 * When mode 1 is used, router can register route (.get, .post, ...), buildRouteNames, and urlFor
 * When mode 2 is used, router can only do urlFor(...)
 * @author William Gozali <will.gozali@cermati.com>
 */

'use strict';

var _ = require('lodash');
var querystring = require('querystring');
var util = require('util');

var helper = require('./helper');
var constants = require('./constants');

var baseUrl;
var routeTable;
var PUSH = constants.PUSH;
var POP = constants.POP;

// Basic functionality
var routerBase = {
  urlFor: urlFor,
  absoluteUrlFor: absoluteUrlFor,
  setBaseUrl: setBaseUrl,
  getRouteTable: getRouteTable
};

/**
 * Create a router object, provided the express app
 * It wraps `add` function defined above, so it can be easier to use
 *
 * It adapts express routing structure, can be called with this pattern:
 * router.METHOD([name], path, [middleware...], middleware)
 *
 * @author William Gozali <will.gozali@cermati.com>
 * Later, we can:
 *   In routes/index:
 *   var router = require('../router')(app);
 *   var article = require('../modules/article');
 *   router.all('/articles*', middleware.requireLogin); // Just middleware, no name necessary
 *   router.use('articles', '/articles', article.routes);
 *
 *   In article/index module:
 *   var express = keystone.express;
 *   var app = express();
 *   var router = require('../../router')(app);
 *   router.get('list', '/', require('./views/list'));
 *   router.get('detail', '/:title', require('./views/detail'));
 *   router.post('save', '/:title', middleware.requireAdmin, require('./views/save')); // Can add middleware
 */
var router = function (app) {
  var router = routerBase;

  _.each(routerBase, function (value, key) {
    router[key] = value;
  });

  constants.METHODS.forEach(function (method) {
    router[method] = add.bind(null, method, app);
  });

  router.use = add.bind(null, 'use', app);
  router.all = add.bind(null, 'all', app);
  router.buildRouteNames = buildRouteNames.bind(null, app);

  return router;
};

// If "required" without invoking `(app)`, simply attach basic functionalities to returned constructor
_.each(routerBase, function (value, key) {
  router[key] = value;
});

module.exports = router;

/**
 * Generic method to register a route, for any method (get, post, all, ...)
 * It wraps around express' routing function, and jot down the routing hierarchy
 *
 * Takes arguments with pattern:
 * method, app, [name,] path, [middleware...,] routeController
 *
 * Where:
 *   method - Routing method, like 'get', 'post', or special 'use'
 *   app - A keystone.express instance
 *   name - Name of this particular route. Without name, this route and its children won't be registered to routeTable.
 *      You can skip this parameter when registering middleware (eg: middleware.requireLogin)
 *   path - Path for this particular route (eg: '/article', '/article/:slug')
 *   middleware - Sequences of middleware. This is optional
 *   routeController - The controller to handle this route, can also be middleware
 * @author William Gozali <will.gozali@cermati.com>
 */
function add() {
  var args = _.values(arguments);

  var method = args[0];
  var app = args[1];
  var name;
  var path;
  var offset;
  var middlewares;

  if (_.isString(args[3])) {
    // Name is provided
    name = args[2];
    path = args[3];
    offset = 4;

    if (!helper.isValidName(name)) {
      throw new Error('Invalid route name: ' + name);
    }
    if (!helper.isValidPathForNamedRoute(path)) {
      throw new Error('Invalid path for named route: ' + path + ', it won\'t make sense for urlFor');
    }
  } else {
    // No name provided
    path = args[2];
    offset = 3;
  }
  middlewares = _.flattenDeep(args.slice(offset));

  if (!app.routeTraversal) {
    // Singleton for each app
    app.routeTraversal = [];
  }

  // Prepare to get deeper
  if (name) {
    app.routeTraversal.push({
      operation: PUSH,
      name: name,
      path: path
    });
  }

  // Register this for express to do its stuff
  app[method](path, middlewares);

  var routeController = _.last(middlewares);
  if (_.isArray(routeController.routeTraversal)) {
    /*
     As with our function signature, the last element of the middlewares is assumed to be
     the "handler" for the controller logic.
     Even if it is not (could be just a middleware for login guard), then routeController.routeTraversal
     is guaranteed to be undefined, it won't even enter this loop
     */
    for (var i = 0; i < routeController.routeTraversal.length; i++) {
      app.routeTraversal.push(routeController.routeTraversal[i]);
    }
  }

  // Finished this stack, prepare to backtrack
  if (name) {
    app.routeTraversal.push({
      operation: POP,
      name: name,
      path: path
    });
  }
}

/**
 * Generate the route names
 * After all routing hierarchies are known, call this to build the route names
 *
 * Generated route table maps a route name to 2 fields:
 * 1. pattern: the raw pattern as string (eg: /artikel/kategori/:category)
 * 2. tokens: tokenized pattern, used to optimize urlFor
 *
 * Example for the generated routeTable:
 *   routeTable['article.list'].pattern = '/artikel'
 *   routeTable['article.list'].tokens = [
 *     {text: ''},
 *     {text: 'artikel'}
 *   ]
 *
 *   routeTable['article.category'].pattern = '/artikel/kategori/:category'
 *   routeTable['article.category'].tokens = [
 *     {text: ''}
 *     {text: 'artikel'},
 *     {text: 'kategori'},
 *     {text: 'category', input: true}
 *   ]
 * @author William Gozali <will.gozali@cermati.com>
 * @param app - The express app
 */
function buildRouteNames(app) {
  if (!_.isUndefined(routeTable)) {
    throw new Error('Route table has been built before!');
  }

  var stack = [];
  routeTable = {};

  if (!app.routeTraversal) {
    return;
  }

  // Simulate the routing traversal while generating route names
  var previousEvent;
  for (var i = 0; i < app.routeTraversal.length; i++) {
    var event = app.routeTraversal[i];
    if (event.operation === PUSH) {
      stack.push({
        name: event.name,
        path: event.path
      });
    } else {
      if (helper.isTerminalRoute(previousEvent, event)) {
        var nameHierarchy = _.map(stack, 'name');
        var patternHierarchy = _.map(stack, 'path');
        helper.register(routeTable, nameHierarchy, patternHierarchy);
      }

      if (stack.length === 0) {
        throw new Error('Stack in generating route names is not balanced, please report this issue');
      }

      stack.pop();
    }

    previousEvent = event;
  }

  if (stack.length !== 0) {
    throw new Error('Leftover element exists in the stack while generating route names, please report this issue');
  }
}

/**
 * Given a route name and its params, return the final URL
 * This function works only after buildRouteNames is executed
 * Throws error when the params is not sufficient to build URL
 *
 * Optimized using routeTable[NAME].tokens:
 * 1. Avoid matching using regex, and do replace in place (directly in the string)
 * 2. Avoid removing ':' at the front of input token, it was done when generating route table
 *
 * @author William Gozali <will.gozali@cermati.com>
 * @param {string} routeName - Name of the route
 * @param {Object} [params] - Params to be fed to url pattern
 * @param {Object} [queries] - Queries to be appended in the end of url
 * @returns {string}
 *
 * @example
 * Let's say after routing is done, we have:
 *   routeTable['creditCard.list'].pattern = '/kartu-kredit'
 *   routeTable['creditCard.detail'].pattern = '/kartu-kredit/:slug'
 *   routeTable['creditCard.apply'].pattern = '/kartu-kredit/:slug/ajukan'
 *
 * In controller:
 *   var router = require('../router'); // No need to pass app
 *   router.urlFor('creditCard.list') => '/kartu-kredit'
 *   router.urlFor('creditCard.list', {}, {issuer: 'abc'}) => '/kartu-kredit?issuer=abc'
 *   router.urlFor('creditCard.list', {}, {issuer: 'abc', bonus: 'dog'}) => '/kartu-kredit?issuer=abc&bonus=dog'
 *   router.urlFor('creditCard.detail', {slug: 'myCard'}) => '/kartu-kredit/myCard'
 *   router.urlFor('creditCard.detail', {slug: 'myCard'}, {'no-layout': true}) => '/kartu-kredit/myCard?no-layout=true'
 *   router.urlFor('creditCard.apply', {slug: 'myCard'}) => '/kartu-kredit/myCard/ajukan'
 *   router.urlFor('creditCard.apply', {title: 'myCard'}) throws error because :slug is not filled
 */
function urlFor(routeName, params, queries) {
  if (_.isUndefined(routeTable[routeName])) {
    throw new Error('Attempted to use undefined routeName: ' + routeName);
  }

  var url = routeTable[routeName].tokens.map(function (token) {
    var filledToken;

    if (!token.input) {
      filledToken = token.text;
    } else {
      if (_.isUndefined(params[token.text])) {
        var message = util.format('Incomplete parameter for pattern: %s = %s', routeTable[routeName].pattern, token.text);
        throw new Error(message);
      }
      filledToken = params[token.text];
    }

    return filledToken;
  }).join('/');

  if (queries) {
    url = util.format('%s?%s', url, querystring.stringify(queries));
  }

  return url;
}

/**
 * Creates an absolute url for the given routeName, params, and queries.
 *
 * @example
 *   process.env.BASE_URL = 'https://cermati.com';
 *   route.absoluteUrlFor('me.applications') === `https://cermati.com/me/applications`;
 *
 * @author Sendy Halim <sendy@cermati.com>
 * @param {string} routeName - Name of the route.
 * @param {Object} [params] - Params to be fed to url pattern.
 * @param {Object} [queries] - Queries to be appended in the end of url.
 * @returns {string}
 */
function absoluteUrlFor(routeName, params, queries) {
  if (!baseUrl) {
    throw new Error('Please set baseUrl with .setBaseUrl before!');
  }

  return baseUrl.concat(urlFor(routeName, params, queries));
}

/**
 * Set base URL so later we can call `absoluteUrlFor`
 * @author William Gozali <will.gozali@cermati.com>
 * @param _baseUrl
 */
function setBaseUrl(_baseUrl) {
  baseUrl = _baseUrl;
}

/**
 * Return object with route name as keys and the pattern as values
 * @author William Gozali <will.gozali@cermati.com>
 * @returns {Object}
 */
function getRouteTable() {
  var table = {};
  _.keys(routeTable).forEach(function (key) {
    table[key] = routeTable[key].pattern;
  });

  return table;
}
<commit_msg>Remove unneeded code<commit_after>/**
 * Module to track defined routing and their naming
 * This will build a route name table by traversing the routes hierarchy, starting from routes/index
 * The result will be stored as singleton object, routeTable
 *
 * This pattern is used in:
 * https://github.com/alexmingoia/koa-router
 * https://github.com/alubbe/named-routes
 *
 * Sadly, none of them can be used for our modular architecture in express.js
 * 1. var router = require('/path/to/this/router')(app);
 * 2. var router = require('/path/to/this/router');
 *
 * When mode 1 is used, router can register route (.get, .post, ...), buildRouteNames, and urlFor
 * When mode 2 is used, router can only do urlFor(...)
 * @author William Gozali <will.gozali@cermati.com>
 */

'use strict';

var _ = require('lodash');
var querystring = require('querystring');
var util = require('util');

var helper = require('./helper');
var constants = require('./constants');

var baseUrl;
var routeTable;
var PUSH = constants.PUSH;
var POP = constants.POP;

// Basic functionality
var routerBase = {
  urlFor: urlFor,
  absoluteUrlFor: absoluteUrlFor,
  setBaseUrl: setBaseUrl,
  getRouteTable: getRouteTable
};

/**
 * Create a router object, provided the express app
 * It wraps `add` function defined above, so it can be easier to use
 *
 * It adapts express routing structure, can be called with this pattern:
 * router.METHOD([name], path, [middleware...], middleware)
 *
 * @author William Gozali <will.gozali@cermati.com>
 * Later, we can:
 *   In routes/index:
 *   var router = require('../router')(app);
 *   var article = require('../modules/article');
 *   router.all('/articles*', middleware.requireLogin); // Just middleware, no name necessary
 *   router.use('articles', '/articles', article.routes);
 *
 *   In module/article/index module:
 *   var express = keystone.express;
 *   var app = express();
 *   var router = require('../../router')(app);
 *   router.get('list', '/', require('./views/list'));
 *   router.get('detail', '/:title', require('./views/detail'));
 *   router.post('save', '/:title', middleware.requireAdmin, require('./views/save')); // Can add middleware
 */
var router = function (app) {
  var router = routerBase;

  // Extend with HTTP methods
  constants.METHODS.forEach(function (method) {
    router[method] = add.bind(null, method, app);
  });

  // Extend with additional functions
  router.use = add.bind(null, 'use', app);
  router.all = add.bind(null, 'all', app);
  router.buildRouteNames = buildRouteNames.bind(null, app);

  return router;
};

// If "required" without invoking `(app)`, simply attach basic functionalities to returned constructor
_.each(routerBase, function (value, key) {
  router[key] = value;
});

module.exports = router;

/**
 * Generic method to register a route, for any method (get, post, all, ...)
 * It wraps around express' routing function, and jot down the routing hierarchy
 *
 * Takes arguments with pattern:
 * method, app, [name,] path, [middleware...,] routeController
 *
 * Where:
 *   method - Routing method, like 'get', 'post', or special 'use'
 *   app - A keystone.express instance
 *   name - Name of this particular route. Without name, this route and its children won't be registered to routeTable.
 *      You can skip this parameter when registering middleware (eg: middleware.requireLogin)
 *   path - Path for this particular route (eg: '/article', '/article/:slug')
 *   middleware - Sequences of middleware. This is optional
 *   routeController - The controller to handle this route, can also be middleware
 * @author William Gozali <will.gozali@cermati.com>
 */
function add() {
  var args = _.values(arguments);

  var method = args[0];
  var app = args[1];
  var name;
  var path;
  var offset;
  var middlewares;

  if (_.isString(args[3])) {
    // Name is provided
    name = args[2];
    path = args[3];
    offset = 4;

    if (!helper.isValidName(name)) {
      throw new Error('Invalid route name: ' + name);
    }
    if (!helper.isValidPathForNamedRoute(path)) {
      throw new Error('Invalid path for named route: ' + path + ', it won\'t make sense for urlFor');
    }
  } else {
    // No name provided
    path = args[2];
    offset = 3;
  }
  middlewares = _.flattenDeep(args.slice(offset));

  if (!app.routeTraversal) {
    // Singleton for each app
    app.routeTraversal = [];
  }

  // Prepare to get deeper
  if (name) {
    app.routeTraversal.push({
      operation: PUSH,
      name: name,
      path: path
    });
  }

  // Register this for express to do its stuff
  app[method](path, middlewares);

  var routeController = _.last(middlewares);
  if (_.isArray(routeController.routeTraversal)) {
    /*
     As with our function signature, the last element of the middlewares is assumed to be
     the "handler" for the controller logic.
     Even if it is not (could be just a middleware for login guard), then routeController.routeTraversal
     is guaranteed to be undefined, it won't even enter this loop
     */
    for (var i = 0; i < routeController.routeTraversal.length; i++) {
      app.routeTraversal.push(routeController.routeTraversal[i]);
    }
  }

  // Finished this stack, prepare to backtrack
  if (name) {
    app.routeTraversal.push({
      operation: POP,
      name: name,
      path: path
    });
  }
}

/**
 * Generate the route names
 * After all routing hierarchies are known, call this to build the route names
 *
 * Generated route table maps a route name to 2 fields:
 * 1. pattern: the raw pattern as string (eg: /artikel/kategori/:category)
 * 2. tokens: tokenized pattern, used to optimize urlFor
 *
 * Example for the generated routeTable:
 *   routeTable['article.list'].pattern = '/artikel'
 *   routeTable['article.list'].tokens = [
 *     {text: ''},
 *     {text: 'artikel'}
 *   ]
 *
 *   routeTable['article.category'].pattern = '/artikel/kategori/:category'
 *   routeTable['article.category'].tokens = [
 *     {text: ''}
 *     {text: 'artikel'},
 *     {text: 'kategori'},
 *     {text: 'category', input: true}
 *   ]
 * @author William Gozali <will.gozali@cermati.com>
 * @param app - The express app
 */
function buildRouteNames(app) {
  if (!_.isUndefined(routeTable)) {
    throw new Error('Route table has been built before!');
  }

  var stack = [];
  routeTable = {};

  if (!app.routeTraversal) {
    return;
  }

  // Simulate the routing traversal while generating route names
  var previousEvent;
  for (var i = 0; i < app.routeTraversal.length; i++) {
    var event = app.routeTraversal[i];
    if (event.operation === PUSH) {
      stack.push({
        name: event.name,
        path: event.path
      });
    } else {
      if (helper.isTerminalRoute(previousEvent, event)) {
        var nameHierarchy = _.map(stack, 'name');
        var patternHierarchy = _.map(stack, 'path');
        helper.register(routeTable, nameHierarchy, patternHierarchy);
      }

      if (stack.length === 0) {
        throw new Error('Stack in generating route names is not balanced, please report this issue');
      }

      stack.pop();
    }

    previousEvent = event;
  }

  if (stack.length !== 0) {
    throw new Error('Leftover element exists in the stack while generating route names, please report this issue');
  }
}

/**
 * Given a route name and its params, return the final URL
 * This function works only after buildRouteNames is executed
 * Throws error when the params is not sufficient to build URL
 *
 * Optimized using routeTable[NAME].tokens:
 * 1. Avoid matching using regex, and do replace in place (directly in the string)
 * 2. Avoid removing ':' at the front of input token, it was done when generating route table
 *
 * @author William Gozali <will.gozali@cermati.com>
 * @param {string} routeName - Name of the route
 * @param {Object} [params] - Params to be fed to url pattern
 * @param {Object} [queries] - Queries to be appended in the end of url
 * @returns {string}
 *
 * @example
 * Let's say after routing is done, we have:
 *   routeTable['creditCard.list'].pattern = '/kartu-kredit'
 *   routeTable['creditCard.detail'].pattern = '/kartu-kredit/:slug'
 *   routeTable['creditCard.apply'].pattern = '/kartu-kredit/:slug/ajukan'
 *
 * In controller:
 *   var router = require('../router'); // No need to pass app
 *   router.urlFor('creditCard.list') => '/kartu-kredit'
 *   router.urlFor('creditCard.list', {}, {issuer: 'abc'}) => '/kartu-kredit?issuer=abc'
 *   router.urlFor('creditCard.list', {}, {issuer: 'abc', bonus: 'dog'}) => '/kartu-kredit?issuer=abc&bonus=dog'
 *   router.urlFor('creditCard.detail', {slug: 'myCard'}) => '/kartu-kredit/myCard'
 *   router.urlFor('creditCard.detail', {slug: 'myCard'}, {'no-layout': true}) => '/kartu-kredit/myCard?no-layout=true'
 *   router.urlFor('creditCard.apply', {slug: 'myCard'}) => '/kartu-kredit/myCard/ajukan'
 *   router.urlFor('creditCard.apply', {title: 'myCard'}) throws error because :slug is not filled
 */
function urlFor(routeName, params, queries) {
  if (_.isUndefined(routeTable[routeName])) {
    throw new Error('Attempted to use undefined routeName: ' + routeName);
  }

  var url = routeTable[routeName].tokens.map(function (token) {
    var filledToken;

    if (!token.input) {
      filledToken = token.text;
    } else {
      if (_.isUndefined(params[token.text])) {
        var message = util.format('Incomplete parameter for pattern: %s = %s', routeTable[routeName].pattern, token.text);
        throw new Error(message);
      }
      filledToken = params[token.text];
    }

    return filledToken;
  }).join('/');

  if (queries) {
    url = util.format('%s?%s', url, querystring.stringify(queries));
  }

  return url;
}

/**
 * Creates an absolute url for the given routeName, params, and queries.
 *
 * @example
 *   process.env.BASE_URL = 'https://cermati.com';
 *   route.absoluteUrlFor('me.applications') === `https://cermati.com/me/applications`;
 *
 * @author Sendy Halim <sendy@cermati.com>
 * @param {string} routeName - Name of the route.
 * @param {Object} [params] - Params to be fed to url pattern.
 * @param {Object} [queries] - Queries to be appended in the end of url.
 * @returns {string}
 */
function absoluteUrlFor(routeName, params, queries) {
  if (!baseUrl) {
    throw new Error('Please set baseUrl with .setBaseUrl before!');
  }

  return baseUrl.concat(urlFor(routeName, params, queries));
}

/**
 * Set base URL so later we can call `absoluteUrlFor`
 * @author William Gozali <will.gozali@cermati.com>
 * @param _baseUrl
 */
function setBaseUrl(_baseUrl) {
  baseUrl = _baseUrl;
}

/**
 * Return object with route name as keys and the pattern as values
 * @author William Gozali <will.gozali@cermati.com>
 * @returns {Object}
 */
function getRouteTable() {
  var table = {};
  _.keys(routeTable).forEach(function (key) {
    table[key] = routeTable[key].pattern;
  });

  return table;
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

var Path = require('path');
var Hapi = require('hapi');

var handlebars = require('handlebars');
handlebars = require('handlebars-helper-sri').register(handlebars);

var helpers = require('./lib/helpers.js');

var server = new Hapi.Server();
server.connection({ port: 4000 });

server.register(require('vision'), function (err) {
  server.views({
    engines: {
      html: handlebars
    },
    path: Path.join(__dirname, 'templates')
  });
});

server.register(require('inert'), function (err) {
  /**
   * Serve index.js
   */
  server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
      var browsers = helpers.shuffleArray([
        { 'name': 'Firefox', 'url': 'https://www.mozilla.org/firefox/channel/#developer' },
        { 'name': 'Chrome', 'url': 'https://www.google.com/chrome/browser/desktop/' }
      ]);
      reply.view('index', { 'title': 'SRI Hash Generator', 'browsers': browsers });
    }
  });

  /**
   * Serve public files
   */
  server.route({
    method: 'GET',
    path: '/{param*}',
    handler: {
      directory: {
        path: 'public',
        lookupCompressed: true
      }
    }
  });

  /**
   * Return SRI lookup in JSON format
   */
  server.route({
    method: 'POST',
    path: '/generate',
    handler: function (request, reply) {
      var options = {
        url: request.payload.url,
        algorithms: request.payload.algorithms
      };
      helpers.generate(options, function (result) {
        reply(
          JSON // jshint ignore:line
            .stringify(result)
        ).type('application/json');
      });
    }
  });

  /**
   * Return SRI lookup in HTML format.
   * Deprecated, pending move to isomorphic app.
   */
  server.route({
    method: 'POST',
    path: '/hash',
    handler: function (request, reply) {
      helpers.generateElement(
        request.payload.url,
        request.payload.algorithms,
        function (result) {
          reply.view('hash', { 'hash': result });
        }
      );
    }
  });
});

server.start(function () {
  console.log('Server running at:', server.info.uri);
});
<commit_msg>Allow setting the port through an environment variable<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';

var Path = require('path');
var Hapi = require('hapi');

var handlebars = require('handlebars');
handlebars = require('handlebars-helper-sri').register(handlebars);

var helpers = require('./lib/helpers.js');

var server = new Hapi.Server();
server.connection({ port: process.env.PORT || 4000 });

server.register(require('vision'), function (err) {
  server.views({
    engines: {
      html: handlebars
    },
    path: Path.join(__dirname, 'templates')
  });
});

server.register(require('inert'), function (err) {
  /**
   * Serve index.js
   */
  server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {
      var browsers = helpers.shuffleArray([
        { 'name': 'Firefox', 'url': 'https://www.mozilla.org/firefox/channel/#developer' },
        { 'name': 'Chrome', 'url': 'https://www.google.com/chrome/browser/desktop/' }
      ]);
      reply.view('index', { 'title': 'SRI Hash Generator', 'browsers': browsers });
    }
  });

  /**
   * Serve public files
   */
  server.route({
    method: 'GET',
    path: '/{param*}',
    handler: {
      directory: {
        path: 'public',
        lookupCompressed: true
      }
    }
  });

  /**
   * Return SRI lookup in JSON format
   */
  server.route({
    method: 'POST',
    path: '/generate',
    handler: function (request, reply) {
      var options = {
        url: request.payload.url,
        algorithms: request.payload.algorithms
      };
      helpers.generate(options, function (result) {
        reply(
          JSON // jshint ignore:line
            .stringify(result)
        ).type('application/json');
      });
    }
  });

  /**
   * Return SRI lookup in HTML format.
   * Deprecated, pending move to isomorphic app.
   */
  server.route({
    method: 'POST',
    path: '/hash',
    handler: function (request, reply) {
      helpers.generateElement(
        request.payload.url,
        request.payload.algorithms,
        function (result) {
          reply.view('hash', { 'hash': result });
        }
      );
    }
  });
});

server.start(function () {
  console.log('Server running at:', server.info.uri);
});
<|endoftext|>


<commit_before>var request = require('request')
  , Parser = require('./lib/parser')
  , split = require('split')
  , multiparty = require('multiparty')
  , ss = require('stream-stream')
  , api = 'https://www.googleapis.com'

var Gmail = function (key) {
  if (!key) {
    throw new Error('Access key required')
  }
  this.key = key
}

var retrieve = function (key, q, endpoint, opts) {
  var result = new Parser({objectMode: true})
    , combined = ss()
    , opts = opts || {}

  request({
    url: api + '/gmail/v1/users/me/' + endpoint,
    json: true,
    qs: {
      q: q
    },
    headers: {
      'Authorization': 'Bearer ' + key
    }
  }, function (err, response, body) {
    if (err) {
      return result.emit('error', err)
    }

    if (body.error) {
      return result.emit('error', new Error(body.error.message))
    }

    var messages = body[endpoint].map(function (m) {
      return {
        'Content-Type': 'application/http',
        body: 'GET ' + api + '/gmail/v1/users/me/' + endpoint + '/' + m.id + '\n'
      }
    })

    result.resultSizeEstimate = body.resultSizeEstimate
    messages.length = opts.max || 100

    var r = request({
      method: 'POST',
      url: api + '/batch',
      multipart: messages,
      headers: {
        'Authorization': 'Bearer ' + key,
        'content-type': 'multipart/mixed'
      }
    })

    r.on('error', function (e) {
      result.emit('error', e)
    })

    r.on('response', function (res) {
      res.pipe(process.stdout)
      var type = res.headers['content-type']
        , form = new multiparty.Form

      res.headers['content-type'] = type.replace('multipart/mixed', 'multipart/related')

      form.on('part', function (part) {
        combined.write(part.pipe(split('\r\n')).pipe(new Parser))
      }).parse(res)
      form.on('close', function () {
        combined.end()
      })

    })
  })

  return combined.pipe(result)
}

/*
 * Fetches email that matches the query. Returns a stream of messages with a max of 100 messages
 * since the batch api sets a limit of 100.
 *
 * e.g. to search an inbox: gmail.messages('label:inbox')
 */
Gmail.prototype.messages = function (q, opts) {
  return retrieve(this.key, q, 'messages', opts)
}

Gmail.prototype.threads = function (q, opts) {
  return retrieve(this.key, q, 'threads', opts)
}

module.exports = Gmail
<commit_msg>adds thread and messages count<commit_after>var request = require('request')
  , Parser = require('./lib/parser')
  , split = require('split')
  , multiparty = require('multiparty')
  , ss = require('stream-stream')
  , api = 'https://www.googleapis.com'

var Gmail = function (key) {
  if (!key) {
    throw new Error('Access key required')
  }
  this.key = key
}

var retrieveCount = function (key, q, endpoint, next) {
  request({
    url: api + '/gmail/v1/users/me/' + endpoint,
    json: true,
    qs: {
      q: q,
      fields: 'resultSizeEstimate'
    },
    headers: {
      'Authorization': 'Bearer ' + key
    }
  }, function (err, response, body) {
    if (err) {
      return next(error)
    }
    if (body.error) {
      return next(new Error(body.error.message))
    }
    return next(null, body.resultSizeEstimate)
  })
}

var retrieve = function (key, q, endpoint, opts) {
  var result = new Parser({objectMode: true})
    , combined = ss()
    , opts = opts || {}

  request({
    url: api + '/gmail/v1/users/me/' + endpoint,
    json: true,
    qs: {
      q: q
    },
    headers: {
      'Authorization': 'Bearer ' + key
    }
  }, function (err, response, body) {
    if (err) {
      return result.emit('error', err)
    }

    if (body.error) {
      return result.emit('error', new Error(body.error.message))
    }

    var messages = body[endpoint].map(function (m) {
      return {
        'Content-Type': 'application/http',
        body: 'GET ' + api + '/gmail/v1/users/me/' + endpoint + '/' + m.id + '\n'
      }
    })

    result.resultSizeEstimate = body.resultSizeEstimate
    messages.length = opts.max || 100

    var r = request({
      method: 'POST',
      url: api + '/batch',
      multipart: messages,
      headers: {
        'Authorization': 'Bearer ' + key,
        'content-type': 'multipart/mixed'
      }
    })

    r.on('error', function (e) {
      result.emit('error', e)
    })

    r.on('response', function (res) {
      res.pipe(process.stdout)
      var type = res.headers['content-type']
        , form = new multiparty.Form

      res.headers['content-type'] = type.replace('multipart/mixed', 'multipart/related')

      form.on('part', function (part) {
        combined.write(part.pipe(split('\r\n')).pipe(new Parser))
      }).parse(res)
      form.on('close', function () {
        combined.end()
      })

    })
  })

  return combined.pipe(result)
}

/*
 * Feteches the number of estimated messages matching the query
 * Invokes callback with err and estimated number
 */
Gmail.prototype.estimatedMessages = function (q, next) {
  return retrieveCount(this.key, q, 'messages', next)
}

/*
 * Fetches email that matches the query. Returns a stream of messages with a max of 100 messages
 * since the batch api sets a limit of 100.
 *
 * e.g. to search an inbox: gmail.messages('label:inbox')
 */
Gmail.prototype.messages = function (q, opts) {
  return retrieve(this.key, q, 'messages', opts)
}

/*
 * Feteches the number of estimated threads matching the query
 * Invokes callback with err and estimated number
 */
Gmail.prototype.estimatedThreads = function (q, next) {
  return retrieveCount(this.key, q, 'threads', next)
}

Gmail.prototype.threads = function (q, opts) {
  return retrieve(this.key, q, 'threads', opts)
}

module.exports = Gmail
<|endoftext|>


<commit_before>var events = require('events')
var inherits = require('inherits')
var duplexify = require('duplexify')
var lpstream = require('length-prefixed-stream')
var stream = require('readable-stream')
var randomBytes = require('randombytes')
var createHmac = require('create-hmac')
var encryption = require('sodium-encryption')
var increment = require('increment-buffer')
var equals = require('buffer-equals')
var varint = require('varint')
var xtend = require('xtend')
var pe = require('passthrough-encoding')
var messages = require('./messages')

var KEEP_ALIVE = Buffer([0])

var DEFAULT_TYPES = [
  messages.Handshake,
  messages.Have,
  messages.Want,
  messages.Request,
  messages.Data,
  messages.Cancel,
  null, // pause
  null  // resume
]

var DEFAULT_EVENTS = [
  'handshake',
  'have',
  'want',
  'request',
  'data',
  'cancel',
  'pause',
  'resume'
]

while (DEFAULT_EVENTS.length < 64) { // reserved
  DEFAULT_EVENTS.push('unknown')
  DEFAULT_TYPES.push(null)
}

module.exports = use({})

function use (extensions) {
  var extensionNames = Object.keys(extensions).sort()
  var types = DEFAULT_TYPES.slice(0)
  var eventNames = DEFAULT_EVENTS.slice(0)

  function Channel (protocol) {
    events.EventEmitter.call(this)

    this.state = null // set by someone else
    this.protocol = protocol
    this.opened = false
    this.closed = false
    this.key = null
    this.discoveryKey = null
    this.local = -1
    this.remote = -1
    this.buffer = []

    this._nonce = randomBytes(24)
    this._remoteNonce = null
    this._ready = false

    this._firstNonce = Buffer(24)
    this._nonce.copy(this._firstNonce)
    this.on('handshake', this._onhandshake)
  }

  inherits(Channel, events.EventEmitter)

  Channel.prototype.remoteSupports = function (name) {
    return this.protocol.remoteSupports(name)
  }

  Channel.prototype.handshake = function (message) {
    return this.protocol._send(this, 0, message)
  }

  Channel.prototype.have = function (message) {
    return this.protocol._send(this, 1, message)
  }

  Channel.prototype.want = function (message) {
    return this.protocol._send(this, 2, message)
  }

  Channel.prototype.request = function (message) {
    return this.protocol._send(this, 3, message)
  }

  Channel.prototype.data = function (message) {
    return this.protocol._send(this, 4, message)
  }

  Channel.prototype.cancel = function (message) {
    return this.protocol._send(this, 5, message)
  }

  Channel.prototype.pause = function () {
    return this.protocol._send(this, 6, null)
  }

  Channel.prototype.resume = function () {
    return this.protocol._send(this, 7, null)
  }

  Channel.prototype.end = function () {
    this.protocol._close(this)
  }

  Channel.prototype.destroy = function (err) {
    this.protocol.destroy(err)
  }

  Channel.prototype._onhandshake = function (handshake) {
    this.protocol._onhandshake(handshake)
  }

  extensionNames.forEach(function (name, type) {
    if (Channel.prototype[name] || name === 'error' || name === 'tick') {
      throw new Error('Invalid extension name')
    }

    var enc = isEncoder(extensions[name]) ? extensions[name] : pe

    types.push(enc)
    eventNames.push(name)

    Channel.prototype[name] = function (message) {
      return this.protocol._send(this, DEFAULT_TYPES.length + type, message)
    }
  })

  function Protocol (opts, onopen) {
    if (!(this instanceof Protocol)) return new Protocol(opts, onopen)
    if (typeof opts === 'function') {
      onopen = opts
      opts = null
    }
    if (!opts) opts = {}

    var self = this
    duplexify.call(this)

    var encrypt = opts.private !== undefined ? opts.private : opts.encrypt

    this.channels = {}
    this.private = encrypt !== false

    this.id = opts.id || randomBytes(32)
    this.remoteId = null

    this._finalized = false
    this._paused = 0
    this._local = []
    this._remote = []

    this._remoteExtensions = new Array(extensions.length)
    this._localExtensions = new Array(extensions.length)
    for (var i = 0; i < extensions.length; i++) {
      this._remoteExtensions[i] = this._localExtensions[i] = -1
    }

    this._keepAlive = 0
    this._remoteKeepAlive = 0
    this._interval = null

    this._encode = stream.Readable()
    this._encode._read = noop

    this._decode = lpstream.decode({allowEmpty: true, limit: 5 * 1024 * 1024})
    this._decode.on('data', parse)

    this.setReadable(this._encode)
    this.setWritable(this._decode)

    this.on('close', onfinalize)
    this.on('end', onfinalize)
    this.on('finish', this.finalize)

    if (onopen) this.on('open', onopen)

    function onfinalize () {
      if (self._finalized) return
      self._finalized = true
      self.destroyed = true // defined by duplexify

      clearInterval(this._interval)

      var keys = Object.keys(self.channels)
      for (var i = 0; i < keys.length; i++) {
        self._close(self.channels[keys[i]])
      }
    }

    function parse (data) {
      self._parse(data)
    }
  }

  inherits(Protocol, duplexify)

  Protocol.use = function (ext) {
    if (typeof ext === 'string') ext = toObject(ext)
    return use(xtend(ext, extensions))
  }

  Protocol.parseDiscoveryKey = function (buf) {
    if (buf[0] !== 0) throw Error('Invalid message')
    return messages.Open.decode(buf, 1).feed
  }

  Protocol.prototype.finalize = function () {
    this._encode.push(null)
  }

  Protocol.prototype.setTimeout = function (ms, ontimeout) {
    if (this.destroyed) return
    if (ontimeout) this.once('timeout', ontimeout)

    var self = this

    this._keepAlive = 0
    this._remoteKeepAlive = 0

    clearInterval(this._interval)
    this._interval = setInterval(kick, (ms / 4) | 0)
    if (this._interval.unref) this._interval.unref()

    function kick () {
      if (self._remoteKeepAlive > 4) {
        clearInterval(self._interval)
        self.emit('timeout')
        return
      }

      self._tick()
      self._remoteKeepAlive++
      if (self._keepAlive > 2) {
        self._encode.push(KEEP_ALIVE)
        self._keepAlive = 0
      } else {
        self._keepAlive++
      }
    }
  }

  Protocol.prototype.remoteSupports = function (name) {
    var i = typeof name === 'string' ? extensionNames.indexOf(name) : name
    return i > -1 && this._localExtensions[i] > -1
  }

  Protocol.prototype.open = function (key, opts) {
    if (this.destroyed) return null // already finalized
    if (!opts) opts = {}

    var d = opts.discoveryKey || discoveryKey(key)
    var keyHex = d.toString('hex')
    var ch = this.channels[keyHex]

    if (!ch) {
      ch = new Channel(this)
      ch.discoveryKey = d
      this.channels[keyHex] = ch
    }

    if (ch.local > -1) return ch

    if (opts.state) ch.state = opts.state

    ch.key = key
    ch.local = this._local.indexOf(null)
    if (ch.local === -1) ch.local = this._local.push(null) - 1
    this._local[ch.local] = ch

    var open = messages.Open.encode({
      feed: ch.discoveryKey,
      nonce: ch._nonce
    })

    this._sendRaw(ch, open)

    if (!this.remoteId && opts.handshake !== false) {
      ch.handshake({
        id: this.id,
        extensions: extensionNames
      })
    }

    this.emit('channel', ch)
    this._open(ch)
    if (ch.buffer.length) this._parseSoon(ch)

    ch._ready = true // to avoid premature events

    return ch
  }

  Protocol.prototype._send = function (channel, type, message) {
    if (channel.closed) return false

    var enc = types[type]
    var len = enc ? enc.encodingLength(message) : 0
    var buf = Buffer(len + 1)

    buf[0] = type
    if (enc) enc.encode(message, buf, 1)

    if (this.private) buf = this._encrypt(channel, buf)

    return this._sendRaw(channel, buf)
  }

  Protocol.prototype._sendRaw = function (channel, buf) {
    this._keepAlive = 0

    var len = buf.length + varint.encodingLength(channel.local)
    var box = Buffer(varint.encodingLength(len) + len)
    var offset = 0

    varint.encode(len, box, offset)
    offset += varint.encode.bytes

    varint.encode(channel.local, box, offset)
    offset += varint.encode.bytes

    buf.copy(box, offset)

    return this._encode.push(box)
  }

  Protocol.prototype._pause = function () {
    if (!this._paused++) this._decode.pause()
  }

  Protocol.prototype._resume = function () {
    if (!--this._paused) this._decode.resume()
  }

  Protocol.prototype._parseSoon = function (channel) {
    var self = this

    this._pause()
    process.nextTick(drain)

    function drain () {
      if (self.destroyed || channel.closed) return

      var buffer = channel.buffer
      channel.buffer = []

      while (buffer.length) self._parse(buffer.shift())
      if (!self.destroyed) self._resume()
    }
  }

  Protocol.prototype._parse = function (data) {
    this._remoteKeepAlive = 0

    if (!data.length || this.destroyed) return

    var remote = varint.decode(data, 0)
    var offset = varint.decode.bytes

    if (remote >= this._remote.length) this._remote.push(null)
    if (remote > this._remote.length) return this.destroy(new Error('Unexpected channel number'))

    if (!this._remote[remote]) this._onopen(remote, data, offset)
    else if (offset !== data.length) this._onmessage(remote, data, offset)
    else this._onclose(remote)
  }

  Protocol.prototype._tick = function () {
    for (var i = 0; i < this._local.length; i++) {
      var ch = this._local[i]
      if (ch) ch.emit('tick')
    }
  }

  Protocol.prototype._parseType = function (type) {
    if (type > 127) return -1
    if (type < 64) return type
    if (type - 64 >= this._remoteExtensions.length) return -1

    type = this._remoteExtensions[type - 64]
    if (type === -1) return -1
    return type + 64
  }

  Protocol.prototype._onopen = function (remote, data, offset) {
    try {
      var open = messages.Open.decode(data, offset)
    } catch (err) {
      return this.destroy(err)
    }

    if (open.feed.length !== 32 || open.nonce.length !== 24) return this.destroy(new Error('Invalid open message'))

    var keyHex = open.feed.toString('hex')
    var ch = this.channels[keyHex]

    if (!ch) {
      ch = new Channel(this)
      ch.discoveryKey = open.feed
      this.channels[keyHex] = ch
    }

    if (ch.remote > -1) return this.destroy(new Error('Double open for same channel'))

    ch.remote = remote
    ch._remoteNonce = open.nonce
    this._remote[remote] = ch
    this._open(ch)

    if (!this.destroyed && ch.local === -1) this.emit('open', ch.discoveryKey)
  }

  Protocol.prototype._onmessage = function (remote, data, offset) {
    var channel = this._remote[remote]

    if (!channel.key || channel.buffer.length || !channel._ready) {
      if (channel.buffer.length === 16) return this.destroy(new Error('Buffer overflow'))
      channel.buffer.push(data)
      return
    }

    var box = this._decrypt(channel, data.slice(offset))
    if (!box || !box.length) return this.destroy(new Error('Invalid message'))

    var type = this._parseType(box[0])
    if (type < 0) return

    if (type && !this.remoteId) return this.destroy(new Error('Did not receive handshake'))
    if (type >= types.length) return

    var enc = types[type]

    try {
      var message = enc ? enc.decode(box, 1) : null
    } catch (err) {
      return this.destroy(err)
    }

    channel.emit(eventNames[type], message)
  }

  Protocol.prototype._onclose = function (remote) {
    var channel = this._remote[remote]

    this._remote[remote] = null
    channel.remote = -1

    if (channel.local > -1) this._close(channel)

    var keyHex = channel.discoveryKey.toString('hex')
    if (this.channels[keyHex] === channel) delete this.channels[keyHex]
  }

  Protocol.prototype._onhandshake = function (handshake) {
    if (this.remoteId) return // already handshaked
    this.remoteId = handshake.id

    var exts = handshake.extensions
    // extensions *must* be sorted
    var local = 0
    var remote = 0

    while (local < extensionNames.length && remote < exts.length && remote < 64) {
      if (extensionNames[local] === exts[remote]) {
        this._localExtensions[local] = remote
        this._remoteExtensions[remote] = local
        local++
        remote++
      } else if (extensionNames[local] < exts[remote]) {
        local++
      } else {
        remote++
      }
    }

    this.emit('handshake')
  }

  Protocol.prototype._open = function (channel) {
    if (channel.local === -1 || channel.remote === -1) return
    if (equals(channel._remoteNonce, channel._firstNonce)) return this.destroy(new Error('Remote echoed nonce'))
    channel.opened = true
    channel.emit('open')
  }

  Protocol.prototype._close = function (channel) {
    if (channel.closed) return
    channel.closed = true

    if (!this.destroyed) this._sendRaw(channel, Buffer(0))

    this._local[channel.local] = null
    channel.local = -1
    channel.emit('end')
  }

  Protocol.prototype._encrypt = function (channel, buf) {
    if (!this.private) return buf
    var box = encryption.encrypt(buf, channel._nonce, channel.key)
    increment(channel._nonce)
    return box
  }

  Protocol.prototype._decrypt = function (channel, box) {
    if (!this.private) return box
    var buf = box.length < 16 ? null : encryption.decrypt(box, channel._remoteNonce, channel.key)
    if (!buf) return null
    increment(channel._remoteNonce)
    return buf
  }

  function discoveryKey (key) {
    return createHmac('sha256', key).update('hypercore').digest()
  }

  function isEncoder (val) {
    return val && typeof val.encode === 'function'
  }

  function toObject (name) {
    var tmp = {}
    tmp[name] = true
    return tmp
  }

  function noop () {}

  return Protocol
}
<commit_msg>support limit option and bump default limit to 8mb<commit_after>var events = require('events')
var inherits = require('inherits')
var duplexify = require('duplexify')
var lpstream = require('length-prefixed-stream')
var stream = require('readable-stream')
var randomBytes = require('randombytes')
var createHmac = require('create-hmac')
var encryption = require('sodium-encryption')
var increment = require('increment-buffer')
var equals = require('buffer-equals')
var varint = require('varint')
var xtend = require('xtend')
var pe = require('passthrough-encoding')
var messages = require('./messages')

var KEEP_ALIVE = Buffer([0])

var DEFAULT_TYPES = [
  messages.Handshake,
  messages.Have,
  messages.Want,
  messages.Request,
  messages.Data,
  messages.Cancel,
  null, // pause
  null  // resume
]

var DEFAULT_EVENTS = [
  'handshake',
  'have',
  'want',
  'request',
  'data',
  'cancel',
  'pause',
  'resume'
]

while (DEFAULT_EVENTS.length < 64) { // reserved
  DEFAULT_EVENTS.push('unknown')
  DEFAULT_TYPES.push(null)
}

module.exports = use({})

function use (extensions) {
  var extensionNames = Object.keys(extensions).sort()
  var types = DEFAULT_TYPES.slice(0)
  var eventNames = DEFAULT_EVENTS.slice(0)

  function Channel (protocol) {
    events.EventEmitter.call(this)

    this.state = null // set by someone else
    this.protocol = protocol
    this.opened = false
    this.closed = false
    this.key = null
    this.discoveryKey = null
    this.local = -1
    this.remote = -1
    this.buffer = []

    this._nonce = randomBytes(24)
    this._remoteNonce = null
    this._ready = false

    this._firstNonce = Buffer(24)
    this._nonce.copy(this._firstNonce)
    this.on('handshake', this._onhandshake)
  }

  inherits(Channel, events.EventEmitter)

  Channel.prototype.remoteSupports = function (name) {
    return this.protocol.remoteSupports(name)
  }

  Channel.prototype.handshake = function (message) {
    return this.protocol._send(this, 0, message)
  }

  Channel.prototype.have = function (message) {
    return this.protocol._send(this, 1, message)
  }

  Channel.prototype.want = function (message) {
    return this.protocol._send(this, 2, message)
  }

  Channel.prototype.request = function (message) {
    return this.protocol._send(this, 3, message)
  }

  Channel.prototype.data = function (message) {
    return this.protocol._send(this, 4, message)
  }

  Channel.prototype.cancel = function (message) {
    return this.protocol._send(this, 5, message)
  }

  Channel.prototype.pause = function () {
    return this.protocol._send(this, 6, null)
  }

  Channel.prototype.resume = function () {
    return this.protocol._send(this, 7, null)
  }

  Channel.prototype.end = function () {
    this.protocol._close(this)
  }

  Channel.prototype.destroy = function (err) {
    this.protocol.destroy(err)
  }

  Channel.prototype._onhandshake = function (handshake) {
    this.protocol._onhandshake(handshake)
  }

  extensionNames.forEach(function (name, type) {
    if (Channel.prototype[name] || name === 'error' || name === 'tick') {
      throw new Error('Invalid extension name')
    }

    var enc = isEncoder(extensions[name]) ? extensions[name] : pe

    types.push(enc)
    eventNames.push(name)

    Channel.prototype[name] = function (message) {
      return this.protocol._send(this, DEFAULT_TYPES.length + type, message)
    }
  })

  function Protocol (opts, onopen) {
    if (!(this instanceof Protocol)) return new Protocol(opts, onopen)
    if (typeof opts === 'function') {
      onopen = opts
      opts = null
    }
    if (!opts) opts = {}

    var self = this
    duplexify.call(this)

    var encrypt = opts.private !== undefined ? opts.private : opts.encrypt

    this.channels = {}
    this.private = encrypt !== false

    this.id = opts.id || randomBytes(32)
    this.remoteId = null

    this._finalized = false
    this._paused = 0
    this._local = []
    this._remote = []

    this._remoteExtensions = new Array(extensions.length)
    this._localExtensions = new Array(extensions.length)
    for (var i = 0; i < extensions.length; i++) {
      this._remoteExtensions[i] = this._localExtensions[i] = -1
    }

    this._keepAlive = 0
    this._remoteKeepAlive = 0
    this._interval = null

    this._encode = stream.Readable()
    this._encode._read = noop

    this._decode = lpstream.decode({allowEmpty: true, limit: opts.limit || 8 * 1024 * 1024})
    this._decode.on('data', parse)

    this.setReadable(this._encode)
    this.setWritable(this._decode)

    this.on('close', onfinalize)
    this.on('end', onfinalize)
    this.on('finish', this.finalize)

    if (onopen) this.on('open', onopen)

    function onfinalize () {
      if (self._finalized) return
      self._finalized = true
      self.destroyed = true // defined by duplexify

      clearInterval(this._interval)

      var keys = Object.keys(self.channels)
      for (var i = 0; i < keys.length; i++) {
        self._close(self.channels[keys[i]])
      }
    }

    function parse (data) {
      self._parse(data)
    }
  }

  inherits(Protocol, duplexify)

  Protocol.use = function (ext) {
    if (typeof ext === 'string') ext = toObject(ext)
    return use(xtend(ext, extensions))
  }

  Protocol.parseDiscoveryKey = function (buf) {
    if (buf[0] !== 0) throw Error('Invalid message')
    return messages.Open.decode(buf, 1).feed
  }

  Protocol.prototype.finalize = function () {
    this._encode.push(null)
  }

  Protocol.prototype.setTimeout = function (ms, ontimeout) {
    if (this.destroyed) return
    if (ontimeout) this.once('timeout', ontimeout)

    var self = this

    this._keepAlive = 0
    this._remoteKeepAlive = 0

    clearInterval(this._interval)
    this._interval = setInterval(kick, (ms / 4) | 0)
    if (this._interval.unref) this._interval.unref()

    function kick () {
      if (self._remoteKeepAlive > 4) {
        clearInterval(self._interval)
        self.emit('timeout')
        return
      }

      self._tick()
      self._remoteKeepAlive++
      if (self._keepAlive > 2) {
        self._encode.push(KEEP_ALIVE)
        self._keepAlive = 0
      } else {
        self._keepAlive++
      }
    }
  }

  Protocol.prototype.remoteSupports = function (name) {
    var i = typeof name === 'string' ? extensionNames.indexOf(name) : name
    return i > -1 && this._localExtensions[i] > -1
  }

  Protocol.prototype.open = function (key, opts) {
    if (this.destroyed) return null // already finalized
    if (!opts) opts = {}

    var d = opts.discoveryKey || discoveryKey(key)
    var keyHex = d.toString('hex')
    var ch = this.channels[keyHex]

    if (!ch) {
      ch = new Channel(this)
      ch.discoveryKey = d
      this.channels[keyHex] = ch
    }

    if (ch.local > -1) return ch

    if (opts.state) ch.state = opts.state

    ch.key = key
    ch.local = this._local.indexOf(null)
    if (ch.local === -1) ch.local = this._local.push(null) - 1
    this._local[ch.local] = ch

    var open = messages.Open.encode({
      feed: ch.discoveryKey,
      nonce: ch._nonce
    })

    this._sendRaw(ch, open)

    if (!this.remoteId && opts.handshake !== false) {
      ch.handshake({
        id: this.id,
        extensions: extensionNames
      })
    }

    this.emit('channel', ch)
    this._open(ch)
    if (ch.buffer.length) this._parseSoon(ch)

    ch._ready = true // to avoid premature events

    return ch
  }

  Protocol.prototype._send = function (channel, type, message) {
    if (channel.closed) return false

    var enc = types[type]
    var len = enc ? enc.encodingLength(message) : 0
    var buf = Buffer(len + 1)

    buf[0] = type
    if (enc) enc.encode(message, buf, 1)

    if (this.private) buf = this._encrypt(channel, buf)

    return this._sendRaw(channel, buf)
  }

  Protocol.prototype._sendRaw = function (channel, buf) {
    this._keepAlive = 0

    var len = buf.length + varint.encodingLength(channel.local)
    var box = Buffer(varint.encodingLength(len) + len)
    var offset = 0

    varint.encode(len, box, offset)
    offset += varint.encode.bytes

    varint.encode(channel.local, box, offset)
    offset += varint.encode.bytes

    buf.copy(box, offset)

    return this._encode.push(box)
  }

  Protocol.prototype._pause = function () {
    if (!this._paused++) this._decode.pause()
  }

  Protocol.prototype._resume = function () {
    if (!--this._paused) this._decode.resume()
  }

  Protocol.prototype._parseSoon = function (channel) {
    var self = this

    this._pause()
    process.nextTick(drain)

    function drain () {
      if (self.destroyed || channel.closed) return

      var buffer = channel.buffer
      channel.buffer = []

      while (buffer.length) self._parse(buffer.shift())
      if (!self.destroyed) self._resume()
    }
  }

  Protocol.prototype._parse = function (data) {
    this._remoteKeepAlive = 0

    if (!data.length || this.destroyed) return

    var remote = varint.decode(data, 0)
    var offset = varint.decode.bytes

    if (remote >= this._remote.length) this._remote.push(null)
    if (remote > this._remote.length) return this.destroy(new Error('Unexpected channel number'))

    if (!this._remote[remote]) this._onopen(remote, data, offset)
    else if (offset !== data.length) this._onmessage(remote, data, offset)
    else this._onclose(remote)
  }

  Protocol.prototype._tick = function () {
    for (var i = 0; i < this._local.length; i++) {
      var ch = this._local[i]
      if (ch) ch.emit('tick')
    }
  }

  Protocol.prototype._parseType = function (type) {
    if (type > 127) return -1
    if (type < 64) return type
    if (type - 64 >= this._remoteExtensions.length) return -1

    type = this._remoteExtensions[type - 64]
    if (type === -1) return -1
    return type + 64
  }

  Protocol.prototype._onopen = function (remote, data, offset) {
    try {
      var open = messages.Open.decode(data, offset)
    } catch (err) {
      return this.destroy(err)
    }

    if (open.feed.length !== 32 || open.nonce.length !== 24) return this.destroy(new Error('Invalid open message'))

    var keyHex = open.feed.toString('hex')
    var ch = this.channels[keyHex]

    if (!ch) {
      ch = new Channel(this)
      ch.discoveryKey = open.feed
      this.channels[keyHex] = ch
    }

    if (ch.remote > -1) return this.destroy(new Error('Double open for same channel'))

    ch.remote = remote
    ch._remoteNonce = open.nonce
    this._remote[remote] = ch
    this._open(ch)

    if (!this.destroyed && ch.local === -1) this.emit('open', ch.discoveryKey)
  }

  Protocol.prototype._onmessage = function (remote, data, offset) {
    var channel = this._remote[remote]

    if (!channel.key || channel.buffer.length || !channel._ready) {
      if (channel.buffer.length === 16) return this.destroy(new Error('Buffer overflow'))
      channel.buffer.push(data)
      return
    }

    var box = this._decrypt(channel, data.slice(offset))
    if (!box || !box.length) return this.destroy(new Error('Invalid message'))

    var type = this._parseType(box[0])
    if (type < 0) return

    if (type && !this.remoteId) return this.destroy(new Error('Did not receive handshake'))
    if (type >= types.length) return

    var enc = types[type]

    try {
      var message = enc ? enc.decode(box, 1) : null
    } catch (err) {
      return this.destroy(err)
    }

    channel.emit(eventNames[type], message)
  }

  Protocol.prototype._onclose = function (remote) {
    var channel = this._remote[remote]

    this._remote[remote] = null
    channel.remote = -1

    if (channel.local > -1) this._close(channel)

    var keyHex = channel.discoveryKey.toString('hex')
    if (this.channels[keyHex] === channel) delete this.channels[keyHex]
  }

  Protocol.prototype._onhandshake = function (handshake) {
    if (this.remoteId) return // already handshaked
    this.remoteId = handshake.id

    var exts = handshake.extensions
    // extensions *must* be sorted
    var local = 0
    var remote = 0

    while (local < extensionNames.length && remote < exts.length && remote < 64) {
      if (extensionNames[local] === exts[remote]) {
        this._localExtensions[local] = remote
        this._remoteExtensions[remote] = local
        local++
        remote++
      } else if (extensionNames[local] < exts[remote]) {
        local++
      } else {
        remote++
      }
    }

    this.emit('handshake')
  }

  Protocol.prototype._open = function (channel) {
    if (channel.local === -1 || channel.remote === -1) return
    if (equals(channel._remoteNonce, channel._firstNonce)) return this.destroy(new Error('Remote echoed nonce'))
    channel.opened = true
    channel.emit('open')
  }

  Protocol.prototype._close = function (channel) {
    if (channel.closed) return
    channel.closed = true

    if (!this.destroyed) this._sendRaw(channel, Buffer(0))

    this._local[channel.local] = null
    channel.local = -1
    channel.emit('end')
  }

  Protocol.prototype._encrypt = function (channel, buf) {
    if (!this.private) return buf
    var box = encryption.encrypt(buf, channel._nonce, channel.key)
    increment(channel._nonce)
    return box
  }

  Protocol.prototype._decrypt = function (channel, box) {
    if (!this.private) return box
    var buf = box.length < 16 ? null : encryption.decrypt(box, channel._remoteNonce, channel.key)
    if (!buf) return null
    increment(channel._remoteNonce)
    return buf
  }

  function discoveryKey (key) {
    return createHmac('sha256', key).update('hypercore').digest()
  }

  function isEncoder (val) {
    return val && typeof val.encode === 'function'
  }

  function toObject (name) {
    var tmp = {}
    tmp[name] = true
    return tmp
  }

  function noop () {}

  return Protocol
}
<|endoftext|>