From e7b24ec5add28ee2d3b323044d807941836ad316 Mon Sep 17 00:00:00 2001 From: Sergey Kolesnik Date: Mon, 19 Feb 2024 00:52:46 +0300 Subject: [PATCH] feat(tags): helpful array functions: unique, zip, sorted, groupby --- docs/reference__syntax.md | 78 +++++++++++++++++++++++++++++++- src/extensions/customized_eta.ts | 12 +++-- src/tags.ts | 52 +++++++++++++++++++++ 3 files changed, 136 insertions(+), 6 deletions(-) diff --git a/docs/reference__syntax.md b/docs/reference__syntax.md index 0c61eea..b13871b 100644 --- a/docs/reference__syntax.md +++ b/docs/reference__syntax.md @@ -156,8 +156,12 @@ But there are the special `out` & `outn` functions to output info within ` ``{.. #### ***Template*** -- ` ``{ for (const i of [1, 2, 3]) out(i) }`` ` -- ` ``{ for (const i of [1, 2, 3]) outn(i) }`` ` +- ```javascript + ``{ for (const i of [1, 2, 3]) out(i) }`` + ``` +- ```javascript + ``{ for (const i of [1, 2, 3]) outn(i) }`` + ``` #### ***Rendered*** - 123 @@ -167,8 +171,78 @@ But there are the special `out` & `outn` functions to output info within ` ``{.. + + +Also the `Array` type extended with helpful functions: +- `Array.zip` +- `Array.unique` +- `Array.sorted` +- `Array.groupby` + + + +#### ***Template*** +- ```javascript + ``JSON.stringify(Array.zip([1, 2, 3], ['a', 'b']))`` + ``` +- ```javascript + ``[1, 2, 2, 3, 2, 1, 1].unique()`` + ``` + +#### ***Rendered*** +- [[1,"a"],[2,"b"]] +- 1,2,3 + + + + +#### ***Template*** +- ```javascript + var items = [ + { type: "vegetables", quantity: 5 }, + { type: "fruit", quantity: 1 }, + { type: "meat", quantity: 23 }, + { type: "fruit", quantity: 5 }, + { type: "meat", quantity: 22 }, + ] + ``` +- ```javascript + ``items.sorted((x) => [x.type, x.quantity]).map(JSON.stringify).join('\n')`` + ``` +- ```javascript + ``JSON.stringify(items.groupby((x) => x.type), null, 4)`` + ``` + +#### ***Rendered*** +- ```javascript +// .sorted +{"type": "fruit", "quantity": 1 } +{"type": "fruit", "quantity": 5 } +{"type": "meat", "quantity": 22 } +{"type": "meat", "quantity": 23 } +{"type": "vegetables", "quantity": 5 } +``` +- ```javascript +// .groupby +{ + "vegetables": [ + {"type": "vegetables", "quantity": 5 } + ], + "fruit": [ + { "type": "fruit", "quantity": 1 }, + { "type": "fruit", "quantity": 5 } + ], + "meat": [ + { "type": "meat", "quantity": 23 }, + { "type": "meat", "quantity": 22 } + ] +} +``` + + + ## <%...%> :id=standard-syntax diff --git a/src/extensions/customized_eta.ts b/src/extensions/customized_eta.ts index 54c61e2..e0b3eea 100644 --- a/src/extensions/customized_eta.ts +++ b/src/extensions/customized_eta.ts @@ -73,6 +73,7 @@ const eta = new CustomizedEta({ 'function out(x){__eta.res+=__eta.f(x)}', 'function outn(x){__eta.res+=__eta.f(x)+"\\n"}', ].join('\n') + '\n', + bodyHeader: '_.init()', autoEscape: false, /** Automatically XML-escape interpolations */ // escapeFunction: eta.XMLEscape, @@ -243,12 +244,14 @@ function compile(this: Eta, str: string, options?: Partial): TemplateFu } } function compileToString(this: Eta, str: string, options?: Partial): string { - const config = this.config; - const isAsync = options && options.async; + const config = this.config + // @ts-expect-error + const bodyHeader = config.bodyHeader + const isAsync = options && options.async - const compileBody = this.compileBody; + const compileBody = this.compileBody - const buffer: Array = this.parse.call(this, str); + const buffer: Array = this.parse.call(this, str) let res = `${config.functionHeader} let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction${ @@ -259,6 +262,7 @@ let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFuncti : "" }} ${config.debug ? "try {" : ""}${config.useWith ? "with(" + config.varName + "||{}){" : ""} +${bodyHeader} ${compileBody.call(this, buffer)} ${config.useWith ? "}" : ""}${ config.debug diff --git a/src/tags.ts b/src/tags.ts index e59d8af..7b84f0f 100644 --- a/src/tags.ts +++ b/src/tags.ts @@ -526,12 +526,59 @@ async function links( return linksInBlock } + +/* internal */ +function array_zip(...arr: any[]) { + return Array( + Math.min(...arr.map(a => a.length)) + ) + .fill(undefined) + .map((_, i) => arr.map(a => a[i])) +} +function array_unique() { + // @ts-expect-error + return [...new Set(this)] +} +function array_groupby(key: Function) { + // @ts-expect-error + return Object.groupBy(this, key) +} +function array_sorted(key: Function) { + // @ts-expect-error + return this + .map((x) => [(key ? key(x) : x), x]) + .sort((a, b) => { + a = a[0]; b = b[0]; + if (!Array.isArray(a)) a = [a]; + if (!Array.isArray(b)) b = [b]; + for (let i = 0; i < a.length; i++) { + const xa = a[i].toString() + const xb = b[i].toString() + const d = xa.localeCompare(xb, 'en', {numeric: true}) + if (d !== 0) return d; + } + return 0 + }) + .map((p) => p[1]) +} + + function bindContext(f, context) { const func = f.bind(null, context) const signature = f.toString().replace('context, ', '') func.toString = () => signature return func } +function _initContext() { + // @ts-expect-error + Array.zip = array_zip + // @ts-expect-error + Array.prototype.unique = array_unique + // @ts-expect-error + Array.prototype.groupby = array_groupby + // @ts-expect-error + Array.prototype.sorted = array_sorted +} export function getTemplateTagsDatesContext() { const todayObj = dayjs().startOf('second') @@ -560,6 +607,11 @@ export function getTemplateTagsContext(context: ILogseqContext) { const datesContext = getTemplateTagsDatesContext() return new Context({ + _: { + init: _initContext, + array_zip, + }, + ref, bref, tag, embed, empty, bool, when, fill, zeros, spaces,