Skip to content

Commit c7bbe2d

Browse files
jd-solankiantfu
andauthored
feat(template): support object style templates (#34)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
1 parent 80fefe0 commit c7bbe2d

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

src/string.test.ts

+62
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ it('template', () => {
1515
'{0} + {1} = {2}{3}',
1616
1,
1717
'1',
18+
// @ts-expect-error disallow non-literal on type
1819
{ v: 2 },
1920
[2, 3],
2021
),
@@ -34,6 +35,67 @@ it('template', () => {
3435
).toEqual('Hi')
3536
})
3637

38+
it('namedTemplate', () => {
39+
expect(
40+
template(
41+
'{greet}! My name is {name}.',
42+
{ greet: 'Hello', name: 'Anthony' },
43+
),
44+
).toEqual('Hello! My name is Anthony.')
45+
46+
expect(
47+
template(
48+
'{a} + {b} = {result}',
49+
{ a: 1, b: 2, result: 3 },
50+
),
51+
).toEqual('1 + 2 = 3')
52+
53+
expect(
54+
template(
55+
'{1} + {b} = 3',
56+
{ 1: 'a', b: 2 },
57+
),
58+
).toEqual('a + 2 = 3')
59+
60+
// Without fallback return the variable name
61+
expect(
62+
template(
63+
'{10}',
64+
{},
65+
),
66+
).toEqual('10')
67+
68+
expect(
69+
template(
70+
'{11}',
71+
null,
72+
),
73+
).toEqual('undefined')
74+
75+
expect(
76+
template(
77+
'{11}',
78+
undefined,
79+
),
80+
).toEqual('undefined')
81+
82+
expect(
83+
template(
84+
'{10}',
85+
{},
86+
'unknown',
87+
),
88+
).toEqual('unknown')
89+
90+
expect(
91+
template(
92+
'{1} {2} {3} {4}',
93+
{ 4: 'known key' },
94+
k => String(+k * 2),
95+
),
96+
).toEqual('2 4 6 known key')
97+
})
98+
3799
it('slash', () => {
38100
expect(slash('\\123')).toEqual('/123')
39101
expect(slash('\\\\')).toEqual('//')

src/string.ts

+34-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isObject } from './is'
2+
13
/**
24
* Replace backslash to slash
35
*
@@ -31,6 +33,8 @@ export function ensureSuffix(suffix: string, str: string) {
3133

3234
/**
3335
* Dead simple template engine, just like Python's `.format()`
36+
* Support passing variables as either in index based or object/name based approach
37+
* While using object/name based approach, you can pass a fallback value as the third argument
3438
*
3539
* @category String
3640
* @example
@@ -41,14 +45,38 @@ export function ensureSuffix(suffix: string, str: string) {
4145
* 'Anthony'
4246
* ) // Hello Inès! My name is Anthony.
4347
* ```
48+
*
49+
* ```
50+
* const result = namedTemplate(
51+
* '{greet}! My name is {name}.',
52+
* { greet: 'Hello', name: 'Anthony' }
53+
* ) // Hello! My name is Anthony.
54+
* ```
55+
*
56+
* * const result = namedTemplate(
57+
* '{greet}! My name is {name}.',
58+
* { greet: 'Hello' }, // name isn't passed hence fallback will be used for name
59+
* 'placeholder'
60+
* ) // Hello! My name is placeholder.
61+
* ```
4462
*/
63+
export function template(str: string, object: Record<string | number, any>, fallback?: string | ((key: string) => string)): string
64+
export function template(str: string, ...args: (string | number | BigInt | undefined | null)[]): string
4565
export function template(str: string, ...args: any[]): string {
46-
return str.replace(/{(\d+)}/g, (match, key) => {
47-
const index = Number(key)
48-
if (Number.isNaN(index))
49-
return match
50-
return args[index]
51-
})
66+
const [firstArg, fallback] = args
67+
68+
if (isObject(firstArg)) {
69+
const vars = firstArg as Record<string, any>
70+
return str.replace(/{([\w\d]+)}/g, (_, key) => vars[key] || ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))
71+
}
72+
else {
73+
return str.replace(/{(\d+)}/g, (_, key) => {
74+
const index = Number(key)
75+
if (Number.isNaN(index))
76+
return key
77+
return args[index]
78+
})
79+
}
5280
}
5381

5482
// port from nanoid

0 commit comments

Comments
 (0)