From ebaa285a8b047f3c15eeedb24ce92e1a2b23c4f8 Mon Sep 17 00:00:00 2001 From: jd-solanki Date: Wed, 28 Jun 2023 13:09:30 +0530 Subject: [PATCH 1/6] feat(namedTemplate): new function --- src/string.test.ts | 42 +++++++++++++++++++++++++++++++++++++++++- src/string.ts | 19 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/string.test.ts b/src/string.test.ts index 6b5cc64..e1dd323 100644 --- a/src/string.test.ts +++ b/src/string.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import { capitalize, ensurePrefix, ensureSuffix, slash, template } from './string' +import { capitalize, ensurePrefix, ensureSuffix, namedTemplate, slash, template } from './string' it('template', () => { expect( @@ -34,6 +34,46 @@ it('template', () => { ).toEqual('Hi') }) +it('namedTemplate', () => { + expect( + namedTemplate( + '{greet}! My name is {name}.', + { greet: 'Hello', name: 'Anthony' }, + ), + ).toEqual('Hello! My name is Anthony.') + + expect( + namedTemplate( + '{a} + {b} = {result}', + { a: 1, b: 2, result: 3 } + ), + ).toEqual('1 + 2 = 3') + + // Without fallback return the variable name + expect( + namedTemplate( + '{10}', + {}, + ), + ).toEqual('10') + + expect( + namedTemplate( + '{10}', + {}, + 'unknown' + ), + ).toEqual('unknown') + + expect( + namedTemplate( + '{10}', + {}, + '' + ), + ).toEqual('') +}) + it('slash', () => { expect(slash('\\123')).toEqual('/123') expect(slash('\\\\')).toEqual('//') diff --git a/src/string.ts b/src/string.ts index 971a513..4d8c2e5 100644 --- a/src/string.ts +++ b/src/string.ts @@ -51,6 +51,25 @@ export function template(str: string, ...args: any[]): string { }) } +/** + * Dead simple named template engine, just like Python's + * + * @category String + * @example + * ``` + * const result = template( + * '{greet}! My name is {name}.', + * { greet: 'Hello', name: 'Anthony' } + * ) // Hello! My name is Anthony. + * ``` + */ +export function namedTemplate(str: string, vars: Record, fallback: null | undefined | string = null): string { + return str.replace( + /{(\w+)}/g, + (_, variable) => vars[variable] || (fallback ?? variable), + ) +} + // port from nanoid // https://github.com/ai/nanoid const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' From 76eaec9b6f21f3ae7df242960896ec33211ecbe5 Mon Sep 17 00:00:00 2001 From: jd-solanki Date: Wed, 28 Jun 2023 13:17:13 +0530 Subject: [PATCH 2/6] chore: desc updated --- src/string.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/string.ts b/src/string.ts index 4d8c2e5..e4c0545 100644 --- a/src/string.ts +++ b/src/string.ts @@ -52,7 +52,8 @@ export function template(str: string, ...args: any[]): string { } /** - * Dead simple named template engine, just like Python's + * Similar to `template` function, but with named variables for better readability + * If not fallback is provided, the variable name will be used by default * * @category String * @example From d3df16237286e52734451cf3cac57ecd7a529ae4 Mon Sep 17 00:00:00 2001 From: jd-solanki Date: Wed, 28 Jun 2023 13:17:57 +0530 Subject: [PATCH 3/6] chore: fix typo --- src/string.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string.ts b/src/string.ts index e4c0545..8d8eef8 100644 --- a/src/string.ts +++ b/src/string.ts @@ -53,7 +53,7 @@ export function template(str: string, ...args: any[]): string { /** * Similar to `template` function, but with named variables for better readability - * If not fallback is provided, the variable name will be used by default + * If no fallback is provided, variable name will be used by default * * @category String * @example From eebabed17bdf450bac46d10f12fd5d088c0bdd02 Mon Sep 17 00:00:00 2001 From: jd-solanki Date: Wed, 28 Jun 2023 13:18:35 +0530 Subject: [PATCH 4/6] chore: fix typo in comment --- src/string.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/string.ts b/src/string.ts index 8d8eef8..f3807a1 100644 --- a/src/string.ts +++ b/src/string.ts @@ -58,7 +58,7 @@ export function template(str: string, ...args: any[]): string { * @category String * @example * ``` - * const result = template( + * const result = namedTemplate( * '{greet}! My name is {name}.', * { greet: 'Hello', name: 'Anthony' } * ) // Hello! My name is Anthony. From 698e98e68e2111ce2a721b7369798f6bf342f19c Mon Sep 17 00:00:00 2001 From: jd-solanki Date: Thu, 29 Jun 2023 11:53:54 +0530 Subject: [PATCH 5/6] feat: merged namedTemplate with template function --- src/string.test.ts | 26 ++++++++++++++++++------ src/string.ts | 49 +++++++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/string.test.ts b/src/string.test.ts index e1dd323..38e70f7 100644 --- a/src/string.test.ts +++ b/src/string.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import { capitalize, ensurePrefix, ensureSuffix, namedTemplate, slash, template } from './string' +import { capitalize, ensurePrefix, ensureSuffix, slash, template } from './string' it('template', () => { expect( @@ -36,14 +36,14 @@ it('template', () => { it('namedTemplate', () => { expect( - namedTemplate( + template( '{greet}! My name is {name}.', { greet: 'Hello', name: 'Anthony' }, ), ).toEqual('Hello! My name is Anthony.') expect( - namedTemplate( + template( '{a} + {b} = {result}', { a: 1, b: 2, result: 3 } ), @@ -51,14 +51,28 @@ it('namedTemplate', () => { // Without fallback return the variable name expect( - namedTemplate( + template( '{10}', {}, ), ).toEqual('10') expect( - namedTemplate( + template( + '{11}', + null, + ), + ).toEqual('undefined') + + expect( + template( + '{11}', + undefined, + ), + ).toEqual('undefined') + + expect( + template( '{10}', {}, 'unknown' @@ -66,7 +80,7 @@ it('namedTemplate', () => { ).toEqual('unknown') expect( - namedTemplate( + template( '{10}', {}, '' diff --git a/src/string.ts b/src/string.ts index f3807a1..2a44c78 100644 --- a/src/string.ts +++ b/src/string.ts @@ -1,3 +1,5 @@ +import { isObject } from "./is" + /** * Replace backslash to slash * @@ -31,6 +33,8 @@ export function ensureSuffix(suffix: string, str: string) { /** * Dead simple template engine, just like Python's `.format()` + * Support passing variables as either in index based or object/name based approach + * While using object/name based approach, you can pass a fallback value as the third argument * * @category String * @example @@ -41,34 +45,35 @@ export function ensureSuffix(suffix: string, str: string) { * 'Anthony' * ) // Hello Inès! My name is Anthony. * ``` - */ -export function template(str: string, ...args: any[]): string { - return str.replace(/{(\d+)}/g, (match, key) => { - const index = Number(key) - if (Number.isNaN(index)) - return match - return args[index] - }) -} - -/** - * Similar to `template` function, but with named variables for better readability - * If no fallback is provided, variable name will be used by default - * - * @category String - * @example - * ``` + * +* ``` * const result = namedTemplate( * '{greet}! My name is {name}.', * { greet: 'Hello', name: 'Anthony' } * ) // Hello! My name is Anthony. * ``` + * + * * const result = namedTemplate( + * '{greet}! My name is {name}.', + * { greet: 'Hello' }, // name isn't passed hence fallback will be used for name + * 'placeholder' + * ) // Hello! My name is placeholder. + * ``` */ -export function namedTemplate(str: string, vars: Record, fallback: null | undefined | string = null): string { - return str.replace( - /{(\w+)}/g, - (_, variable) => vars[variable] || (fallback ?? variable), - ) +export function template(str: string, ...args: any[]): string { + const [firstArg, fallback] = args + + if (isObject(firstArg)) { + const vars = firstArg as Record + return str.replace(/{(\w+)}/g, (_, key) => vars[key] || (fallback ?? key)) + } else { + return str.replace(/{(\d+)}/g, (_, key) => { + const index = Number(key) + if (Number.isNaN(index)) + return key + return args[index] + }) + } } // port from nanoid From 78bca90e91e9f3611a262fc7e8abb6768554e873 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 30 Jun 2023 13:00:32 +0200 Subject: [PATCH 6/6] chore: update --- src/string.test.ts | 20 ++++++++++++++------ src/string.ts | 15 +++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/string.test.ts b/src/string.test.ts index 38e70f7..fdc89de 100644 --- a/src/string.test.ts +++ b/src/string.test.ts @@ -15,6 +15,7 @@ it('template', () => { '{0} + {1} = {2}{3}', 1, '1', + // @ts-expect-error disallow non-literal on type { v: 2 }, [2, 3], ), @@ -45,10 +46,17 @@ it('namedTemplate', () => { expect( template( '{a} + {b} = {result}', - { a: 1, b: 2, result: 3 } + { a: 1, b: 2, result: 3 }, ), ).toEqual('1 + 2 = 3') + expect( + template( + '{1} + {b} = 3', + { 1: 'a', b: 2 }, + ), + ).toEqual('a + 2 = 3') + // Without fallback return the variable name expect( template( @@ -75,17 +83,17 @@ it('namedTemplate', () => { template( '{10}', {}, - 'unknown' + 'unknown', ), ).toEqual('unknown') expect( template( - '{10}', - {}, - '' + '{1} {2} {3} {4}', + { 4: 'known key' }, + k => String(+k * 2), ), - ).toEqual('') + ).toEqual('2 4 6 known key') }) it('slash', () => { diff --git a/src/string.ts b/src/string.ts index 2a44c78..d8274be 100644 --- a/src/string.ts +++ b/src/string.ts @@ -1,4 +1,4 @@ -import { isObject } from "./is" +import { isObject } from './is' /** * Replace backslash to slash @@ -45,14 +45,14 @@ export function ensureSuffix(suffix: string, str: string) { * 'Anthony' * ) // Hello Inès! My name is Anthony. * ``` - * + * * ``` * const result = namedTemplate( * '{greet}! My name is {name}.', * { greet: 'Hello', name: 'Anthony' } * ) // Hello! My name is Anthony. * ``` - * + * * * const result = namedTemplate( * '{greet}! My name is {name}.', * { greet: 'Hello' }, // name isn't passed hence fallback will be used for name @@ -60,13 +60,16 @@ export function ensureSuffix(suffix: string, str: string) { * ) // Hello! My name is placeholder. * ``` */ +export function template(str: string, object: Record, fallback?: string | ((key: string) => string)): string +export function template(str: string, ...args: (string | number | BigInt | undefined | null)[]): string export function template(str: string, ...args: any[]): string { const [firstArg, fallback] = args - + if (isObject(firstArg)) { const vars = firstArg as Record - return str.replace(/{(\w+)}/g, (_, key) => vars[key] || (fallback ?? key)) - } else { + return str.replace(/{([\w\d]+)}/g, (_, key) => vars[key] || ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key)) + } + else { return str.replace(/{(\d+)}/g, (_, key) => { const index = Number(key) if (Number.isNaN(index))