silk-gui
Version:
GUI for developers and Node OS
179 lines (167 loc) • 4.24 kB
JavaScript
var Cache = require('../cache')
var config = require('../config')
var dirParser = require('./directive')
var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g
var cache, tagRE, htmlRE, firstChar, lastChar
/**
* Escape a string so it can be used in a RegExp
* constructor.
*
* @param {String} str
*/
function escapeRegex (str) {
return str.replace(regexEscapeRE, '\\$&')
}
/**
* Compile the interpolation tag regex.
*
* @return {RegExp}
*/
function compileRegex () {
config._delimitersChanged = false
var open = config.delimiters[0]
var close = config.delimiters[1]
firstChar = open.charAt(0)
lastChar = close.charAt(close.length - 1)
var firstCharRE = escapeRegex(firstChar)
var lastCharRE = escapeRegex(lastChar)
var openRE = escapeRegex(open)
var closeRE = escapeRegex(close)
tagRE = new RegExp(
firstCharRE + '?' + openRE +
'(.+?)' +
closeRE + lastCharRE + '?',
'g'
)
htmlRE = new RegExp(
'^' + firstCharRE + openRE +
'.*' +
closeRE + lastCharRE + '$'
)
// reset cache
cache = new Cache(1000)
}
/**
* Parse a template text string into an array of tokens.
*
* @param {String} text
* @return {Array<Object> | null}
* - {String} type
* - {String} value
* - {Boolean} [html]
* - {Boolean} [oneTime]
*/
exports.parse = function (text) {
if (config._delimitersChanged) {
compileRegex()
}
var hit = cache.get(text)
if (hit) {
return hit
}
if (!tagRE.test(text)) {
return null
}
var tokens = []
var lastIndex = tagRE.lastIndex = 0
var match, index, value, first, oneTime, partial
/* jshint boss:true */
while (match = tagRE.exec(text)) {
index = match.index
// push text token
if (index > lastIndex) {
tokens.push({
value: text.slice(lastIndex, index)
})
}
// tag token
first = match[1].charCodeAt(0)
oneTime = first === 0x2A // *
partial = first === 0x3E // >
value = (oneTime || partial)
? match[1].slice(1)
: match[1]
tokens.push({
tag: true,
value: value.trim(),
html: htmlRE.test(match[0]),
oneTime: oneTime,
partial: partial
})
lastIndex = index + match[0].length
}
if (lastIndex < text.length) {
tokens.push({
value: text.slice(lastIndex)
})
}
cache.put(text, tokens)
return tokens
}
/**
* Format a list of tokens into an expression.
* e.g. tokens parsed from 'a {{b}} c' can be serialized
* into one single expression as '"a " + b + " c"'.
*
* @param {Array} tokens
* @param {Vue} [vm]
* @return {String}
*/
exports.tokensToExp = function (tokens, vm) {
return tokens.length > 1
? tokens.map(function (token) {
return formatToken(token, vm)
}).join('+')
: formatToken(tokens[0], vm, true)
}
/**
* Format a single token.
*
* @param {Object} token
* @param {Vue} [vm]
* @param {Boolean} single
* @return {String}
*/
function formatToken (token, vm, single) {
return token.tag
? vm && token.oneTime
? '"' + vm.$eval(token.value) + '"'
: single
? token.value
: inlineFilters(token.value)
: '"' + token.value + '"'
}
/**
* For an attribute with multiple interpolation tags,
* e.g. attr="some-{{thing | filter}}", in order to combine
* the whole thing into a single watchable expression, we
* have to inline those filters. This function does exactly
* that. This is a bit hacky but it avoids heavy changes
* to directive parser and watcher mechanism.
*
* @param {String} exp
* @return {String}
*/
var filterRE = /[^|]\|[^|]/
function inlineFilters (exp) {
if (!filterRE.test(exp)) {
return '(' + exp + ')'
} else {
var dir = dirParser.parse(exp)[0]
if (!dir.filters) {
return '(' + exp + ')'
} else {
exp = dir.expression
for (var i = 0, l = dir.filters.length; i < l; i++) {
var filter = dir.filters[i]
var args = filter.args
? ',"' + filter.args.join('","') + '"'
: ''
filter = 'this.$options.filters["' + filter.name + '"]'
exp = '(' + filter + '.read||' + filter + ')' +
'.apply(this,[' + exp + args + '])'
}
return exp
}
}
}