Skip to content

Commit

Permalink
feat(template): support object style templates (#34)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
jd-solanki and antfu committed Jun 30, 2023
1 parent 80fefe0 commit c7bbe2d
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 6 deletions.
62 changes: 62 additions & 0 deletions src/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ it('template', () => {
'{0} + {1} = {2}{3}',
1,
'1',
// @ts-expect-error disallow non-literal on type
{ v: 2 },
[2, 3],
),
Expand All @@ -34,6 +35,67 @@ it('template', () => {
).toEqual('Hi')
})

it('namedTemplate', () => {
expect(
template(
'{greet}! My name is {name}.',
{ greet: 'Hello', name: 'Anthony' },
),
).toEqual('Hello! My name is Anthony.')

expect(
template(
'{a} + {b} = {result}',
{ 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(
'{10}',
{},
),
).toEqual('10')

expect(
template(
'{11}',
null,
),
).toEqual('undefined')

expect(
template(
'{11}',
undefined,
),
).toEqual('undefined')

expect(
template(
'{10}',
{},
'unknown',
),
).toEqual('unknown')

expect(
template(
'{1} {2} {3} {4}',
{ 4: 'known key' },
k => String(+k * 2),
),
).toEqual('2 4 6 known key')
})

it('slash', () => {
expect(slash('\\123')).toEqual('/123')
expect(slash('\\\\')).toEqual('//')
Expand Down
40 changes: 34 additions & 6 deletions src/string.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isObject } from './is'

/**
* Replace backslash to slash
*
Expand Down Expand Up @@ -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
Expand All @@ -41,14 +45,38 @@ 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
* 'placeholder'
* ) // Hello! My name is placeholder.
* ```
*/
export function template(str: string, object: Record<string | number, any>, 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 {
return str.replace(/{(\d+)}/g, (match, key) => {
const index = Number(key)
if (Number.isNaN(index))
return match
return args[index]
})
const [firstArg, fallback] = args

if (isObject(firstArg)) {
const vars = firstArg as Record<string, any>
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))
return key
return args[index]
})
}
}

// port from nanoid
Expand Down

0 comments on commit c7bbe2d

Please sign in to comment.