mirror of
https://github.com/zoriya/typebox.git
synced 2026-06-01 10:35:14 +00:00
Template Literal DSL
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/template-dsl
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
export * from './template-dsl'
|
||||
@@ -0,0 +1,31 @@
|
||||
# TemplateLiteral DSL
|
||||
|
||||
This example implements a small string based DSL for dynamically parsing strings into `TTemplateLiteral` types at runtime. The `template-dsl.ts` script provided with this example contains the full implementation. It can be copied and pasted into projects with TypeBox 0.28.0 or higher installed.
|
||||
|
||||
The example is a candiate for possible inclusion in TypeBox under a `Type.TemplateLiteral(template_dsl_string)` overloaded type function.
|
||||
|
||||
## Example
|
||||
|
||||
The DSL supports a similar syntax to TypeScript template literal type syntax. The following shows general usage.
|
||||
|
||||
```typescript
|
||||
import { Static } from '@sinclair/typebox'
|
||||
import { TemplateLiteral } from './template-dsl'
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Path
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
const Path = TemplateLiteral('/users/${number}/posts/${string}')
|
||||
|
||||
type Path = Static<typeof Path> // type Path = '/users/${number}/posts/${string}'
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Bytes
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
const Byte = TemplateLiteral('${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}${0|1}')
|
||||
|
||||
type Byte = Static<typeof Byte> // type Byte = '00000000' | '00000001' | '00000010' ... | '11111111'
|
||||
```
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
|
||||
@sinclair/typebox/template-dsl
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------*/
|
||||
|
||||
import { Type, TTemplateLiteral, TLiteral, TTemplateLiteralKind, TNumber, TString, TBoolean, TBigInt, Assert, Ensure, UnionType } from '@sinclair/typebox'
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
export type Trim<T> = T extends `${' '}${infer U}` ? Trim<U> : T extends `${infer U}${' '}` ? Trim<U> : T
|
||||
// -------------------------------------------------------------------------
|
||||
// TTemplateLiteralParser
|
||||
// -------------------------------------------------------------------------
|
||||
// prettier-ignore
|
||||
export type TTemplateLiteralParserUnionLiteral<T extends string> =
|
||||
T extends `${infer L}|${infer R}` ? [TLiteral<Trim<L>>, ...TTemplateLiteralParserUnionLiteral<R>] :
|
||||
T extends `${infer L}` ? [TLiteral<Trim<L>>] :
|
||||
[]
|
||||
export type TTemplateLiteralParserUnion<T extends string> = UnionType<TTemplateLiteralParserUnionLiteral<T>>
|
||||
// prettier-ignore
|
||||
export type TTemplateLiteralParserTerminal<T extends string> =
|
||||
T extends 'boolean' ? TBoolean :
|
||||
T extends 'bigint' ? TBigInt :
|
||||
T extends 'number' ? TNumber :
|
||||
T extends 'string' ? TString :
|
||||
TTemplateLiteralParserUnion<T>
|
||||
// prettier-ignore
|
||||
export type TTemplateLiteralParserTemplate<T extends string> =
|
||||
T extends `{${infer L}}${infer R}` ? [TTemplateLiteralParserTerminal<L>, ...TTemplateLiteralParserTemplate<R>] :
|
||||
T extends `${infer L}$${infer R}` ? [TLiteral<L>, ...TTemplateLiteralParserTemplate<R>] :
|
||||
T extends `${infer L}` ? [TLiteral<L>] :
|
||||
[]
|
||||
export type TTemplateLiteralParser<T extends string> = Ensure<TTemplateLiteral<Assert<TTemplateLiteralParserTemplate<T>, TTemplateLiteralKind[]>>>
|
||||
// ---------------------------------------------------------------------
|
||||
// TemplateLiteralParser
|
||||
// ---------------------------------------------------------------------
|
||||
namespace TemplateLiteralParser {
|
||||
export function * ParseUnion(template: string): IterableIterator<TTemplateLiteralKind> {
|
||||
const trim = template.trim()
|
||||
if(trim === 'boolean') return yield Type.Boolean()
|
||||
if(trim === 'number') return yield Type.Number()
|
||||
if(trim === 'bigint') return yield Type.BigInt()
|
||||
if(trim === 'string') return yield Type.String()
|
||||
const literals = trim.split('|').map(literal => Type.Literal(literal.trim()))
|
||||
return yield literals.length === 0 ? Type.Never() : literals.length === 1 ? literals[0] : Type.Union(literals)
|
||||
}
|
||||
export function * ParseTerminal(template: string): IterableIterator<TTemplateLiteralKind> {
|
||||
if(template[1] !== '{') {
|
||||
const L = Type.Literal('$')
|
||||
const R = ParseLiteral(template.slice(1))
|
||||
return yield * [L, ...R]
|
||||
}
|
||||
for(let i = 2; i < template.length; i++) {
|
||||
if(template[i] === '}') {
|
||||
const L = ParseUnion(template.slice(2, i))
|
||||
const R = ParseLiteral(template.slice(i+1))
|
||||
return yield * [...L, ...R]
|
||||
}
|
||||
}
|
||||
yield Type.Literal(template)
|
||||
}
|
||||
export function * ParseLiteral(template: string): IterableIterator<TTemplateLiteralKind> {
|
||||
for(let i = 0; i < template.length; i++) {
|
||||
if(template[i] === '$') {
|
||||
const L = Type.Literal(template.slice(0, i))
|
||||
const R = ParseTerminal(template.slice(i))
|
||||
return yield * [L, ...R]
|
||||
}
|
||||
}
|
||||
yield Type.Literal(template)
|
||||
}
|
||||
export function Parse(template: string): TTemplateLiteral {
|
||||
return Type.TemplateLiteral([...ParseLiteral(template)])
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// TemplateLiteral DSL
|
||||
// ------------------------------------------------------------------------------------------
|
||||
export function TemplateLiteral<T extends string>(template: T): TTemplateLiteralParser<T> {
|
||||
return TemplateLiteralParser.Parse(template)
|
||||
}
|
||||
Reference in New Issue
Block a user