Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(template): support object style templates #34

Merged
merged 6 commits into from
Jun 30, 2023
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