dist/tmpl.js

Summary

Maintainability
F
3 wks
Test Coverage

/* riot-tmpl v3.0.8, @license MIT, (c) 2015 Muut Inc. + contributors */
;(function (window) {     // eslint-disable-line no-extra-semi
  'use strict'

  var skipRegex = (function () { //eslint-disable-line no-unused-vars

    var beforeReChars = '[{(,;:?=|&!^~>%*/'

    var beforeReWords = [
      'case',
      'default',
      'do',
      'else',
      'in',
      'instanceof',
      'prefix',
      'return',
      'typeof',
      'void',
      'yield'
    ]

    var wordsLastChar = beforeReWords.reduce(function (s, w) {
      return s + w.slice(-1)
    }, '')

    var RE_REGEX = /^\/(?=[^*>/])[^[/\\]*(?:(?:\\.|\[(?:\\.|[^\]\\]*)*\])[^[\\/]*)*?\/[gimuy]*/
    var RE_VN_CHAR = /[$\w]/

    function prev (code, pos) {
      while (--pos >= 0 && /\s/.test(code[pos]));
      return pos
    }

    function _skipRegex (code, start) {

      var re = /.*/g
      var pos = re.lastIndex = start++
      var match = re.exec(code)[0].match(RE_REGEX)

      if (match) {
        var next = pos + match[0].length

        pos = prev(code, pos)
        var c = code[pos]

        if (pos < 0 || ~beforeReChars.indexOf(c)) {
          return next
        }

        if (c === '.') {

          if (code[pos - 1] === '.') {
            start = next
          }

        } else if (c === '+' || c === '-') {

          if (code[--pos] !== c ||
              (pos = prev(code, pos)) < 0 ||
              !RE_VN_CHAR.test(code[pos])) {
            start = next
          }

        } else if (~wordsLastChar.indexOf(c)) {

          var end = pos + 1

          while (--pos >= 0 && RE_VN_CHAR.test(code[pos]));
          if (~beforeReWords.indexOf(code.slice(pos + 1, end))) {
            start = next
          }
        }
      }

      return start
    }

    return _skipRegex

  })()

  /**
   * riot.util.brackets
   *
   * - `brackets    ` - Returns a string or regex based on its parameter
   * - `brackets.set` - Change the current riot brackets
   *
   * @module
   */

  var brackets = (function (UNDEF) {

    var
      REGLOB = 'g',

      R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g,

      R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|`[^`\\]*(?:\\[\S\s][^`\\]*)*`/g,

      S_QBLOCKS = R_STRINGS.source + '|' +
        /(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
        /\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?([^<]\/)[gim]*/.source,

      UNSUPPORTED = RegExp('[\\' + 'x00-\\x1F<>a-zA-Z0-9\'",;\\\\]'),

      NEED_ESCAPE = /(?=[[\]()*+?.^$|])/g,

      S_QBLOCK2 = R_STRINGS.source + '|' + /(\/)(?![*\/])/.source,

      FINDBRACES = {
        '(': RegExp('([()])|'   + S_QBLOCK2, REGLOB),
        '[': RegExp('([[\\]])|' + S_QBLOCK2, REGLOB),
        '{': RegExp('([{}])|'   + S_QBLOCK2, REGLOB)
      },

      DEFAULT = '{ }'

    var _pairs = [
      '{', '}',
      '{', '}',
      /{[^}]*}/,
      /\\([{}])/g,
      /\\({)|{/g,
      RegExp('\\\\(})|([[({])|(})|' + S_QBLOCK2, REGLOB),
      DEFAULT,
      /^\s*{\^?\s*([$\w]+)(?:\s*,\s*(\S+))?\s+in\s+(\S.*)\s*}/,
      /(^|[^\\]){=[\S\s]*?}/
    ]

    var
      cachedBrackets = UNDEF,
      _regex,
      _cache = [],
      _settings

    function _loopback (re) { return re }

    function _rewrite (re, bp) {
      if (!bp) bp = _cache
      return new RegExp(
        re.source.replace(/{/g, bp[2]).replace(/}/g, bp[3]), re.global ? REGLOB : ''
      )
    }

    function _create (pair) {
      if (pair === DEFAULT) return _pairs

      var arr = pair.split(' ')

      if (arr.length !== 2 || UNSUPPORTED.test(pair)) {
        throw new Error('Unsupported brackets "' + pair + '"')
      }
      arr = arr.concat(pair.replace(NEED_ESCAPE, '\\').split(' '))

      arr[4] = _rewrite(arr[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4], arr)
      arr[5] = _rewrite(pair.length > 3 ? /\\({|})/g : _pairs[5], arr)
      arr[6] = _rewrite(_pairs[6], arr)
      arr[7] = RegExp('\\\\(' + arr[3] + ')|([[({])|(' + arr[3] + ')|' + S_QBLOCK2, REGLOB)
      arr[8] = pair
      return arr
    }

    function _brackets (reOrIdx) {
      return reOrIdx instanceof RegExp ? _regex(reOrIdx) : _cache[reOrIdx]
    }

    _brackets.split = function split (str, tmpl, _bp) {
      // istanbul ignore next: _bp is for the compiler
      if (!_bp) _bp = _cache

      var
        parts = [],
        match,
        isexpr,
        start,
        pos,
        re = _bp[6]

      var qblocks = []
      var prevStr = ''
      var mark, lastIndex

      isexpr = start = re.lastIndex = 0

      while ((match = re.exec(str))) {

        lastIndex = re.lastIndex
        pos = match.index

        if (isexpr) {

          if (match[2]) {

            var ch = match[2]
            var rech = FINDBRACES[ch]
            var ix = 1

            rech.lastIndex = lastIndex
            while ((match = rech.exec(str))) {
              if (match[1]) {
                if (match[1] === ch) ++ix
                else if (!--ix) break
              } else {
                rech.lastIndex = pushQBlock(match.index, rech.lastIndex, match[2])
              }
            }
            re.lastIndex = ix ? str.length : rech.lastIndex
            continue
          }

          if (!match[3]) {
            re.lastIndex = pushQBlock(pos, lastIndex, match[4])
            continue
          }
        }

        if (!match[1]) {
          unescapeStr(str.slice(start, pos))
          start = re.lastIndex
          re = _bp[6 + (isexpr ^= 1)]
          re.lastIndex = start
        }
      }

      if (str && start < str.length) {
        unescapeStr(str.slice(start))
      }

      parts.qblocks = qblocks

      return parts

      function unescapeStr (s) {
        if (prevStr) {
          s = prevStr + s
          prevStr = ''
        }
        if (tmpl || isexpr) {
          parts.push(s && s.replace(_bp[5], '$1'))
        } else {
          parts.push(s)
        }
      }

      function pushQBlock(_pos, _lastIndex, slash) { //eslint-disable-line
        if (slash) {
          _lastIndex = skipRegex(str, _pos)
        }

        if (tmpl && _lastIndex > _pos + 2) {
          mark = '\u2057' + qblocks.length + '~'
          qblocks.push(str.slice(_pos, _lastIndex))
          prevStr += str.slice(start, _pos) + mark
          start = _lastIndex
        }
        return _lastIndex
      }
    }

    _brackets.hasExpr = function hasExpr (str) {
      return _cache[4].test(str)
    }

    _brackets.loopKeys = function loopKeys (expr) {
      var m = expr.match(_cache[9])

      return m
        ? { key: m[1], pos: m[2], val: _cache[0] + m[3].trim() + _cache[1] }
        : { val: expr.trim() }
    }

    _brackets.array = function array (pair) {
      return pair ? _create(pair) : _cache
    }

    function _reset (pair) {
      if ((pair || (pair = DEFAULT)) !== _cache[8]) {
        _cache = _create(pair)
        _regex = pair === DEFAULT ? _loopback : _rewrite
        _cache[9] = _regex(_pairs[9])
      }
      cachedBrackets = pair
    }

    function _setSettings (o) {
      var b

      o = o || {}
      b = o.brackets
      Object.defineProperty(o, 'brackets', {
        set: _reset,
        get: function () { return cachedBrackets },
        enumerable: true
      })
      _settings = o
      _reset(b)
    }

    Object.defineProperty(_brackets, 'settings', {
      set: _setSettings,
      get: function () { return _settings }
    })

    /* istanbul ignore next: in the browser riot is always in the scope */
    _brackets.settings = typeof riot !== 'undefined' && riot.settings || {}
    _brackets.set = _reset
    _brackets.skipRegex = skipRegex

    _brackets.R_STRINGS = R_STRINGS
    _brackets.R_MLCOMMS = R_MLCOMMS
    _brackets.S_QBLOCKS = S_QBLOCKS
    _brackets.S_QBLOCK2 = S_QBLOCK2

    return _brackets

  })()

  /**
   * @module tmpl
   *
   * tmpl          - Root function, returns the template value, render with data
   * tmpl.hasExpr  - Test the existence of a expression inside a string
   * tmpl.loopKeys - Get the keys for an 'each' loop (used by `_each`)
   */

  var tmpl = (function () {

    var _cache = {}

    function _tmpl (str, data) {
      if (!str) return str

      return (_cache[str] || (_cache[str] = _create(str))).call(
        data, _logErr.bind({
          data: data,
          tmpl: str
        })
      )
    }

    _tmpl.hasExpr = brackets.hasExpr

    _tmpl.loopKeys = brackets.loopKeys

    // istanbul ignore next
    _tmpl.clearCache = function () { _cache = {} }

    _tmpl.errorHandler = null

    function _logErr (err, ctx) {

      err.riotData = {
        tagName: ctx && ctx.__ && ctx.__.tagName,
        _riot_id: ctx && ctx._riot_id  //eslint-disable-line camelcase
      }

      if (_tmpl.errorHandler) _tmpl.errorHandler(err)
      else if (
        typeof console !== 'undefined' &&
        typeof console.error === 'function'
      ) {
        console.error(err.message)
        console.log('<%s> %s', err.riotData.tagName || 'Unknown tag', this.tmpl) // eslint-disable-line
        console.log(this.data) // eslint-disable-line
      }
    }

    function _create (str) {
      var expr = _getTmpl(str)

      if (expr.slice(0, 11) !== 'try{return ') expr = 'return ' + expr

      return new Function('E', expr + ';')    // eslint-disable-line no-new-func
    }

    var RE_DQUOTE = /\u2057/g
    var RE_QBMARK = /\u2057(\d+)~/g

    function _getTmpl (str) {
      var parts = brackets.split(str.replace(RE_DQUOTE, '"'), 1)
      var qstr = parts.qblocks
      var expr

      if (parts.length > 2 || parts[0]) {
        var i, j, list = []

        for (i = j = 0; i < parts.length; ++i) {

          expr = parts[i]

          if (expr && (expr = i & 1

              ? _parseExpr(expr, 1, qstr)

              : '"' + expr
                  .replace(/\\/g, '\\\\')
                  .replace(/\r\n?|\n/g, '\\n')
                  .replace(/"/g, '\\"') +
                '"'

            )) list[j++] = expr

        }

        expr = j < 2 ? list[0]
             : '[' + list.join(',') + '].join("")'

      } else {

        expr = _parseExpr(parts[1], 0, qstr)
      }

      if (qstr.length) {
        expr = expr.replace(RE_QBMARK, function (_, pos) {
          return qstr[pos]
            .replace(/\r/g, '\\r')
            .replace(/\n/g, '\\n')
        })
      }
      return expr
    }

    var RE_CSNAME = /^(?:(-?[_A-Za-z\xA0-\xFF][-\w\xA0-\xFF]*)|\u2057(\d+)~):/
    var
      RE_BREND = {
        '(': /[()]/g,
        '[': /[[\]]/g,
        '{': /[{}]/g
      }

    function _parseExpr (expr, asText, qstr) {

      expr = expr
        .replace(/\s+/g, ' ').trim()
        .replace(/\ ?([[\({},?\.:])\ ?/g, '$1')

      if (expr) {
        var
          list = [],
          cnt = 0,
          match

        while (expr &&
              (match = expr.match(RE_CSNAME)) &&
              !match.index
          ) {
          var
            key,
            jsb,
            re = /,|([[{(])|$/g

          expr = RegExp.rightContext
          key  = match[2] ? qstr[match[2]].slice(1, -1).trim().replace(/\s+/g, ' ') : match[1]

          while (jsb = (match = re.exec(expr))[1]) skipBraces(jsb, re)

          jsb  = expr.slice(0, match.index)
          expr = RegExp.rightContext

          list[cnt++] = _wrapExpr(jsb, 1, key)
        }

        expr = !cnt ? _wrapExpr(expr, asText)
             : cnt > 1 ? '[' + list.join(',') + '].join(" ").trim()' : list[0]
      }
      return expr

      function skipBraces (ch, re) {
        var
          mm,
          lv = 1,
          ir = RE_BREND[ch]

        ir.lastIndex = re.lastIndex
        while (mm = ir.exec(expr)) {
          if (mm[0] === ch) ++lv
          else if (!--lv) break
        }
        re.lastIndex = lv ? expr.length : ir.lastIndex
      }
    }

    // istanbul ignore next: not both
    var // eslint-disable-next-line max-len
      JS_CONTEXT = '"in this?this:' + (typeof window !== 'object' ? 'global' : 'window') + ').',
      JS_VARNAME = /[,{][\$\w]+(?=:)|(^ *|[^$\w\.{])(?!(?:typeof|true|false|null|undefined|in|instanceof|is(?:Finite|NaN)|void|NaN|new|Date|RegExp|Math)(?![$\w]))([$_A-Za-z][$\w]*)/g,
      JS_NOPROPS = /^(?=(\.[$\w]+))\1(?:[^.[(]|$)/

    function _wrapExpr (expr, asText, key) {
      var tb

      expr = expr.replace(JS_VARNAME, function (match, p, mvar, pos, s) {
        if (mvar) {
          pos = tb ? 0 : pos + match.length

          if (mvar !== 'this' && mvar !== 'global' && mvar !== 'window') {
            match = p + '("' + mvar + JS_CONTEXT + mvar
            if (pos) tb = (s = s[pos]) === '.' || s === '(' || s === '['
          } else if (pos) {
            tb = !JS_NOPROPS.test(s.slice(pos))
          }
        }
        return match
      })

      if (tb) {
        expr = 'try{return ' + expr + '}catch(e){E(e,this)}'
      }

      if (key) {

        expr = (tb
            ? 'function(){' + expr + '}.call(this)' : '(' + expr + ')'
          ) + '?"' + key + '":""'

      } else if (asText) {

        expr = 'function(v){' + (tb
            ? expr.replace('return ', 'v=') : 'v=(' + expr + ')'
          ) + ';return v||v===0?v:""}.call(this)'
      }

      return expr
    }

    return _tmpl

  })()

  tmpl.version = brackets.version = 'v3.0.8'

  /* istanbul ignore else */
  if (typeof module === 'object' && module.exports) {
    module.exports = {
      tmpl: tmpl, brackets: brackets
    }
  } else if (typeof define === 'function' && typeof define.amd !== 'undefined') {
    define(function () {
      return {
        tmpl: tmpl, brackets: brackets
      }
    })
  } else if (window) {
    window.tmpl = tmpl
    window.brackets = brackets
  }

})(typeof window === 'object' ? /* istanbul ignore next */ window : void 0)