text: var route = (function () {
      'use strict';
    
      var observable = function(el) {
    
        /**
         * Extend the original object or create a new empty one
         * @type { Object }
         */
    
        el = el || {};
    
        /**
         * Private variables
         */
        var callbacks = {},
          slice = Array.prototype.slice;
    
        /**
         * Public Api
         */
    
        // extend the el object adding the observable methods
        Object.defineProperties(el, {
          /**
           * Listen to the given `event` ands
           * execute the `callback` each time an event is triggered.
           * @param  { String } event - event id
           * @param  { Function } fn - callback function
           * @returns { Object } el
           */
          on: {
            value: function(event, fn) {
              if (typeof fn == 'function')
                { (callbacks[event] = callbacks[event] || []).push(fn); }
              return el
            },
            enumerable: false,
            writable: false,
            configurable: false
          },
    
          /**
           * Removes the given `event` listeners
           * @param   { String } event - event id
           * @param   { Function } fn - callback function
           * @returns { Object } el
           */
          off: {
            value: function(event, fn) {
              if (event == '*' && !fn) { callbacks = {}; }
              else {
                if (fn) {
                  var arr = callbacks[event];
                  for (var i = 0, cb; cb = arr && arr[i]; ++i) {
                    if (cb == fn) { arr.splice(i--, 1); }
                  }
                } else { delete callbacks[event]; }
              }
              return el
            },
            enumerable: false,
            writable: false,
            configurable: false
          },
    
          /**
           * Listen to the given `event` and
           * execute the `callback` at most once
           * @param   { String } event - event id
           * @param   { Function } fn - callback function
           * @returns { Object } el
           */
          one: {
            value: function(event, fn) {
              function on() {
                el.off(event, on);
                fn.apply(el, arguments);
              }
              return el.on(event, on)
            },
            enumerable: false,
            writable: false,
            configurable: false
          },
    
          /**
           * Execute all callback functions that listen to
           * the given `event`
           * @param   { String } event - event id
           * @returns { Object } el
           */
          trigger: {
            value: function(event) {
              var arguments$1 = arguments;
    
    
              // getting the arguments
              var arglen = arguments.length - 1,
                args = new Array(arglen),
                fns,
                fn,
                i;
    
              for (i = 0; i < arglen; i++) {
                args[i] = arguments$1[i + 1]; // skip first argument
              }
    
              fns = slice.call(callbacks[event] || [], 0);
    
              for (i = 0; fn = fns[i]; ++i) {
                fn.apply(el, args);
              }
    
              if (callbacks['*'] && event != '*')
                { el.trigger.apply(el, ['*', event].concat(args)); }
    
              return el
            },
            enumerable: false,
            writable: false,
            configurable: false
          }
        });
    
        return el
    
      };
    
      /**
       * Simple client-side router
       * @module riot-route
       */
    
      var RE_ORIGIN = /^.+?\/\/+[^/]+/,
        EVENT_LISTENER = 'EventListener',
        REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
        ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
        HAS_ATTRIBUTE = 'hasAttribute',
        POPSTATE = 'popstate',
        HASHCHANGE = 'hashchange',
        TRIGGER = 'trigger',
        MAX_EMIT_STACK_LEVEL = 3,
        win = typeof window != 'undefined' && window,
        doc = typeof document != 'undefined' && document,
        hist = win && history,
        loc = win && (hist.location || win.location), // see html5-history-api
        prot = Router.prototype, // to minify more
        clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
        central = observable();
    
      var
        started = false,
        routeFound = false,
        debouncedEmit,
        current,
        parser,
        secondParser,
        emitStack = [],
        emitStackLevel = 0;
    
      /**
       * Default parser. You can replace it via router.parser method.
       * @param {string} path - current path (normalized)
       * @returns {array} array
       */
      function DEFAULT_PARSER(path) {
        return path.split(/[/?#]/)
      }
    
      /**
       * Default parser (second). You can replace it via router.parser method.
       * @param {string} path - current path (normalized)
       * @param {string} filter - filter string (normalized)
       * @returns {array} array
       */
      function DEFAULT_SECOND_PARSER(path, filter) {
        var f = filter
          .replace(/\?/g, '\\?')
          .replace(/\*/g, '([^/?#]+?)')
          .replace(/\.\./, '.*');
        var re = new RegExp(("^" + f + "$"));
        var args = path.match(re);
    
        if (args) { return args.slice(1) }
      }
    
      /**
       * Simple/cheap debounce implementation
       * @param   {function} fn - callback
       * @param   {number} delay - delay in seconds
       * @returns {function} debounced function
       */
      function debounce(fn, delay) {
        var t;
        return function () {
          clearTimeout(t);
          t = setTimeout(fn, delay);
        }
      }
    
      /**
       * Set the window listeners to trigger the routes
       * @param {boolean} autoExec - see route.start
       */
      function start(autoExec) {
        debouncedEmit = debounce(emit, 1);
        win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit);
        win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit);
        doc[ADD_EVENT_LISTENER](clickEvent, click);
    
        if (autoExec) { emit(true); }
      }
    
      /**
       * Router class
       */
      function Router() {
        this.$ = [];
        observable(this); // make it observable
        central.on('stop', this.s.bind(this));
        central.on('emit', this.e.bind(this));
      }
    
      function normalize(path) {
        return path.replace(/^\/|\/$/, '')
      }
    
      function isString(str) {
        return typeof str == 'string'
      }
    
      /**
       * Get the part after domain name
       * @param {string} href - fullpath
       * @returns {string} path from root
       */
      function getPathFromRoot(href) {
        return (href || loc.href).replace(RE_ORIGIN, '')
      }
    
      /**
       * Get the part after base
       * @param {string} href - fullpath
       * @returns {string} path from base
       */
      function getPathFromBase(href) {
        var base = route._.base;
        return base[0] === '#'
          ? (href || loc.href || '').split(base)[1] || ''
          : (loc ? getPathFromRoot(href) : href || '').replace(base, '')
      }
    
      function emit(force) {
        // the stack is needed for redirections
        var isRoot = emitStackLevel === 0;
        if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) { return }
    
        emitStackLevel++;
        emitStack.push(function() {
          var path = getPathFromBase();
          if (force || path !== current) {
            central[TRIGGER]('emit', path);
            current = path;
          }
        });
    
        if (isRoot) {
          var first;
          while (first = emitStack.shift()) { first(); } // stack increses within this call
          emitStackLevel = 0;
        }
      }
    
      function click(e) {
        if (
          e.which !== 1 // not left click
          || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
          || e.defaultPrevented // or default prevented
        ) { return }
    
        var el = e.target;
        while (el && el.nodeName !== 'A') { el = el.parentNode; }
    
        if (
          !el || el.nodeName !== 'A' // not A tag
          || el[HAS_ATTRIBUTE]('download') // has download attr
          || !el[HAS_ATTRIBUTE]('href') // has no href attr
          || el.target && el.target !== '_self' // another window or frame
          || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) === -1 // cross origin
        ) { return }
    
        var base = route._.base;
    
        if (el.href !== loc.href
          && (
            el.href.split('#')[0] === loc.href.split('#')[0] // internal jump
            || base[0] !== '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
            || base[0] === '#' && el.href.split(base)[0] !== loc.href.split(base)[0] // outside of #base
            || !go(getPathFromBase(el.href), el.title || doc.title) // route not found
          )) { return }
    
        e.preventDefault();
      }
    
      /**
       * Go to the path
       * @param {string} path - destination path
       * @param {string} title - page title
       * @param {boolean} shouldReplace - use replaceState or pushState
       * @returns {boolean} - route not found flag
       */
      function go(path, title, shouldReplace) {
        // Server-side usage: directly execute handlers for the path
        if (!hist) { return central[TRIGGER]('emit', getPathFromBase(path)) }
    
        path = route._.base + normalize(path);
        title = title || doc.title;
        // browsers ignores the second parameter `title`
        shouldReplace
          ? hist.replaceState(null, title, path)
          : hist.pushState(null, title, path);
        // so we need to set it manually
        doc.title = title;
        routeFound = false;
        emit();
        return routeFound
      }
    
      /**
       * Go to path or set action
       * a single string:                go there
       * two strings:                    go there with setting a title
       * two strings and boolean:        replace history with setting a title
       * a single function:              set an action on the default route
       * a string/RegExp and a function: set an action on the route
       * @param {(string|function)} first - path / action / filter
       * @param {(string|RegExp|function)} second - title / action
       * @param {boolean} third - replace flag
       */
      prot.m = function(first, second, third) {
        if (isString(first) && (!second || isString(second))) { go(first, second, third || false); }
        else if (second) { this.r(first, second); }
        else { this.r('@', first); }
      };
    
      /**
       * Stop routing
       */
      prot.s = function() {
        this.off('*');
        this.$ = [];
      };
    
      /**
       * Emit
       * @param {string} path - path
       */
      prot.e = function(path) {
        this.$.concat('@').some(function(filter) {
          var args = (filter === '@' ? parser : secondParser)(normalize(path), normalize(filter));
          if (typeof args != 'undefined') {
            this[TRIGGER].apply(null, [filter].concat(args));
            return routeFound = true // exit from loop
          }
        }, this);
      };
    
      /**
       * Register route
       * @param {string} filter - filter for matching to url
       * @param {function} action - action to register
       */
      prot.r = function(filter, action) {
        if (filter !== '@') {
          filter = '/' + normalize(filter);
          this.$.push(filter);
        }
    
        this.on(filter, action);
      };
    
      var mainRouter = new Router();
      var route = mainRouter.m.bind(mainRouter);
    
      // adding base and getPathFromBase to route so we can access them in route.tag's script
      route._ = { base: null, getPathFromBase: getPathFromBase };
    
      /**
       * Create a sub router
       * @returns {function} the method of a new Router object
       */
      route.create = function() {
        var newSubRouter = new Router();
        // assign sub-router's main method
        var router = newSubRouter.m.bind(newSubRouter);
        // stop only this sub-router
        router.stop = newSubRouter.s.bind(newSubRouter);
        return router
      };
    
      /**
       * Set the base of url
       * @param {(str|RegExp)} arg - a new base or '#' or '#!'
       */
      route.base = function(arg) {
        route._.base = arg || '#';
        current = getPathFromBase(); // recalculate current path
      };
    
      /** Exec routing right now **/
      route.exec = function() {
        emit(true);
      };
    
      /**
       * Replace the default router to yours
       * @param {function} fn - your parser function
       * @param {function} fn2 - your secondParser function
       */
      route.parser = function(fn, fn2) {
        if (!fn && !fn2) {
          // reset parser for testing...
          parser = DEFAULT_PARSER;
          secondParser = DEFAULT_SECOND_PARSER;
        }
        if (fn) { parser = fn; }
        if (fn2) { secondParser = fn2; }
      };
    
      /**
       * Helper function to get url query as an object
       * @returns {object} parsed query
       */
      route.query = function() {
        var q = {};
        var href = loc.href || current;
        href.replace(/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v; });
        return q
      };
    
      /** Stop routing **/
      route.stop = function () {
        if (started) {
          if (win) {
            win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit);
            win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit);
            doc[REMOVE_EVENT_LISTENER](clickEvent, click);
          }
    
          central[TRIGGER]('stop');
          started = false;
        }
      };
    
      /**
       * Start routing
       * @param {boolean} autoExec - automatically exec after starting if true
       */
      route.start = function (autoExec) {
        if (!started) {
          if (win) {
            if (document.readyState === 'interactive' || document.readyState === 'complete') {
              start(autoExec);
            } else {
              document.onreadystatechange = function () {
                if (document.readyState === 'interactive') {
                  // the timeout is needed to solve
                  // a weird safari bug https://github.com/riot/route/issues/33
                  setTimeout(function() { start(autoExec); }, 1);
                }
              };
            }
          }
    
          started = true;
        }
      };
    
      /** Prepare the router **/
      route.base();
      route.parser();
    
      return route;
    
    }());
  
    export {route};
  