<commit_before>var _ = require('underscore'),
    async = require('async'),
    build = require('./build'),
    combine = require('./jsCombine'),
    configLoader = require('./config'),
    Context = require('./context'),
    EventEmitter = require('events').EventEmitter,
    fs = require('fs'),
    fu = require('./fileUtil'),
    Mixin = require('./mixins'),
    plugin = require('./plugin'),
    WatchManager = require('./watch-manager');

exports.build = build;
exports.fileUtil = fu;
exports.plugin = plugin.plugin;
exports.combine = combine.combine;
exports.config = configLoader;

/**
 *
 * @name init
 * @function This function initializes a Lumbar instance
 * @param {string} lumbarFile The lumbarFile is the main
 *  file. Its responsible to define all the platforms,
 *  packages, modules, and templates for Lumbar to use.
 * @param {Object} options supports the following options:
 *   packageConfigFile (string): name of the package config file.
 *   outdir (string): path to directory of where to output the files.
 *   minimize (boolean): Should we minimize the files?
 * @return {Object.<Function>}
 */
exports.init = function(lumbarFile, options) {
    var outdir = options.outdir,
        plugins = plugin.create(options),
        mixins = new Mixin(options),
        config,
        configCache,
        context;

    function logError(err) {
      if (err) {
        event.emit('error', err);
      }
    }

    function loadConfig(path, callback) {
      try {
        fu.resetCache();

        configCache = {};
        config = configLoader.load(path);
        plugins.initialize(config);

        config.outdir = options.outdir = outdir || config.attributes.output;

        context = new Context(options, config, plugins, mixins, event);
        context.options = options;
        context.configCache = configCache;

        mixins.initialize(context, function(err) {
          if (err) {
            return callback(err);
          }

          plugins.loadConfig(context, function(err) {
            if (err) {
              return callback(err);
            }

            // Ensure that we have the proper build output
            if (!config.outdir) {
              return callback(new Error('Output must be defined on the command line or config file.'));
            }
            context.outdir = config.outdir;

            fu.ensureDirs(config.outdir + '/.', function() {
              var stat = fs.statSync(config.outdir);
              if (!stat.isDirectory()) {
                callback(new Error('Output must be a directory'));
              } else {
                callback();
              }
            });
          });
        });
      } catch (err) {
        callback(err);
      }
    }

    function buildPackages(packageName, modules, callback) {
      if (!callback) {
        callback = modules;
        modules = undefined;
      }

      // Allow a string or a list as modules input
      if (!_.isArray(modules)) {
        modules = [modules];
      } else if (!modules.length) {
        // Special case empty array input to build all
        modules = [undefined];
      }

      var options = {};
      if (typeof packageName === 'object') {
        options = packageName;
        packageName = options.package;
      }

      var packageNames = packageName ? [packageName] : config.packageList(),
          contexts = [];

      packageNames.forEach(function(pkg) {
        modules.forEach(function(module) {
          options.package = pkg;
          options.module = module || undefined;   // '' -> undefined

          var platforms = config.platformList(pkg);
          platforms.forEach(function(platform) {
            options.platform = platform;

            var newContext = context.clone(options);
            contexts.push(newContext);
          });
        });
      });

      async.forEach(contexts, buildPlatform, callback);
    }
    function buildPlatform(context, callback) {
      var modes = context.mode ? [context.mode] : plugins.modes();

      async.forEach(modes, function(mode, callback) {
          buildMode(mode, context, callback);
        },
        callback);
    }
    function buildMode(mode, context, callback) {
      var modules = context.module ? [context.module] : config.moduleList(context.package);

      context = context.clone();
      context.mode = mode;
      context.modeCache = {};

      if (context.fileConfig) {
        processConfig(context.fileConfig, callback);
      } else {
        plugins.outputConfigs(context, function(err, configs) {
          if (err) {
            return callback(err);
          }
          async.forEach(configs, processConfig, callback);
        });
      }

      function processConfig(fileConfig, callback) {
        var fileContext = context.clone(true);
        fileContext.fileConfig = fileConfig;
        fileContext.resources = [];
        fileContext.combineResources = {};
        fileContext.fileCache = fileContext.combined ? {} : undefined;

        async.forEach(modules, function(module, callback) {
          var moduleContext = fileContext.clone();
          moduleContext.module = module;

          buildModule(moduleContext, callback);
        },
        function(err) {
          if (err) {
            return callback(err);
          }

          plugins.modeComplete(fileContext, callback);
        });
      }
    }
    function buildModule(context, callback) {
      var module = config.module(context.module);
      if (!module) {
        return callback(new Error('Unable to find module "' + context.module + '"'));
      }

      context.module = module;
      context.fileCache = context.combined ? context.fileCache : {};
      context.moduleCache = {};

      var resource = context.resource;
      if (resource) {
        resource = resource.originalResource || resource;
        processResources([resource]);
      } else {
        // Load all resources associated with this module
        build.loadResources(context, function(err, resources) {
          if (err) {
            return callback(err);
          }
          processResources(resources);
        });
      }

      function processResources(resources) {
        build.processResources(resources, context, function(err, resources) {
          if (err) {
            return callback(err);
          }

          context.moduleResources = resources;
          plugins.module(context, callback);
        });
      }
    }

    var event = new EventEmitter(),
        watch;

    function watchOutputHandler(status) {
      if (!watch) {
        // We've been cleaned up but residuals may still exist, do nothing on this exec
        return;
      }

      if (status.fileConfig.isPrimary) {
        delete status.fileConfig;
      } else if (status.fileConfig.isPrimary === false) {
        // This config is directly linked to another meaning we don't want to watch on it as
        // it will be rebuilt.
        return;
      }

      var originalConfig = config;
      watch.moduleOutput(status, function() {
        if (config !== originalConfig) {
          // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch)
          return;
        }

        buildPlatform(context.clone(status), logError);
      });
    }

    return _.extend(event, {
      config: function() {
        return config;
      },

      use: function(plugin) {
        plugins.use(plugin);
      },
      /**
       *
       * @name build
       * @function This function builds out the package(s).
       * @param {string} packageName the name of the package listed under
       *  'packages' from the lumbarFile passed in during the call to init().
       * @param {Function} callback the node process Function
       */
      build: function(packageName, modules, callback) {
        loadConfig(lumbarFile, function(err) {
          if (err) {
            return callback(err);
          }

          buildPackages(packageName, modules, callback);
        });
      },
      watch: function(packageName, modules, callback) {
        if (!fs.watch) {
          throw new Error('Watch requires fs.watch, introduced in Node v0.6.0');
        }

        watch = new WatchManager();
        watch.on('watch-change', function(info) {
          event.emit('watch-change', info);
        });

        var self = this;
        loadConfig(lumbarFile, function(err) {
          if (err) {
            logError(err);
          }

          if (!callback) {
            callback = modules;
            modules = undefined;
          }

          // Watch for changes in the config file
          var mixinPaths = _.filter(_.pluck(mixins.configs, 'path'), function(path) { return path; });
          watch.configFile(lumbarFile, mixinPaths, function() {
            config = undefined;
            self.watch(packageName, callback);
          });

          // If we have errored do not exec everything as it could be in an indeterminate state
          if (err) {
            return;
          }

          // Watch the individual components
          event.removeListener('output', watchOutputHandler);
          event.on('output', watchOutputHandler);

          // Actual build everything
          var packages = packageName ? [packageName] : config.packageList();
          packages.forEach(function(name) {
            buildPackages(name, modules, logError);
          });
        });
      },
      unwatch: function() {
        event.removeListener('output', watchOutputHandler);
        if (watch) {
          watch.removeAllListeners();
          watch.reset();
          watch = undefined;
        }
      }
    });
};
<commit_msg>Throw if no callback is passed<commit_after>var _ = require('underscore'),
    async = require('async'),
    build = require('./build'),
    combine = require('./jsCombine'),
    configLoader = require('./config'),
    Context = require('./context'),
    EventEmitter = require('events').EventEmitter,
    fs = require('fs'),
    fu = require('./fileUtil'),
    Mixin = require('./mixins'),
    plugin = require('./plugin'),
    WatchManager = require('./watch-manager');

exports.build = build;
exports.fileUtil = fu;
exports.plugin = plugin.plugin;
exports.combine = combine.combine;
exports.config = configLoader;

/**
 *
 * @name init
 * @function This function initializes a Lumbar instance
 * @param {string} lumbarFile The lumbarFile is the main
 *  file. Its responsible to define all the platforms,
 *  packages, modules, and templates for Lumbar to use.
 * @param {Object} options supports the following options:
 *   packageConfigFile (string): name of the package config file.
 *   outdir (string): path to directory of where to output the files.
 *   minimize (boolean): Should we minimize the files?
 * @return {Object.<Function>}
 */
