mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-20 15:00:42 -07:00
refactor: strictly typed
This commit is contained in:
Generated
+2032
-1920
File diff suppressed because it is too large
Load Diff
+8
-1
@@ -12,7 +12,7 @@
|
|||||||
"test": "npm run unit && npm run e2e",
|
"test": "npm run unit && npm run e2e",
|
||||||
"coverage-html": "nyc npm run unit && nyc report --reporter=html",
|
"coverage-html": "nyc npm run unit && nyc report --reporter=html",
|
||||||
"coverage-coveralls": "nyc npm run unit && nyc report --reporter=text-lcov | coveralls",
|
"coverage-coveralls": "nyc npm run unit && nyc report --reporter=text-lcov | coveralls",
|
||||||
"build": "rollup -c && ls -lh dist",
|
"build": "rollup -c rollup.config.ts && ls -lh dist",
|
||||||
"version": "npm run build && git add -A dist"
|
"version": "npm run build && git add -A dist"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -46,8 +46,14 @@
|
|||||||
"@semantic-release/git": "^7.0.8",
|
"@semantic-release/git": "^7.0.8",
|
||||||
"@semantic-release/npm": "^5.1.4",
|
"@semantic-release/npm": "^5.1.4",
|
||||||
"@semantic-release/release-notes-generator": "^7.1.4",
|
"@semantic-release/release-notes-generator": "^7.1.4",
|
||||||
|
"@types/chai": "^4.1.7",
|
||||||
|
"@types/chai-as-promised": "^7.1.0",
|
||||||
|
"@types/express": "^4.16.1",
|
||||||
"@types/jsdom": "^12.2.2",
|
"@types/jsdom": "^12.2.2",
|
||||||
"@types/mocha": "^5.2.6",
|
"@types/mocha": "^5.2.6",
|
||||||
|
"@types/sinon": "^7.0.6",
|
||||||
|
"@types/sinon-chai": "^3.2.2",
|
||||||
|
"@types/supertest": "^2.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^1.3.0",
|
"@typescript-eslint/eslint-plugin": "^1.3.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
@@ -75,6 +81,7 @@
|
|||||||
"supertest": "^3.4.2",
|
"supertest": "^3.4.2",
|
||||||
"ts-node": "^8.0.2",
|
"ts-node": "^8.0.2",
|
||||||
"tsconfig-paths": "^3.8.0",
|
"tsconfig-paths": "^3.8.0",
|
||||||
|
"tslib": "^1.9.3",
|
||||||
"typescript": "^3.3.3"
|
"typescript": "^3.3.3"
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import { last } from 'src/util/underscore'
|
import { last } from 'src/util/underscore'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'join': (v, arg) => v.join(arg === undefined ? ' ' : arg),
|
'join': (v: any[], arg: string) => v.join(arg === undefined ? ' ' : arg),
|
||||||
'last': v => last(v),
|
'last': <T>(v: T[]): T => last(v),
|
||||||
'first': v => v[0],
|
'first': <T>(v: T[]): T => v[0],
|
||||||
'map': (arr, arg) => arr.map(v => v[arg]),
|
'map': <T1, T2>(arr: {[key: string]: T1}[], arg: string): T1[] => arr.map(v => v[arg]),
|
||||||
'reverse': v => v.reverse(),
|
'reverse': (v: any[]) => v.reverse(),
|
||||||
'sort': (v, arg) => v.sort(arg),
|
'sort': <T>(v: T[], arg: (lhs: T, rhs: T) => number) => v.sort(arg),
|
||||||
'size': v => v.length,
|
'size': (v: string | any[]) => v.length,
|
||||||
'slice': (v, begin, length) => {
|
'concat': <T1, T2>(v: T1[], arg: T2[] | T2): Array<T1 | T2> => Array.prototype.concat.call(v, arg),
|
||||||
|
'slice': <T>(v: T[], begin: number, length: number): T[] => {
|
||||||
if (length === undefined) length = 1
|
if (length === undefined) length = 1
|
||||||
return v.slice(begin, begin + length)
|
return v.slice(begin, begin + length)
|
||||||
},
|
},
|
||||||
'uniq': function (arr) {
|
'uniq': function<T> (arr: T[]): T[] {
|
||||||
const u = {}
|
const u = {}
|
||||||
return (arr || []).filter(val => {
|
return (arr || []).filter(val => {
|
||||||
if (u.hasOwnProperty(val)) {
|
if (u.hasOwnProperty(String(val))) return false
|
||||||
return false
|
u[String(val)] = true
|
||||||
}
|
|
||||||
u[val] = true
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import strftime from 'src/util/strftime'
|
|||||||
import { isString } from 'src/util/underscore'
|
import { isString } from 'src/util/underscore'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'date': (v, arg) => {
|
'date': (v: string | Date, arg: string) => {
|
||||||
let date = v
|
let date = v
|
||||||
if (v === 'now') {
|
if (v === 'now') {
|
||||||
date = new Date()
|
date = new Date()
|
||||||
@@ -13,6 +13,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidDate (date) {
|
function isValidDate (date: any): date is Date {
|
||||||
return date instanceof Date && !isNaN(date.getTime())
|
return date instanceof Date && !isNaN(date.getTime())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ const unescapeMap = {
|
|||||||
''': "'"
|
''': "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
function escape (str) {
|
function escape (str: string) {
|
||||||
return String(str).replace(/&|<|>|"|'/g, m => escapeMap[m])
|
return String(str).replace(/&|<|>|"|'/g, m => escapeMap[m])
|
||||||
}
|
}
|
||||||
|
|
||||||
function unescape (str) {
|
function unescape (str: string) {
|
||||||
return String(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
|
return String(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'escape': escape,
|
'escape': escape,
|
||||||
'escape_once': str => escape(unescape(str)),
|
'escape_once': (str: string) => escape(unescape(str)),
|
||||||
'newline_to_br': v => v.replace(/\n/g, '<br />'),
|
'newline_to_br': (v: string) => v.replace(/\n/g, '<br />'),
|
||||||
'strip_html': v => String(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
|
'strip_html': (v: string) => v.replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-13
@@ -1,25 +1,25 @@
|
|||||||
export default {
|
export default {
|
||||||
'abs': v => Math.abs(v),
|
'abs': (v: number) => Math.abs(v),
|
||||||
'ceil': v => Math.ceil(v),
|
'ceil': (v: number) => Math.ceil(v),
|
||||||
'divided_by': (v, arg) => v / arg,
|
'divided_by': (v: number, arg: number) => v / arg,
|
||||||
'floor': v => Math.floor(v),
|
'floor': (v: number) => Math.floor(v),
|
||||||
'minus': bindFixed((v, arg) => v - arg),
|
'minus': bindFixed((v: number, arg: number) => v - arg),
|
||||||
'modulo': bindFixed((v, arg) => v % arg),
|
'modulo': bindFixed((v: number, arg: number) => v % arg),
|
||||||
'round': (v, arg) => {
|
'round': (v: number, arg: number = 0) => {
|
||||||
const amp = Math.pow(10, arg || 0)
|
const amp = Math.pow(10, arg)
|
||||||
return Math.round(v * amp) / amp
|
return Math.round(v * amp) / amp
|
||||||
},
|
},
|
||||||
'plus': bindFixed((v, arg) => Number(v) + Number(arg)),
|
'plus': bindFixed((v: number, arg: number) => Number(v) + Number(arg)),
|
||||||
'times': (v, arg) => v * arg
|
'times': (v: number, arg: number) => v * arg
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFixed (v) {
|
function getFixed (v: number) {
|
||||||
const p = String(v).split('.')
|
const p = String(v).split('.')
|
||||||
return (p.length > 1) ? p[1].length : 0
|
return (p.length > 1) ? p[1].length : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindFixed (cb) {
|
function bindFixed (cb: (v: number, arg: number) => number) {
|
||||||
return (l, r) => {
|
return (l: number, r: number) => {
|
||||||
const f = Math.max(getFixed(l), getFixed(r))
|
const f = Math.max(getFixed(l), getFixed(r))
|
||||||
return cb(l, r).toFixed(f)
|
return cb(l, r).toFixed(f)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { isTruthy } from 'src/render/syntax'
|
import { isTruthy } from 'src/render/syntax'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'default': (v, arg) => isTruthy(v) ? v : arg
|
'default': <T1, T2>(v: T1, arg: T2): T1 | T2 => isTruthy(v) ? v : arg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
|
import FilterImpl from "src/template/filter/filter-impl";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'append': (v, arg) => v + arg,
|
'append': (v: string, arg: string) => v + arg,
|
||||||
'prepend': (v, arg) => arg + v,
|
'prepend': (v: string, arg: string) => arg + v,
|
||||||
'capitalize': str => String(str).charAt(0).toUpperCase() + str.slice(1),
|
'capitalize': (str: string) => String(str).charAt(0).toUpperCase() + str.slice(1),
|
||||||
'concat': (v, arg) => Array.prototype.concat.call(v, arg),
|
'lstrip': (v: string) => String(v).replace(/^\s+/, ''),
|
||||||
'lstrip': v => String(v).replace(/^\s+/, ''),
|
'downcase': (v: string) => v.toLowerCase(),
|
||||||
'downcase': v => v.toLowerCase(),
|
'upcase': (str: string) => String(str).toUpperCase(),
|
||||||
'upcase': str => String(str).toUpperCase(),
|
'remove': (v: string, arg: string) => v.split(arg).join(''),
|
||||||
'remove': (v, arg) => v.split(arg).join(''),
|
'remove_first': (v: string, l: string) => v.replace(l, ''),
|
||||||
'remove_first': (v, l) => v.replace(l, ''),
|
'replace': (v: string, pattern: string, replacement: string) =>
|
||||||
'replace': (v, pattern, replacement) =>
|
|
||||||
String(v).split(pattern).join(replacement),
|
String(v).split(pattern).join(replacement),
|
||||||
'replace_first': (v, arg1, arg2) => String(v).replace(arg1, arg2),
|
'replace_first': (v: string, arg1: string, arg2: string) => String(v).replace(arg1, arg2),
|
||||||
'rstrip': str => String(str).replace(/\s+$/, ''),
|
'rstrip': (str: string) => String(str).replace(/\s+$/, ''),
|
||||||
'split': (v, arg) => String(v).split(arg),
|
'split': (v: string, arg: string) => String(v).split(arg),
|
||||||
'strip': (v) => String(v).trim(),
|
'strip': (v: string) => String(v).trim(),
|
||||||
'strip_newlines': v => String(v).replace(/\n/g, ''),
|
'strip_newlines': (v: string) => String(v).replace(/\n/g, ''),
|
||||||
'truncate': (v, l, o) => {
|
'truncate': (v: string, l: number = 16, o: string = '...') => {
|
||||||
v = String(v)
|
v = String(v)
|
||||||
o = (o === undefined) ? '...' : o
|
|
||||||
l = l || 16
|
|
||||||
if (v.length <= l) return v
|
if (v.length <= l) return v
|
||||||
return v.substr(0, l - o.length) + o
|
return v.substr(0, l - o.length) + o
|
||||||
},
|
},
|
||||||
'truncatewords': (v, l, o) => {
|
'truncatewords': (v: string, l: number = v.length, o: string = '...') => {
|
||||||
if (o === undefined) o = '...'
|
|
||||||
const arr = v.split(' ')
|
const arr = v.split(' ')
|
||||||
let ret = arr.slice(0, l).join(' ')
|
let ret = arr.slice(0, l).join(' ')
|
||||||
if (arr.length > l) ret += o
|
if (arr.length > l) ret += o
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
}
|
} as {[key: string]: FilterImpl}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default {
|
export default {
|
||||||
'url_decode': x => x.split('+').map(decodeURIComponent).join(' '),
|
'url_decode': (x: string) => x.split('+').map(decodeURIComponent).join(' '),
|
||||||
'url_encode': x => x.split(' ').map(encodeURIComponent).join('+')
|
'url_encode': (x: string) => x.split(' ').map(encodeURIComponent).join('+')
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import { identifier } from 'src/parser/lexical'
|
import { identifier } from 'src/parser/lexical'
|
||||||
import { AssignScope } from 'src/scope/scopes'
|
import { AssignScope } from 'src/scope/scopes'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
const re = new RegExp(`(${identifier.source})\\s*=([^]*)`)
|
const re = new RegExp(`(${identifier.source})\\s*=([^]*)`)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (token) {
|
parse: function (token: TagToken) {
|
||||||
const match = token.args.match(re)
|
const match = token.args.match(re) as RegExpMatchArray
|
||||||
assert(match, `illegal token ${token.raw}`)
|
assert(match, `illegal token ${token.raw}`)
|
||||||
this.key = match[1]
|
this.key = match[1]
|
||||||
this.value = match[2]
|
this.value = match[2]
|
||||||
},
|
},
|
||||||
render: function (scope) {
|
render: function (scope: Scope) {
|
||||||
const ctx = new AssignScope()
|
const ctx = new AssignScope()
|
||||||
ctx[this.key] = this.liquid.evalValue(this.value, scope)
|
ctx[this.key] = this.liquid.evalValue(this.value, scope)
|
||||||
scope.push(ctx)
|
scope.push(ctx)
|
||||||
return Promise.resolve('')
|
return Promise.resolve('')
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
import BlockMode from 'src/scope/block-mode'
|
import BlockMode from 'src/scope/block-mode'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (token, remainTokens) {
|
parse: function (token: TagToken, remainTokens: Token[]) {
|
||||||
const match = /\w+/.exec(token.args)
|
const match = /\w+/.exec(token.args)
|
||||||
this.block = match ? match[0] : ''
|
this.block = match ? match[0] : ''
|
||||||
|
this.tpls = [] as ITemplate[]
|
||||||
this.tpls = []
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
|
||||||
.on('tag:endblock', () => stream.stop())
|
.on('tag:endblock', () => stream.stop())
|
||||||
.on('template', tpl => this.tpls.push(tpl))
|
.on('template', (tpl: ITemplate) => this.tpls.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
throw new Error(`tag ${token.raw} not closed`)
|
throw new Error(`tag ${token.raw} not closed`)
|
||||||
})
|
})
|
||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
render: async function (scope) {
|
render: async function (scope: Scope) {
|
||||||
const childDefined = scope.blocks[this.block]
|
const childDefined = scope.blocks[this.block]
|
||||||
const html = childDefined !== undefined
|
const html = childDefined !== undefined
|
||||||
? childDefined
|
? childDefined
|
||||||
@@ -26,4 +31,4 @@ export default {
|
|||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { CaptureScope } from 'src/scope/scopes'
|
|||||||
import TagToken from 'src/parser/tag-token'
|
import TagToken from 'src/parser/tag-token'
|
||||||
import Token from 'src/parser/token'
|
import Token from 'src/parser/token'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
const re = new RegExp(`(${identifier.source})`)
|
const re = new RegExp(`(${identifier.source})`)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
const match = tagToken.args.match(re)
|
const match = tagToken.args.match(re) as RegExpMatchArray
|
||||||
assert(match, `${tagToken.args} not valid identifier`)
|
assert(match, `${tagToken.args} not valid identifier`)
|
||||||
|
|
||||||
this.variable = match[1]
|
this.variable = match[1]
|
||||||
@@ -17,7 +18,7 @@ export default {
|
|||||||
|
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||||
stream.on('tag:endcapture', () => stream.stop())
|
stream.on('tag:endcapture', () => stream.stop())
|
||||||
.on('template', tpl => this.templates.push(tpl))
|
.on('template', (tpl) => this.templates.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||||
})
|
})
|
||||||
@@ -29,4 +30,4 @@ export default {
|
|||||||
ctx[this.variable] = html
|
ctx[this.variable] = html
|
||||||
scope.push(ctx)
|
scope.push(ctx)
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { evalExp } from 'src/render/syntax'
|
import { evalExp } from 'src/render/syntax'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken, remainTokens) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
this.cond = tagToken.args
|
this.cond = tagToken.args
|
||||||
this.cases = []
|
this.cases = []
|
||||||
this.elseTemplates = []
|
this.elseTemplates = []
|
||||||
|
|
||||||
let p = []
|
let p: ITemplate[] = []
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('tag:when', token => {
|
.on('tag:when', (token: TagToken) => {
|
||||||
this.cases.push({
|
this.cases.push({
|
||||||
val: token.args,
|
val: token.args,
|
||||||
templates: p = []
|
templates: p = []
|
||||||
@@ -16,7 +22,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.on('tag:else', () => (p = this.elseTemplates))
|
.on('tag:else', () => (p = this.elseTemplates))
|
||||||
.on('tag:endcase', () => stream.stop())
|
.on('tag:endcase', () => stream.stop())
|
||||||
.on('template', tpl => p.push(tpl))
|
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||||
})
|
})
|
||||||
@@ -24,7 +30,7 @@ export default {
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function (scope) {
|
render: function (scope: Scope) {
|
||||||
for (let i = 0; i < this.cases.length; i++) {
|
for (let i = 0; i < this.cases.length; i++) {
|
||||||
const branch = this.cases[i]
|
const branch = this.cases[i]
|
||||||
const val = evalExp(branch.val, scope)
|
const val = evalExp(branch.val, scope)
|
||||||
@@ -35,4 +41,4 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import TagToken from "src/parser/tag-token";
|
||||||
|
import Token from "src/parser/token";
|
||||||
|
import ITagImplOptions from "src/template/tag/itag-impl-options";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken, remainTokens) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||||
stream
|
stream
|
||||||
.on('token', token => {
|
.on('token', (token: TagToken) => {
|
||||||
if (token.name === 'endcomment') stream.stop()
|
if (token.name === 'endcomment') stream.stop()
|
||||||
})
|
})
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
@@ -10,4 +14,4 @@ export default {
|
|||||||
})
|
})
|
||||||
stream.start()
|
stream.start()
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import { value as rValue } from 'src/parser/lexical'
|
import { value as rValue } from 'src/parser/lexical'
|
||||||
import { evalValue } from 'src/render/syntax'
|
import { evalValue } from 'src/render/syntax'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
const groupRE = new RegExp(`^(?:(${rValue.source})\\s*:\\s*)?(.*)$`)
|
const groupRE = new RegExp(`^(?:(${rValue.source})\\s*:\\s*)?(.*)$`)
|
||||||
const candidatesRE = new RegExp(rValue.source, 'g')
|
const candidatesRE = new RegExp(rValue.source, 'g')
|
||||||
|
|
||||||
export default {
|
export default <ITagImplOptions>{
|
||||||
parse: function (tagToken) {
|
parse: function (tagToken: TagToken) {
|
||||||
let match = groupRE.exec(tagToken.args)
|
let match: RegExpExecArray | null = groupRE.exec(tagToken.args) as RegExpExecArray
|
||||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||||
|
|
||||||
this.group = match[1] || ''
|
this.group = match[1] || ''
|
||||||
@@ -21,11 +24,10 @@ export default {
|
|||||||
assert(this.candidates.length, `empty candidates: ${tagToken.raw}`)
|
assert(this.candidates.length, `empty candidates: ${tagToken.raw}`)
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function (scope) {
|
render: function (scope: Scope) {
|
||||||
const group = evalValue(this.group, scope)
|
const group = evalValue(this.group, scope)
|
||||||
const fingerprint = `cycle:${group}:` + this.candidates.join(',')
|
const fingerprint = `cycle:${group}:` + this.candidates.join(',')
|
||||||
|
const groups = scope.groups
|
||||||
const groups = scope.opts.groups = scope.opts.groups || {}
|
|
||||||
let idx = groups[fingerprint]
|
let idx = groups[fingerprint]
|
||||||
|
|
||||||
if (idx === undefined) {
|
if (idx === undefined) {
|
||||||
@@ -38,4 +40,4 @@ export default {
|
|||||||
|
|
||||||
return evalValue(candidate, scope)
|
return evalValue(candidate, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,11 @@ import { identifier } from 'src/parser/lexical'
|
|||||||
import { CaptureScope, AssignScope, DecrementScope } from 'src/scope/scopes'
|
import { CaptureScope, AssignScope, DecrementScope } from 'src/scope/scopes'
|
||||||
import TagToken from 'src/parser/tag-token'
|
import TagToken from 'src/parser/tag-token'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (token: TagToken) {
|
parse: function (token: TagToken) {
|
||||||
const match = token.args.match(identifier)
|
const match = token.args.match(identifier) as RegExpMatchArray
|
||||||
assert(match, `illegal identifier ${token.args}`)
|
assert(match, `illegal identifier ${token.args}`)
|
||||||
this.variable = match[0]
|
this.variable = match[0]
|
||||||
},
|
},
|
||||||
@@ -26,4 +27,4 @@ export default {
|
|||||||
}
|
}
|
||||||
return --context[this.variable]
|
return --context[this.variable]
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
+85
-79
@@ -3,7 +3,13 @@ import { isString, isObject, isArray } from 'src/util/underscore'
|
|||||||
import { evalExp } from 'src/render/syntax'
|
import { evalExp } from 'src/render/syntax'
|
||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import { identifier, value, hash } from 'src/parser/lexical'
|
import { identifier, value, hash } from 'src/parser/lexical'
|
||||||
import { RenderBreakError } from 'src/util/error'
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import Hash from 'src/template/tag/hash';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
||||||
`(${value.source})` +
|
`(${value.source})` +
|
||||||
@@ -11,83 +17,83 @@ const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
|||||||
`(?:\\s+(reversed))?` +
|
`(?:\\s+(reversed))?` +
|
||||||
`(?:\\s+${hash.source})*$`)
|
`(?:\\s+${hash.source})*$`)
|
||||||
|
|
||||||
function parse (tagToken, remainTokens) {
|
export default <ITagImplOptions>{
|
||||||
const match = re.exec(tagToken.args)
|
type: 'block',
|
||||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
this.variable = match[1]
|
const match = re.exec(tagToken.args) as RegExpExecArray
|
||||||
this.collection = match[2]
|
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||||
this.reversed = !!match[3]
|
this.variable = match[1]
|
||||||
|
this.collection = match[2]
|
||||||
this.templates = []
|
this.reversed = !!match[3]
|
||||||
this.elseTemplates = []
|
|
||||||
|
this.templates = []
|
||||||
let p
|
this.elseTemplates = []
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
|
||||||
.on('start', () => (p = this.templates))
|
let p
|
||||||
.on('tag:else', () => (p = this.elseTemplates))
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('tag:endfor', () => stream.stop())
|
.on('start', () => (p = this.templates))
|
||||||
.on('template', tpl => p.push(tpl))
|
.on('tag:else', () => (p = this.elseTemplates))
|
||||||
.on('end', () => {
|
.on('tag:endfor', () => stream.stop())
|
||||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||||
|
.on('end', () => {
|
||||||
|
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.start()
|
||||||
|
},
|
||||||
|
render: async function (scope: Scope, hash: Hash) {
|
||||||
|
let collection = evalExp(this.collection, scope)
|
||||||
|
|
||||||
|
if (!isArray(collection)) {
|
||||||
|
if (isString(collection) && collection.length > 0) {
|
||||||
|
collection = [collection] as string[]
|
||||||
|
} else if (isObject(collection)) {
|
||||||
|
collection = Object.keys(collection).map((key) => [key, collection[key]]) as Array<[string, any]>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isArray(collection) || !collection.length) {
|
||||||
|
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = hash.offset || 0
|
||||||
|
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
||||||
|
|
||||||
|
collection = collection.slice(offset, offset + limit)
|
||||||
|
if (this.reversed) collection.reverse()
|
||||||
|
|
||||||
|
const contexts = collection.map((item: string, i: number) => {
|
||||||
|
const ctx = {}
|
||||||
|
ctx[this.variable] = item
|
||||||
|
ctx['forloop'] = {
|
||||||
|
first: i === 0,
|
||||||
|
index: i + 1,
|
||||||
|
index0: i,
|
||||||
|
last: i === collection.length - 1,
|
||||||
|
length: collection.length,
|
||||||
|
rindex: collection.length - i,
|
||||||
|
rindex0: collection.length - i - 1
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
})
|
})
|
||||||
|
|
||||||
stream.start()
|
let html = ''
|
||||||
}
|
let finished = false
|
||||||
|
await mapSeries(contexts, async context => {
|
||||||
async function render (scope, hash) {
|
if (finished) return
|
||||||
let collection = evalExp(this.collection, scope)
|
|
||||||
|
scope.push(context)
|
||||||
if (!isArray(collection)) {
|
try {
|
||||||
if (isString(collection) && collection.length > 0) {
|
html += await this.liquid.renderer.renderTemplates(this.templates, scope)
|
||||||
collection = [collection]
|
} catch (e) {
|
||||||
} else if (isObject(collection)) {
|
if (e.name === 'RenderBreakError') {
|
||||||
collection = Object.keys(collection).map((key) => [key, collection[key]])
|
html += e.resolvedHTML
|
||||||
}
|
if (e.message === 'break') {
|
||||||
|
finished = true
|
||||||
|
}
|
||||||
|
} else throw e
|
||||||
|
}
|
||||||
|
scope.pop(context)
|
||||||
|
})
|
||||||
|
return html
|
||||||
}
|
}
|
||||||
if (!isArray(collection) || !collection.length) {
|
}
|
||||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = hash.offset || 0
|
|
||||||
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
|
||||||
|
|
||||||
collection = collection.slice(offset, offset + limit)
|
|
||||||
if (this.reversed) collection.reverse()
|
|
||||||
|
|
||||||
const contexts = collection.map((item, i) => {
|
|
||||||
const ctx = {}
|
|
||||||
ctx[this.variable] = item
|
|
||||||
ctx['forloop'] = {
|
|
||||||
first: i === 0,
|
|
||||||
index: i + 1,
|
|
||||||
index0: i,
|
|
||||||
last: i === collection.length - 1,
|
|
||||||
length: collection.length,
|
|
||||||
rindex: collection.length - i,
|
|
||||||
rindex0: collection.length - i - 1
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
})
|
|
||||||
|
|
||||||
let html = ''
|
|
||||||
let finished = false
|
|
||||||
await mapSeries(contexts, async context => {
|
|
||||||
if (finished) return
|
|
||||||
|
|
||||||
scope.push(context)
|
|
||||||
try {
|
|
||||||
html += await this.liquid.renderer.renderTemplates(this.templates, scope)
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof RenderBreakError) {
|
|
||||||
html += e.resolvedHTML
|
|
||||||
if (e.message === 'break') {
|
|
||||||
finished = true
|
|
||||||
}
|
|
||||||
} else throw e
|
|
||||||
}
|
|
||||||
scope.pop(context)
|
|
||||||
})
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { parse, render }
|
|
||||||
+12
-6
@@ -1,17 +1,23 @@
|
|||||||
import { evalExp, isTruthy } from 'src/render/syntax'
|
import { evalExp, isTruthy } from 'src/render/syntax'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken, remainTokens) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
this.branches = []
|
this.branches = []
|
||||||
this.elseTemplates = []
|
this.elseTemplates = []
|
||||||
|
|
||||||
let p
|
let p
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('start', () => this.branches.push({
|
.on('start', () => this.branches.push({
|
||||||
cond: tagToken.args,
|
cond: tagToken.args,
|
||||||
templates: (p = [])
|
templates: (p = [])
|
||||||
}))
|
}))
|
||||||
.on('tag:elsif', token => {
|
.on('tag:elsif', (token: TagToken) => {
|
||||||
this.branches.push({
|
this.branches.push({
|
||||||
cond: token.args,
|
cond: token.args,
|
||||||
templates: p = []
|
templates: p = []
|
||||||
@@ -19,7 +25,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.on('tag:else', () => (p = this.elseTemplates))
|
.on('tag:else', () => (p = this.elseTemplates))
|
||||||
.on('tag:endif', () => stream.stop())
|
.on('tag:endif', () => stream.stop())
|
||||||
.on('template', tpl => p.push(tpl))
|
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||||
})
|
})
|
||||||
@@ -27,7 +33,7 @@ export default {
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function (scope) {
|
render: function (scope: Scope) {
|
||||||
for (const branch of this.branches) {
|
for (const branch of this.branches) {
|
||||||
const cond = evalExp(branch.cond, scope)
|
const cond = evalExp(branch.cond, scope)
|
||||||
if (isTruthy(cond)) {
|
if (isTruthy(cond)) {
|
||||||
@@ -36,4 +42,4 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
return this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import assert from 'src/util/assert'
|
|||||||
import { value, quotedLine } from 'src/parser/lexical'
|
import { value, quotedLine } from 'src/parser/lexical'
|
||||||
import { evalValue } from 'src/render/syntax'
|
import { evalValue } from 'src/render/syntax'
|
||||||
import BlockMode from 'src/scope/block-mode'
|
import BlockMode from 'src/scope/block-mode'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import Hash from 'src/template/tag/hash';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
const staticFileRE = /[^\s,]+/
|
const staticFileRE = /[^\s,]+/
|
||||||
const withRE = new RegExp(`with\\s+(${value.source})`)
|
const withRE = new RegExp(`with\\s+(${value.source})`)
|
||||||
|
|
||||||
export default {
|
export default <ITagImplOptions>{
|
||||||
parse: function (token) {
|
parse: function (token: TagToken) {
|
||||||
let match = staticFileRE.exec(token.args)
|
let match = staticFileRE.exec(token.args)
|
||||||
if (match) {
|
if (match) {
|
||||||
this.staticValue = match[0]
|
this.staticValue = match[0]
|
||||||
@@ -23,7 +27,7 @@ export default {
|
|||||||
this.with = match[1]
|
this.with = match[1]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: async function (scope, hash) {
|
render: async function (scope: Scope, hash: Hash) {
|
||||||
let filepath
|
let filepath
|
||||||
if (scope.opts.dynamicPartials) {
|
if (scope.opts.dynamicPartials) {
|
||||||
if (quotedLine.exec(this.value)) {
|
if (quotedLine.exec(this.value)) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import { identifier } from 'src/parser/lexical'
|
import { identifier } from 'src/parser/lexical'
|
||||||
import { CaptureScope, AssignScope, IncrementScope } from 'src/scope/scopes'
|
import { CaptureScope, AssignScope, IncrementScope } from 'src/scope/scopes'
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (token) {
|
parse: function (token) {
|
||||||
const match = token.args.match(identifier)
|
const match = token.args.match(identifier)
|
||||||
assert(match, `illegal identifier ${token.args}`)
|
assert(match, `illegal identifier ${token.args}`)
|
||||||
this.variable = match[0]
|
this.variable = match![0]
|
||||||
},
|
},
|
||||||
render: function (scope) {
|
render: function (scope) {
|
||||||
let context = scope.findContextFor(
|
let context = scope.findContextFor(
|
||||||
@@ -26,4 +27,4 @@ export default {
|
|||||||
context[this.variable]++
|
context[this.variable]++
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
@@ -2,11 +2,16 @@ import assert from 'src/util/assert'
|
|||||||
import { value as rValue } from 'src/parser/lexical'
|
import { value as rValue } from 'src/parser/lexical'
|
||||||
import { evalValue } from 'src/render/syntax'
|
import { evalValue } from 'src/render/syntax'
|
||||||
import BlockMode from 'src/scope/block-mode'
|
import BlockMode from 'src/scope/block-mode'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import Hash from 'src/template/tag/hash';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
|
||||||
const staticFileRE = /\S+/
|
const staticFileRE = /\S+/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (token, remainTokens) {
|
parse: function (token: TagToken, remainTokens: Token[]) {
|
||||||
let match = staticFileRE.exec(token.args)
|
let match = staticFileRE.exec(token.args)
|
||||||
if (match) {
|
if (match) {
|
||||||
this.staticLayout = match[0]
|
this.staticLayout = match[0]
|
||||||
@@ -19,7 +24,7 @@ export default {
|
|||||||
|
|
||||||
this.tpls = this.liquid.parser.parse(remainTokens)
|
this.tpls = this.liquid.parser.parse(remainTokens)
|
||||||
},
|
},
|
||||||
render: async function (scope, hash) {
|
render: async function (scope: Scope, hash: Hash) {
|
||||||
const layout = scope.opts.dynamicPartials
|
const layout = scope.opts.dynamicPartials
|
||||||
? evalValue(this.layout, scope)
|
? evalValue(this.layout, scope)
|
||||||
: this.staticLayout
|
: this.staticLayout
|
||||||
@@ -38,4 +43,4 @@ export default {
|
|||||||
scope.pop(hash)
|
scope.pop(hash)
|
||||||
return partial
|
return partial
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
export default {
|
import TagToken from "src/parser/tag-token";
|
||||||
parse: function (tagToken, remainTokens) {
|
import Token from "src/parser/token";
|
||||||
|
import ITagImplOptions from "src/template/tag/itag-impl-options";
|
||||||
|
|
||||||
|
export default <ITagImplOptions>{
|
||||||
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
this.tokens = []
|
this.tokens = []
|
||||||
|
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||||
stream
|
stream
|
||||||
.on('token', token => {
|
.on('token', (token: TagToken) => {
|
||||||
if (token.name === 'endraw') stream.stop()
|
if (token.name === 'endraw') stream.stop()
|
||||||
else this.tokens.push(token)
|
else this.tokens.push(token)
|
||||||
})
|
})
|
||||||
@@ -14,6 +18,6 @@ export default {
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
return this.tokens.map(token => token.raw).join('')
|
return this.tokens.map((token: Token) => token.raw).join('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,21 @@ import { mapSeries } from 'src/util/promise'
|
|||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import { evalExp } from 'src/render/syntax'
|
import { evalExp } from 'src/render/syntax'
|
||||||
import { identifier, value, hash } from 'src/parser/lexical'
|
import { identifier, value, hash } from 'src/parser/lexical'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import Hash from 'src/template/tag/hash';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
||||||
`(${value.source})` +
|
`(${value.source})` +
|
||||||
`(?:\\s+${hash.source})*$`)
|
`(?:\\s+${hash.source})*$`)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken, remainTokens) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
const match = re.exec(tagToken.args)
|
const match = re.exec(tagToken.args) as RegExpExecArray
|
||||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||||
|
|
||||||
this.variable = match[1]
|
this.variable = match[1]
|
||||||
@@ -17,10 +24,10 @@ export default {
|
|||||||
this.templates = []
|
this.templates = []
|
||||||
|
|
||||||
let p
|
let p
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('start', () => (p = this.templates))
|
.on('start', () => (p = this.templates))
|
||||||
.on('tag:endtablerow', () => stream.stop())
|
.on('tag:endtablerow', () => stream.stop())
|
||||||
.on('template', tpl => p.push(tpl))
|
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||||
})
|
})
|
||||||
@@ -28,20 +35,20 @@ export default {
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: async function (scope, hash) {
|
render: async function (scope: Scope, hash: Hash) {
|
||||||
let collection = evalExp(this.collection, scope) || []
|
let collection = evalExp(this.collection, scope) || []
|
||||||
const offset = hash.offset || 0
|
const offset = hash.offset || 0
|
||||||
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
||||||
|
|
||||||
collection = collection.slice(offset, offset + limit)
|
collection = collection.slice(offset, offset + limit)
|
||||||
const cols = hash.cols || collection.length
|
const cols = hash.cols || collection.length
|
||||||
const contexts = collection.map(item => {
|
const contexts = collection.map((item: any) => {
|
||||||
const ctx = {}
|
const ctx = {}
|
||||||
ctx[this.variable] = item
|
ctx[this.variable] = item
|
||||||
return ctx
|
return ctx
|
||||||
})
|
})
|
||||||
|
|
||||||
let row
|
let row: number = 0
|
||||||
let html = ''
|
let html = ''
|
||||||
await mapSeries(contexts, async (context, idx) => {
|
await mapSeries(contexts, async (context, idx) => {
|
||||||
row = Math.floor(idx / cols) + 1
|
row = Math.floor(idx / cols) + 1
|
||||||
@@ -65,4 +72,4 @@ export default {
|
|||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
import { evalExp, isFalsy } from 'src/render/syntax'
|
import { evalExp, isFalsy } from 'src/render/syntax'
|
||||||
|
import TagToken from 'src/parser/tag-token';
|
||||||
|
import Token from 'src/parser/token';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITagImplOptions from 'src/template/tag/itag-impl-options';
|
||||||
|
import ParseStream from 'src/parser/parse-stream';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
parse: function (tagToken, remainTokens) {
|
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||||
this.templates = []
|
this.templates = []
|
||||||
this.elseTemplates = []
|
this.elseTemplates = []
|
||||||
let p
|
let p
|
||||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||||
.on('start', () => {
|
.on('start', () => {
|
||||||
p = this.templates
|
p = this.templates
|
||||||
this.cond = tagToken.args
|
this.cond = tagToken.args
|
||||||
@@ -20,10 +26,10 @@ export default {
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function (scope) {
|
render: function (scope: Scope) {
|
||||||
const cond = evalExp(this.cond, scope)
|
const cond = evalExp(this.cond, scope)
|
||||||
return isFalsy(cond)
|
return isFalsy(cond)
|
||||||
? this.liquid.renderer.renderTemplates(this.templates, scope)
|
? this.liquid.renderer.renderTemplates(this.templates, scope)
|
||||||
: this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
: this.liquid.renderer.renderTemplates(this.elseTemplates, scope)
|
||||||
}
|
}
|
||||||
}
|
} as ITagImplOptions
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { last } from '../util/underscore'
|
import { last } from '../util/underscore'
|
||||||
import IFS from './ifs'
|
import IFS from './ifs'
|
||||||
|
|
||||||
function domResolve (root, path) {
|
function domResolve (root: string, path: string) {
|
||||||
const base = document.createElement('base')
|
const base = document.createElement('base')
|
||||||
base.href = root
|
base.href = root
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ function domResolve (root, path) {
|
|||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve (root, filepath, ext) {
|
function resolve (root: string, filepath: string, ext: string) {
|
||||||
if (root.length && last(root) !== '/') root += '/'
|
if (root.length && last(root) !== '/') root += '/'
|
||||||
const url = domResolve(root, filepath)
|
const url = domResolve(root, filepath)
|
||||||
return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => {
|
return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
export default interface IFS {
|
export default interface IFS {
|
||||||
exists: (filepath?: string) => Promise<boolean>
|
exists: (filepath: string) => Promise<boolean>
|
||||||
readFile: (filepath:string) => Promise<string>
|
readFile: (filepath:string) => Promise<string>
|
||||||
resolve: (root: string, file: string, ext: string) => string
|
resolve: (root: string, file: string, ext: string) => string
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -3,11 +3,11 @@ import { resolve, extname } from 'path'
|
|||||||
import { stat, readFile } from 'fs'
|
import { stat, readFile } from 'fs'
|
||||||
import IFS from './ifs'
|
import IFS from './ifs'
|
||||||
|
|
||||||
const statAsync = _.promisify(stat) as (filepath: string) => Promise<object>
|
const statAsync = _.promisify(stat)
|
||||||
const readFileAsync = _.promisify(readFile) as (filepath: string, encoding: string) => Promise<string>
|
const readFileAsync = _.promisify<string, string, string>(readFile)
|
||||||
|
|
||||||
const fs: IFS = {
|
const fs: IFS = {
|
||||||
exists: filepath => {
|
exists: (filepath: string) => {
|
||||||
return statAsync(filepath).then(() => true).catch(() => false)
|
return statAsync(filepath).then(() => true).catch(() => false)
|
||||||
},
|
},
|
||||||
readFile: filepath => {
|
readFile: filepath => {
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export interface NormalizedFullOptions extends NormalizedOptions {
|
|||||||
trim_tag_left: boolean
|
trim_tag_left: boolean
|
||||||
trim_output_right: boolean
|
trim_output_right: boolean
|
||||||
trim_output_left: boolean
|
trim_output_left: boolean
|
||||||
|
tag_delimiter_left: string,
|
||||||
|
tag_delimiter_right: string,
|
||||||
|
output_delimiter_left: string,
|
||||||
|
output_delimiter_right: string,
|
||||||
greedy: boolean
|
greedy: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ const defaultOptions: NormalizedFullOptions = {
|
|||||||
strict_variables: false
|
strict_variables: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalize (options: LiquidOptions): NormalizedOptions {
|
export function normalize (options?: LiquidOptions): NormalizedOptions {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
if (options.hasOwnProperty('root')) {
|
if (options.hasOwnProperty('root')) {
|
||||||
options.root = normalizeStringArray(options.root)
|
options.root = normalizeStringArray(options.root)
|
||||||
@@ -77,11 +81,11 @@ export function normalize (options: LiquidOptions): NormalizedOptions {
|
|||||||
return options as NormalizedOptions
|
return options as NormalizedOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyDefault (options: NormalizedOptions): NormalizedFullOptions {
|
export function applyDefault (options?: NormalizedOptions): NormalizedFullOptions {
|
||||||
return { ...defaultOptions, ...options }
|
return { ...defaultOptions, ...options }
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeStringArray (value: string | string[]): string[] {
|
function normalizeStringArray (value: any): string[] {
|
||||||
if (_.isArray(value)) return value as string[]
|
if (_.isArray(value)) return value as string[]
|
||||||
if (_.isString(value)) return [value as string]
|
if (_.isString(value)) return [value as string]
|
||||||
return []
|
return []
|
||||||
|
|||||||
+10
-12
@@ -6,7 +6,7 @@ import ITemplate from './template/itemplate'
|
|||||||
import Tokenizer from './parser/tokenizer'
|
import Tokenizer from './parser/tokenizer'
|
||||||
import Render from './render/render'
|
import Render from './render/render'
|
||||||
import Tag from './template/tag/tag'
|
import Tag from './template/tag/tag'
|
||||||
import Filter from './template/filter'
|
import Filter from './template/filter/filter'
|
||||||
import Parser from './parser/parser'
|
import Parser from './parser/parser'
|
||||||
import ITagImplOptions from './template/tag/itag-impl-options'
|
import ITagImplOptions from './template/tag/itag-impl-options'
|
||||||
import Value from './template/value'
|
import Value from './template/value'
|
||||||
@@ -14,19 +14,17 @@ import { isTruthy, isFalsy, evalExp, evalValue } from './render/syntax'
|
|||||||
import builtinTags from './builtin/tags'
|
import builtinTags from './builtin/tags'
|
||||||
import builtinFilters from './builtin/filters'
|
import builtinFilters from './builtin/filters'
|
||||||
import { LiquidOptions, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
|
import { LiquidOptions, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
|
||||||
|
import FilterImpl from './template/filter/filter-impl';
|
||||||
|
|
||||||
export default class Liquid {
|
export default class Liquid {
|
||||||
public options: NormalizedFullOptions
|
public options: NormalizedFullOptions
|
||||||
private cache: object
|
public renderer: Render
|
||||||
private parser: Parser
|
public parser: Parser
|
||||||
private renderer: Render
|
private cache: object = {}
|
||||||
private tokenizer: Tokenizer
|
private tokenizer: Tokenizer
|
||||||
|
|
||||||
constructor (opts: LiquidOptions = {}) {
|
constructor (opts: LiquidOptions = {}) {
|
||||||
this.options = applyDefault(normalize(opts))
|
this.options = applyDefault(normalize(opts))
|
||||||
if (this.options.cache) {
|
|
||||||
this.cache = {}
|
|
||||||
}
|
|
||||||
this.parser = new Parser(this)
|
this.parser = new Parser(this)
|
||||||
this.renderer = new Render()
|
this.renderer = new Render()
|
||||||
this.tokenizer = new Tokenizer(this.options)
|
this.tokenizer = new Tokenizer(this.options)
|
||||||
@@ -47,7 +45,7 @@ export default class Liquid {
|
|||||||
const tpl = await this.parse(html)
|
const tpl = await this.parse(html)
|
||||||
return this.render(tpl, ctx, opts)
|
return this.render(tpl, ctx, opts)
|
||||||
}
|
}
|
||||||
async getTemplate (file, opts?: LiquidOptions) {
|
async getTemplate (file: string, opts?: LiquidOptions) {
|
||||||
const options = normalize(opts)
|
const options = normalize(opts)
|
||||||
const roots = options.root ? [...options.root, ...this.options.root] : this.options.root
|
const roots = options.root ? [...options.root, ...this.options.root] : this.options.root
|
||||||
const paths = roots.map(root => fs.resolve(root, file, this.options.extname))
|
const paths = roots.map(root => fs.resolve(root, file, this.options.extname))
|
||||||
@@ -66,7 +64,7 @@ export default class Liquid {
|
|||||||
err.code = 'ENOENT'
|
err.code = 'ENOENT'
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
async renderFile (file, ctx?: object, opts?: LiquidOptions) {
|
async renderFile (file: string, ctx?: object, opts?: LiquidOptions) {
|
||||||
const options = normalize(opts)
|
const options = normalize(opts)
|
||||||
const templates = await this.getTemplate(file, options)
|
const templates = await this.getTemplate(file, options)
|
||||||
return this.render(templates, ctx, opts)
|
return this.render(templates, ctx, opts)
|
||||||
@@ -74,18 +72,18 @@ export default class Liquid {
|
|||||||
evalValue (str: string, scope: Scope) {
|
evalValue (str: string, scope: Scope) {
|
||||||
return new Value(str, this.options.strict_filters).value(scope)
|
return new Value(str, this.options.strict_filters).value(scope)
|
||||||
}
|
}
|
||||||
registerFilter (name, filter) {
|
registerFilter (name: string, filter: FilterImpl) {
|
||||||
return Filter.register(name, filter)
|
return Filter.register(name, filter)
|
||||||
}
|
}
|
||||||
registerTag (name: string, tag: ITagImplOptions) {
|
registerTag (name: string, tag: ITagImplOptions) {
|
||||||
return Tag.register(name, tag)
|
return Tag.register(name, tag)
|
||||||
}
|
}
|
||||||
plugin (plugin) {
|
plugin (plugin: (this: Liquid, L: typeof Liquid) => void) {
|
||||||
return plugin.call(this, Liquid)
|
return plugin.call(this, Liquid)
|
||||||
}
|
}
|
||||||
express () {
|
express () {
|
||||||
const self = this
|
const self = this
|
||||||
return function (filePath: string, ctx: object, cb: (err: Error, html?: string) => void) {
|
return function (this: any, filePath: string, ctx: object, cb: (err: Error | null, html?: string) => void) {
|
||||||
const opts = { root: this.root }
|
const opts = { root: this.root }
|
||||||
self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb)
|
self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import Token from './token'
|
import Token from './token'
|
||||||
|
import { last } from 'src/util/underscore'
|
||||||
|
|
||||||
export default class DelimitedToken extends Token {
|
export default class DelimitedToken extends Token {
|
||||||
trimLeft: boolean
|
trimLeft: boolean
|
||||||
trimRight: boolean
|
trimRight: boolean
|
||||||
constructor (raw, value, pos, input, file, line) {
|
constructor (raw: string, value: string, input: string, line: number, pos: number, file?: string) {
|
||||||
super(raw, pos, input, file, line)
|
super(raw, input, line, pos, file)
|
||||||
this.trimLeft = value[0] === '-'
|
this.trimLeft = value[0] === '-'
|
||||||
this.trimRight = value[value.length - 1] === '-'
|
this.trimRight = last(value) === '-'
|
||||||
this.value = value
|
this.value = value
|
||||||
.slice(
|
.slice(
|
||||||
this.trimLeft ? 1 : 0,
|
this.trimLeft ? 1 : 0,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Token from './token'
|
import Token from './token'
|
||||||
|
|
||||||
export default class HTMLToken extends Token {
|
export default class HTMLToken extends Token {
|
||||||
constructor (str, begin, input, file, line) {
|
constructor (str: string, input: string, line: number, col: number, file?: string) {
|
||||||
super(str, begin, input, file, line)
|
super(str, input, line, col, file)
|
||||||
this.type = 'html'
|
this.type = 'html'
|
||||||
this.value = str
|
this.value = str
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,27 +49,27 @@ export const operators = [
|
|||||||
/==|!=|<=|>=|<|>|\s+contains\s+/
|
/==|!=|<=|>=|<|>|\s+contains\s+/
|
||||||
]
|
]
|
||||||
|
|
||||||
export function isInteger (str) {
|
export function isInteger (str: string) {
|
||||||
return integerLine.test(str)
|
return integerLine.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLiteral (str) {
|
export function isLiteral (str: string) {
|
||||||
return literalLine.test(str)
|
return literalLine.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRange (str) {
|
export function isRange (str: string) {
|
||||||
return rangeLine.test(str)
|
return rangeLine.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVariable (str) {
|
export function isVariable (str: string) {
|
||||||
return variableLine.test(str)
|
return variableLine.test(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchValue (str) {
|
export function matchValue (str: string) {
|
||||||
return value.exec(str)
|
return value.exec(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseLiteral (str) {
|
export function parseLiteral (str: string) {
|
||||||
let res = str.match(numberLine)
|
let res = str.match(numberLine)
|
||||||
if (res) {
|
if (res) {
|
||||||
return Number(str)
|
return Number(str)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import DelimitedToken from './delimited-token'
|
import DelimitedToken from './delimited-token'
|
||||||
|
|
||||||
export default class OutputToken extends DelimitedToken {
|
export default class OutputToken extends DelimitedToken {
|
||||||
constructor (raw, value, pos, input, file, line) {
|
constructor (raw: string, value: string, input: string, line: number, pos: number, file?: string) {
|
||||||
super(raw, value, pos, input, file, line)
|
super(raw, value, input, line, pos, file)
|
||||||
this.type = 'output'
|
this.type = 'output'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -1,37 +1,37 @@
|
|||||||
import Token from 'src/parser/token'
|
import Token from 'src/parser/token'
|
||||||
import ITemplate from 'src/template/itemplate'
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
import TagToken from './tag-token';
|
||||||
|
|
||||||
type parseToken = (token: Token, remainTokens: Array<Token>) => ITemplate
|
type ParseToken = ((token: Token, remainTokens: Array<Token>) => ITemplate)
|
||||||
type eventHandler = ((arg?: Token | ITemplate) => void)
|
|
||||||
|
|
||||||
export default class ParseStream {
|
export default class ParseStream {
|
||||||
private tokens: Array<Token>
|
private tokens: Array<Token>
|
||||||
private handlers: {[key: string]: eventHandler} = {}
|
private handlers: {[key: string]: (arg: any) => void} = {}
|
||||||
private stopRequested: boolean
|
private stopRequested: boolean = false
|
||||||
private parseToken: parseToken
|
private parseToken: ParseToken
|
||||||
|
|
||||||
constructor (tokens: Array<Token>, parseToken: parseToken) {
|
constructor (tokens: Array<Token>, parseToken: ParseToken) {
|
||||||
this.tokens = tokens
|
this.tokens = tokens
|
||||||
this.parseToken = parseToken
|
this.parseToken = parseToken
|
||||||
}
|
}
|
||||||
on (name: string, cb: eventHandler) {
|
on<T extends ITemplate | Token | undefined> (name: string, cb: (arg: T) => void): ParseStream {
|
||||||
this.handlers[name] = cb
|
this.handlers[name] = cb
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
trigger (event: string, arg?: Token | ITemplate) {
|
trigger <T extends Token | ITemplate>(event: string, arg?: T) {
|
||||||
const h = this.handlers[event]
|
const h = this.handlers[event]
|
||||||
if (typeof h === 'function') {
|
if (typeof h === 'function') {
|
||||||
h(arg)
|
h(arg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
start () {
|
start () {
|
||||||
this.trigger('start')
|
this.trigger('start')
|
||||||
let token
|
let token: Token | undefined
|
||||||
while (!this.stopRequested && (token = this.tokens.shift())) {
|
while (!this.stopRequested && (token = this.tokens.shift())) {
|
||||||
if (this.trigger('token', token)) continue
|
if (this.trigger('token', token)) continue
|
||||||
if (token.type === 'tag' &&
|
if (token.type === 'tag' && this.trigger(`tag:${(<TagToken>token).name}`, token)) {
|
||||||
this.trigger(`tag:${token.name}`, token)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const template = this.parseToken(token, this.tokens)
|
const template = this.parseToken(token, this.tokens)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import OutputToken from './output-token'
|
|||||||
import Tag from 'src/template/tag/tag'
|
import Tag from 'src/template/tag/tag'
|
||||||
import Output from 'src/template/output'
|
import Output from 'src/template/output'
|
||||||
import HTML from 'src/template/html'
|
import HTML from 'src/template/html'
|
||||||
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
|
||||||
export default class Parser {
|
export default class Parser {
|
||||||
liquid: Liquid
|
liquid: Liquid
|
||||||
@@ -16,7 +17,7 @@ export default class Parser {
|
|||||||
}
|
}
|
||||||
parse (tokens: Array<Token>) {
|
parse (tokens: Array<Token>) {
|
||||||
let token
|
let token
|
||||||
const templates = []
|
const templates: ITemplate[] = []
|
||||||
while ((token = tokens.shift())) {
|
while ((token = tokens.shift())) {
|
||||||
templates.push(this.parseToken(token, tokens))
|
templates.push(this.parseToken(token, tokens))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import * as lexical from './lexical'
|
|||||||
export default class TagToken extends DelimitedToken {
|
export default class TagToken extends DelimitedToken {
|
||||||
name: string
|
name: string
|
||||||
args: string
|
args: string
|
||||||
constructor (raw, value, pos, input, file, line) {
|
constructor (raw: string, value: string, input: string, line: number, pos: number, file?: string) {
|
||||||
super(raw, value, pos, input, file, line)
|
super(raw, value, input, line, pos, file)
|
||||||
this.type = 'tag'
|
this.type = 'tag'
|
||||||
const match = this.value.match(lexical.tagLine)
|
const match = this.value.match(lexical.tagLine)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
|||||||
+4
-3
@@ -1,15 +1,16 @@
|
|||||||
export default class Token {
|
export default class Token {
|
||||||
type: string
|
type: string = 'notset'
|
||||||
line: number
|
line: number
|
||||||
col: number
|
col: number
|
||||||
raw: string
|
raw: string
|
||||||
input: string
|
input: string
|
||||||
file: string
|
file?: string
|
||||||
value: string
|
value: string
|
||||||
constructor (raw, col, input, file, line) {
|
constructor (raw: string, input: string, line: number, col: number, file?: string) {
|
||||||
this.col = col
|
this.col = col
|
||||||
this.line = line
|
this.line = line
|
||||||
this.raw = raw
|
this.raw = raw
|
||||||
|
this.value = raw
|
||||||
this.input = input
|
this.input = input
|
||||||
this.file = file
|
this.file = file
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default class Tokenizer {
|
|||||||
this.options = applyDefault(options)
|
this.options = applyDefault(options)
|
||||||
}
|
}
|
||||||
tokenize (input: string, file?: string) {
|
tokenize (input: string, file?: string) {
|
||||||
const tokens = []
|
const tokens: Token[] = []
|
||||||
const tagL = this.options.tag_delimiter_left
|
const tagL = this.options.tag_delimiter_left
|
||||||
const tagR = this.options.tag_delimiter_right
|
const tagR = this.options.tag_delimiter_right
|
||||||
const outputL = this.options.output_delimiter_left
|
const outputL = this.options.output_delimiter_left
|
||||||
@@ -34,7 +34,7 @@ export default class Tokenizer {
|
|||||||
}
|
}
|
||||||
if (state === ParseState.HTML) {
|
if (state === ParseState.HTML) {
|
||||||
if (input.substr(p, outputL.length) === outputL) {
|
if (input.substr(p, outputL.length) === outputL) {
|
||||||
if (buffer) tokens.push(new HTMLToken(buffer, col, input, file, line))
|
if (buffer) tokens.push(new HTMLToken(buffer, input, line, col, file))
|
||||||
buffer = outputL
|
buffer = outputL
|
||||||
line = curLine
|
line = curLine
|
||||||
col = p - lineBegin + 1
|
col = p - lineBegin + 1
|
||||||
@@ -42,7 +42,7 @@ export default class Tokenizer {
|
|||||||
state = ParseState.OUTPUT
|
state = ParseState.OUTPUT
|
||||||
continue
|
continue
|
||||||
} else if (input.substr(p, tagL.length) === tagL) {
|
} else if (input.substr(p, tagL.length) === tagL) {
|
||||||
if (buffer) tokens.push(new HTMLToken(buffer, col, input, file, line))
|
if (buffer) tokens.push(new HTMLToken(buffer, input, line, col, file))
|
||||||
buffer = tagL
|
buffer = tagL
|
||||||
line = curLine
|
line = curLine
|
||||||
col = p - lineBegin + 1
|
col = p - lineBegin + 1
|
||||||
@@ -52,7 +52,7 @@ export default class Tokenizer {
|
|||||||
}
|
}
|
||||||
} else if (state === ParseState.OUTPUT && input.substr(p, outputR.length) === outputR) {
|
} else if (state === ParseState.OUTPUT && input.substr(p, outputR.length) === outputR) {
|
||||||
buffer += outputR
|
buffer += outputR
|
||||||
tokens.push(new OutputToken(buffer, buffer.slice(outputL.length, -outputR.length), col, input, file, line))
|
tokens.push(new OutputToken(buffer, buffer.slice(outputL.length, -outputR.length), input, line, col, file))
|
||||||
p += outputR.length
|
p += outputR.length
|
||||||
buffer = ''
|
buffer = ''
|
||||||
line = curLine
|
line = curLine
|
||||||
@@ -61,7 +61,7 @@ export default class Tokenizer {
|
|||||||
continue
|
continue
|
||||||
} else if (input.substr(p, tagR.length) === tagR) {
|
} else if (input.substr(p, tagR.length) === tagR) {
|
||||||
buffer += tagR
|
buffer += tagR
|
||||||
tokens.push(new TagToken(buffer, buffer.slice(tagL.length, -tagR.length), col, input, file, line))
|
tokens.push(new TagToken(buffer, buffer.slice(tagL.length, -tagR.length), input, line, col, file))
|
||||||
p += tagR.length
|
p += tagR.length
|
||||||
buffer = ''
|
buffer = ''
|
||||||
line = curLine
|
line = curLine
|
||||||
@@ -75,11 +75,11 @@ export default class Tokenizer {
|
|||||||
const t = state === ParseState.OUTPUT ? 'output' : 'tag'
|
const t = state === ParseState.OUTPUT ? 'output' : 'tag'
|
||||||
const str = buffer.length > 16 ? buffer.slice(0, 13) + '...' : buffer
|
const str = buffer.length > 16 ? buffer.slice(0, 13) + '...' : buffer
|
||||||
throw new TokenizationError(
|
throw new TokenizationError(
|
||||||
new Error(`${t} "${str}" not closed`),
|
`${t} "${str}" not closed`,
|
||||||
new Token(buffer, col, input, file, line)
|
new Token(buffer, input, line, col, file)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (buffer) tokens.push(new HTMLToken(buffer, col, input, file, line))
|
if (buffer) tokens.push(new HTMLToken(buffer, input, line, col, file))
|
||||||
|
|
||||||
whiteSpaceCtrl(tokens, this.options)
|
whiteSpaceCtrl(tokens, this.options)
|
||||||
return tokens
|
return tokens
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ export default function whiteSpaceCtrl (tokens: Token[], options: NormalizedFull
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldTrimLeft (token: DelimitedToken, inRaw: boolean, options) {
|
function shouldTrimLeft (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) {
|
||||||
if (inRaw) return false
|
if (inRaw) return false
|
||||||
if (token.type === 'tag') return token.trimLeft || options.trim_tag_left
|
if (token.type === 'tag') return token.trimLeft || options.trim_tag_left
|
||||||
if (token.type === 'output') return token.trimLeft || options.trim_output_left
|
if (token.type === 'output') return token.trimLeft || options.trim_output_left
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldTrimRight (token: DelimitedToken, inRaw: boolean, options) {
|
function shouldTrimRight (token: DelimitedToken, inRaw: boolean, options: NormalizedFullOptions) {
|
||||||
if (inRaw) return false
|
if (inRaw) return false
|
||||||
if (token.type === 'tag') return token.trimRight || options.trim_tag_right
|
if (token.type === 'tag') return token.trimRight || options.trim_tag_right
|
||||||
if (token.type === 'output') return token.trimRight || options.trim_output_right
|
if (token.type === 'output') return token.trimRight || options.trim_output_right
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { RenderBreakError, RenderError } from 'src/util/error'
|
import { RenderBreakError, RenderError } from 'src/util/error'
|
||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
import ITemplate from 'src/template/itemplate';
|
||||||
|
|
||||||
export default class Render {
|
export default class Render {
|
||||||
async renderTemplates (templates, scope) {
|
async renderTemplates (templates: ITemplate[], scope: Scope) {
|
||||||
assert(scope, 'unable to evalTemplates: scope undefined')
|
assert(scope, 'unable to evalTemplates: scope undefined')
|
||||||
|
|
||||||
let html = ''
|
let html = ''
|
||||||
@@ -10,11 +12,11 @@ export default class Render {
|
|||||||
try {
|
try {
|
||||||
html += await tpl.render(scope)
|
html += await tpl.render(scope)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof RenderBreakError) {
|
if (e.name === 'RenderBreakError') {
|
||||||
e.resolvedHTML = html
|
e.resolvedHTML = html
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
throw e instanceof RenderError ? e : new RenderError(e, tpl)
|
throw e.name === 'RenderError' ? e : new RenderError(e, tpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
|
|||||||
+16
-18
@@ -1,23 +1,25 @@
|
|||||||
import * as lexical from '../parser/lexical'
|
import * as lexical from '../parser/lexical'
|
||||||
import assert from '../util/assert'
|
import assert from '../util/assert'
|
||||||
|
import Scope from 'src/scope/scope'
|
||||||
|
import { range } from 'src/util/underscore'
|
||||||
|
|
||||||
const operators = {
|
const operators = {
|
||||||
'==': (l, r) => l === r,
|
'==': (l: any, r: any) => l === r,
|
||||||
'!=': (l, r) => l !== r,
|
'!=': (l: any, r: any) => l !== r,
|
||||||
'>': (l, r) => l !== null && r !== null && l > r,
|
'>': (l: any, r: any) => l !== null && r !== null && l > r,
|
||||||
'<': (l, r) => l !== null && r !== null && l < r,
|
'<': (l: any, r: any) => l !== null && r !== null && l < r,
|
||||||
'>=': (l, r) => l !== null && r !== null && l >= r,
|
'>=': (l: any, r: any) => l !== null && r !== null && l >= r,
|
||||||
'<=': (l, r) => l !== null && r !== null && l <= r,
|
'<=': (l: any, r: any) => l !== null && r !== null && l <= r,
|
||||||
'contains': (l, r) => {
|
'contains': (l: any, r: any) => {
|
||||||
if (!l) return false
|
if (!l) return false
|
||||||
if (typeof l.indexOf !== 'function') return false
|
if (typeof l.indexOf !== 'function') return false
|
||||||
return l.indexOf(r) > -1
|
return l.indexOf(r) > -1
|
||||||
},
|
},
|
||||||
'and': (l, r) => isTruthy(l) && isTruthy(r),
|
'and': (l: any, r: any) => isTruthy(l) && isTruthy(r),
|
||||||
'or': (l, r) => isTruthy(l) || isTruthy(r)
|
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalExp (exp, scope) {
|
export function evalExp (exp: string, scope: Scope): any {
|
||||||
assert(scope, 'unable to evalExp: scope undefined')
|
assert(scope, 'unable to evalExp: scope undefined')
|
||||||
const operatorREs = lexical.operators
|
const operatorREs = lexical.operators
|
||||||
let match
|
let match
|
||||||
@@ -35,17 +37,13 @@ export function evalExp (exp, scope) {
|
|||||||
if ((match = exp.match(lexical.rangeLine))) {
|
if ((match = exp.match(lexical.rangeLine))) {
|
||||||
const low = evalValue(match[1], scope)
|
const low = evalValue(match[1], scope)
|
||||||
const high = evalValue(match[2], scope)
|
const high = evalValue(match[2], scope)
|
||||||
const range = []
|
return range(low, high + 1)
|
||||||
for (let j = low; j <= high; j++) {
|
|
||||||
range.push(j)
|
|
||||||
}
|
|
||||||
return range
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return evalValue(exp, scope)
|
return evalValue(exp, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalValue (str, scope) {
|
export function evalValue (str: string, scope: Scope) {
|
||||||
str = str && str.trim()
|
str = str && str.trim()
|
||||||
if (!str) return undefined
|
if (!str) return undefined
|
||||||
|
|
||||||
@@ -58,10 +56,10 @@ export function evalValue (str, scope) {
|
|||||||
throw new TypeError(`cannot eval '${str}' as value`)
|
throw new TypeError(`cannot eval '${str}' as value`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTruthy (val) {
|
export function isTruthy (val: any): boolean {
|
||||||
return !isFalsy(val)
|
return !isFalsy(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFalsy (val) {
|
export function isFalsy (val: any): boolean {
|
||||||
return val === false || undefined === val || val === null
|
return val === false || undefined === val || val === null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export default interface IContext {
|
||||||
|
[key: string]: any;
|
||||||
|
liquid_method_missing?: (key: string) => any;
|
||||||
|
}
|
||||||
+14
-10
@@ -1,20 +1,23 @@
|
|||||||
import * as _ from '../util/underscore'
|
import * as _ from '../util/underscore'
|
||||||
|
import { __assign } from 'tslib'
|
||||||
import * as lexical from '../parser/lexical'
|
import * as lexical from '../parser/lexical'
|
||||||
import assert from '../util/assert'
|
import assert from '../util/assert'
|
||||||
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
|
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
|
||||||
import BlockMode from './block-mode'
|
import BlockMode from './block-mode'
|
||||||
|
import IContext from './icontext';
|
||||||
|
|
||||||
export default class Scope {
|
export default class Scope {
|
||||||
opts: NormalizedFullOptions
|
opts: NormalizedFullOptions
|
||||||
contexts: Array<object>
|
contexts: Array<IContext>
|
||||||
blocks: object = {}
|
blocks: object = {}
|
||||||
|
groups: {[key: string]: number} = {}
|
||||||
blockMode: BlockMode = BlockMode.OUTPUT
|
blockMode: BlockMode = BlockMode.OUTPUT
|
||||||
constructor (ctx: object = {}, opts?: NormalizedFullOptions) {
|
constructor (ctx: object = {}, opts?: NormalizedFullOptions) {
|
||||||
this.opts = applyDefault(opts)
|
this.opts = applyDefault(opts)
|
||||||
this.contexts = [ctx || {}]
|
this.contexts = [ctx || {}]
|
||||||
}
|
}
|
||||||
getAll () {
|
getAll () {
|
||||||
return this.contexts.reduce((ctx, val) => _.assign(ctx, val), _.create(null))
|
return this.contexts.reduce((ctx, val) => __assign(ctx, val), {})
|
||||||
}
|
}
|
||||||
get (path: string): any {
|
get (path: string): any {
|
||||||
const paths = this.propertyAccessSeq(path)
|
const paths = this.propertyAccessSeq(path)
|
||||||
@@ -36,6 +39,7 @@ export default class Scope {
|
|||||||
scope[key] = {}
|
scope[key] = {}
|
||||||
}
|
}
|
||||||
scope = scope[key]
|
scope = scope[key]
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
unshift (ctx: object) {
|
unshift (ctx: object) {
|
||||||
@@ -44,7 +48,7 @@ export default class Scope {
|
|||||||
push (ctx: object) {
|
push (ctx: object) {
|
||||||
return this.contexts.push(ctx)
|
return this.contexts.push(ctx)
|
||||||
}
|
}
|
||||||
pop (ctx?: object): object {
|
pop (ctx?: object): object | undefined {
|
||||||
if (!arguments.length) {
|
if (!arguments.length) {
|
||||||
return this.contexts.pop()
|
return this.contexts.pop()
|
||||||
}
|
}
|
||||||
@@ -64,7 +68,7 @@ export default class Scope {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
readProperty (obj, key) {
|
readProperty (obj: IContext, key: string) {
|
||||||
let val
|
let val
|
||||||
if (_.isNil(obj)) {
|
if (_.isNil(obj)) {
|
||||||
val = undefined
|
val = undefined
|
||||||
@@ -72,7 +76,7 @@ export default class Scope {
|
|||||||
obj = toLiquid(obj)
|
obj = toLiquid(obj)
|
||||||
val = key === 'size' ? readSize(obj) : obj[key]
|
val = key === 'size' ? readSize(obj) : obj[key]
|
||||||
if (_.isFunction(obj.liquid_method_missing)) {
|
if (_.isFunction(obj.liquid_method_missing)) {
|
||||||
val = obj.liquid_method_missing(key)
|
val = obj.liquid_method_missing!(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_.isNil(val) && this.opts.strict_variables) {
|
if (_.isNil(val) && this.opts.strict_variables) {
|
||||||
@@ -89,9 +93,9 @@ export default class Scope {
|
|||||||
* accessSeq("foo['b]r']") // ['foo', 'b]r']
|
* accessSeq("foo['b]r']") // ['foo', 'b]r']
|
||||||
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
||||||
*/
|
*/
|
||||||
propertyAccessSeq (str) {
|
propertyAccessSeq (str: string) {
|
||||||
str = String(str)
|
str = String(str)
|
||||||
const seq = []
|
const seq: string[] = []
|
||||||
let name = ''
|
let name = ''
|
||||||
let j
|
let j
|
||||||
let i = 0
|
let i = 0
|
||||||
@@ -141,7 +145,7 @@ export default class Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toLiquid (obj) {
|
function toLiquid (obj: IContext) {
|
||||||
if (_.isFunction(obj.to_liquid)) {
|
if (_.isFunction(obj.to_liquid)) {
|
||||||
return obj.to_liquid()
|
return obj.to_liquid()
|
||||||
}
|
}
|
||||||
@@ -151,13 +155,13 @@ function toLiquid (obj) {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSize (obj) {
|
function readSize (obj: IContext) {
|
||||||
if (!_.isNil(obj.size)) return obj.size
|
if (!_.isNil(obj.size)) return obj.size
|
||||||
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
||||||
return obj.size
|
return obj.size
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchRightBracket (str, begin) {
|
function matchRightBracket (str: string, begin: number) {
|
||||||
let stack = 1 // count of '[' - count of ']'
|
let stack = 1 // count of '[' - count of ']'
|
||||||
for (let i = begin; i < str.length; i++) {
|
for (let i = begin; i < str.length; i++) {
|
||||||
if (str[i] === '[') {
|
if (str[i] === '[') {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
type FilterImpl = (value: any, ...args: any[]) => any
|
||||||
|
|
||||||
|
export default FilterImpl
|
||||||
@@ -2,19 +2,18 @@ import assert from 'src/util/assert'
|
|||||||
import * as lexical from 'src/parser/lexical'
|
import * as lexical from 'src/parser/lexical'
|
||||||
import { evalValue } from 'src/render/syntax'
|
import { evalValue } from 'src/render/syntax'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
import FilterImpl from './filter-impl'
|
||||||
type impl = (value: any, ...args: any[]) => any
|
|
||||||
|
|
||||||
const valueRE = new RegExp(`${lexical.value.source}`, 'g')
|
const valueRE = new RegExp(`${lexical.value.source}`, 'g')
|
||||||
|
|
||||||
export default class Filter {
|
export default class Filter {
|
||||||
name: string
|
name: string
|
||||||
impl: impl
|
impl: FilterImpl
|
||||||
args: string[]
|
args: string[]
|
||||||
private static impls: {[key: string]: impl} = {}
|
private static impls: {[key: string]: FilterImpl} = {}
|
||||||
|
|
||||||
constructor (str: string, strictFilters: boolean = false) {
|
constructor (str: string, strictFilters: boolean = false) {
|
||||||
const match = lexical.filterLine.exec(str)
|
const match = lexical.filterLine.exec(str) as string[]
|
||||||
assert(match, 'illegal filter: ' + str)
|
assert(match, 'illegal filter: ' + str)
|
||||||
|
|
||||||
const name = match[1]
|
const name = match[1]
|
||||||
@@ -27,7 +26,7 @@ export default class Filter {
|
|||||||
this.args = this.parseArgs(argList)
|
this.args = this.parseArgs(argList)
|
||||||
}
|
}
|
||||||
parseArgs (argList: string): string[] {
|
parseArgs (argList: string): string[] {
|
||||||
let match; const args = []
|
let match; const args: string[] = []
|
||||||
while ((match = valueRE.exec(argList.trim()))) {
|
while ((match = valueRE.exec(argList.trim()))) {
|
||||||
const v = match[0]
|
const v = match[0]
|
||||||
const re = new RegExp(`${v}\\s*:`, 'g')
|
const re = new RegExp(`${v}\\s*:`, 'g')
|
||||||
@@ -39,10 +38,9 @@ export default class Filter {
|
|||||||
}
|
}
|
||||||
render (value: any, scope: Scope): any {
|
render (value: any, scope: Scope): any {
|
||||||
const args = this.args.map(arg => evalValue(arg, scope))
|
const args = this.args.map(arg => evalValue(arg, scope))
|
||||||
args.unshift(value)
|
return this.impl.apply(null, [value, ...args])
|
||||||
return this.impl.apply(null, args)
|
|
||||||
}
|
}
|
||||||
static register (name, filter) {
|
static register (name: string, filter: FilterImpl) {
|
||||||
Filter.impls[name] = filter
|
Filter.impls[name] = filter
|
||||||
}
|
}
|
||||||
static clear () {
|
static clear () {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import Template from 'src/template/template'
|
import Template from 'src/template/template'
|
||||||
import ITemplate from 'src/template/itemplate'
|
import ITemplate from 'src/template/itemplate'
|
||||||
import Token from 'src/parser/token'
|
import HTMLToken from 'src/parser/html-token'
|
||||||
|
|
||||||
export default class extends Template implements ITemplate {
|
export default class extends Template<HTMLToken> implements ITemplate {
|
||||||
str: string
|
str: string
|
||||||
constructor (token: Token) {
|
constructor (token: HTMLToken) {
|
||||||
super(token)
|
super(token)
|
||||||
this.str = token.value
|
this.str = token.value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ITemplate from 'src/template/itemplate'
|
|||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
import OutputToken from 'src/parser/output-token'
|
import OutputToken from 'src/parser/output-token'
|
||||||
|
|
||||||
export default class Output extends Template implements ITemplate {
|
export default class Output extends Template<OutputToken> implements ITemplate {
|
||||||
value: Value
|
value: Value
|
||||||
constructor (token: OutputToken, strictFilters?: boolean) {
|
constructor (token: OutputToken, strictFilters?: boolean) {
|
||||||
super(token)
|
super(token)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { create, stringify } from 'src/util/underscore'
|
||||||
|
import assert from 'src/util/assert'
|
||||||
|
import Scope from 'src/scope/scope'
|
||||||
|
import ITagImpl from './itag-impl'
|
||||||
|
import ITagImplOptions from './itag-impl-options'
|
||||||
|
import Liquid from 'src/liquid'
|
||||||
|
import Hash from './hash'
|
||||||
|
import Template from 'src/template/template'
|
||||||
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
import TagToken from 'src/parser/tag-token'
|
||||||
|
import Token from 'src/parser/token'
|
||||||
|
|
||||||
|
export default class Tag extends Template<TagToken> implements ITemplate {
|
||||||
|
name: string
|
||||||
|
private impl: ITagImpl
|
||||||
|
static impls: { [key: string]: ITagImplOptions } = {}
|
||||||
|
|
||||||
|
constructor (token: TagToken, tokens: Token[], liquid: Liquid) {
|
||||||
|
super(token)
|
||||||
|
this.name = token.name
|
||||||
|
|
||||||
|
const impl = Tag.impls[token.name]
|
||||||
|
assert(impl, `tag ${token.name} not found`)
|
||||||
|
this.impl = create<ITagImplOptions, ITagImpl>(impl)
|
||||||
|
this.impl.liquid = liquid
|
||||||
|
if (this.impl.parse) {
|
||||||
|
this.impl.parse(token, tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async render (scope: Scope) {
|
||||||
|
const hash = new Hash(this.token.args, scope)
|
||||||
|
const impl = this.impl
|
||||||
|
if (typeof impl.render !== 'function') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const html = await impl.render(scope, hash)
|
||||||
|
return stringify(html)
|
||||||
|
}
|
||||||
|
static register (name: string, tag: ITagImplOptions) {
|
||||||
|
Tag.impls[name] = tag
|
||||||
|
}
|
||||||
|
static clear () {
|
||||||
|
Tag.impls = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { hashCapture } from 'src/parser/lexical'
|
import { hashCapture } from 'src/parser/lexical'
|
||||||
import { evalValue } from 'src/render/syntax'
|
import { evalValue } from 'src/render/syntax'
|
||||||
|
import Scope from 'src/scope/scope';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key-Value Pairs Representing Tag Arguments
|
* Key-Value Pairs Representing Tag Arguments
|
||||||
@@ -9,7 +10,7 @@ import { evalValue } from 'src/render/syntax'
|
|||||||
*/
|
*/
|
||||||
export default class Hash {
|
export default class Hash {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
constructor (markup, scope) {
|
constructor (markup: string, scope: Scope) {
|
||||||
let match
|
let match
|
||||||
hashCapture.lastIndex = 0
|
hashCapture.lastIndex = 0
|
||||||
while ((match = hashCapture.exec(markup))) {
|
while ((match = hashCapture.exec(markup))) {
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ import Liquid from 'src/liquid'
|
|||||||
import ITagImplOptions from './itag-impl-options'
|
import ITagImplOptions from './itag-impl-options'
|
||||||
|
|
||||||
export default interface ITagImpl extends ITagImplOptions {
|
export default interface ITagImpl extends ITagImplOptions {
|
||||||
liquid: Liquid
|
liquid: Liquid,
|
||||||
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ import ITemplate from 'src/template/itemplate'
|
|||||||
import TagToken from 'src/parser/tag-token'
|
import TagToken from 'src/parser/tag-token'
|
||||||
import Token from 'src/parser/token'
|
import Token from 'src/parser/token'
|
||||||
|
|
||||||
export default class Tag extends Template implements ITemplate {
|
export default class Tag extends Template<TagToken> implements ITemplate {
|
||||||
name: string
|
name: string
|
||||||
token: TagToken
|
|
||||||
private impl: ITagImpl
|
private impl: ITagImpl
|
||||||
static impls: { [key: string]: ITagImplOptions } = {}
|
static impls: { [key: string]: ITagImplOptions } = {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import Token from 'src/parser/token'
|
export default abstract class Template<T> {
|
||||||
|
token: T;
|
||||||
export default class Template {
|
constructor (token: T) {
|
||||||
token: Token;
|
|
||||||
constructor (token) {
|
|
||||||
this.token = token
|
this.token = token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
import { evalExp } from 'src/render/syntax'
|
import { evalExp } from 'src/render/syntax'
|
||||||
import * as lexical from 'src/parser/lexical'
|
import * as lexical from 'src/parser/lexical'
|
||||||
import assert from 'src/util/assert'
|
import assert from 'src/util/assert'
|
||||||
import Filter from './filter'
|
import Filter from './filter/filter'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
initial: any
|
initial: any
|
||||||
filters: Array<any>
|
filters: Array<Filter> = []
|
||||||
constructor (str: string, strictFilters?: boolean) {
|
constructor (str: string, strictFilters?: boolean) {
|
||||||
let match = lexical.matchValue(str)
|
let match: RegExpExecArray | null = lexical.matchValue(str) as RegExpExecArray
|
||||||
assert(match, `illegal value string: ${str}`)
|
assert(match, `illegal value string: ${str}`)
|
||||||
|
|
||||||
const initial = match[0]
|
this.initial = match[0]
|
||||||
str = str.substr(match.index + match[0].length)
|
str = str.substr(match.index + match[0].length)
|
||||||
|
|
||||||
const filters = []
|
|
||||||
while ((match = lexical.filter.exec(str))) {
|
while ((match = lexical.filter.exec(str))) {
|
||||||
filters.push([match[0].trim()])
|
this.filters.push(new Filter(match[0].trim(), strictFilters))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initial = initial
|
|
||||||
this.filters = filters.map(str => new Filter(str, strictFilters))
|
|
||||||
}
|
}
|
||||||
value (scope: Scope) {
|
value (scope: Scope) {
|
||||||
return this.filters.reduce(
|
return this.filters.reduce(
|
||||||
|
|||||||
+35
-61
@@ -1,98 +1,77 @@
|
|||||||
import * as _ from './underscore'
|
import * as _ from './underscore'
|
||||||
|
import { __extends } from 'tslib'
|
||||||
import Token from 'src/parser/token'
|
import Token from 'src/parser/token'
|
||||||
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
|
||||||
function captureStack () {
|
abstract class LiquidError extends Error {
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
Error.captureStackTrace(this, this.constructor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class LiquidError {
|
|
||||||
name: string
|
|
||||||
message: string
|
|
||||||
stack: string
|
|
||||||
private file: string
|
|
||||||
private input: string
|
|
||||||
private token: Token
|
private token: Token
|
||||||
private originalError: Error
|
private originalError: Error
|
||||||
constructor (err, token) {
|
constructor (err: Error, token: Token) {
|
||||||
this.input = token.input
|
super(err.message)
|
||||||
this.file = token.file
|
|
||||||
this.originalError = err
|
this.originalError = err
|
||||||
this.token = token
|
this.token = token
|
||||||
}
|
}
|
||||||
captureStackTrace (obj) {
|
protected update() {
|
||||||
this.name = obj.constructor.name
|
|
||||||
|
|
||||||
captureStack.call(obj)
|
|
||||||
const err = this.originalError
|
const err = this.originalError
|
||||||
const context = mkContext(this.input, this.token.line)
|
const context = mkContext(this.token)
|
||||||
this.message = mkMessage(err.message, this.token)
|
this.message = mkMessage(err.message, this.token)
|
||||||
this.stack = this.message + '\n' + context +
|
this.stack = this.message + '\n' + context +
|
||||||
'\n' + (this.stack || this.message) +
|
'\n' + this.stack + '\nFrom ' + err.stack
|
||||||
(err.stack ? '\nFrom ' + err.stack : '')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TokenizationError extends LiquidError {
|
export class TokenizationError extends LiquidError {
|
||||||
constructor (message, token) {
|
constructor (message: string, token: Token) {
|
||||||
super({ message }, token)
|
super(new Error(message), token)
|
||||||
super.captureStackTrace(this)
|
this.name = 'TokenizationError'
|
||||||
|
super.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenizationError.prototype = _.create(Error.prototype) as any
|
|
||||||
TokenizationError.prototype.constructor = TokenizationError
|
|
||||||
|
|
||||||
export class ParseError extends LiquidError {
|
export class ParseError extends LiquidError {
|
||||||
constructor (err, token) {
|
constructor (err: Error, token: Token) {
|
||||||
super(err, token)
|
super(err, token)
|
||||||
|
this.name = 'ParseError'
|
||||||
this.message = err.message
|
this.message = err.message
|
||||||
super.captureStackTrace(this)
|
super.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParseError.prototype = _.create(Error.prototype) as any
|
|
||||||
ParseError.prototype.constructor = ParseError
|
|
||||||
|
|
||||||
export class RenderError extends LiquidError {
|
export class RenderError extends LiquidError {
|
||||||
constructor (err, tpl) {
|
constructor (err: Error, tpl: ITemplate) {
|
||||||
super(err, tpl.token)
|
super(err, tpl.token)
|
||||||
|
this.name = 'RenderError'
|
||||||
this.message = err.message
|
this.message = err.message
|
||||||
super.captureStackTrace(this)
|
super.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RenderError.prototype = _.create(Error.prototype) as any
|
|
||||||
RenderError.prototype.constructor = RenderError
|
|
||||||
|
|
||||||
export class RenderBreakError {
|
export class RenderBreakError extends Error {
|
||||||
message: string
|
resolvedHTML: string = ''
|
||||||
resolvedHTML: string
|
constructor (message: string) {
|
||||||
constructor (message) {
|
super(message)
|
||||||
captureStack.call(this)
|
this.name = 'RenderBreakError'
|
||||||
this.message = message + ''
|
this.message = message + ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RenderBreakError.prototype = _.create(Error.prototype) as any
|
|
||||||
RenderBreakError.prototype.constructor = RenderBreakError
|
|
||||||
|
|
||||||
export class AssertionError {
|
export class AssertionError extends Error {
|
||||||
message: string
|
constructor (message: string) {
|
||||||
constructor (message) {
|
super(message)
|
||||||
captureStack.call(this)
|
this.name = 'AssertionError'
|
||||||
this.message = message + ''
|
this.message = message + ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertionError.prototype = _.create(Error.prototype) as any
|
|
||||||
AssertionError.prototype.constructor = AssertionError
|
|
||||||
|
|
||||||
function mkContext (input, targetLine) {
|
function mkContext (token: Token) {
|
||||||
const lines = input.split('\n')
|
const lines = token.input.split('\n')
|
||||||
const begin = Math.max(targetLine - 2, 1)
|
const begin = Math.max(token.line - 2, 1)
|
||||||
const end = Math.min(targetLine + 3, lines.length)
|
const end = Math.min(token.line + 3, lines.length)
|
||||||
|
|
||||||
const context = _
|
const context = _
|
||||||
.range(begin, end + 1)
|
.range(begin, end + 1)
|
||||||
.map(lineNumber => {
|
.map(lineNumber => {
|
||||||
const indicator = (lineNumber === targetLine) ? '>> ' : ' '
|
const indicator = (lineNumber === token.line) ? '>> ' : ' '
|
||||||
const num = _.padStart(String(lineNumber), String(end).length)
|
const num = _.padStart(String(lineNumber), String(end).length)
|
||||||
const text = lines[lineNumber - 1]
|
const text = lines[lineNumber - 1]
|
||||||
return `${indicator}${num}| ${text}`
|
return `${indicator}${num}| ${text}`
|
||||||
@@ -102,13 +81,8 @@ function mkContext (input, targetLine) {
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkMessage (msg, token) {
|
function mkMessage (msg: string, token: Token) {
|
||||||
msg = msg || ''
|
if (token.file) msg += `, file:${token.file}`
|
||||||
if (token.file) {
|
msg += `, line:${token.line}, col:${token.col}`
|
||||||
msg += ', file:' + token.file
|
|
||||||
}
|
|
||||||
if (token.line) {
|
|
||||||
msg += `, line:${token.line}, col:${token.col}`
|
|
||||||
}
|
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-17
@@ -1,26 +1,15 @@
|
|||||||
/*
|
|
||||||
* Call functions in serial until someone resolved.
|
|
||||||
* @param iterable the array to iterate with.
|
|
||||||
* @param iteratee returns a new promise.
|
|
||||||
* The iteratee is invoked with three arguments: (value, index, iterable).
|
|
||||||
*/
|
|
||||||
export function anySeries (iterable, iteratee) {
|
|
||||||
let ret: Promise<any> = Promise.reject(new Error('init'))
|
|
||||||
iterable.forEach(function (item, idx) {
|
|
||||||
ret = ret.catch(() => iteratee(item, idx, iterable))
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Call functions in serial until someone rejected.
|
* Call functions in serial until someone rejected.
|
||||||
* @param {Array} iterable the array to iterate with.
|
* @param {Array} iterable the array to iterate with.
|
||||||
* @param {Array} iteratee returns a new promise.
|
* @param {Array} iteratee returns a new promise.
|
||||||
* The iteratee is invoked with three arguments: (value, index, iterable).
|
* The iteratee is invoked with three arguments: (value, index, iterable).
|
||||||
*/
|
*/
|
||||||
export function mapSeries (iterable, iteratee) {
|
export function mapSeries<T1, T2> (
|
||||||
let ret: Promise<any> = Promise.resolve('init')
|
iterable: T1[],
|
||||||
const result = []
|
iteratee: (item: T1, idx: number, iterable: T1[]) => Promise<T2> | T2
|
||||||
|
): Promise<T2[]> {
|
||||||
|
let ret = Promise.resolve(0)
|
||||||
|
const result: T2[] = []
|
||||||
iterable.forEach(function (item, idx) {
|
iterable.forEach(function (item, idx) {
|
||||||
ret = ret
|
ret = ret
|
||||||
.then(() => iteratee(item, idx, iterable))
|
.then(() => iteratee(item, idx, iterable))
|
||||||
|
|||||||
+39
-39
@@ -16,18 +16,18 @@ const suffixes = {
|
|||||||
'default': 'th'
|
'default': 'th'
|
||||||
}
|
}
|
||||||
|
|
||||||
function abbr (str) {
|
function abbr (str: string) {
|
||||||
return str.slice(0, 3)
|
return str.slice(0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prototype extensions
|
// prototype extensions
|
||||||
const _date = {
|
const _date = {
|
||||||
daysInMonth: function (d) {
|
daysInMonth: function (d: Date) {
|
||||||
const feb = _date.isLeapYear(d) ? 29 : 28
|
const feb = _date.isLeapYear(d) ? 29 : 28
|
||||||
return [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
return [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
},
|
},
|
||||||
|
|
||||||
getDayOfYear: function (d) {
|
getDayOfYear: function (d: Date) {
|
||||||
let num = 0
|
let num = 0
|
||||||
for (let i = 0; i < d.getMonth(); ++i) {
|
for (let i = 0; i < d.getMonth(); ++i) {
|
||||||
num += _date.daysInMonth(d)[i]
|
num += _date.daysInMonth(d)[i]
|
||||||
@@ -35,7 +35,7 @@ const _date = {
|
|||||||
return num + d.getDate()
|
return num + d.getDate()
|
||||||
},
|
},
|
||||||
|
|
||||||
getWeekOfYear: function (d, startDay) {
|
getWeekOfYear: function (d: Date, startDay: number) {
|
||||||
// Skip to startDay of this week
|
// Skip to startDay of this week
|
||||||
const now = this.getDayOfYear(d) + (startDay - d.getDay())
|
const now = this.getDayOfYear(d) + (startDay - d.getDay())
|
||||||
// Find the first startDay of the year
|
// Find the first startDay of the year
|
||||||
@@ -44,111 +44,111 @@ const _date = {
|
|||||||
return padStart(String(Math.floor((now - then) / 7) + 1), 2, '0')
|
return padStart(String(Math.floor((now - then) / 7) + 1), 2, '0')
|
||||||
},
|
},
|
||||||
|
|
||||||
isLeapYear: function (d) {
|
isLeapYear: function (d: Date) {
|
||||||
const year = d.getFullYear()
|
const year = d.getFullYear()
|
||||||
return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)))
|
return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)))
|
||||||
},
|
},
|
||||||
|
|
||||||
getSuffix: function (d) {
|
getSuffix: function (d: Date) {
|
||||||
const str = d.getDate().toString()
|
const str = d.getDate().toString()
|
||||||
const index = parseInt(str.slice(-1))
|
const index = parseInt(str.slice(-1))
|
||||||
return suffixes[index] || suffixes['default']
|
return suffixes[index] || suffixes['default']
|
||||||
},
|
},
|
||||||
|
|
||||||
century: function (d) {
|
century: function (d: Date) {
|
||||||
return parseInt(d.getFullYear().toString().substring(0, 2), 10)
|
return parseInt(d.getFullYear().toString().substring(0, 2), 10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatCodes = {
|
const formatCodes = {
|
||||||
a: function (d) {
|
a: function (d: Date) {
|
||||||
return dayNamesShort[d.getDay()]
|
return dayNamesShort[d.getDay()]
|
||||||
},
|
},
|
||||||
A: function (d) {
|
A: function (d: Date) {
|
||||||
return dayNames[d.getDay()]
|
return dayNames[d.getDay()]
|
||||||
},
|
},
|
||||||
b: function (d) {
|
b: function (d: Date) {
|
||||||
return monthNamesShort[d.getMonth()]
|
return monthNamesShort[d.getMonth()]
|
||||||
},
|
},
|
||||||
B: function (d) {
|
B: function (d: Date) {
|
||||||
return monthNames[d.getMonth()]
|
return monthNames[d.getMonth()]
|
||||||
},
|
},
|
||||||
c: function (d) {
|
c: function (d: Date) {
|
||||||
return d.toLocaleString()
|
return d.toLocaleString()
|
||||||
},
|
},
|
||||||
C: function (d) {
|
C: function (d: Date) {
|
||||||
return _date.century(d)
|
return _date.century(d)
|
||||||
},
|
},
|
||||||
d: function (d) {
|
d: function (d: Date) {
|
||||||
return padStart(d.getDate(), 2, '0')
|
return padStart(d.getDate(), 2, '0')
|
||||||
},
|
},
|
||||||
e: function (d) {
|
e: function (d: Date) {
|
||||||
return padStart(d.getDate(), 2)
|
return padStart(d.getDate(), 2)
|
||||||
},
|
},
|
||||||
H: function (d) {
|
H: function (d: Date) {
|
||||||
return padStart(d.getHours(), 2, '0')
|
return padStart(d.getHours(), 2, '0')
|
||||||
},
|
},
|
||||||
I: function (d) {
|
I: function (d: Date) {
|
||||||
return padStart(String(d.getHours() % 12 || 12), 2, '0')
|
return padStart(String(d.getHours() % 12 || 12), 2, '0')
|
||||||
},
|
},
|
||||||
j: function (d) {
|
j: function (d: Date) {
|
||||||
return padStart(_date.getDayOfYear(d), 3, '0')
|
return padStart(_date.getDayOfYear(d), 3, '0')
|
||||||
},
|
},
|
||||||
k: function (d) {
|
k: function (d: Date) {
|
||||||
return padStart(d.getHours(), 2)
|
return padStart(d.getHours(), 2)
|
||||||
},
|
},
|
||||||
l: function (d) {
|
l: function (d: Date) {
|
||||||
return padStart(String(d.getHours() % 12 || 12), 2)
|
return padStart(String(d.getHours() % 12 || 12), 2)
|
||||||
},
|
},
|
||||||
L: function (d) {
|
L: function (d: Date) {
|
||||||
return padStart(d.getMilliseconds(), 3, '0')
|
return padStart(d.getMilliseconds(), 3, '0')
|
||||||
},
|
},
|
||||||
m: function (d) {
|
m: function (d: Date) {
|
||||||
return padStart(d.getMonth() + 1, 2, '0')
|
return padStart(d.getMonth() + 1, 2, '0')
|
||||||
},
|
},
|
||||||
M: function (d) {
|
M: function (d: Date) {
|
||||||
return padStart(d.getMinutes(), 2, '0')
|
return padStart(d.getMinutes(), 2, '0')
|
||||||
},
|
},
|
||||||
p: function (d) {
|
p: function (d: Date) {
|
||||||
return (d.getHours() < 12 ? 'AM' : 'PM')
|
return (d.getHours() < 12 ? 'AM' : 'PM')
|
||||||
},
|
},
|
||||||
P: function (d) {
|
P: function (d: Date) {
|
||||||
return (d.getHours() < 12 ? 'am' : 'pm')
|
return (d.getHours() < 12 ? 'am' : 'pm')
|
||||||
},
|
},
|
||||||
q: function (d) {
|
q: function (d: Date) {
|
||||||
return _date.getSuffix(d)
|
return _date.getSuffix(d)
|
||||||
},
|
},
|
||||||
s: function (d) {
|
s: function (d: Date) {
|
||||||
return Math.round(d.valueOf() / 1000)
|
return Math.round(d.valueOf() / 1000)
|
||||||
},
|
},
|
||||||
S: function (d) {
|
S: function (d: Date) {
|
||||||
return padStart(d.getSeconds(), 2, '0')
|
return padStart(d.getSeconds(), 2, '0')
|
||||||
},
|
},
|
||||||
u: function (d) {
|
u: function (d: Date) {
|
||||||
return d.getDay() || 7
|
return d.getDay() || 7
|
||||||
},
|
},
|
||||||
U: function (d) {
|
U: function (d: Date) {
|
||||||
return _date.getWeekOfYear(d, 0)
|
return _date.getWeekOfYear(d, 0)
|
||||||
},
|
},
|
||||||
w: function (d) {
|
w: function (d: Date) {
|
||||||
return d.getDay()
|
return d.getDay()
|
||||||
},
|
},
|
||||||
W: function (d) {
|
W: function (d: Date) {
|
||||||
return _date.getWeekOfYear(d, 1)
|
return _date.getWeekOfYear(d, 1)
|
||||||
},
|
},
|
||||||
x: function (d) {
|
x: function (d: Date) {
|
||||||
return d.toLocaleDateString()
|
return d.toLocaleDateString()
|
||||||
},
|
},
|
||||||
X: function (d) {
|
X: function (d: Date) {
|
||||||
return d.toLocaleTimeString()
|
return d.toLocaleTimeString()
|
||||||
},
|
},
|
||||||
y: function (d) {
|
y: function (d: Date) {
|
||||||
return d.getFullYear().toString().substring(2, 4)
|
return d.getFullYear().toString().substring(2, 4)
|
||||||
},
|
},
|
||||||
Y: function (d) {
|
Y: function (d: Date) {
|
||||||
return d.getFullYear()
|
return d.getFullYear()
|
||||||
},
|
},
|
||||||
z: function (d) {
|
z: function (d: Date) {
|
||||||
const tz = d.getTimezoneOffset() / 60 * 100
|
const tz = d.getTimezoneOffset() / 60 * 100
|
||||||
return (tz > 0 ? '-' : '+') + padStart(String(Math.abs(tz)), 4, '0')
|
return (tz > 0 ? '-' : '+') + padStart(String(Math.abs(tz)), 4, '0')
|
||||||
},
|
},
|
||||||
@@ -159,7 +159,7 @@ const formatCodes = {
|
|||||||
(formatCodes as any).h = formatCodes.b;
|
(formatCodes as any).h = formatCodes.b;
|
||||||
(formatCodes as any).N = formatCodes.L
|
(formatCodes as any).N = formatCodes.L
|
||||||
|
|
||||||
export default function (d, format) {
|
export default function (d: Date, format: string) {
|
||||||
let output = ''
|
let output = ''
|
||||||
let remaining = format
|
let remaining = format
|
||||||
|
|
||||||
@@ -179,6 +179,6 @@ export default function (d, format) {
|
|||||||
// Add the format code
|
// Add the format code
|
||||||
const ch = results[0].charAt(1)
|
const ch = results[0].charAt(1)
|
||||||
const func = formatCodes[ch]
|
const func = formatCodes[ch]
|
||||||
output += func ? func.call(this, d) : '%' + ch
|
output += func ? func.call(null, d) : '%' + ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-43
@@ -14,10 +14,12 @@ export function isFunction (value: any) {
|
|||||||
return typeof value === 'function'
|
return typeof value === 'function'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function promisify (fn) {
|
export function promisify<T1, T2> (fn: (arg1: T1, cb: (err: Error | null, result: T2) => void) => void): (arg1: T1) => Promise<T2>;
|
||||||
return function (...args) {
|
export function promisify<T1, T2, T3> (fn: (arg1: T1, arg2: T2, cb: (err: Error | null, result: T3) => void) => void):(arg1: T1, arg2: T2) => Promise<T3>;
|
||||||
|
export function promisify (fn: any) {
|
||||||
|
return function (...args: any[]) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fn(...args, (err, result) => {
|
fn(...args, (err: Error, result: any) => {
|
||||||
err ? reject(err) : resolve(result)
|
err ? reject(err) : resolve(result)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -35,7 +37,7 @@ export function stringify (value: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultToString (value: any): string {
|
function defaultToString (value: any): string {
|
||||||
const cache = []
|
const cache: string[] = []
|
||||||
return JSON.stringify(value, (key, value) => {
|
return JSON.stringify(value, (key, value) => {
|
||||||
if (isObject(value)) {
|
if (isObject(value)) {
|
||||||
if (cache.indexOf(value) !== -1) {
|
if (cache.indexOf(value) !== -1) {
|
||||||
@@ -75,7 +77,10 @@ export function isError (value: any): boolean {
|
|||||||
* @param {Function} iteratee The function invoked per iteration.
|
* @param {Function} iteratee The function invoked per iteration.
|
||||||
* @return {Object} Returns object.
|
* @return {Object} Returns object.
|
||||||
*/
|
*/
|
||||||
export function forOwn (object, iteratee: ((val: any, key: string, obj: object) => boolean | void)) {
|
export function forOwn <T>(
|
||||||
|
object: {[key: string]: T} | undefined,
|
||||||
|
iteratee: ((val: T, key: string, obj: {[key: string]: T}) => boolean | void)
|
||||||
|
) {
|
||||||
object = object || {}
|
object = object || {}
|
||||||
for (const k in object) {
|
for (const k in object) {
|
||||||
if (object.hasOwnProperty(k)) {
|
if (object.hasOwnProperty(k)) {
|
||||||
@@ -85,45 +90,12 @@ export function forOwn (object, iteratee: ((val: any, key: string, obj: object)
|
|||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
export function last <T>(arr: T[]): T;
|
||||||
* Assigns own enumerable string keyed properties of source objects to the destination object.
|
export function last (arr: string): string;
|
||||||
* Source objects are applied from left to right.
|
export function last (arr: any[] | string): any | string {
|
||||||
* Subsequent sources overwrite property assignments of previous sources.
|
|
||||||
*
|
|
||||||
* Note: This method mutates object and is loosely based on Object.assign.
|
|
||||||
*
|
|
||||||
* @param {Object} object The destination object.
|
|
||||||
* @param {...Object} sources The source objects.
|
|
||||||
* @return {Object} Returns object.
|
|
||||||
*/
|
|
||||||
export function assign (obj: object, ...srcs: object[]): object {
|
|
||||||
obj = isObject(obj) ? obj : {}
|
|
||||||
srcs.forEach(src => binaryAssign(obj, src))
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
function binaryAssign (target: object, src: object): object {
|
|
||||||
for (const key in src) if (src.hasOwnProperty(key)) target[key] = src[key]
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
export function last (arr: any[]): any {
|
|
||||||
return arr[arr.length - 1]
|
return arr[arr.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uniq (arr: any[]): any[] {
|
|
||||||
const u = {}
|
|
||||||
const a = []
|
|
||||||
for (let i = 0, l = arr.length; i < l; ++i) {
|
|
||||||
if (u.hasOwnProperty(arr[i])) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
a.push(arr[i])
|
|
||||||
u[arr[i]] = 1
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Checks if value is the language type of Object.
|
* Checks if value is the language type of Object.
|
||||||
* (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
|
* (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
|
||||||
@@ -144,13 +116,13 @@ export function isObject (value: any): boolean {
|
|||||||
* negative — if you'd like a negative range, use a negative step.
|
* negative — if you'd like a negative range, use a negative step.
|
||||||
*/
|
*/
|
||||||
export function range (start: number, stop?: number, step?: number) {
|
export function range (start: number, stop?: number, step?: number) {
|
||||||
if (arguments.length === 1) {
|
if (stop === undefined) {
|
||||||
stop = start
|
stop = start
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
step = step || 1
|
step = step || 1
|
||||||
|
|
||||||
const arr = []
|
const arr: number[] = []
|
||||||
for (let i = start; i < stop; i += step) {
|
for (let i = start; i < stop; i += step) {
|
||||||
arr.push(i)
|
arr.push(i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { expect } from 'chai'
|
|||||||
import Liquid from '../..'
|
import Liquid from '../..'
|
||||||
|
|
||||||
describe('.evalValue()', function () {
|
describe('.evalValue()', function () {
|
||||||
var engine
|
var engine: Liquid
|
||||||
beforeEach(() => { engine = new Liquid() })
|
beforeEach(() => { engine = new Liquid() })
|
||||||
|
|
||||||
it('should throw when scope undefined', function () {
|
it('should throw when scope undefined', function () {
|
||||||
expect(() => engine.evalValue('{{"foo"}}')).to.throw(/scope undefined/)
|
expect(() => engine.evalValue('{{"foo"}}', null as any)).to.throw(/scope undefined/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ describe('express()', function () {
|
|||||||
const root = resolve(__dirname, '../stub/root')
|
const root = resolve(__dirname, '../stub/root')
|
||||||
const views = resolve(__dirname, '../stub/views')
|
const views = resolve(__dirname, '../stub/views')
|
||||||
const partials = resolve(__dirname, '../stub/partials')
|
const partials = resolve(__dirname, '../stub/partials')
|
||||||
let app, engine
|
let app: express.Application, engine: Liquid
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
app = express()
|
app = express()
|
||||||
@@ -45,7 +45,7 @@ describe('express()', function () {
|
|||||||
}
|
}
|
||||||
const file = '/not-exist.html'
|
const file = '/not-exist.html'
|
||||||
const ctx = {}
|
const ctx = {}
|
||||||
engine.express().call(view, file, ctx, function (err) {
|
engine.express().call(view, file, ctx, function (err: any) {
|
||||||
try {
|
try {
|
||||||
expect(err.code).to.equal('ENOENT')
|
expect(err.code).to.equal('ENOENT')
|
||||||
expect(err.message).to.match(/Failed to lookup/)
|
expect(err.message).to.match(/Failed to lookup/)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as chaiAsPromised from 'chai-as-promised'
|
|||||||
use(chaiAsPromised)
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('.parseAndRender()', function () {
|
describe('.parseAndRender()', function () {
|
||||||
var engine, strictEngine
|
var engine: Liquid, strictEngine: Liquid
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
engine = new Liquid()
|
engine = new Liquid()
|
||||||
strictEngine = new Liquid({
|
strictEngine = new Liquid({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use(chaiAsPromised)
|
|||||||
describe('#renderFile()', function () {
|
describe('#renderFile()', function () {
|
||||||
const root = resolve(__dirname, '../stub/root')
|
const root = resolve(__dirname, '../stub/root')
|
||||||
const views = resolve(__dirname, '../stub/views')
|
const views = resolve(__dirname, '../stub/views')
|
||||||
let engine
|
let engine: Liquid
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
root,
|
root,
|
||||||
|
|||||||
+6
-6
@@ -1,5 +1,5 @@
|
|||||||
import Liquid from '../../dist/liquid.js'
|
import Liquid from '../../dist/liquid.js'
|
||||||
import { createFakeServer, useFakeXMLHttpRequest } from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import { expect, use } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import { JSDOM } from 'jsdom'
|
import { JSDOM } from 'jsdom'
|
||||||
import * as chaiAsPromised from 'chai-as-promised'
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
@@ -7,13 +7,13 @@ import * as chaiAsPromised from 'chai-as-promised'
|
|||||||
use(chaiAsPromised)
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('xhr', () => {
|
describe('xhr', () => {
|
||||||
if (+process.version.match(/^v(\d+)/)[1] < 8) {
|
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
|
||||||
console.info('jsdom not supported, skipping xhr...')
|
console.info('jsdom not supported, skipping xhr...')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let server, engine
|
let server: sinon.SinonFakeServer, engine: Liquid
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
server = createFakeServer()
|
server = sinon.fakeServer.create()
|
||||||
server.autoRespond = true
|
server.autoRespond = true
|
||||||
server.respondWith('GET', 'https://example.com/views/hello.html',
|
server.respondWith('GET', 'https://example.com/views/hello.html',
|
||||||
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
|
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}'])
|
||||||
@@ -22,7 +22,7 @@ describe('xhr', () => {
|
|||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
includeNodeLocations: true
|
includeNodeLocations: true
|
||||||
});
|
});
|
||||||
(global as any).XMLHttpRequest = useFakeXMLHttpRequest();
|
(global as any).XMLHttpRequest = sinon.FakeXMLHttpRequest;
|
||||||
(global as any).document = dom.window.document
|
(global as any).document = dom.window.document
|
||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
root: 'https://example.com/views/',
|
root: 'https://example.com/views/',
|
||||||
@@ -68,7 +68,7 @@ describe('xhr', () => {
|
|||||||
it('should throw error', function () {
|
it('should throw error', function () {
|
||||||
const result = expect(engine.renderFile('hello.html'))
|
const result = expect(engine.renderFile('hello.html'))
|
||||||
.to.be.rejectedWith('An error occurred whilst receiving the response.');
|
.to.be.rejectedWith('An error occurred whilst receiving the response.');
|
||||||
(global as any).XMLHttpRequest.onCreate = function (request) {
|
(global as any).XMLHttpRequest.onCreate = function (request: sinon.SinonFakeXMLHttpRequest) {
|
||||||
setTimeout(() => request.error())
|
setTimeout(() => request.error())
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ export function mock (options: { [path: string]: (string | fileDescriptor) }) {
|
|||||||
return file.content
|
return file.content
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.exists = async function (path) {
|
fs.exists = async function (path: string) {
|
||||||
console.log('mock fs exists called', path)
|
console.log('mock fs exists called', path)
|
||||||
return !!files[path]
|
return !!files[path]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ export const ctx = {
|
|||||||
posts: [{ category: 'foo' }, { category: 'bar' }]
|
posts: [{ category: 'foo' }, { category: 'bar' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function test (src, dst) {
|
export async function test (src: string, dst: string) {
|
||||||
const html = await liquid.parseAndRender(src, ctx)
|
const html = await liquid.parseAndRender(src, ctx)
|
||||||
return expect(html).to.equal(dst)
|
return expect(html).to.equal(dst)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import Liquid from 'src/liquid'
|
import Liquid from 'src/liquid'
|
||||||
import { expect, use } from 'chai'
|
import { expect, use } from 'chai'
|
||||||
import * as chaiAsPromised from 'chai-as-promised'
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
|
import IContext from 'src/scope/icontext';
|
||||||
|
|
||||||
use(chaiAsPromised)
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('tags/for', function () {
|
describe('tags/for', function () {
|
||||||
let liquid, ctx
|
let liquid: Liquid, ctx: IContext
|
||||||
before(function () {
|
before(function () {
|
||||||
liquid = new Liquid()
|
liquid = new Liquid()
|
||||||
liquid.registerTag('throwingTag', {
|
liquid.registerTag('throwingTag', {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { expect } from 'chai'
|
|||||||
import { mock, restore } from 'test/stub/mockfs'
|
import { mock, restore } from 'test/stub/mockfs'
|
||||||
|
|
||||||
describe('tags/include', function () {
|
describe('tags/include', function () {
|
||||||
let liquid
|
let liquid: Liquid
|
||||||
before(function () {
|
before(function () {
|
||||||
liquid = new Liquid({
|
liquid = new Liquid({
|
||||||
root: '/',
|
root: '/',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { expect } from 'chai'
|
|||||||
import { mock, restore } from 'test/stub/mockfs'
|
import { mock, restore } from 'test/stub/mockfs'
|
||||||
|
|
||||||
describe('tags/layout', function () {
|
describe('tags/layout', function () {
|
||||||
let liquid
|
let liquid: Liquid
|
||||||
before(function () {
|
before(function () {
|
||||||
liquid = new Liquid({
|
liquid = new Liquid({
|
||||||
root: '/',
|
root: '/',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as chaiAsPromised from 'chai-as-promised'
|
|||||||
use(chaiAsPromised)
|
use(chaiAsPromised)
|
||||||
|
|
||||||
describe('tags/unless', function () {
|
describe('tags/unless', function () {
|
||||||
let liquid
|
let liquid: Liquid
|
||||||
before(() => { liquid = new Liquid() })
|
before(() => { liquid = new Liquid() })
|
||||||
|
|
||||||
it('should render else when predicate yields true', async function () {
|
it('should render else when predicate yields true', async function () {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const resolve = fs.resolve
|
|||||||
|
|
||||||
describe('fs/browser', function () {
|
describe('fs/browser', function () {
|
||||||
describe('#resolve()', function () {
|
describe('#resolve()', function () {
|
||||||
if (+process.version.match(/^v(\d+)/)[1] < 8) {
|
if (+(process.version.match(/^v(\d+)/) as RegExpMatchArray)[1] < 8) {
|
||||||
console.info('jsdom not supported, skipping template-browser...')
|
console.info('jsdom not supported, skipping template-browser...')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -54,9 +54,9 @@ describe('fs/browser', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#readFile()', () => {
|
describe('#readFile()', () => {
|
||||||
let server
|
let server: sinon.SinonFakeServer
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
server = sinon.createFakeServer()
|
server = sinon.fakeServer.create()
|
||||||
server.autoRespond = true
|
server.autoRespond = true
|
||||||
server.respondWith('GET', 'https://example.com/views/hello.html',
|
server.respondWith('GET', 'https://example.com/views/hello.html',
|
||||||
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']);
|
[200, { 'Content-Type': 'text/plain' }, 'hello {{name}}']);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Liquid from 'src/liquid'
|
|||||||
import { mock, restore } from 'test/stub/mockfs'
|
import { mock, restore } from 'test/stub/mockfs'
|
||||||
|
|
||||||
describe('LiquidOptions#cache', function () {
|
describe('LiquidOptions#cache', function () {
|
||||||
let engine
|
let engine: Liquid
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
root: '/root/',
|
root: '/root/',
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ describe('Liquid', function () {
|
|||||||
})
|
})
|
||||||
after(restore)
|
after(restore)
|
||||||
it('should render single template', function (done) {
|
it('should render single template', function (done) {
|
||||||
render.call({ root: '.' }, 'foo', null, (err, result) => {
|
render.call({ root: '.' }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
|
||||||
if (err) return done(err)
|
if (err) return done(err)
|
||||||
expect(result).to.equal('foo')
|
expect(result).to.equal('foo')
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should render single template with Array-typed root', function (done) {
|
it('should render single template with Array-typed root', function (done) {
|
||||||
render.call({ root: ['.'] }, 'foo', null, (err, result) => {
|
render.call({ root: ['.'] }, 'foo', null as any, (err: Error | null, result: string | undefined) => {
|
||||||
if (err) return done(err)
|
if (err) return done(err)
|
||||||
expect(result).to.equal('foo')
|
expect(result).to.equal('foo')
|
||||||
done()
|
done()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Liquid from 'src/liquid'
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
|
||||||
describe('LiquidOptions#strict_*', function () {
|
describe('LiquidOptions#strict_*', function () {
|
||||||
let engine
|
let engine: Liquid
|
||||||
const ctx = {}
|
const ctx = {}
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ describe('tokenizer', function () {
|
|||||||
const tokens = tokenizer.tokenize(html)
|
const tokens = tokenizer.tokenize(html)
|
||||||
expect(tokens.length).to.equal(1)
|
expect(tokens.length).to.equal(1)
|
||||||
expect(tokens[0]).instanceOf(TagToken)
|
expect(tokens[0]).instanceOf(TagToken)
|
||||||
expect(tokens[0].args).to.equal('a:a\nb:1.23')
|
expect((tokens[0] as TagToken).args).to.equal('a:a\nb:1.23')
|
||||||
expect(tokens[0].raw).to.equal('{%foo\na:a\nb:1.23\n%}')
|
expect(tokens[0].raw).to.equal('{%foo\na:a\nb:1.23\n%}')
|
||||||
})
|
})
|
||||||
it('should handle multiple lines value', function () {
|
it('should handle multiple lines value', function () {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { expect } from 'chai'
|
|||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
import Token from 'src/parser/token'
|
import Token from 'src/parser/token'
|
||||||
import Tag from 'src/template/tag/tag'
|
import Tag from 'src/template/tag/tag'
|
||||||
import Filter from 'src/template/filter'
|
import Filter from 'src/template/filter/Filter'
|
||||||
import Render from 'src/render/render'
|
import Render from 'src/render/render'
|
||||||
import HTML from 'src/template/html'
|
import HTML from 'src/template/html'
|
||||||
|
|
||||||
describe('render', function () {
|
describe('render', function () {
|
||||||
let render
|
let render: Render
|
||||||
before(function () {
|
before(function () {
|
||||||
Filter.clear()
|
Filter.clear()
|
||||||
Tag.clear()
|
Tag.clear()
|
||||||
@@ -16,7 +16,7 @@ describe('render', function () {
|
|||||||
|
|
||||||
describe('.renderTemplates()', function () {
|
describe('.renderTemplates()', function () {
|
||||||
it('should throw when scope undefined', function () {
|
it('should throw when scope undefined', function () {
|
||||||
expect(render.renderTemplates([])).to.be.rejectedWith(/scope undefined/)
|
expect(render.renderTemplates([], null as any)).to.be.rejectedWith(/scope undefined/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render html', async function () {
|
it('should render html', async function () {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { expect } from 'chai'
|
|||||||
import { evalExp, evalValue, isTruthy } from 'src/render/syntax'
|
import { evalExp, evalValue, isTruthy } from 'src/render/syntax'
|
||||||
|
|
||||||
describe('expression', function () {
|
describe('expression', function () {
|
||||||
let scope
|
let scope: Scope
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
scope = new Scope({
|
scope = new Scope({
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
import IContext from 'src/scope/icontext';
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('scope', function () {
|
describe('scope', function () {
|
||||||
let scope, ctx
|
let scope: Scope, ctx: IContext
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
ctx = {
|
ctx = {
|
||||||
foo: 'zoo',
|
foo: 'zoo',
|
||||||
@@ -68,7 +69,7 @@ describe('scope', function () {
|
|||||||
}
|
}
|
||||||
expect(fn).to.not.throw()
|
expect(fn).to.not.throw()
|
||||||
expect(scope.get('notdefined')).to.equal(undefined)
|
expect(scope.get('notdefined')).to.equal(undefined)
|
||||||
expect(scope.get(false)).to.equal(undefined)
|
expect(scope.get(false as any)).to.equal(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw for invalid path', function () {
|
it('should throw for invalid path', function () {
|
||||||
@@ -169,7 +170,7 @@ describe('scope', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('strict_variables', function () {
|
describe('strict_variables', function () {
|
||||||
let scope
|
let scope: Scope
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
scope = new Scope(ctx, {
|
scope = new Scope(ctx, {
|
||||||
strict_variables: true
|
strict_variables: true
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import * as sinonChai from 'sinon-chai'
|
import * as sinonChai from 'sinon-chai'
|
||||||
import Filter from 'src/template/filter'
|
import Filter from 'src/template/filter/filter'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
|
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('filter', function () {
|
describe('filter', function () {
|
||||||
let scope
|
let scope: Scope
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
Filter.clear()
|
Filter.clear()
|
||||||
scope = new Scope()
|
scope = new Scope()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as chai from 'chai'
|
|||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
import Output from 'src/template/output'
|
import Output from 'src/template/output'
|
||||||
import OutputToken from 'src/parser/output-token'
|
import OutputToken from 'src/parser/output-token'
|
||||||
import Filter from 'src/template/filter'
|
import Filter from 'src/template/filter/filter'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ describe('Output', function () {
|
|||||||
return expect(html).equal('{"num":2,"circular":{"bar":"bar"}}')
|
return expect(html).equal('{"num":2,"circular":{"bar":"bar"}}')
|
||||||
})
|
})
|
||||||
it('should skip function property', async function () {
|
it('should skip function property', async function () {
|
||||||
const scope = new Scope({ obj: { foo: 'foo', bar: x => x } })
|
const scope = new Scope({ obj: { foo: 'foo', bar: (x: any) => x } })
|
||||||
const output = new Output({ value: 'obj' } as OutputToken)
|
const output = new Output({ value: 'obj' } as OutputToken)
|
||||||
const html = await output.render(scope)
|
const html = await output.render(scope)
|
||||||
return expect(html).to.equal('{"foo":"foo"}')
|
return expect(html).to.equal('{"foo":"foo"}')
|
||||||
@@ -53,7 +53,7 @@ describe('Output', function () {
|
|||||||
return expect(str).to.equal('FOO')
|
return expect(str).to.equal('FOO')
|
||||||
})
|
})
|
||||||
it('should respect to .liquid_method_missing()', async () => {
|
it('should respect to .liquid_method_missing()', async () => {
|
||||||
const scope = new Scope({ obj: { liquid_method_missing: x => x.toUpperCase() } })
|
const scope = new Scope({ obj: { liquid_method_missing: (x: string) => x.toUpperCase() } })
|
||||||
const output = new Output({ value: 'obj.foo' } as OutputToken)
|
const output = new Output({ value: 'obj.foo' } as OutputToken)
|
||||||
const str = await output.render(scope)
|
const str = await output.render(scope)
|
||||||
return expect(str).to.equal('FOO')
|
return expect(str).to.equal('FOO')
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const expect = chai.expect
|
|||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
|
|
||||||
describe('tag', function () {
|
describe('tag', function () {
|
||||||
let scope
|
let scope: Scope
|
||||||
before(function () {
|
before(function () {
|
||||||
scope = new Scope({
|
scope = new Scope({
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
@@ -56,7 +56,7 @@ describe('tag', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('hash', function () {
|
describe('hash', function () {
|
||||||
let spy, token
|
let spy: sinon.SinonSpy, token: TagToken
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
spy = sinon.spy()
|
spy = sinon.spy()
|
||||||
Tag.register('foo', {
|
Tag.register('foo', {
|
||||||
@@ -67,7 +67,7 @@ describe('tag', function () {
|
|||||||
value: 'foo aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo',
|
value: 'foo aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo',
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
args: 'aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo'
|
args: 'aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo'
|
||||||
}
|
} as TagToken
|
||||||
})
|
})
|
||||||
it('should call tag.render with scope', async function () {
|
it('should call tag.render with scope', async function () {
|
||||||
await new Tag(token, [], liquid).render(scope)
|
await new Tag(token, [], liquid).render(scope)
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import * as chai from 'chai'
|
|||||||
import * as sinonChai from 'sinon-chai'
|
import * as sinonChai from 'sinon-chai'
|
||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import Scope from 'src/scope/scope'
|
import Scope from 'src/scope/scope'
|
||||||
import Filter from 'src/template/filter'
|
import Filter from 'src/template/filter/filter'
|
||||||
import Value from 'src/template/value'
|
import Value from 'src/template/value'
|
||||||
|
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
const add = (l, r) => l + r
|
const add = (l: number, r: number) => l + r
|
||||||
|
|
||||||
describe('Value', function () {
|
describe('Value', function () {
|
||||||
beforeEach(() => Filter.clear())
|
beforeEach(() => Filter.clear())
|
||||||
|
|||||||
+2
-44
@@ -32,10 +32,10 @@ describe('error', function () {
|
|||||||
expect(err.stack).to.contain(message.join('\n'))
|
expect(err.stack).to.contain(message.join('\n'))
|
||||||
expect(err.name).to.equal('TokenizationError')
|
expect(err.name).to.equal('TokenizationError')
|
||||||
})
|
})
|
||||||
it('should contain the whole template content in err.input', async function () {
|
it('should contain the whole template content in err.token.input', async function () {
|
||||||
const html = 'bar\nfoo{% . a %}\nfoo'
|
const html = 'bar\nfoo{% . a %}\nfoo'
|
||||||
const err = await expect(engine.parseAndRender(html)).be.rejected
|
const err = await expect(engine.parseAndRender(html)).be.rejected
|
||||||
expect(err.input).to.equal(html)
|
expect(err.token.input).to.equal(html)
|
||||||
})
|
})
|
||||||
it('should contain line number in err.token.line', async function () {
|
it('should contain line number in err.token.line', async function () {
|
||||||
const err = await expect(engine.parseAndRender('1\n2\n{% . a %}\n4')).be.rejected
|
const err = await expect(engine.parseAndRender('1\n2\n{% . a %}\n4')).be.rejected
|
||||||
@@ -49,25 +49,12 @@ describe('error', function () {
|
|||||||
expect(err.stack).to.contain('at Liquid.parse')
|
expect(err.stack).to.contain('at Liquid.parse')
|
||||||
})
|
})
|
||||||
describe('captureStackTrace compatibility', function () {
|
describe('captureStackTrace compatibility', function () {
|
||||||
const captureStackTrace = Error.captureStackTrace
|
|
||||||
before(() => (Error.captureStackTrace = null))
|
|
||||||
after(() => (Error.captureStackTrace = captureStackTrace))
|
|
||||||
it('should be empty when captureStackTrace undefined', async function () {
|
it('should be empty when captureStackTrace undefined', async function () {
|
||||||
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected
|
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected
|
||||||
expect(err.stack).to.contain('illegal tag syntax')
|
expect(err.stack).to.contain('illegal tag syntax')
|
||||||
expect(err.stack).to.not.contain('at Object.parse')
|
expect(err.stack).to.not.contain('at Object.parse')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('should contain file path in err.file', async function () {
|
|
||||||
const html = '<html>\n<head>\n\n{% . a %}\n\n'
|
|
||||||
mock({
|
|
||||||
'/foo.html': html
|
|
||||||
})
|
|
||||||
const err = await expect(engine.renderFile('/foo.html')).be.rejected
|
|
||||||
restore()
|
|
||||||
expect(err.name).to.equal('TokenizationError')
|
|
||||||
expect(err.file).to.equal(path.resolve('/foo.html'))
|
|
||||||
})
|
|
||||||
it('should throw error with line and pos if tag unmatched', async function () {
|
it('should throw error with line and pos if tag unmatched', async function () {
|
||||||
const err = await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).be.rejected
|
const err = await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).be.rejected
|
||||||
expect(err.name).to.equal('TokenizationError')
|
expect(err.name).to.equal('TokenizationError')
|
||||||
@@ -187,12 +174,6 @@ describe('error', function () {
|
|||||||
expect(err.stack).to.contain(message.join('\n'))
|
expect(err.stack).to.contain(message.join('\n'))
|
||||||
expect(err.name).to.equal('RenderError')
|
expect(err.name).to.equal('RenderError')
|
||||||
})
|
})
|
||||||
it('should contain the whole template content in err.input', async function () {
|
|
||||||
const html = 'bar\nfoo{%throwingTag%}\nfoo'
|
|
||||||
const err = await expect(engine.parseAndRender(html)).be.rejected
|
|
||||||
expect(err.input).to.equal(html)
|
|
||||||
expect(err.name).to.equal('RenderError')
|
|
||||||
})
|
|
||||||
it('should contain line number in err.token.line', async function () {
|
it('should contain line number in err.token.line', async function () {
|
||||||
const src = '1\n2\n{{1|throwingFilter}}\n4'
|
const src = '1\n2\n{{1|throwingFilter}}\n4'
|
||||||
const err = await expect(engine.parseAndRender(src)).be.rejected
|
const err = await expect(engine.parseAndRender(src)).be.rejected
|
||||||
@@ -204,18 +185,6 @@ describe('error', function () {
|
|||||||
expect(err.message).to.contain('intended render reject')
|
expect(err.message).to.contain('intended render reject')
|
||||||
expect(err.stack).to.match(/at .*:\d+:\d+/)
|
expect(err.stack).to.match(/at .*:\d+:\d+/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should contain file path in err.file', async function () {
|
|
||||||
const html = '<html>\n<head>\n\n{% throwingTag %}\n\n'
|
|
||||||
mock({
|
|
||||||
'/foo.html': html
|
|
||||||
})
|
|
||||||
const err = await expect(engine.renderFile('/foo.html')).be.rejected
|
|
||||||
restore()
|
|
||||||
console.log(err, err.name)
|
|
||||||
expect(err.name).to.equal('RenderError')
|
|
||||||
expect(err.file).to.equal(path.resolve('/foo.html'))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('ParseError', function () {
|
describe('ParseError', function () {
|
||||||
@@ -296,16 +265,5 @@ describe('error', function () {
|
|||||||
expect(err.stack).to.contain('ParseError: tag -a not found')
|
expect(err.stack).to.contain('ParseError: tag -a not found')
|
||||||
expect(err.stack).to.match(/at .*:\d+:\d+\)/)
|
expect(err.stack).to.match(/at .*:\d+:\d+\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should contain file path in err.file', async function () {
|
|
||||||
const html = '<html>\n<head>\n\n{% raw %}\n\n'
|
|
||||||
mock({
|
|
||||||
'/foo.html': html
|
|
||||||
})
|
|
||||||
const err = await expect(engine.renderFile('/foo.html')).be.rejected
|
|
||||||
restore()
|
|
||||||
expect(err.name).to.equal('ParseError')
|
|
||||||
expect(err.file).to.equal(path.resolve('/foo.html'))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,68 +1,26 @@
|
|||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import * as sinonChai from 'sinon-chai'
|
import * as sinonChai from 'sinon-chai'
|
||||||
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
|
chai.use(chaiAsPromised)
|
||||||
|
|
||||||
const P = require('src/util/promise')
|
const P = require('src/util/promise')
|
||||||
|
|
||||||
describe('util/promise', function () {
|
describe('util/promise', function () {
|
||||||
describe('.anySeries()', function () {
|
|
||||||
it('should resolve in series', function () {
|
|
||||||
const spy1 = sinon.spy()
|
|
||||||
const spy2 = sinon.spy()
|
|
||||||
return P
|
|
||||||
.anySeries(
|
|
||||||
['first', 'second'],
|
|
||||||
(item, idx) => new Promise(function (resolve, reject) {
|
|
||||||
if (idx === 0) {
|
|
||||||
setTimeout(function () {
|
|
||||||
spy1()
|
|
||||||
reject(new Error('first cb'))
|
|
||||||
}, 10)
|
|
||||||
} else {
|
|
||||||
spy2()
|
|
||||||
resolve('foo')
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.then(() => expect(spy2).to.have.been.calledAfter(spy1))
|
|
||||||
})
|
|
||||||
it('should reject when all rejected', function () {
|
|
||||||
const p = P.anySeries(['first', 'second', 'third'],
|
|
||||||
item => Promise.reject(new Error(item)))
|
|
||||||
return expect(p).to.be.rejectedWith('third')
|
|
||||||
})
|
|
||||||
it('should resolve the value that first callback resolved', async () => {
|
|
||||||
const result = await P.anySeries(
|
|
||||||
['first', 'second'],
|
|
||||||
item => Promise.resolve(item)
|
|
||||||
)
|
|
||||||
return expect(result).to.equal('first')
|
|
||||||
})
|
|
||||||
it('should not call rest of callbacks once resolved', () => {
|
|
||||||
const spy = sinon.spy()
|
|
||||||
return P
|
|
||||||
.anySeries(['first', 'second'], (item, idx) => {
|
|
||||||
if (idx > 0) {
|
|
||||||
spy()
|
|
||||||
}
|
|
||||||
return Promise.resolve(item)
|
|
||||||
})
|
|
||||||
.then(() => expect(spy).to.not.have.been.called)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('.mapSeries()', async function () {
|
describe('.mapSeries()', async function () {
|
||||||
it('should resolve when all resolved', async function () {
|
it('should resolve when all resolved', async function () {
|
||||||
const result = await P.mapSeries(
|
const result = await P.mapSeries(
|
||||||
['first', 'second', 'third'],
|
['first', 'second', 'third'],
|
||||||
item => Promise.resolve(item)
|
(item: string) => Promise.resolve(item)
|
||||||
)
|
)
|
||||||
return expect(result).to.deep.equal(['first', 'second', 'third'])
|
return expect(result).to.deep.equal(['first', 'second', 'third'])
|
||||||
})
|
})
|
||||||
it('should reject with the error that first callback rejected', () => {
|
it('should reject with the error that first callback rejected', () => {
|
||||||
const p = P.mapSeries(['first', 'second'],
|
const p = P.mapSeries(['first', 'second'],
|
||||||
item => Promise.reject(item))
|
(item: string) => Promise.reject(item))
|
||||||
return expect(p).to.rejectedWith('first')
|
return expect(p).to.rejectedWith('first')
|
||||||
})
|
})
|
||||||
it('should resolve in series', function () {
|
it('should resolve in series', function () {
|
||||||
@@ -71,7 +29,7 @@ describe('util/promise', function () {
|
|||||||
return P
|
return P
|
||||||
.mapSeries(
|
.mapSeries(
|
||||||
['first', 'second'],
|
['first', 'second'],
|
||||||
(item, idx) => new Promise(function (resolve) {
|
(item: string, idx: number) => new Promise(function (resolve) {
|
||||||
if (idx === 0) {
|
if (idx === 0) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
spy1()
|
spy1()
|
||||||
@@ -87,7 +45,7 @@ describe('util/promise', function () {
|
|||||||
it('should not call rest of callbacks once rejected', () => {
|
it('should not call rest of callbacks once rejected', () => {
|
||||||
const spy = sinon.spy()
|
const spy = sinon.spy()
|
||||||
return P
|
return P
|
||||||
.mapSeries(['first', 'second'], (item, idx) => {
|
.mapSeries(['first', 'second'], (item: string, idx: number) => {
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
spy()
|
spy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import t from 'src/util/strftime'
|
|||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
describe('util/strftime', function () {
|
describe('util/strftime', function () {
|
||||||
let now
|
let now: Date
|
||||||
let then
|
let then: Date
|
||||||
before(function () {
|
before(function () {
|
||||||
mockUTC()
|
mockUTC()
|
||||||
now = new Date('2016-01-04T13:15:23.000Z')
|
now = new Date('2016-01-04T13:15:23.000Z')
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as sinonChai from 'sinon-chai'
|
|||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import { RenderError, RenderBreakError } from 'src/util/error'
|
import { RenderError, RenderBreakError } from 'src/util/error'
|
||||||
import * as _ from 'src/util/underscore'
|
import * as _ from 'src/util/underscore'
|
||||||
|
import ITemplate from 'src/template/itemplate'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
@@ -17,7 +18,7 @@ describe('util/underscore', function () {
|
|||||||
token: {
|
token: {
|
||||||
input: 'xx'
|
input: 'xx'
|
||||||
}
|
}
|
||||||
}
|
} as ITemplate
|
||||||
expect(_.isError(new RenderError(new Error(), tpl))).to.be.true
|
expect(_.isError(new RenderError(new Error(), tpl))).to.be.true
|
||||||
})
|
})
|
||||||
it('should return true for RenderBreakError', function () {
|
it('should return true for RenderBreakError', function () {
|
||||||
@@ -104,7 +105,7 @@ describe('util/underscore', function () {
|
|||||||
})
|
})
|
||||||
describe('.isObject()', function () {
|
describe('.isObject()', function () {
|
||||||
it('should return true for function', function () {
|
it('should return true for function', function () {
|
||||||
expect(_.isObject(x => x)).to.be.true
|
expect(_.isObject((x: any) => x)).to.be.true
|
||||||
})
|
})
|
||||||
it('should return true for plain object', function () {
|
it('should return true for plain object', function () {
|
||||||
expect(_.isObject({})).to.be.true
|
expect(_.isObject({})).to.be.true
|
||||||
@@ -116,49 +117,4 @@ describe('util/underscore', function () {
|
|||||||
expect(_.isObject(2)).to.be.false
|
expect(_.isObject(2)).to.be.false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('.assign()', function () {
|
|
||||||
it('should handle null dst', function () {
|
|
||||||
expect(_.assign(null, {
|
|
||||||
foo: 'bar'
|
|
||||||
})).to.deep.equal({
|
|
||||||
foo: 'bar'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should assign 2 objects', function () {
|
|
||||||
const src = {
|
|
||||||
foo: 'foo',
|
|
||||||
bar: 'bar'
|
|
||||||
}
|
|
||||||
const dst = {
|
|
||||||
foo: 'bar',
|
|
||||||
kaa: 'kaa'
|
|
||||||
}
|
|
||||||
expect(_.assign(dst, src)).to.deep.equal({
|
|
||||||
foo: 'foo',
|
|
||||||
bar: 'bar',
|
|
||||||
kaa: 'kaa'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('should assign 3 objects', function () {
|
|
||||||
expect(_.assign({
|
|
||||||
foo: 'foo'
|
|
||||||
}, {
|
|
||||||
bar: 'bar'
|
|
||||||
}, {
|
|
||||||
car: 'car'
|
|
||||||
})).to.deep.equal({
|
|
||||||
foo: 'foo',
|
|
||||||
bar: 'bar',
|
|
||||||
car: 'car'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('.uniq()', function () {
|
|
||||||
it('should handle empty array', function () {
|
|
||||||
expect(_.uniq([])).to.deep.equal([])
|
|
||||||
})
|
|
||||||
it('should do uniq', function () {
|
|
||||||
expect(_.uniq([1, 'a', 'a', 1])).to.deep.equal([1, 'a'])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"strict": true,
|
||||||
"emitDecoratorMetadata": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"src/fs": ["src/fs/node"],
|
"src/fs": ["src/fs/node"],
|
||||||
|
|||||||
Reference in New Issue
Block a user