Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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