exports.init = function(lumbarFile, options) {
    var outdir = options.outdir,
        plugins = plugin.create(options),
        mixins = new Mixin(options),
        config,
        configCache,
        context;

    function logError(err) {
      if (err) {
        event.emit('error', err);
      }
    }

    function loadConfig(path, callback) {
      try {
        fu.resetCache();

        configCache = {};
        config = configLoader.load(path);
        plugins.initialize(config);

        config.outdir = options.outdir = outdir || config.attributes.output;

        context = new Context(options, config, plugins, mixins, event);
        context.options = options;
        context.configCache = configCache;

        mixins.initialize(context, function(err) {
          if (err) {
            return callback(err);
          }

          plugins.loadConfig(context, function(err) {
            if (err) {
              return callback(err);
            }

            // Ensure that we have the proper build output
            if (!config.outdir) {
              return callback(new Error('Output must be defined on the command line or config file.'));
            }
            context.outdir = config.outdir;

            fu.ensureDirs(config.outdir + '/.', function() {
              var stat = fs.statSync(config.outdir);
              if (!stat.isDirectory()) {
                callback(new Error('Output must be a directory'));
              } else {
                callback();
              }
            });
          });
        });
      } catch (err) {
        callback(err);
      }
    }

    function buildPackages(packageName, modules, callback) {
      if (!callback) {
        callback = modules;
        modules = undefined;
      }

      // Allow a string or a list as modules input
      if (!_.isArray(modules)) {
        modules = [modules];
      } else if (!modules.length) {
        // Special case empty array input to build all
        modules = [undefined];
      }

      var options = {};
      if (typeof packageName === 'object') {
        options = packageName;
        packageName = options.package;
      }

      var packageNames = packageName ? [packageName] : config.packageList(),
          contexts = [];

      packageNames.forEach(function(pkg) {
        modules.forEach(function(module) {
          options.package = pkg;
          options.module = module || undefined;   // '' -> undefined

          var platforms = config.platformList(pkg);
          platforms.forEach(function(platform) {
            options.platform = platform;

            var newContext = context.clone(options);
            contexts.push(newContext);
          });
        });
      });

      async.forEach(contexts, buildPlatform, callback);
    }
    function buildPlatform(context, callback) {
      var modes = context.mode ? [context.mode] : plugins.modes();

      async.forEach(modes, function(mode, callback) {
          buildMode(mode, context, callback);
        },
        callback);
    }
    function buildMode(mode, context, callback) {
      var modules = context.module ? [context.module] : config.moduleList(context.package);

      context = context.clone();
      context.mode = mode;
      context.modeCache = {};

      if (context.fileConfig) {
        processConfig(context.fileConfig, callback);
      } else {
        plugins.outputConfigs(context, function(err, configs) {
          if (err) {
            return callback(err);
          }
          async.forEach(configs, processConfig, callback);
        });
      }

      function processConfig(fileConfig, callback) {
        var fileContext = context.clone(true);
        fileContext.fileConfig = fileConfig;
        fileContext.resources = [];
        fileContext.combineResources = {};
        fileContext.fileCache = fileContext.combined ? {} : undefined;

        async.forEach(modules, function(module, callback) {
          var moduleContext = fileContext.clone();
          moduleContext.module = module;

          buildModule(moduleContext, callback);
        },
        function(err) {
          if (err) {
            return callback(err);
          }

          plugins.modeComplete(fileContext, callback);
        });
      }
    }
    function buildModule(context, callback) {
      var module = config.module(context.module);
      if (!module) {
        return callback(new Error('Unable to find module "' + context.module + '"'));
      }

      context.module = module;
      context.fileCache = context.combined ? context.fileCache : {};
      context.moduleCache = {};

      var resource = context.resource;
      if (resource) {
        resource = resource.originalResource || resource;
        processResources([resource]);
      } else {
        // Load all resources associated with this module
        build.loadResources(context, function(err, resources) {
          if (err) {
            return callback(err);
          }
          processResources(resources);
        });
      }

      function processResources(resources) {
        build.processResources(resources, context, function(err, resources) {
          if (err) {
            return callback(err);
          }

          context.moduleResources = resources;
          plugins.module(context, callback);
        });
      }
    }

    var event = new EventEmitter(),
        watch;

    function watchOutputHandler(status) {
      if (!watch) {
        // We've been cleaned up but residuals may still exist, do nothing on this exec
        return;
      }

      if (status.fileConfig.isPrimary) {
        delete status.fileConfig;
      } else if (status.fileConfig.isPrimary === false) {
        // This config is directly linked to another meaning we don't want to watch on it as
        // it will be rebuilt.
        return;
      }

      var originalConfig = config;
      watch.moduleOutput(status, function() {
        if (config !== originalConfig) {
          // Ignore builds that may have occured at the same time as a config file change (i.e. a branch switch)
          return;
        }

        buildPlatform(context.clone(status), logError);
      });
    }

    return _.extend(event, {
      config: function() {
        return config;
      },

      use: function(plugin) {
        plugins.use(plugin);
      },
      /**
       *
       * @name build
       * @function This function builds out the package(s).
       * @param {string} packageName the name of the package listed under
       *  'packages' from the lumbarFile passed in during the call to init().
       * @param {Function} callback the node process Function
       */
      build: function(packageName, modules, callback) {
        loadConfig(lumbarFile, function(err) {
          if (err) {
            if (!callback) {
              throw err;
            }
            return callback(err);
          }

          buildPackages(packageName, modules, callback);
        });
      },
      watch: function(packageName, modules, callback) {
        if (!fs.watch) {
          throw new Error('Watch requires fs.watch, introduced in Node v0.6.0');
        }

        watch = new WatchManager();
        watch.on('watch-change', function(info) {
          event.emit('watch-change', info);
        });

        var self = this;
        loadConfig(lumbarFile, function(err) {
          if (err) {
            logError(err);
          }

          if (!callback) {
            callback = modules;
            modules = undefined;
          }

          // Watch for changes in the config file
          var mixinPaths = _.filter(_.pluck(mixins.configs, 'path'), function(path) { return path; });
          watch.configFile(lumbarFile, mixinPaths, function() {
            config = undefined;
            self.watch(packageName, callback);
          });

          // If we have errored do not exec everything as it could be in an indeterminate state
          if (err) {
            return;
          }

          // Watch the individual components
          event.removeListener('output', watchOutputHandler);
          event.on('output', watchOutputHandler);

          // Actual build everything
          var packages = packageName ? [packageName] : config.packageList();
          packages.forEach(function(name) {
            buildPackages(name, modules, logError);
          });
        });
      },
      unwatch: function() {
        event.removeListener('output', watchOutputHandler);
        if (watch) {
          watch.removeAllListeners();
          watch.reset();
          watch = undefined;
        }
      }
    });
};
<|endoftext|>


<commit_before>var utils = require('./utils')
var storage = require('./storage')

function Config () {
  this.config = {}
  this.defaults = {
    VERSION: this.VERSION,
    apiHost: 'intake.opbeat.com',
    orgId: null,
    appId: null,
    token: null,
    context: {
      user: {
        uuid: _generateUUID()
      },
      extra: null
    }
  }
}

Config.prototype.init = function () {
  var scriptData = _getConfigFromScript()

  this.setConfig(scriptData)
}

Config.prototype.get = function (key) {
  return key.split('.').reduce(function (obj, i) {
    return obj[i]
  }, this.config)
},

Config.prototype.set = function (key, value) {
  var levels = key.split('.')
  var max_level = levels.length - 1
  var target = this.config

  levels.some(function (level, i) {
    if (typeof level === 'undefined') {
      return true
    }
    if (i === max_level) {
      target[level] = value
    } else {
      var obj = target[level] || {}
      target[level] = obj
      target = obj
    }
  })
},

Config.prototype.setConfig = function (properties) {
  properties = properties || {}
  this.config = utils.mergeObject(this.defaults, properties)
}

Config.prototype.isValid = function () {
  var requiredKeys = ['appId', 'orgId', 'token']
  var values = requiredKeys.map(function (key) {
    return this.config[key] === undefined
  }.bind(this))

  return values.indexOf(true) === -1
}

function _generateUUID () {
  var key = 'opbeat-uuid'
  var uuid = storage.get(key)

  if (!uuid) {
    uuid = utils.generateUuid()
    storage.set(key, uuid)
  }

  return uuid
}

var _getConfigFromScript = function () {
  var script = utils.getCurrentScript()
  var config = _getDataAttributesFromNode(script)
  return config
}

function _getDataAttributesFromNode (node) {
  var dataAttrs = {}
  var dataRegex = /^data\-([\w\-]+)$/

  if (node) {
    var attrs = node.attributes
    for (var i = 0; i < attrs.length; i++) {
      var attr = attrs[i]
      if (dataRegex.test(attr.nodeName)) {
        var key = attr.nodeName.match(dataRegex)[1]

        // camelCase key
        key = key.split('-').map(function (group, index) {
          return index > 0 ? group.charAt(0).toUpperCase() + group.substring(1) : group
        }).join('')

        dataAttrs[key] = attr.value || attr.nodeValue
      }
    }
  }

  return dataAttrs
}

module.exports = new Config()
<commit_msg>Add VERSION<commit_after>var utils = require('./utils')
var storage = require('./storage')

