feat: support custom key-value separator, #752

This commit is contained in:
Yang Jun
2024-09-22 21:55:44 +08:00
committed by Jun Yang
parent 5ada07c2c6
commit 6aeed2586a
10 changed files with 18 additions and 11 deletions
+3
View File
@@ -68,6 +68,8 @@ export interface LiquidOptions {
greedy?: boolean; greedy?: boolean;
/** `fs` is used to override the default file-system module with a custom implementation. */ /** `fs` is used to override the default file-system module with a custom implementation. */
fs?: FS; fs?: FS;
/** keyValue separator */
keyValueSeparator?: string;
/** Render from in-memory `templates` mapping instead of file system. File system related options like `fs`, 'root', and `relativeReference` will be ignored when `templates` is specified. */ /** Render from in-memory `templates` mapping instead of file system. File system related options like `fs`, 'root', and `relativeReference` will be ignored when `templates` is specified. */
templates?: {[key: string]: string}; templates?: {[key: string]: string};
/** the global scope passed down to all partial and layout templates, i.e. templates included by `include`, `layout` and `render` tags. */ /** the global scope passed down to all partial and layout templates, i.e. templates included by `include`, `layout` and `render` tags. */
@@ -166,6 +168,7 @@ export const defaultOptions: NormalizedFullOptions = {
partials: ['.'], partials: ['.'],
relativeReference: true, relativeReference: true,
jekyllInclude: false, jekyllInclude: false,
keyValueSeparator: ':',
cache: undefined, cache: undefined,
extname: '', extname: '',
fs: fs, fs: fs,
+4 -4
View File
@@ -1,6 +1,6 @@
import { FilteredValueToken, TagToken, HTMLToken, HashToken, QuotedToken, LiquidTagToken, OutputToken, ValueToken, Token, RangeToken, FilterToken, TopLevelToken, PropertyAccessToken, OperatorToken, LiteralToken, IdentifierToken, NumberToken } from '../tokens' import { FilteredValueToken, TagToken, HTMLToken, HashToken, QuotedToken, LiquidTagToken, OutputToken, ValueToken, Token, RangeToken, FilterToken, TopLevelToken, PropertyAccessToken, OperatorToken, LiteralToken, IdentifierToken, NumberToken } from '../tokens'
import { OperatorHandler } from '../render/operator' import { OperatorHandler } from '../render/operator'
import { TrieNode, LiteralValue, Trie, createTrie, ellipsis, literalValues, TokenizationError, TYPES, QUOTE, BLANK, NUMBER, SIGN, isWord } from '../util' import { TrieNode, LiteralValue, Trie, createTrie, ellipsis, literalValues, TokenizationError, TYPES, QUOTE, BLANK, NUMBER, SIGN, isWord, isString } from '../util'
import { Operators, Expression } from '../render' import { Operators, Expression } from '../render'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options' import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { FilterArg } from './filter-arg' import { FilterArg } from './filter-arg'
@@ -261,7 +261,7 @@ export class Tokenizer {
return this.readIdentifier().getText() return this.readIdentifier().getText()
} }
readHashes (jekyllStyle?: boolean) { readHashes (jekyllStyle?: boolean | string) {
const hashes = [] const hashes = []
while (true) { while (true) {
const hash = this.readHash(jekyllStyle) const hash = this.readHash(jekyllStyle)
@@ -270,7 +270,7 @@ export class Tokenizer {
} }
} }
readHash (jekyllStyle?: boolean): HashToken | undefined { readHash (jekyllStyle?: boolean | string): HashToken | undefined {
this.skipBlank() this.skipBlank()
if (this.peek() === ',') ++this.p if (this.peek() === ',') ++this.p
const begin = this.p const begin = this.p
@@ -279,7 +279,7 @@ export class Tokenizer {
let value let value
this.skipBlank() this.skipBlank()
const sep = jekyllStyle ? '=' : ':' const sep = isString(jekyllStyle) ? jekyllStyle : (jekyllStyle ? '=' : ':')
if (this.peek() === sep) { if (this.peek() === sep) {
++this.p ++this.p
value = this.readValue() value = this.readValue()
+1 -1
View File
@@ -25,7 +25,7 @@ export default class extends Tag {
this.variable = variable.content this.variable = variable.content
this.collection = collection this.collection = collection
this.hash = new Hash(this.tokenizer.remaining()) this.hash = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator)
this.templates = [] this.templates = []
this.elseTemplates = [] this.elseTemplates = []
+1 -1
View File
@@ -21,7 +21,7 @@ export default class extends Tag {
} else tokenizer.p = begin } else tokenizer.p = begin
} else tokenizer.p = begin } else tokenizer.p = begin
this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude) this.hash = new Hash(tokenizer.remaining(), liquid.options.jekyllInclude || liquid.options.keyValueSeparator)
} }
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> { * render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash, withVar } = this const { liquid, hash, withVar } = this
+1 -1
View File
@@ -12,7 +12,7 @@ export default class extends Tag {
super(token, remainTokens, liquid) super(token, remainTokens, liquid)
this.file = parseFilePath(this.tokenizer, this.liquid, parser) this.file = parseFilePath(this.tokenizer, this.liquid, parser)
this['currentFile'] = token.file this['currentFile'] = token.file
this.args = new Hash(this.tokenizer.remaining()) this.args = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator)
this.templates = parser.parseTokens(remainTokens) this.templates = parser.parseTokens(remainTokens)
} }
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> { * render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
+1 -1
View File
@@ -45,7 +45,7 @@ export default class extends Tag {
tokenizer.p = begin tokenizer.p = begin
break break
} }
this.hash = new Hash(tokenizer.remaining()) this.hash = new Hash(tokenizer.remaining(), liquid.options.keyValueSeparator)
} }
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> { * render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash } = this const { liquid, hash } = this
+1 -1
View File
@@ -21,7 +21,7 @@ export default class extends Tag {
this.variable = variable.content this.variable = variable.content
this.collection = collectionToken this.collection = collectionToken
this.args = new Hash(this.tokenizer.remaining()) this.args = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator)
this.templates = [] this.templates = []
let p let p
+4
View File
@@ -39,4 +39,8 @@ describe('Hash', function () {
num3: 4 num3: 4
}) })
}) })
it('should support custom separator', async function () {
const hash = await toPromise(new Hash('num=2.3', '=').render(new Context()))
expect(hash.num).toBe(2.3)
})
}) })
+1 -1
View File
@@ -15,7 +15,7 @@ type HashValueTokens = Record<string, Token | undefined>
*/ */
export class Hash { export class Hash {
hash: HashValueTokens = {} hash: HashValueTokens = {}
constructor (markup: string, jekyllStyle?: boolean) { constructor (markup: string, jekyllStyle?: boolean | string) {
const tokenizer = new Tokenizer(markup, {}) const tokenizer = new Tokenizer(markup, {})
for (const hash of tokenizer.readHashes(jekyllStyle)) { for (const hash of tokenizer.readHashes(jekyllStyle)) {
this.hash[hash.name.content] = hash.value this.hash[hash.name.content] = hash.value
+1 -1
View File
@@ -21,7 +21,7 @@ export function createTagClass (options: TagImplOptions): TagClass {
} }
} }
* render (ctx: Context, emitter: Emitter): TagRenderReturn { * render (ctx: Context, emitter: Emitter): TagRenderReturn {
const hash = (yield new Hash(this.token.args).render(ctx)) as Record<string, any> 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) return yield options.render.call(this, ctx, emitter, hash)
} }
} }