From 6aeed2586a70a5bd4f878d2cb32547e35677458d Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Sun, 22 Sep 2024 21:44:23 +0800 Subject: [PATCH] feat: support custom key-value separator, #752 --- src/liquid-options.ts | 3 +++ src/parser/tokenizer.ts | 8 ++++---- src/tags/for.ts | 2 +- src/tags/include.ts | 2 +- src/tags/layout.ts | 2 +- src/tags/render.ts | 2 +- src/tags/tablerow.ts | 2 +- src/template/hash.spec.ts | 4 ++++ src/template/hash.ts | 2 +- src/template/tag-options-adapter.ts | 2 +- 10 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/liquid-options.ts b/src/liquid-options.ts index 7cea8b4465..e93583d168 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -68,6 +68,8 @@ export interface LiquidOptions { greedy?: boolean; /** `fs` is used to override the default file-system module with a custom implementation. */ 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. */ 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. */ @@ -166,6 +168,7 @@ export const defaultOptions: NormalizedFullOptions = { partials: ['.'], relativeReference: true, jekyllInclude: false, + keyValueSeparator: ':', cache: undefined, extname: '', fs: fs, diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 775a79888a..365ae3f9c6 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -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 { 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 { NormalizedFullOptions, defaultOptions } from '../liquid-options' import { FilterArg } from './filter-arg' @@ -261,7 +261,7 @@ export class Tokenizer { return this.readIdentifier().getText() } - readHashes (jekyllStyle?: boolean) { + readHashes (jekyllStyle?: boolean | string) { const hashes = [] while (true) { 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() if (this.peek() === ',') ++this.p const begin = this.p @@ -279,7 +279,7 @@ export class Tokenizer { let value this.skipBlank() - const sep = jekyllStyle ? '=' : ':' + const sep = isString(jekyllStyle) ? jekyllStyle : (jekyllStyle ? '=' : ':') if (this.peek() === sep) { ++this.p value = this.readValue() diff --git a/src/tags/for.ts b/src/tags/for.ts index 9c1581f4c4..de05a10953 100644 --- a/src/tags/for.ts +++ b/src/tags/for.ts @@ -25,7 +25,7 @@ export default class extends Tag { this.variable = variable.content this.collection = collection - this.hash = new Hash(this.tokenizer.remaining()) + this.hash = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator) this.templates = [] this.elseTemplates = [] diff --git a/src/tags/include.ts b/src/tags/include.ts index 909e0da7a6..a14645ef36 100644 --- a/src/tags/include.ts +++ b/src/tags/include.ts @@ -21,7 +21,7 @@ export default class extends Tag { } 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 { const { liquid, hash, withVar } = this diff --git a/src/tags/layout.ts b/src/tags/layout.ts index e928adf118..527b9d820e 100644 --- a/src/tags/layout.ts +++ b/src/tags/layout.ts @@ -12,7 +12,7 @@ export default class extends Tag { super(token, remainTokens, liquid) this.file = parseFilePath(this.tokenizer, this.liquid, parser) 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) } * render (ctx: Context, emitter: Emitter): Generator { diff --git a/src/tags/render.ts b/src/tags/render.ts index 1fff29238e..584f15b4cd 100644 --- a/src/tags/render.ts +++ b/src/tags/render.ts @@ -45,7 +45,7 @@ export default class extends Tag { tokenizer.p = begin break } - this.hash = new Hash(tokenizer.remaining()) + this.hash = new Hash(tokenizer.remaining(), liquid.options.keyValueSeparator) } * render (ctx: Context, emitter: Emitter): Generator { const { liquid, hash } = this diff --git a/src/tags/tablerow.ts b/src/tags/tablerow.ts index 454506b778..40551ea5e0 100644 --- a/src/tags/tablerow.ts +++ b/src/tags/tablerow.ts @@ -21,7 +21,7 @@ export default class extends Tag { this.variable = variable.content this.collection = collectionToken - this.args = new Hash(this.tokenizer.remaining()) + this.args = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator) this.templates = [] let p diff --git a/src/template/hash.spec.ts b/src/template/hash.spec.ts index 53a761526a..0db3e67531 100644 --- a/src/template/hash.spec.ts +++ b/src/template/hash.spec.ts @@ -39,4 +39,8 @@ describe('Hash', function () { 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) + }) }) diff --git a/src/template/hash.ts b/src/template/hash.ts index 0cbff8eea7..2581e78d5f 100644 --- a/src/template/hash.ts +++ b/src/template/hash.ts @@ -15,7 +15,7 @@ type HashValueTokens = Record */ export class Hash { hash: HashValueTokens = {} - constructor (markup: string, jekyllStyle?: boolean) { + constructor (markup: string, jekyllStyle?: boolean | string) { const tokenizer = new Tokenizer(markup, {}) for (const hash of tokenizer.readHashes(jekyllStyle)) { this.hash[hash.name.content] = hash.value diff --git a/src/template/tag-options-adapter.ts b/src/template/tag-options-adapter.ts index cb4478afd9..a8a4931ae4 100644 --- a/src/template/tag-options-adapter.ts +++ b/src/template/tag-options-adapter.ts @@ -21,7 +21,7 @@ export function createTagClass (options: TagImplOptions): TagClass { } } * render (ctx: Context, emitter: Emitter): TagRenderReturn { - const hash = (yield new Hash(this.token.args).render(ctx)) as Record + const hash = (yield new Hash(this.token.args, ctx.opts.keyValueSeparator).render(ctx)) as Record return yield options.render.call(this, ctx, emitter, hash) } }