function Config () {
  this.VERSION = '0.0.1' // TODO: extract this on build from package.json
  this.config = {}
  this.defaults = {
    VERSION: this.VERSION,
    apiHost: 'intake.opbeat.com',
    orgId: null,
    appId: null,
    token: null,
    context: {
      user: {
        uuid: _generateUUID()
      },
      extra: null
    }
  }
}

Config.prototype.init = function () {
  var scriptData = _getConfigFromScript()

  this.setConfig(scriptData)
}

Config.prototype.get = function (key) {
  return key.split('.').reduce(function (obj, i) {
    return obj[i]
  }, this.config)
},

Config.prototype.set = function (key, value) {
  var levels = key.split('.')
  var max_level = levels.length - 1
  var target = this.config

  levels.some(function (level, i) {
    if (typeof level === 'undefined') {
      return true
    }
    if (i === max_level) {
      target[level] = value
    } else {
      var obj = target[level] || {}
      target[level] = obj
      target = obj
    }
  })
},

Config.prototype.setConfig = function (properties) {
  properties = properties || {}
  this.config = utils.mergeObject(this.defaults, properties)
}

Config.prototype.isValid = function () {
  var requiredKeys = ['appId', 'orgId', 'token']
  var values = requiredKeys.map(function (key) {
    return this.config[key] === undefined
  }.bind(this))

  return values.indexOf(true) === -1
}

function _generateUUID () {
  var key = 'opbeat-uuid'
  var uuid = storage.get(key)

  if (!uuid) {
    uuid = utils.generateUuid()
    storage.set(key, uuid)
  }

  return uuid
}

var _getConfigFromScript = function () {
  var script = utils.getCurrentScript()
  var config = _getDataAttributesFromNode(script)
  return config
}

function _getDataAttributesFromNode (node) {
  var dataAttrs = {}
  var dataRegex = /^data\-([\w\-]+)$/

  if (node) {
    var attrs = node.attributes
    for (var i = 0; i < attrs.length; i++) {
      var attr = attrs[i]
      if (dataRegex.test(attr.nodeName)) {
        var key = attr.nodeName.match(dataRegex)[1]

        // camelCase key
        key = key.split('-').map(function (group, index) {
          return index > 0 ? group.charAt(0).toUpperCase() + group.substring(1) : group
        }).join('')

        dataAttrs[key] = attr.value || attr.nodeValue
      }
    }
  }

  return dataAttrs
}

module.exports = new Config()
<|endoftext|>


<commit_before>/* @flow */

export function getUserAgent() : string {
    return window.navigator.mockUserAgent || window.navigator.userAgent;
}

export function isDevice() : boolean {
    let userAgent = getUserAgent();
    if (userAgent.match(/Android|webOS|iPhone|iPad|iPod|bada|Symbian|Palm|CriOS|BlackBerry|IEMobile|WindowsMobile|Opera Mini/i)) {
        return true;
    }

    return false;
}

export function isWebView() : boolean {
    let userAgent = getUserAgent();
    return (/(iPhone|iPod|iPad|Macintosh).*AppleWebKit(?!.*Safari)/i).test(userAgent) ||
        (/\bwv\b/).test(userAgent) ||
    (/Android.*Version\/(\d)\.(\d)/i).test(userAgent);
}

export function isFacebookWebView(ua? : string = getUserAgent()) : boolean {
    return (ua.indexOf('FBAN') !== -1) || (ua.indexOf('FBAV') !== -1);
}

export function isFirefoxIOS(ua? : string = getUserAgent()) : boolean {
    return (/FxiOS/i).test(ua);
}

export function isOperaMini(ua? : string = getUserAgent()) : boolean {
    return ua.indexOf('Opera Mini') > -1;
}

export function isAndroid(ua? : string = getUserAgent()) : boolean {
    return (/Android/).test(ua);
}

export function isIos(ua? : string = getUserAgent()) : boolean {
    return (/iPhone|iPod|iPad/).test(ua);
}

export function isGoogleSearchApp(ua? : string = getUserAgent()) : boolean {
    return (/\bGSA\b/).test(ua);
}

export function isQQBrowser(ua? : string = getUserAgent()) : boolean {
    return (/QQBrowser/).test(ua);
}

export function isIosWebview(ua? : string = getUserAgent()) : boolean {
    if (isIos(ua)) {
        if (isGoogleSearchApp(ua)) {
            return true;
        }
        return (/.+AppleWebKit(?!.*Safari)/).test(ua);
    }
    return false;
}

export function isAndroidWebview(ua? : string = getUserAgent()) : boolean {
    if (isAndroid(ua)) {
        return (/Version\/[\d.]+/).test(ua) && !isOperaMini(ua);
    }
    return false;
}

export function isIE() : boolean {

    if (window.document.documentMode) {
        return true;
    }

    return Boolean(
        window.navigator &&
        window.navigator.userAgent &&
        (/Edge|MSIE/i).test(window.navigator.userAgent)
    );
}

export function isIECompHeader() : boolean {
    let mHttp = window.document.querySelector('meta[http-equiv="X-UA-Compatible"]');
    let mContent = window.document.querySelector('meta[content="IE=edge"]');
    if (mHttp && mContent) {
        return true;
    }
    return false;
}

export function isElectron() : boolean {
    if (typeof process !== 'undefined' && process.versions && process.versions.electron) {
        return true;
    }
    return false;
}

export function isIEIntranet() : boolean {

    // This status check only works for older versions of IE with document.documentMode set

    if (window.document.documentMode) {
        try {
            let status = window.status;

            window.status = 'testIntranetMode';

            if (window.status === 'testIntranetMode') {
                window.status = status;

                return true;
            }

            return false;

        } catch (err) {

            return false;
        }
    }

    return false;
}

export function isMacOsCna() : boolean {
    let userAgent = getUserAgent();
    return (/Macintosh.*AppleWebKit(?!.*Safari)/i).test(userAgent);
}

