mirror of
https://github.com/harttle/liquidjs.git
synced 2026-10-03 00:55:13 -07:00
* feat!: drop TagImplOptions in favor of Tag classes (#839) Remove tag-options-adapter and the registerTag object-literal overload. Custom tags must extend Tag. Co-authored-by: Cursor <[email protected]> * test: drop TagImplOptions-specific e2e coverage (#839) Remove #570 v9 object-literal registration test and unused metadata_file setup in #573. Co-authored-by: Cursor <[email protected]> * test: use inline Tag classes in register-tags spec Co-authored-by: Cursor <[email protected]> * test: remove dead throwingTag setup and duplicate throw stub for.spec kept throwingTag registration after #713 removed its test. Reuse ThrowingTag in liquid.spec instead of IntendedRenderErrorTag. Co-authored-by: Cursor <[email protected]> * test: remove dead throwingTag setup and duplicate throw stub for.spec kept throwingTag registration after #713 removed its test. Reuse ThrowingTag in liquid.spec instead of IntendedRenderErrorTag. Co-authored-by: Cursor <[email protected]> * fix(demo): ignore killall exit when express server already stopped Co-authored-by: Cursor <[email protected]> * fix(demo): revert unrelated return->exit change in express test Co-authored-by: Cursor <[email protected]> * fix(demo): use exit in express test script (no enclosing function) Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -7,13 +7,13 @@ while ! grep -q "Express running" "$LOG_FILE"; do
|
|||||||
if ! kill -0 $SERVER_PID; then
|
if ! kill -0 $SERVER_PID; then
|
||||||
echo "Server exited unexpectedly."
|
echo "Server exited unexpectedly."
|
||||||
cat $LOG_FILE
|
cat $LOG_FILE
|
||||||
return 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
curl http://127.0.0.1:3000 | grep -q 'Welcome to LiquidJS'
|
curl http://127.0.0.1:3000 | grep -q 'Welcome to LiquidJS'
|
||||||
RESULT=$?
|
RESULT=$?
|
||||||
killall node
|
killall node || true
|
||||||
rm $LOG_FILE
|
rm $LOG_FILE
|
||||||
if [ $RESULT != 0 ]; then
|
if [ $RESULT != 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
import { Context } from './context'
|
import { Context } from './context'
|
||||||
import { toPromise, toValueSync, isFunction, forOwn, isString, strictUniq } from './util'
|
import { toPromise, toValueSync, forOwn, isString, strictUniq } from './util'
|
||||||
import { TagClass, createTagClass, TagImplOptions, FilterImplOptions, Template, Value, StaticAnalysisOptions, StaticAnalysis, analyze, analyzeSync, SegmentArray } from './template'
|
import { TagClass, FilterImplOptions, Template, Value, StaticAnalysisOptions, StaticAnalysis, analyze, analyzeSync, SegmentArray } from './template'
|
||||||
import { LookupType } from './fs/loader'
|
import { LookupType } from './fs/loader'
|
||||||
import { Render } from './render'
|
import { Render } from './render'
|
||||||
import { Parser } from './parser'
|
import { Parser } from './parser'
|
||||||
@@ -101,8 +101,8 @@ export class Liquid {
|
|||||||
public registerFilter (name: string, filter: FilterImplOptions) {
|
public registerFilter (name: string, filter: FilterImplOptions) {
|
||||||
this.filters[name] = filter
|
this.filters[name] = filter
|
||||||
}
|
}
|
||||||
public registerTag (name: string, tag: TagClass | TagImplOptions) {
|
public registerTag (name: string, tag: TagClass) {
|
||||||
this.tags[name] = isFunction(tag) ? tag : createTagClass(tag)
|
this.tags[name] = tag
|
||||||
}
|
}
|
||||||
public plugin (plugin: (this: Liquid, L: typeof Liquid) => void) {
|
public plugin (plugin: (this: Liquid, L: typeof Liquid) => void) {
|
||||||
return plugin.call(this, Liquid)
|
return plugin.call(this, Liquid)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export * from './template'
|
export * from './template'
|
||||||
export * from './template-impl'
|
export * from './template-impl'
|
||||||
export * from './tag'
|
export * from './tag'
|
||||||
export * from './tag-options-adapter'
|
|
||||||
export * from './filter'
|
export * from './filter'
|
||||||
export * from './filter-impl-options'
|
export * from './filter-impl-options'
|
||||||
export * from './hash'
|
export * from './hash'
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { isFunction } from '../util'
|
|
||||||
import { Hash } from './hash'
|
|
||||||
import { Tag, TagClass, TagRenderReturn } from './tag'
|
|
||||||
import { TagToken, TopLevelToken } from '../tokens'
|
|
||||||
import { Emitter } from '../emitters'
|
|
||||||
import { Context } from '../context'
|
|
||||||
import type { Liquid } from '../liquid'
|
|
||||||
|
|
||||||
export interface TagImplOptions {
|
|
||||||
[key: string]: any
|
|
||||||
parse?: (this: Tag & TagImplOptions, token: TagToken, remainingTokens: TopLevelToken[]) => void;
|
|
||||||
render: (this: Tag & TagImplOptions, ctx: Context, emitter: Emitter, hash: Record<string, any>) => TagRenderReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTagClass (options: TagImplOptions): TagClass {
|
|
||||||
return class extends Tag {
|
|
||||||
constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) {
|
|
||||||
super(token, tokens, liquid)
|
|
||||||
if (isFunction(options.parse)) {
|
|
||||||
options.parse.call(this, token, tokens)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
* render (ctx: Context, emitter: Emitter): TagRenderReturn {
|
|
||||||
const hash = (yield new Hash(this.token.args, ctx.opts.keyValueSeparator).render(ctx)) as Record<string, any>
|
|
||||||
return yield options.render.call(this, ctx, emitter, hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -14,9 +14,9 @@ for demo in $(ls demo); do
|
|||||||
npm link liquidjs
|
npm link liquidjs
|
||||||
|
|
||||||
if npm test; then
|
if npm test; then
|
||||||
echo [success] demo/webpack
|
echo "[success] demo/$demo"
|
||||||
else
|
else
|
||||||
echo [fail] demo/webpack
|
echo "[fail] demo/$demo"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
cd -
|
cd -
|
||||||
|
|||||||
+1
-26
@@ -1,4 +1,4 @@
|
|||||||
import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError, IfTag } from '../..'
|
import { Tokenizer, Context, Liquid, Drop, toValueSync, LiquidError, IfTag } from '../..'
|
||||||
import { spawnSync } from 'child_process'
|
import { spawnSync } from 'child_process'
|
||||||
import { resolve as resolvePath } from 'path'
|
import { resolve as resolvePath } from 'path'
|
||||||
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
|
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
|
||||||
@@ -362,33 +362,8 @@ describe('Issues', function () {
|
|||||||
const html = await liquid.parseAndRender(tpl)
|
const html = await liquid.parseAndRender(tpl)
|
||||||
expect(html).toMatch(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
|
expect(html).toMatch(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
|
||||||
})
|
})
|
||||||
it('tag registration compatible to v9 #570', async () => {
|
|
||||||
const liquid = new Liquid()
|
|
||||||
liquid.registerTag('metadata_file', {
|
|
||||||
parse (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
|
||||||
this.str = tagToken.args
|
|
||||||
},
|
|
||||||
async render (ctx: Context) {
|
|
||||||
const content = await Promise.resolve(`{{${this.str}}}`)
|
|
||||||
return this.liquid.parseAndRender(content.toString(), ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const tpl = '{% metadata_file foo %}'
|
|
||||||
const ctx = { foo: 'FOO' }
|
|
||||||
const html = await liquid.parseAndRender(tpl, ctx)
|
|
||||||
expect(html).toBe('FOO')
|
|
||||||
})
|
|
||||||
it('date filter should return parsed input when no format is provided #573', async () => {
|
it('date filter should return parsed input when no format is provided #573', async () => {
|
||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
liquid.registerTag('metadata_file', {
|
|
||||||
parse (tagToken: TagToken, remainTokens: TopLevelToken[]) {
|
|
||||||
this.str = tagToken.args
|
|
||||||
},
|
|
||||||
async render (ctx: Context) {
|
|
||||||
const content = await Promise.resolve(`{{${this.str}}}`)
|
|
||||||
return this.liquid.parseAndRender(content.toString(), ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const tpl = `{{ 'now' | date }}`
|
const tpl = `{{ 'now' | date }}`
|
||||||
const html = await liquid.parseAndRender(tpl)
|
const html = await liquid.parseAndRender(tpl)
|
||||||
// sample: Thursday, February 2, 2023 at 6:25 pm +0000
|
// sample: Thursday, February 2, 2023 at 6:25 pm +0000
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Liquid, Context, isFalsy } from '../../../src'
|
import { Liquid, Context, isFalsy } from '../../../src'
|
||||||
import { mock, restore } from '../../stub/mockfs'
|
import { mock, restore } from '../../stub/mockfs'
|
||||||
import { drainStream } from '../../stub/stream'
|
import { drainStream } from '../../stub/stream'
|
||||||
|
import { ThrowingTag } from '../../stub/tags'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
|
|
||||||
describe('Liquid', function () {
|
describe('Liquid', function () {
|
||||||
@@ -231,11 +232,7 @@ describe('Liquid', function () {
|
|||||||
'/root/error.html': 'A{%throwingTag%}B'
|
'/root/error.html': 'A{%throwingTag%}B'
|
||||||
})
|
})
|
||||||
engine = new Liquid({ root: ['/root/'] })
|
engine = new Liquid({ root: ['/root/'] })
|
||||||
engine.registerTag('throwingTag', {
|
engine.registerTag('throwingTag', ThrowingTag)
|
||||||
render: function () {
|
|
||||||
throw new Error('intended render error')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
afterEach(restore)
|
afterEach(restore)
|
||||||
it('should render a simple value', async () => {
|
it('should render a simple value', async () => {
|
||||||
@@ -244,7 +241,7 @@ describe('Liquid', function () {
|
|||||||
})
|
})
|
||||||
it('should throw RenderError when tag throws', async () => {
|
it('should throw RenderError when tag throws', async () => {
|
||||||
const stream = await engine.renderFileToNodeStream('error.html')
|
const stream = await engine.renderFileToNodeStream('error.html')
|
||||||
expect(drainStream(stream)).rejects.toThrow(/intended render error/)
|
expect(drainStream(stream)).rejects.toThrow(/intended error/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('#analyze', () => {
|
describe('#analyze', () => {
|
||||||
|
|||||||
@@ -1,38 +1,57 @@
|
|||||||
import { Liquid } from '../../../src/liquid'
|
import { Liquid } from '../../../src/liquid'
|
||||||
|
import { Tag } from '../../../src/template/tag'
|
||||||
|
import type { Context } from '../../../src/context'
|
||||||
|
import type { TagToken, TopLevelToken } from '../../../src/tokens'
|
||||||
|
|
||||||
describe('liquid#registerTag()', function () {
|
describe('liquid#registerTag()', function () {
|
||||||
it('should support render to simple string', async () => {
|
it('should support render to simple string', async () => {
|
||||||
|
class SimpleStringTag extends Tag {
|
||||||
|
render () {
|
||||||
|
return 'B'
|
||||||
|
}
|
||||||
|
}
|
||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
liquid.registerTag('simple-string', {
|
liquid.registerTag('simple-string', SimpleStringTag)
|
||||||
render: () => 'B'
|
|
||||||
})
|
|
||||||
const html = await liquid.parseAndRender(`A{% simple-string %}C`)
|
const html = await liquid.parseAndRender(`A{% simple-string %}C`)
|
||||||
return expect(html).toBe('ABC')
|
return expect(html).toBe('ABC')
|
||||||
})
|
})
|
||||||
it('should support async tag render', async () => {
|
it('should support async tag render', async () => {
|
||||||
|
class AsyncStringTag extends Tag {
|
||||||
|
async render () {
|
||||||
|
return 'B'
|
||||||
|
}
|
||||||
|
}
|
||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
liquid.registerTag('async-string', {
|
liquid.registerTag('async-string', AsyncStringTag)
|
||||||
render: async () => 'B'
|
|
||||||
})
|
|
||||||
const html = await liquid.parseAndRender(`A{% async-string %}C`)
|
const html = await liquid.parseAndRender(`A{% async-string %}C`)
|
||||||
return expect(html).toBe('ABC')
|
return expect(html).toBe('ABC')
|
||||||
})
|
})
|
||||||
it('should have access to ctx in render()', async () => {
|
it('should have access to ctx in render()', async () => {
|
||||||
|
class DynamicStringTag extends Tag {
|
||||||
|
async render (ctx: Context) {
|
||||||
|
return ctx.get(['c'])
|
||||||
|
}
|
||||||
|
}
|
||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
liquid.registerTag('dynamic-string', {
|
liquid.registerTag('dynamic-string', DynamicStringTag)
|
||||||
render: async (ctx) => ctx.get(['c'])
|
|
||||||
})
|
|
||||||
const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, {
|
const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, {
|
||||||
c: 'B'
|
c: 'B'
|
||||||
})
|
})
|
||||||
return expect(html).toBe('ABC')
|
return expect(html).toBe('ABC')
|
||||||
})
|
})
|
||||||
it('should have access to tag arguments', async () => {
|
it('should have access to tag arguments', async () => {
|
||||||
|
class ArgumentReflectorTag extends Tag {
|
||||||
|
variable: string
|
||||||
|
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
|
||||||
|
super(token, remainTokens, liquid)
|
||||||
|
this.variable = token.args.split('=')[1]
|
||||||
|
}
|
||||||
|
async render (ctx: Context) {
|
||||||
|
return ctx.get([this.variable])
|
||||||
|
}
|
||||||
|
}
|
||||||
const liquid = new Liquid()
|
const liquid = new Liquid()
|
||||||
liquid.registerTag('argument-reflector', {
|
liquid.registerTag('argument-reflector', ArgumentReflectorTag)
|
||||||
parse: function (token) { this.variable = token.args.split('=')[1] },
|
|
||||||
render: async function (ctx) { return ctx.get(this.variable) }
|
|
||||||
})
|
|
||||||
const html = await liquid.parseAndRender(`A{% argument-reflector variable=c %}C`, {
|
const html = await liquid.parseAndRender(`A{% argument-reflector variable=c %}C`, {
|
||||||
c: 'B'
|
c: 'B'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { RenderError } from '../../../src/util/error'
|
|||||||
import { Liquid } from '../../../src/liquid'
|
import { Liquid } from '../../../src/liquid'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { mock, restore } from '../../stub/mockfs'
|
import { mock, restore } from '../../stub/mockfs'
|
||||||
import { throwIntendedError, rejectIntendedError } from '../../stub/util'
|
import { throwIntendedError } from '../../stub/util'
|
||||||
|
import { ThrowingTag, RejectingTag, ThrowsOnParseTag } from '../../stub/tags'
|
||||||
|
|
||||||
const strictEngine = new Liquid({
|
const strictEngine = new Liquid({
|
||||||
strictVariables: true,
|
strictVariables: true,
|
||||||
@@ -13,9 +14,9 @@ const strictCatchingEngine = new Liquid({
|
|||||||
strictVariables: true,
|
strictVariables: true,
|
||||||
strictFilters: true
|
strictFilters: true
|
||||||
})
|
})
|
||||||
strictEngine.registerTag('throwingTag', { render: throwIntendedError })
|
strictEngine.registerTag('throwingTag', ThrowingTag)
|
||||||
strictEngine.registerFilter('throwingFilter', throwIntendedError)
|
strictEngine.registerFilter('throwingFilter', throwIntendedError)
|
||||||
strictCatchingEngine.registerTag('throwingTag', { render: throwIntendedError })
|
strictCatchingEngine.registerTag('throwingTag', ThrowingTag)
|
||||||
strictCatchingEngine.registerFilter('throwingFilter', throwIntendedError)
|
strictCatchingEngine.registerFilter('throwingFilter', throwIntendedError)
|
||||||
|
|
||||||
describe('error', function () {
|
describe('error', function () {
|
||||||
@@ -83,8 +84,8 @@ describe('error', function () {
|
|||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
root: '/'
|
root: '/'
|
||||||
})
|
})
|
||||||
engine.registerTag('throwingTag', { render: throwIntendedError })
|
engine.registerTag('throwingTag', ThrowingTag)
|
||||||
engine.registerTag('rejectingTag', { render: rejectIntendedError })
|
engine.registerTag('rejectingTag', RejectingTag)
|
||||||
engine.registerFilter('throwingFilter', throwIntendedError)
|
engine.registerFilter('throwingFilter', throwIntendedError)
|
||||||
})
|
})
|
||||||
it('should throw RenderError when tag throws', async function () {
|
it('should throw RenderError when tag throws', async function () {
|
||||||
@@ -244,10 +245,7 @@ describe('error', function () {
|
|||||||
let engine: Liquid
|
let engine: Liquid
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
engine = new Liquid()
|
engine = new Liquid()
|
||||||
engine.registerTag('throwsOnParse', {
|
engine.registerTag('throwsOnParse', ThrowsOnParseTag)
|
||||||
parse: throwIntendedError,
|
|
||||||
render: () => ''
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
it('should throw ParseError when filter not defined', async function () {
|
it('should throw ParseError when filter not defined', async function () {
|
||||||
await expect(strictEngine.parseAndRender('{{1 | a}}')).rejects.toMatchObject({
|
await expect(strictEngine.parseAndRender('{{1 | a}}')).rejects.toMatchObject({
|
||||||
@@ -337,11 +335,7 @@ describe('error', function () {
|
|||||||
engine = new Liquid({
|
engine = new Liquid({
|
||||||
root: '/'
|
root: '/'
|
||||||
})
|
})
|
||||||
engine.registerTag('throwingTag', {
|
engine.registerTag('throwingTag', ThrowingTag)
|
||||||
render: function () {
|
|
||||||
throw new Error('intended error')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
it('should throw RenderError when tag throws', function () {
|
it('should throw RenderError when tag throws', function () {
|
||||||
const src = '{%throwingTag%}'
|
const src = '{%throwingTag%}'
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ describe('tags/for', function () {
|
|||||||
let liquid: Liquid, scope: Scope
|
let liquid: Liquid, scope: Scope
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
liquid = new Liquid()
|
liquid = new Liquid()
|
||||||
liquid.registerTag('throwingTag', {
|
|
||||||
render: function () { throw new Error('intended render error') }
|
|
||||||
})
|
|
||||||
scope = {
|
scope = {
|
||||||
one: 1,
|
one: 1,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { throwIntendedError, rejectIntendedError } from './util'
|
||||||
|
import { Tag } from '../../src/template/tag'
|
||||||
|
import type { TagToken, TopLevelToken } from '../../src/tokens'
|
||||||
|
import type { Liquid } from '../../src/liquid'
|
||||||
|
|
||||||
|
export class ThrowingTag extends Tag {
|
||||||
|
render () {
|
||||||
|
throwIntendedError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RejectingTag extends Tag {
|
||||||
|
async render () {
|
||||||
|
await rejectIntendedError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ThrowsOnParseTag extends Tag {
|
||||||
|
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
|
||||||
|
super(token, remainTokens, liquid)
|
||||||
|
throwIntendedError()
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user