Skip to content

Commit

Permalink
refactor: more consistent tags to make it easier to iterate over, #524
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Nov 27, 2022
1 parent 4e1a30a commit 1380ac9
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 82 deletions.
9 changes: 9 additions & 0 deletions demo/template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# LiquidJS for Node.js

## Get Started

```bash
cd demo/nodejs
npm install
npm start
```
25 changes: 25 additions & 0 deletions demo/template/get-outputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Output, Template, Tag } from 'liquidjs'
import { isLayoutTag, isIfTag, isUnlessTag, isLiquidTag, isCaseTag, isCaptureTag, isTablerowTag, isForTag } from './type-guards'

/**
* iterate over all `{{ output }}`
*/
export function * getOutputs (templates: Template[]): Generator<Output, void> {
for (const template of templates) {
if (template instanceof Tag) {
if (isIfTag(template) || isUnlessTag(template) || isCaseTag(template)) {
for (const branch of template.branches) {
yield * getOutputs(branch.templates)
}
yield * getOutputs(template.elseTemplates)
} else if (isForTag(template)) {
yield * getOutputs(template.templates)
yield * getOutputs(template.elseTemplates)
} else if (isLiquidTag(template) || isCaptureTag(template) || isTablerowTag(template) || isLayoutTag(template)) {
yield * getOutputs(template.templates)
}
} else if (template instanceof Output) {
yield template
}
}
}
16 changes: 16 additions & 0 deletions demo/template/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Liquid } from 'liquidjs'
import { getOutputs } from './get-outputs'

const engine = new Liquid({
root: __dirname,
extname: '.liquid'
})

const templates = engine.parseFileSync('todolist')

for (const output of getOutputs(templates)) {
const token = output.token
const [line, col] = token.getPosition()
const text = token.getText()
console.log(`[${line}:${col}] ${text}`)
}
17 changes: 17 additions & 0 deletions demo/template/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "liquidjs-demo-template",
"private": true,
"description": "liquid template parsing",
"main": "index.ts",
"scripts": {
"start": "ts-node index.ts"
},
"dependencies": {
"liquidjs": "latest",
"ts-node": "^8.10.2",
"typescript": "^4.2.4"
},
"devDependencies": {
"@types/node": "^14.14.37"
}
}
13 changes: 13 additions & 0 deletions demo/template/todolist.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% layout 'html.liquid' %}

<h1>{{ title | capitalize }}</h1>
<a>
{% if authed %} Sign out {{ username }}
{% else %} Sign in
{% endif %}
</a>
<ul>
{% for todo in todos %}
<p>{{ todo }}</p>
{% endfor %}
</ul>
8 changes: 8 additions & 0 deletions demo/template/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"downlevelIteration": true,
"types": [
"node"
]
}
}
33 changes: 33 additions & 0 deletions demo/template/type-guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { LayoutTag, ForTag, LiquidTag, CaptureTag, CaseTag, UnlessTag, TablerowTag, Tag, IfTag } from 'liquidjs'

export function isIfTag (tag: Tag): tag is IfTag {
return tag.name === 'if'
}

export function isUnlessTag (tag: Tag): tag is UnlessTag {
return tag.name === 'unless'
}

export function isLiquidTag (tag: Tag): tag is LiquidTag {
return tag.name === 'liquid'
}

export function isCaseTag (tag: Tag): tag is CaseTag {
return tag.name === 'case'
}

export function isCaptureTag (tag: Tag): tag is CaptureTag {
return tag.name === 'capture'
}

export function isTablerowTag (tag: Tag): tag is TablerowTag {
return tag.name === 'tablerow'
}

export function isForTag (tag: Tag): tag is ForTag {
return tag.name === 'for'
}

export function isLayoutTag (tag: Tag): tag is LayoutTag {
return tag.name === 'layout'
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export { Drop } from './drop'
export { Emitter } from './emitters'
export { defaultOperators, Operators, evalToken, evalQuotedToken, Expression, isFalsy, isTruthy } from './render'
export { Context, Scope } from './context'
export { Value, Hash, Template, FilterImplOptions, Tag, Filter } from './template'
export { Value, Hash, Template, FilterImplOptions, Tag, Filter, Output } from './template'
export { Token, TopLevelToken, TagToken, ValueToken } from './tokens'
export { TokenKind, Tokenizer, ParseStream } from './parser'
export { filters } from './filters'
export { tags } from './tags'
export * from './tags'
export { defaultOptions } from './liquid-options'
export { Liquid } from './liquid'
10 changes: 5 additions & 5 deletions src/tags/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { BlockDrop } from '../drop'
import { Liquid, TagToken, TopLevelToken, Template, Context, Emitter, Tag } from '..'

export default class extends Tag {
private block: string
private tpls: Template[] = []
block: string
templates: Template[] = []
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const match = /\w+/.exec(token.args)
Expand All @@ -14,7 +14,7 @@ export default class extends Tag {
const token = remainTokens.shift()!
if (isTagToken(token) && token.name === 'endblock') return
const template = liquid.parser.parseToken(token, remainTokens)
this.tpls.push(template)
this.templates.push(template)
}
throw new Error(`tag ${token.getText()} not closed`)
}
Expand All @@ -29,12 +29,12 @@ export default class extends Tag {
}

private getBlockRender (ctx: Context) {
const { liquid, tpls } = this
const { liquid, templates } = this
const renderChild = ctx.getRegister('blocks')[this.block]
const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) {
// add {{ block.super }} support when rendering
ctx.push({ block: superBlock })
yield liquid.renderer.renderTemplates(tpls, ctx, emitter)
yield liquid.renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
}
return renderChild
Expand Down
4 changes: 2 additions & 2 deletions src/tags/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { evalQuotedToken } from '../render'
import { isTagToken } from '../util'

export default class extends Tag {
private variable: string
private templates: Template[] = []
variable: string
templates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators)
Expand Down
20 changes: 10 additions & 10 deletions src/tags/case.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ValueToken, Liquid, Tokenizer, toValue, evalToken, Value, Emitter, TagToken, TopLevelToken, Context, Template, Tag, ParseStream } from '..'