export function supportsPopups(ua? : string = getUserAgent()) : boolean {
    return !(isIosWebview(ua) || isAndroidWebview(ua) || isOperaMini(ua) ||
        isFirefoxIOS(ua) || isFacebookWebView(ua) || isQQBrowser(ua) || isElectron() || isMacOsCna());
}
<commit_msg>feat(iframe): Use the iFrame for all standalone apps; native and non-native (#747)<commit_after>/* @flow */

export function getUserAgent() : string {
    return window.navigator.mockUserAgent || window.navigator.userAgent;
}

export function isDevice() : boolean {
    let userAgent = getUserAgent();
    if (userAgent.match(/Android|webOS|iPhone|iPad|iPod|bada|Symbian|Palm|CriOS|BlackBerry|IEMobile|WindowsMobile|Opera Mini/i)) {
        return true;
    }

    return false;
}

export function isWebView() : boolean {
    let userAgent = getUserAgent();
    return (/(iPhone|iPod|iPad|Macintosh).*AppleWebKit(?!.*Safari)/i).test(userAgent) ||
        (/\bwv\b/).test(userAgent) ||
    (/Android.*Version\/(\d)\.(\d)/i).test(userAgent);
}

export function isStandAlone() : boolean {
    return (window.navigator.standalone === true || window.matchMedia('(display-mode: standalone)').matches);
}

export function isFacebookWebView(ua? : string = getUserAgent()) : boolean {
    return (ua.indexOf('FBAN') !== -1) || (ua.indexOf('FBAV') !== -1);
}

export function isFirefoxIOS(ua? : string = getUserAgent()) : boolean {
    return (/FxiOS/i).test(ua);
}

export function isOperaMini(ua? : string = getUserAgent()) : boolean {
    return ua.indexOf('Opera Mini') > -1;
}

export function isAndroid(ua? : string = getUserAgent()) : boolean {
    return (/Android/).test(ua);
}

export function isIos(ua? : string = getUserAgent()) : boolean {
    return (/iPhone|iPod|iPad/).test(ua);
}

export function isGoogleSearchApp(ua? : string = getUserAgent()) : boolean {
    return (/\bGSA\b/).test(ua);
}

export function isQQBrowser(ua? : string = getUserAgent()) : boolean {
    return (/QQBrowser/).test(ua);
}

export function isIosWebview(ua? : string = getUserAgent()) : boolean {
    if (isIos(ua)) {
        if (isGoogleSearchApp(ua)) {
            return true;
        }
        return (/.+AppleWebKit(?!.*Safari)/).test(ua);
    }
    return false;
}

export function isAndroidWebview(ua? : string = getUserAgent()) : boolean {
    if (isAndroid(ua)) {
        return (/Version\/[\d.]+/).test(ua) && !isOperaMini(ua);
    }
    return false;
}

export function isIE() : boolean {

    if (window.document.documentMode) {
        return true;
    }

    return Boolean(
        window.navigator &&
        window.navigator.userAgent &&
        (/Edge|MSIE/i).test(window.navigator.userAgent)
    );
}

export function isIECompHeader() : boolean {
    let mHttp = window.document.querySelector('meta[http-equiv="X-UA-Compatible"]');
    let mContent = window.document.querySelector('meta[content="IE=edge"]');
    if (mHttp && mContent) {
        return true;
    }
    return false;
}

export function isElectron() : boolean {
    if (typeof process !== 'undefined' && process.versions && process.versions.electron) {
        return true;
    }
    return false;
}

export function isIEIntranet() : boolean {

    // This status check only works for older versions of IE with document.documentMode set

    if (window.document.documentMode) {
        try {
            let status = window.status;

            window.status = 'testIntranetMode';

            if (window.status === 'testIntranetMode') {
                window.status = status;

                return true;
            }

            return false;

        } catch (err) {

            return false;
        }
    }

    return false;
}

export function isMacOsCna() : boolean {
    let userAgent = getUserAgent();
    return (/Macintosh.*AppleWebKit(?!.*Safari)/i).test(userAgent);
}

export function supportsPopups(ua? : string = getUserAgent()) : boolean {
    return !(isIosWebview(ua) || isAndroidWebview(ua) || isOperaMini(ua) ||
        isFirefoxIOS(ua) || isFacebookWebView(ua) || isQQBrowser(ua) || isElectron() || isMacOsCna() || isStandAlone());
}
<|endoftext|>


<commit_before>/* @flow */

export function getUserAgent() : string {
    return window.navigator.mockUserAgent || window.navigator.userAgent;
}

export function isDevice() : boolean {
    let userAgent = getUserAgent();
    if (userAgent.match(/Android|webOS|iPhone|iPad|iPod|bada|Symbian|Palm|CriOS|BlackBerry|IEMobile|WindowsMobile|Opera Mini/i)) {
        return true;
    }

    return false;
}

export function isWebView() : boolean {
    let userAgent = getUserAgent();
    return (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i).test(userAgent) ||
        (/\bwv\b/).test(userAgent) ||
    (/Android.*Version\/(\d)\.(\d)/i).test(userAgent);
}

export function isFirefoxIOS(ua? : string = getUserAgent()) : boolean {
    return (/FxiOS/i).test(ua);
}

export function isOperaMini(ua? : string = getUserAgent()) : boolean {
    return ua.indexOf('Opera Mini') > -1;
}

export function isAndroid(ua? : string = getUserAgent()) : boolean {
    return (/Android/).test(ua);
}

export function isIos(ua? : string = getUserAgent()) : boolean {
    return (/iPhone|iPod|iPad/).test(ua);
}

export function isGoogleSearchApp(ua? : string = getUserAgent()) : boolean {
    return (/\bGSA\b/).test(ua);
}

export function isIosWebview(ua? : string = getUserAgent()) : boolean {
    if (isIos(ua)) {
        if (isGoogleSearchApp(ua)) {
            return true;
        }
        return (/.+AppleWebKit(?!.*Safari)/).test(ua);
    }
    return false;
}

export function isAndroidWebview(ua? : string = getUserAgent()) : boolean {
    if (isAndroid(ua)) {
        return (/Version\/[\d.]+/).test(ua) && !isOperaMini(ua);
    }
    return false;
}

export function isIE() : boolean {

    if (window.document.documentMode) {
        return true;
    }

    return Boolean(
        window.navigator &&
        window.navigator.userAgent &&
        (/Edge|MSIE/i).test(window.navigator.userAgent)
    );
}

export function isIECompHeader() : boolean {
    let mHttp = window.document.querySelector('meta[http-equiv="X-UA-Compatible"]');
    let mContent = window.document.querySelector('meta[content="IE=edge"]');
    if (mHttp && mContent) {
        return true;
    }
    return false;
}

export function isIEIntranet() : boolean {

    // This status check only works for older versions of IE with document.documentMode set

    if (window.document.documentMode) {
        try {
            let status = window.status;

            window.status = 'testIntranetMode';

            if (window.status === 'testIntranetMode') {
                window.status = status;

                return true;
            }

            return false;

        } catch (err) {

            return false;
        }
    }

    return false;
}

export function supportsPopups(ua? : string = getUserAgent()) : boolean {
    return !(isIosWebview(ua) || isAndroidWebview(ua) || isOperaMini(ua) || isFirefoxIOS(ua));
}
<commit_msg>Add new check for facebook webview<commit_after>/* @flow */

export function getUserAgent() : string {
    return window.navigator.mockUserAgent || window.navigator.userAgent;
}

export function isDevice() : boolean {
    let userAgent = getUserAgent();
    if (userAgent.match(/Android|webOS|iPhone|iPad|iPod|bada|Symbian|Palm|CriOS|BlackBerry|IEMobile|WindowsMobile|Opera Mini/i)) {
        return true;
    }

    return false;
}

export function isWebView() : boolean {
    let userAgent = getUserAgent();
    return (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i).test(userAgent) ||
        (/\bwv\b/).test(userAgent) ||
    (/Android.*Version\/(\d)\.(\d)/i).test(userAgent);
}

export function isFacebookWebView(ua? : string = getUserAgent()) : boolean {
    return (ua.indexOf('FBAN') !== -1) || (ua.indexOf('FBAV') !== -1);
}

export function isFirefoxIOS(ua? : string = getUserAgent()) : boolean {
    return (/FxiOS/i).test(ua);
}

export function isOperaMini(ua? : string = getUserAgent()) : boolean {
    return ua.indexOf('Opera Mini') > -1;
}

export function isAndroid(ua? : string = getUserAgent()) : boolean {
    return (/Android/).test(ua);
}

export function isIos(ua? : string = getUserAgent()) : boolean {
    return (/iPhone|iPod|iPad/).test(ua);
}

export function isGoogleSearchApp(ua? : string = getUserAgent()) : boolean {
    return (/\bGSA\b/).test(ua);
}

export function isIosWebview(ua? : string = getUserAgent()) : boolean {
    if (isIos(ua)) {
        if (isGoogleSearchApp(ua)) {
            return true;
        }
        return (/.+AppleWebKit(?!.*Safari)/).test(ua);
    }
    return false;
}

export function isAndroidWebview(ua? : string = getUserAgent()) : boolean {
    if (isAndroid(ua)) {
        return (/Version\/[\d.]+/).test(ua) && !isOperaMini(ua);
    }
    return false;
}

export function isIE() : boolean {

    if (window.document.documentMode) {
        return true;
    }

    return Boolean(
        window.navigator &&
        window.navigator.userAgent &&
        (/Edge|MSIE/i).test(window.navigator.userAgent)
    );
}

export function isIECompHeader() : boolean {
    let mHttp = window.document.querySelector('meta[http-equiv="X-UA-Compatible"]');
    let mContent = window.document.querySelector('meta[content="IE=edge"]');
    if (mHttp && mContent) {
        return true;
    }
    return false;
}

export function isIEIntranet() : boolean {

    // This status check only works for older versions of IE with document.documentMode set

    if (window.document.documentMode) {
        try {
            let status = window.status;

            window.status = 'testIntranetMode';

            if (window.status === 'testIntranetMode') {
                window.status = status;

                return true;
            }

            return false;

        } catch (err) {

            return false;
        }
    }

    return false;
}

export function supportsPopups(ua? : string = getUserAgent()) : boolean {
    return !(isIosWebview(ua) || isAndroidWebview(ua) || isOperaMini(ua) || isFirefoxIOS(ua) || isFacebookWebView(ua));
}
<|endoftext|>


<commit_before>/**
 * marked - A markdown parser
 * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?=\n| *$)/,
  hr: /^( *[\-*_]){3,} *\n/,
  heading: /^ *(#{1,6}) *([^\0]+?) *#* *\n+/,
  lheading: /^([^\n]+)\n *(=|-){3,}/,
  blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/,
  list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1\2|\1\d+\.)/,
  html: /^ *(?:|<(\w+)[^\0]+?<\/\1>|<\w+[^>]*>) *(?:\n{2,}|\s*$)/,
  text: /^[^\n]+/
};

/**
 * Block Lexer
 */

block.lexer = function(str) {
  var tokens = []
    , links = {};

  str = str.replace(/\r\n|\r/g, '\n')
           .replace(/\t/g, '    ');

  str = str.replace(
    /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^\n]+)")? *$/gm,
    function(__, id, href, title) {
      links[id] = {
        href: href,
        title: title
      };
      return '';
    }
  );

  tokens.links = links;

  return block.token(str, tokens);
};

block.token = function(str, tokens) {
  var str = str.replace(/^ +$/gm, '')
    , loose
    , cap
    , item
    , space
    , i
    , l;

  while (str) {
    // newline
    if (cap = block.newline.exec(str)) {
      str = str.substring(cap[0].length);
      if (cap[0].length > 1) {
        tokens.push({
          type: 'space'
        });
      }
      continue;
    }

    // code
    if (cap = block.code.exec(str)) {
      str = str.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      tokens.push({
        type: 'code',
        text: cap
      });
      continue;
    }

    // heading
    if (cap = block.heading.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // lheading
    if (cap = block.lheading.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // hr
    if (cap = block.hr.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = block.blockquote.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'blockquote_start'
      });
      cap = cap[0].replace(/^ *>/gm, '');
      block.token(cap, tokens);
      tokens.push({
        type: 'blockquote_end'
      });
      continue;
    }

    // list
    if (cap = block.list.exec(str)) {
      str = str.substring(cap[0].length);

      tokens.push({
        type: 'list_start',
        ordered: isFinite(cap[2])
      });

      loose = /\n *\n *(?:[*+-]|\d+\.)/.test(cap[0]);

      // get each top-level item
      cap = cap[0].match(
        /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm
      );

      i = 0;
      l = cap.length;

      for (; i < l; i++) {
        // remove the list items sigil
        // so its seen as the next token
        item = cap[i].replace(/^ *([*+-]|\d+\.) */, '');
        // outdent whatever the
        // list item contains, hacky
        space = /\n( +)/.exec(item);
        if (space) {
          space = new RegExp('^' + space[1], 'gm');
          item = item.replace(space, '');
        }
        tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });
        block.token(item, tokens);
        tokens.push({
          type: 'list_item_end'
        });
      }

      tokens.push({
        type: 'list_end'
      });

      continue;
    }

    // html
    if (cap = block.html.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'html',
        text: cap[0]
      });
      continue;
    }

    // text
    if (cap = block.text.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }
  }

  return tokens;
};