export default class extends Tag {
private cond: Value
private cases: { val?: ValueToken, templates: Template[] }[] = []
private elseTemplates: Template[] = []
value: Value
branches: { value?: ValueToken, templates: Template[] }[] = []
elseTemplates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
this.cond = new Value(tagToken.args, this.liquid)
this.value = new Value(tagToken.args, this.liquid)
this.elseTemplates = []

let p: Template[] = []
Expand All @@ -18,8 +18,8 @@ export default class extends Tag {

while (!tokenizer.end()) {
const value = tokenizer.readValue()
this.cases.push({
val: value,
this.branches.push({
value: value,
templates: p
})
tokenizer.readTo(',')
Expand All @@ -37,10 +37,10 @@ export default class extends Tag {

* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
const r = this.liquid.renderer
const cond = toValue(yield this.cond.value(ctx, ctx.opts.lenientIf))
for (const branch of this.cases) {
const val = yield evalToken(branch.val, ctx, ctx.opts.lenientIf)
if (val === cond) {
const value = toValue(yield this.value.value(ctx, ctx.opts.lenientIf))
for (const branch of this.branches) {
const target = yield evalToken(branch.value, ctx, ctx.opts.lenientIf)
if (target === value) {
yield r.renderTemplates(branch.templates, ctx, emitter)
return
}
Expand Down
10 changes: 5 additions & 5 deletions src/tags/for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ const MODIFIERS = ['offset', 'limit', 'reversed']
type valueof<T> = T[keyof T]

export default class extends Tag {
private variable: string
private collection: ValueToken
private hash: Hash
private templates: Template[]
private elseTemplates: Template[]
variable: string
collection: ValueToken
hash: Hash
templates: Template[]
elseTemplates: Template[]

constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
Expand Down
14 changes: 7 additions & 7 deletions src/tags/if.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Liquid, Tag, Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template } from '..'

export default class extends Tag {
private branches: { predicate: Value, templates: Template[] }[] = []
private elseTemplates: Template[] = []
branches: { value: Value, templates: Template[] }[] = []
elseTemplates: Template[] = []

constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
let p
liquid.parser.parseStream(remainTokens)
.on('start', () => this.branches.push({
predicate: new Value(tagToken.args, this.liquid),
value: new Value(tagToken.args, this.liquid),
templates: (p = [])
}))
.on('tag:elsif', (token: TagToken) => this.branches.push({
predicate: new Value(token.args, this.liquid),
value: new Value(token.args, this.liquid),
templates: (p = [])
}))
.on('tag:else', () => (p = this.elseTemplates))
Expand All @@ -26,9 +26,9 @@ export default class extends Tag {
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, string> {
const r = this.liquid.renderer

for (const { predicate, templates } of this.branches) {
const value = yield predicate.value(ctx, ctx.opts.lenientIf)
if (isTruthy(value, ctx)) {
for (const { value, templates } of this.branches) {
const v = yield value.value(ctx, ctx.opts.lenientIf)
if (isTruthy(v, ctx)) {
yield r.renderTemplates(templates, ctx, emitter)
return
}
Expand Down
66 changes: 44 additions & 22 deletions src/tags/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import assign from './assign'
import For from './for'
import capture from './capture'
import Case from './case'
import comment from './comment'
import include from './include'
import render from './render'
import decrement from './decrement'
import cycle from './cycle'
import If from './if'
import increment from './increment'
import layout from './layout'
import block from './block'
import raw from './raw'
import tablerow from './tablerow'
import unless from './unless'
import Break from './break'
import Continue from './continue'
import echo from './echo'
import liquid from './liquid'
import inlineComment from './inline-comment'
import AssignTag from './assign'
import ForTag from './for'
import CaptureTag from './capture'
import CaseTag from './case'
import CommentTag from './comment'
import IncludeTag from './include'
import RenderTag from './render'
import DecrementTag from './decrement'
import CycleTag from './cycle'
import IfTag from './if'
import IncrementTag from './increment'
import LayoutTag from './layout'
import BlockTag from './block'
import RawTag from './raw'
import TablerowTag from './tablerow'
import UnlessTag from './unless'
import BreakTag from './break'
import ContinueTag from './continue'
import EchoTag from './echo'
import LiquidTag from './liquid'
import InlineCommentTag from './inline-comment'
import type { TagClass } from '../template/tag'

export const tags: Record<string, TagClass> = {
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid, '#': inlineComment
assign: AssignTag,
'for': ForTag,
capture: CaptureTag,
'case': CaseTag,
comment: CommentTag,
include: IncludeTag,
render: RenderTag,
decrement: DecrementTag,
increment: IncrementTag,
cycle: CycleTag,
'if': IfTag,
layout: LayoutTag,
block: BlockTag,
raw: RawTag,
tablerow: TablerowTag,
unless: UnlessTag,
'break': BreakTag,
'continue': ContinueTag,
echo: EchoTag,
liquid: LiquidTag,
'#': InlineCommentTag
}

export { AssignTag, ForTag, CaptureTag, CaseTag, CommentTag, IncludeTag, RenderTag, DecrementTag, IncrementTag, CycleTag, IfTag, LayoutTag, BlockTag, RawTag, TablerowTag, UnlessTag, BreakTag, ContinueTag, EchoTag, LiquidTag, InlineCommentTag }
Loading

0 comments on commit 1380ac9

Please sign in to comment.