/**
 * Inline Processing
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!_])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  tag: /^|^<\/?\w+[^>]*>/,
  link: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\(([^\)]*)\)/,
  reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\s*\[([^\]]*)\]/,
  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
  em: /^_([^_]+)_|^\*([^*]+)\*/,
  code: /^`([^`]+)`|^``([^\0]+?)``/,
  br: /^ {2,}\n(?!\s*$)/,
  text: /^/
};

// hacky, but performant
inline.text = (function() {
  var body = [];

  (function push(rule) {
    rule = inline[rule].source;
    body.push(rule.replace(/(^|[^\[])\^/g, '$1'));
    return push;
  })
  ('escape')
  ('tag')
  ('nolink')
  ('strong')
  ('em')
  ('code')
  ('br');

  return new
    RegExp('^[^\\0]+?(?=' + body.join('|') + '|$)');
})();

/**
 * Inline Lexer
 */

inline.lexer = function(str) {
  var out = ''
    , links = tokens.links
    , link
    , text
    , href
    , cap;

  while (str) {
    // escape
    if (cap = inline.escape.exec(str)) {
      str = str.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = inline.autolink.exec(str)) {
      str = str.substring(cap[0].length);
      if (cap[2] === '@') {
        text = cap[1][6] === ':'
          ? mangle(cap[1].substring(7))
          : mangle(cap[1]);
        href = mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += ''
        + text
        + '';
      continue;
    }

    // tag
    if (cap = inline.tag.exec(str)) {
      str = str.substring(cap[0].length);
      out += cap[0];
      continue;
    }

    // link
    if (cap = inline.link.exec(str)) {
      str = str.substring(cap[0].length);
      text = /^\s*<?([^\s]*?)>?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]);
      link = {
        href: text[1],
        title: text[2]
      };
      out += mlink(cap, link);
      continue;
    }

    // reflink, nolink
    if ((cap = inline.reflink.exec(str))
        || (cap = inline.nolink.exec(str))) {
      str = str.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = links[link];
      if (!link) {
        out += cap[0][0];
        str = cap[0].substring(1) + str;
        continue;
      }
      out += mlink(cap, link);
      continue;
    }

    // strong
    if (cap = inline.strong.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + inline.lexer(cap[2] || cap[1])
        + '';
      continue;
    }

    // em
    if (cap = inline.em.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + inline.lexer(cap[2] || cap[1])
        + '';
      continue;
    }

    // code
    if (cap = inline.code.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + escape(cap[2] || cap[1], true)
        + '';
      continue;
    }

    // br
    if (cap = inline.br.exec(str)) {
      str = str.substring(cap[0].length);
      out += '
';
      continue;
    }

    // text
    if (cap = inline.text.exec(str)) {
      str = str.substring(cap[0].length);
      out += escape(cap[0]);
      continue;
    }
  }

  return out;
};

var mlink = function(cap, link) {
  if (cap[0][0] !== '!') {
    return ''
      + inline.lexer(cap[1])
      + '';
  } else {
    return '';
  }
};

/**
 * Parsing
 */

var tokens
  , token;

var next = function() {
  return token = tokens.pop();
};

var tok = function() {
  switch (token.type) {
    case 'space': {
      return '';
    }
    case 'hr': {
      return '
';
    }
    case 'heading': {
      return '<h'
        + token.depth
        + '>'
        + inline.lexer(token.text)
        + '</h'
        + token.depth
        + '>';
    }
    case 'code': {
      if (token.text[token.text.length-1] === '\n') {
        token.text = token.text.slice(0, -1);
      }
      return '
'
        + escape(token.text, true)
        + '
';
    }
    case 'blockquote_start': {
      var body = [];

      while (next().type !== 'blockquote_end') {
        body.push(tok());
      }

      return '
'
        + body.join('')
        + '
';
    }
    case 'list_start': {
      var type = token.ordered ? 'ol' : 'ul'
        , body = [];

      while (next().type !== 'list_end') {
        body.push(tok());
      }

      return '<'
        + type
        + '>'
        + body.join('')
        + '</'
        + type
        + '>';
    }
    case 'list_item_start': {
      var body = [];

      while (next().type !== 'list_item_end') {
        body.push(token.type === 'text'
          ? text()
          : tok());
      }

      return '
'
        + body.join(' ')
        + '
';
    }
    case 'loose_item_start': {
      var body = [];

      while (next().type !== 'list_item_end') {
        body.push(tok());
      }

      return '
'
        + body.join(' ')
        + '
';
    }
    case 'html': {
      return inline.lexer(token.text);
    }
    case 'text': {
      return '
'
        + text()
        + '
';
    }
  }
};

var text = function() {
  var body = [ token.text ]
    , top;

  while ((top = tokens[tokens.length-1])
         && top.type === 'text') {
    body.push(next().text);
  }

  return inline.lexer(body.join('\n'));
};

var parse = function(src) {
  tokens = src.reverse();

  var out = [];
  while (next()) {
    out.push(tok());
  }

  tokens = null;
  token = null;

  return out.join(' ');
};

/**
 * Helpers
 */

var escape = function(html, dbl) {
  return html
    .replace(!dbl
      ? /&(?!#?\w+;)/g
      : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

var mangle = function(str) {
  var out = ''
    , ch
    , i = 0
    , l = str.length;

  for (; i < l; i++) {
    ch = str.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Expose
 */

var marked = function(str) {
  return parse(block.lexer(str));
};

marked.parser = parse;
marked.lexer = block.lexer;

marked.parse = marked;

if (typeof module !== 'undefined') {
  module.exports = marked;
} else {
  this.marked = marked;
}

}).call(this);
<commit_msg>refactor, move code block line feed check to lexer<commit_after>/**
 * marked - A markdown parser
 * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,
  code: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?=\n| *$)/,
  hr: /^( *[\-*_]){3,} *\n/,
  heading: /^ *(#{1,6}) *([^\0]+?) *#* *\n+/,
  lheading: /^([^\n]+)\n *(=|-){3,}/,
  blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/,
  list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1\2|\1\d+\.)/,
  html: /^ *(?:|<(\w+)[^\0]+?<\/\1>|<\w+[^>]*>) *(?:\n{2,}|\s*$)/,
  text: /^[^\n]+/
};

/**
 * Block Lexer
 */

block.lexer = function(str) {
  var tokens = []
    , links = {};

  str = str.replace(/\r\n|\r/g, '\n')
           .replace(/\t/g, '    ');

  str = str.replace(
    /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^\n]+)")? *$/gm,
    function(__, id, href, title) {
      links[id] = {
        href: href,
        title: title
      };
      return '';
    }
  );

  tokens.links = links;

  return block.token(str, tokens);
};

block.token = function(str, tokens) {
  var str = str.replace(/^ +$/gm, '')
    , loose
    , cap
    , item
    , space
    , i
    , l;

  while (str) {
    // newline
    if (cap = block.newline.exec(str)) {
      str = str.substring(cap[0].length);
      if (cap[0].length > 1) {
        tokens.push({
          type: 'space'
        });
      }
      continue;
    }

    // code
    if (cap = block.code.exec(str)) {
      str = str.substring(cap[0].length);
      cap = cap[0].replace(/^ {4}/gm, '');
      tokens.push({
        type: 'code',
        text: cap[cap.length-1] === '\n'
          ? cap.slice(0, -1)
          : cap
      });
      continue;
    }

    // heading
    if (cap = block.heading.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'heading',
        depth: cap[1].length,
        text: cap[2]
      });
      continue;
    }

    // lheading
    if (cap = block.lheading.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'heading',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      });
      continue;
    }

    // hr
    if (cap = block.hr.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'hr'
      });
      continue;
    }

    // blockquote
    if (cap = block.blockquote.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'blockquote_start'
      });
      cap = cap[0].replace(/^ *>/gm, '');
      block.token(cap, tokens);
      tokens.push({
        type: 'blockquote_end'
      });
      continue;
    }

    // list
    if (cap = block.list.exec(str)) {
      str = str.substring(cap[0].length);

      tokens.push({
        type: 'list_start',
        ordered: isFinite(cap[2])
      });

      loose = /\n *\n *(?:[*+-]|\d+\.)/.test(cap[0]);

      // get each top-level item
      cap = cap[0].match(
        /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm
      );

      i = 0;
      l = cap.length;

      for (; i < l; i++) {
        // remove the list items sigil
        // so its seen as the next token
        item = cap[i].replace(/^ *([*+-]|\d+\.) */, '');
        // outdent whatever the
        // list item contains, hacky
        space = /\n( +)/.exec(item);
        if (space) {
          space = new RegExp('^' + space[1], 'gm');
          item = item.replace(space, '');
        }
        tokens.push({
          type: loose
            ? 'loose_item_start'
            : 'list_item_start'
        });
        block.token(item, tokens);
        tokens.push({
          type: 'list_item_end'
        });
      }

      tokens.push({
        type: 'list_end'
      });

      continue;
    }

    // html
    if (cap = block.html.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'html',
        text: cap[0]
      });
      continue;
    }

    // text
    if (cap = block.text.exec(str)) {
      str = str.substring(cap[0].length);
      tokens.push({
        type: 'text',
        text: cap[0]
      });
      continue;
    }
  }

  return tokens;
};

/**
 * Inline Processing
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!_])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  tag: /^|^<\/?\w+[^>]*>/,
  link: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\(([^\)]*)\)/,
  reflink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]\s*\[([^\]]*)\]/,
  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
  strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/,
  em: /^_([^_]+)_|^\*([^*]+)\*/,
  code: /^`([^`]+)`|^``([^\0]+?)``/,
  br: /^ {2,}\n(?!\s*$)/,
  text: /^/
};

// hacky, but performant
inline.text = (function() {
  var body = [];

  (function push(rule) {
    rule = inline[rule].source;
    body.push(rule.replace(/(^|[^\[])\^/g, '$1'));
    return push;
  })
  ('escape')
  ('tag')
  ('nolink')
  ('strong')
  ('em')
  ('code')
  ('br');

  return new
    RegExp('^[^\\0]+?(?=' + body.join('|') + '|$)');
})();

/**
 * Inline Lexer
 */

inline.lexer = function(str) {
  var out = ''
    , links = tokens.links
    , link
    , text
    , href
    , cap;

  while (str) {
    // escape
    if (cap = inline.escape.exec(str)) {
      str = str.substring(cap[0].length);
      out += cap[1];
      continue;
    }

    // autolink
    if (cap = inline.autolink.exec(str)) {
      str = str.substring(cap[0].length);
      if (cap[2] === '@') {
        text = cap[1][6] === ':'
          ? mangle(cap[1].substring(7))
          : mangle(cap[1]);
        href = mangle('mailto:') + text;
      } else {
        text = escape(cap[1]);
        href = text;
      }
      out += ''
        + text
        + '';
      continue;
    }

    // tag
    if (cap = inline.tag.exec(str)) {
      str = str.substring(cap[0].length);
      out += cap[0];
      continue;
    }

    // link
    if (cap = inline.link.exec(str)) {
      str = str.substring(cap[0].length);
      text = /^\s*<?([^\s]*?)>?(?:\s+"([^\n]+)")?\s*$/.exec(cap[2]);
      link = {
        href: text[1],
        title: text[2]
      };
      out += mlink(cap, link);
      continue;
    }

    // reflink, nolink
    if ((cap = inline.reflink.exec(str))
        || (cap = inline.nolink.exec(str))) {
      str = str.substring(cap[0].length);
      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
      link = links[link];
      if (!link) {
        out += cap[0][0];
        str = cap[0].substring(1) + str;
        continue;
      }
      out += mlink(cap, link);
      continue;
    }

    // strong
    if (cap = inline.strong.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + inline.lexer(cap[2] || cap[1])
        + '';
      continue;
    }

    // em
    if (cap = inline.em.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + inline.lexer(cap[2] || cap[1])
        + '';
      continue;
    }

    // code
    if (cap = inline.code.exec(str)) {
      str = str.substring(cap[0].length);
      out += ''
        + escape(cap[2] || cap[1], true)
        + '';
      continue;
    }

    // br
    if (cap = inline.br.exec(str)) {
      str = str.substring(cap[0].length);
      out += '
';
      continue;
    }

    // text
    if (cap = inline.text.exec(str)) {
      str = str.substring(cap[0].length);
      out += escape(cap[0]);
      continue;
    }
  }

  return out;
};

var mlink = function(cap, link) {
  if (cap[0][0] !== '!') {
    return ''
      + inline.lexer(cap[1])
      + '';
  } else {
    return '';
  }
};

/**
 * Parsing
 */

var tokens
  , token;

var next = function() {
  return token = tokens.pop();
};

var peek = function() {
  return tokens[tokens.length-1] || 0;
};

var tok = function() {
  switch (token.type) {
    case 'space': {
      return '';
    }
    case 'hr': {
      return '
';
    }
    case 'heading': {
      return '<h'
        + token.depth
        + '>'
        + inline.lexer(token.text)
        + '</h'
        + token.depth
        + '>';
    }
    case 'code': {
      return '
'
        + escape(token.text, true)
        + '
';
    }
    case 'blockquote_start': {
      var body = [];

      while (next().type !== 'blockquote_end') {
        body.push(tok());
      }

      return '
'
        + body.join('')
        + '
';
    }
    case 'list_start': {
      var type = token.ordered ? 'ol' : 'ul'
        , body = [];

      while (next().type !== 'list_end') {
        body.push(tok());
      }

      return '<'
        + type
        + '>'
        + body.join('')
        + '</'
        + type
        + '>';
    }
    case 'list_item_start': {
      var body = [];

      while (next().type !== 'list_item_end') {
        body.push(token.type === 'text'
          ? text()
          : tok());
      }

      return '
'
        + body.join(' ')
        + '
';
    }
    case 'loose_item_start': {
      var body = [];

      while (next().type !== 'list_item_end') {
        body.push(tok());
      }

      return '
'
        + body.join(' ')
        + '
';
    }
    case 'html': {
      return inline.lexer(token.text);
    }
    case 'text': {
      return '
'
        + text()
        + '
';
    }
  }
};

var text = function() {
  var body = [ token.text ];

  while (peek().type === 'text') {
    body.push(next().text);
  }

  return inline.lexer(body.join('\n'));
};

var parse = function(src) {
  tokens = src.reverse();

  var out = [];
  while (next()) {
    out.push(tok());
  }

  tokens = null;
  token = null;

  return out.join(' ');
};

/**
 * Helpers
 */

var escape = function(html, dbl) {
  return html
    .replace(!dbl
      ? /&(?!#?\w+;)/g
      : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

var mangle = function(str) {
  var out = ''
    , ch
    , i = 0
    , l = str.length;

  for (; i < l; i++) {
    ch = str.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

/**
 * Expose
 */

var marked = function(str) {
  return parse(block.lexer(str));
};

marked.parser = parse;
marked.lexer = block.lexer;

marked.parse = marked;

if (typeof module !== 'undefined') {
  module.exports = marked;
} else {
  this.marked = marked;
}

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


<commit_before>/**
 * marked - A markdown parser
 * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,

  //block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*)*/,

  // greedier block
  block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*/,

  hr: /^( *[\-*_]){3,}/,
  heading: /^ *(#{1,6}) *([^\n#]*) *#*/,
  lheading: /^([^\n]+)\n *(=|-){3,}/,
  blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/,

  //list: /^( *)([*+-]|\d+\.) [^\n]*(?:\n[^\n]+|\n{2}\1(?:\2|\d+\.)[^\n]*)*/,

  // test
  list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1\2|\d+\.)/,

  //html: /^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/,

  // account for single tags
  html: /^<[^>\n]+> *(?:\n{2}|\s*$)|^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/,

  text: /^[^\n]+/
};

block.keys = [
  'newline',
  'block',
  'heading',
  'lheading',
  'hr',
  'blockquote',
  'list',
  'html',
  'text'
];

/**
 * Lexer
 */

block.lexer = function(str) {
  var tokens = []
    , links = {};

  // normalize whitespace
  str = str.replace(/\r\n/g, '\n')
           .replace(/\r/g, '\n')
           .replace(/\t/g, '    ');

  // experimental
  // str = str.replace(/^ +$/gm, '');

  // grab link definitons
  str = str.replace(
    /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^"]+)")? *(?:\n|$)/gm,
    function(__, id, href, title) {
      links[id] = {
        href: href,
        title: title
      };
      return '';
    }
  );

  tokens.links = links;

  return block.token(str, tokens);
};

block.token = function(str, tokens) {
  var rules = block
    , keys = block.keys
    , len = keys.length
    , key
    , cap
    , loose;

  var scan = function() {
    if (!str) return;
    for (var i = 0; i < len; i++) {
      key = keys[i];
      if (cap = rules[key].exec(str)) {
        str = str.substring(cap[0].length);
        return true;
      }
    }
  };

  while (scan()) {
    switch (key) {
      case 'newline':
        if (cap[0].length > 1) {
          tokens.push({
            type: 'space'
          });
        }
        break;
      case 'hr':
        tokens.push({
          type: 'hr'
        });
        break;
      case 'lheading':
        tokens.push({
          type: 'heading',
          depth: cap[2] === '=' ? 1 : 2,
          text: cap[1]
        });
        break;
      case 'heading':
        tokens.push({
          type: 'heading',
          depth: cap[1].length,
          text: cap[2]
        });
        break;
      case 'block':
        cap = cap[0].replace(/^ {4}/gm, '');
        tokens.push({
          type: 'block',
          text: cap
        });
        break;
      case 'list':
        tokens.push({
          type: 'list_start',
          ordered: isFinite(cap[2])
        });
        loose = /\n *\n *(?:\*|\+|-|\d+\.)/.test(cap[0]);
        //console.log('----');
        //console.log(cap[0]);
        //console.log('----');
        cap = cap[0].match(
          // /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?!\1\2)[^\n]*)*/gm
          // fix ordered lists:
          /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm
        );
        each(cap, function(item) {
          // remove the list items sigil
          // so its seen as the next token
          item = item.replace(/^ *(\*|\+|-|\d+\.) */, '');
          // outdent whatever the
          // list item contains, hacky
          var space = /\n( +)/.exec(item);
          if (space) {
            space = new RegExp('^' + space[1], 'gm');
            item = item.replace(space, '');
          }
          tokens.push({
            type: loose
              ? 'loose_item_start'
              : 'list_item_start'
          });
          block.token(item, tokens);
          tokens.push({
            type: 'list_item_end'
          });
        });
        tokens.push({
          type: 'list_end'
        });
        break;
      case 'html':
      case 'text':
        tokens.push({
          type: key,
          text: cap[0]
        });
        break;
      case 'blockquote':
        tokens.push({
          type: 'blockquote_start'
        });
        cap = cap[0].replace(/^ *>/gm, '');
        block.token(cap, tokens);
        tokens.push({
          type: 'blockquote_end'
        });
        break;
    }
  }

  return tokens;
};

/**
 * Inline Processing
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  tag: /^<[^\n>]+>/,
  link: /^!?\[([^\]]+)\] *\(([^\)]*)\)/,
  reflink: /^!?\[([^\]]+)\] *\[([^\]]*)\]/,
  strong: /^__([\s\S]+?)__|^\*\*([\s\S]+?)\*\*/,
  em: /^_([^_]+)_|^\*([^*]+)\*/,
  code: /^`([^`]+)`|^``([\s\S]+?)``/
};

inline.keys = [
  'escape',
  'autolink',
  'tag',
  'link',
  'reflink',
  'strong',
  'em',
  'code'
];

// hacky, but performant
inline.text = (function(rules) {
  var keys = rules.keys
    , i = 0
    , l = keys.length
    , body = [];

  for (; i < l; i++) {
    body.push(rules[keys[i]].source
      .replace(/(^|[^\[])\^/g, '$1'));
  }

  keys.push('text');

  return new RegExp(
    '^([\\s\\S]+?)(?='
    + body.join('|')
    + '|$)'
  );
})(inline);

/**
 * Inline Lexer
 */

inline.lexer = function(str) {
  var out = ''
    , links = tokens.links
    , link
    , text
    , href;

  var rules = inline
    , keys = inline.keys
    , len = keys.length
    , key
    , cap;

  var scan = function() {
    if (!str) return;
    for (var i = 0; i < len; i++) {
      key = keys[i];
      if (cap = rules[key].exec(str)) {
        str = str.substring(cap[0].length);
        return true;
      }
    }
  };

  while (scan()) {
    switch (key) {
      case 'escape':
        out += cap[1];
        break;
      case 'tag':
        out += cap[0];
        break;
      case 'link':
      case 'reflink':
        if (key === 'reflink') {
          link = cap[2]
            ? links[cap[2]]
            : links[cap[1]];
          if (!link) {
            // apparently markdown says you're
            // supposed to do this ...?
            out += cap[0];
            break;
          }
        } else {
          text = /^\s*([^\s]*)(?:\s+"([^"]+)")?\s*$/.exec(cap[2]);
          link = {
            href: text[1],
            title: text[2]
          };
        }
        if (cap[0][0] !== '!') {
          out += ''
            + inline.lexer(cap[1])
            + '';
        } else {
          out += '';
        }
        break;
      case 'autolink':
        if (cap[2] === '@') {
          text = mangle(cap[1]);
          href = mangle('mailto:') + text;
        } else {
          text = escape(cap[1]);
          href = text;
        }
        out += ''
          + text
          + '';
        break;
      case 'strong':
        out += ''
          + inline.lexer(cap[2] || cap[1])
          + '';
        break;
      case 'em':
        out += ''
          + inline.lexer(cap[2] || cap[1])
          + '';
        break;
      case 'code':
        out += ''
          + escape(cap[2] || cap[1])
          + '';
        break;
      case 'text':
        out += escape(cap[1]);
        break;
      default:
        break;
    }
  }

  return out;
};

/**
 * Parsing
 */

var tokens
  , token;

var next = function() {
  return token = tokens.pop();
};

var tok = function() {
  switch (token.type) {
    case 'space':
      return '';
    case 'hr':
      return '
';
    case 'heading':
      return '<h' + token.depth + '>'
        + inline.lexer(token.text)
        + '</h' + token.depth + '>';
    case 'block':
      return '
'
        + escape(token.text)
        + '
';
    case 'blockquote_start':
      var body = [];
      while (next().type !== 'blockquote_end') {
        body.push(tok());
      }
      return '
'
        + body.join('')
        + '
';
    case 'list_start':
      var body = []
        , type = token.ordered ? 'ol' : 'ul';
      while (next().type !== 'list_end') {
        body.push(tok());
      }
      return '<' + type + '>'
        + body.join('')
        + '</' + type + '>';
    case 'list_item_start':
      var body = [];
      while (next().type !== 'list_item_end') {
        body.push(token.type === 'text'
          ? inline.lexer(token.text)
          : tok());
      }
      return '
'
        + body.join(' ')
        + '
';
    case 'loose_item_start':
      var body = [];
      while (next().type !== 'list_item_end') {
        body.push(tok());
      }
      return '
'
        + body.join(' ')
        + '
';
    case 'html':
      return inline.lexer(token.text);
    case 'text':
      var body = [ token.text ]
        , top;
      while ((top = tokens[tokens.length-1])
             && top.type === 'text') {
        body.push(next().text);
      }
      return '
'
        + inline.lexer(body.join(' '))
        + '
';
  }
};

var parse = function(src) {
  tokens = src.reverse();

  var out = [];
  while (next()) {
    out.push(tok());
  }

  tokens = null;
  token = null;

  return out.join(' ');
};

/**
 * Helpers
 */

var escape = function(html) {
  return html
    .replace(/&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

var mangle = function(str) {
  var out = ''
    , ch
    , i = 0
    , l = str.length;

  for (; i < l; i++) {
    ch = str.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

var each = function(obj, func) {
  var i = 0, l = obj.length;
  for (; i < l; i++) func(obj[i]);
};

/**
 * Expose
 */

var marked = function(str) {
  return parse(block.lexer(str));
};

marked.parser = parse;
marked.lexer = block.lexer;

if (typeof module !== 'undefined') {
  module.exports = marked;
} else {
  this.marked = marked;
}

}).call(this);
<commit_msg>make sure block isnt too greedy<commit_after>/**
 * marked - A markdown parser
 * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
 */

;(function() {

/**
 * Block-Level Grammar
 */

var block = {
  newline: /^\n+/,

  //block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*)*/,

  // greedier block
  block: /^ {4,}[^\n]*(?:\n {4,}[^\n]*|\n)*(?=\n|$)/,

  hr: /^( *[\-*_]){3,}/,
  heading: /^ *(#{1,6}) *([^\n#]*) *#*/,
  lheading: /^([^\n]+)\n *(=|-){3,}/,
  blockquote: /^ *>[^\n]*(?:\n *>[^\n]*)*/,

  //list: /^( *)([*+-]|\d+\.) [^\n]*(?:\n[^\n]+|\n{2}\1(?:\2|\d+\.)[^\n]*)*/,

  // test
  list: /^( *)([*+-]|\d+\.) [^\0]+?(?:\n{2,}(?! )|\s*$)(?!\1\2|\d+\.)/,

  //html: /^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/,

  // account for single tags
  html: /^<[^>\n]+> *(?:\n{2}|\s*$)|^<([^\/\s>]+)[^\n>]*>[^\n]*(?:\n[^\n]+)*\n?<\/\1>/,

  text: /^[^\n]+/
};

block.keys = [
  'newline',
  'block',
  'heading',
  'lheading',
  'hr',
  'blockquote',
  'list',
  'html',
  'text'
];

/**
 * Lexer
 */

block.lexer = function(str) {
  var tokens = []
    , links = {};

  // normalize whitespace
  str = str.replace(/\r\n/g, '\n')
           .replace(/\r/g, '\n')
           .replace(/\t/g, '    ');

  // experimental
  // str = str.replace(/^ +$/gm, '');

  // grab link definitons
  str = str.replace(
    /^ {0,3}\[([^\]]+)\]: *([^ ]+)(?: +"([^"]+)")? *(?:\n|$)/gm,
    function(__, id, href, title) {
      links[id] = {
        href: href,
        title: title
      };
      return '';
    }
  );

  tokens.links = links;

  return block.token(str, tokens);
};

block.token = function(str, tokens) {
  var rules = block
    , keys = block.keys
    , len = keys.length
    , key
    , cap
    , loose;

  var scan = function() {
    if (!str) return;
    for (var i = 0; i < len; i++) {
      key = keys[i];
      if (cap = rules[key].exec(str)) {
        str = str.substring(cap[0].length);
        return true;
      }
    }
  };

  while (scan()) {
    switch (key) {
      case 'newline':
        if (cap[0].length > 1) {
          tokens.push({
            type: 'space'
          });
        }
        break;
      case 'hr':
        tokens.push({
          type: 'hr'
        });
        break;
      case 'lheading':
        tokens.push({
          type: 'heading',
          depth: cap[2] === '=' ? 1 : 2,
          text: cap[1]
        });
        break;
      case 'heading':
        tokens.push({
          type: 'heading',
          depth: cap[1].length,
          text: cap[2]
        });
        break;
      case 'block':
        cap = cap[0].replace(/^ {4}/gm, '');
        tokens.push({
          type: 'block',
          text: cap
        });
        break;
      case 'list':
        tokens.push({
          type: 'list_start',
          ordered: isFinite(cap[2])
        });
        loose = /\n *\n *(?:\*|\+|-|\d+\.)/.test(cap[0]);
        //console.log('----');
        //console.log(cap[0]);
        //console.log('----');
        cap = cap[0].match(
          // /^( *)(\*|\+|-|\d+\.)[^\n]*(?:\n(?!\1\2)[^\n]*)*/gm
          // fix ordered lists:
          /^( *)([*+-]|\d+\.)[^\n]*(?:\n(?!\1(?:\2|\d+\.))[^\n]*)*/gm
        );
        each(cap, function(item) {
          // remove the list items sigil
          // so its seen as the next token
          item = item.replace(/^ *(\*|\+|-|\d+\.) */, '');
          // outdent whatever the
          // list item contains, hacky
          var space = /\n( +)/.exec(item);
          if (space) {
            space = new RegExp('^' + space[1], 'gm');
            item = item.replace(space, '');
          }
          tokens.push({
            type: loose
              ? 'loose_item_start'
              : 'list_item_start'
          });
          block.token(item, tokens);
          tokens.push({
            type: 'list_item_end'
          });
        });
        tokens.push({
          type: 'list_end'
        });
        break;
      case 'html':
      case 'text':
        tokens.push({
          type: key,
          text: cap[0]
        });
        break;
      case 'blockquote':
        tokens.push({
          type: 'blockquote_start'
        });
        cap = cap[0].replace(/^ *>/gm, '');
        block.token(cap, tokens);
        tokens.push({
          type: 'blockquote_end'
        });
        break;
    }
  }

  return tokens;
};

/**
 * Inline Processing
 */

var inline = {
  escape: /^\\([\\`*{}\[\]()#+\-.!])/,
  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
  tag: /^<[^\n>]+>/,
  link: /^!?\[([^\]]+)\] *\(([^\)]*)\)/,
  reflink: /^!?\[([^\]]+)\] *\[([^\]]*)\]/,
  strong: /^__([\s\S]+?)__|^\*\*([\s\S]+?)\*\*/,
  em: /^_([^_]+)_|^\*([^*]+)\*/,
  code: /^`([^`]+)`|^``([\s\S]+?)``/
};

inline.keys = [
  'escape',
  'autolink',
  'tag',
  'link',
  'reflink',
  'strong',
  'em',
  'code'
];

// hacky, but performant
inline.text = (function(rules) {
  var keys = rules.keys
    , i = 0
    , l = keys.length
    , body = [];

  for (; i < l; i++) {
    body.push(rules[keys[i]].source
      .replace(/(^|[^\[])\^/g, '$1'));
  }

  keys.push('text');

  return new RegExp(
    '^([\\s\\S]+?)(?='
    + body.join('|')
    + '|$)'
  );
})(inline);

/**
 * Inline Lexer
 */

inline.lexer = function(str) {
  var out = ''
    , links = tokens.links
    , link
    , text
    , href;

  var rules = inline
    , keys = inline.keys
    , len = keys.length
    , key
    , cap;

  var scan = function() {
    if (!str) return;
    for (var i = 0; i < len; i++) {
      key = keys[i];
      if (cap = rules[key].exec(str)) {
        str = str.substring(cap[0].length);
        return true;
      }
    }
  };

  while (scan()) {
    switch (key) {
      case 'escape':
        out += cap[1];
        break;
      case 'tag':
        out += cap[0];
        break;
      case 'link':
      case 'reflink':
        if (key === 'reflink') {
          link = cap[2]
            ? links[cap[2]]
            : links[cap[1]];
          if (!link) {
            // apparently markdown says you're
            // supposed to do this ...?
            out += cap[0];
            break;
          }
        } else {
          text = /^\s*([^\s]*)(?:\s+"([^"]+)")?\s*$/.exec(cap[2]);
          link = {
            href: text[1],
            title: text[2]
          };
        }
        if (cap[0][0] !== '!') {
          out += ''
            + inline.lexer(cap[1])
            + '';
        } else {
          out += '';
        }
        break;
      case 'autolink':
        if (cap[2] === '@') {
          text = mangle(cap[1]);
          href = mangle('mailto:') + text;
        } else {
          text = escape(cap[1]);
          href = text;
        }
        out += ''
          + text
          + '';
        break;
      case 'strong':
        out += ''
          + inline.lexer(cap[2] || cap[1])
          + '';
        break;
      case 'em':
        out += ''
          + inline.lexer(cap[2] || cap[1])
          + '';
        break;
      case 'code':
        out += ''
          + escape(cap[2] || cap[1])
          + '';
        break;
      case 'text':
        out += escape(cap[1]);
        break;
      default:
        break;
    }
  }

  return out;
};

/**
 * Parsing
 */

var tokens
  , token;

var next = function() {
  return token = tokens.pop();
};

var tok = function() {
  switch (token.type) {
    case 'space':
      return '';
    case 'hr':
      return '
';
    case 'heading':
      return '<h' + token.depth + '>'
        + inline.lexer(token.text)
        + '</h' + token.depth + '>';
    case 'block':
      return '
'
        + escape(token.text)
        + '
';
    case 'blockquote_start':
      var body = [];
      while (next().type !== 'blockquote_end') {
        body.push(tok());
      }
      return '
'
        + body.join('')
        + '
';
    case 'list_start':
      var body = []
        , type = token.ordered ? 'ol' : 'ul';
      while (next().type !== 'list_end') {
        body.push(tok());
      }
      return '<' + type + '>'
        + body.join('')
        + '</' + type + '>';
    case 'list_item_start':
      var body = [];
      while (next().type !== 'list_item_end') {
        body.push(token.type === 'text'
          ? inline.lexer(token.text)
          : tok());
      }
      return '
'
        + body.join(' ')
        + '
';
    case 'loose_item_start':
      var body = [];
      while (next().type !== 'list_item_end') {
        body.push(tok());
      }
      return '
'
        + body.join(' ')
        + '
';
    case 'html':
      return inline.lexer(token.text);
    case 'text':
      var body = [ token.text ]
        , top;
      while ((top = tokens[tokens.length-1])
             && top.type === 'text') {
        body.push(next().text);
      }
      return '
'
        + inline.lexer(body.join(' '))
        + '
';
  }
};

var parse = function(src) {
  tokens = src.reverse();

  var out = [];
  while (next()) {
    out.push(tok());
  }

  tokens = null;
  token = null;

  return out.join(' ');
};

/**
 * Helpers
 */

var escape = function(html) {
  return html
    .replace(/&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
};

var mangle = function(str) {
  var out = ''
    , ch
    , i = 0
    , l = str.length;

  for (; i < l; i++) {
    ch = str.charCodeAt(i);
    if (Math.random() > 0.5) {
      ch = 'x' + ch.toString(16);
    }
    out += '&#' + ch + ';';
  }

  return out;
};

var each = function(obj, func) {
  var i = 0, l = obj.length;
  for (; i < l; i++) func(obj[i]);
};

/**
 * Expose
 */

var marked = function(str) {
  return parse(block.lexer(str));
};

marked.parser = parse;
marked.lexer = block.lexer;

if (typeof module !== 'undefined') {
  module.exports = marked;
} else {
  this.marked = marked;
}

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