Skip to content

Commit 838245b

Browse files
committed
Add basic valibot template wrapper
1 parent e4f84ff commit 838245b

4 files changed

Lines changed: 48 additions & 24 deletions

File tree

backend/src/common/template/formatting.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { formatUser, formatUserBold } from "../../bot/common/discord/format.ts";
22
import { escapeMarkdown, makeMarkdownInlineCodeblock, makeMarkdownMultilineCodeblock, makeMarkdownQuote } from "../../bot/common/discord/markdown.ts";
33
import { dateToUnixSeconds, humanizeDuration } from "../time.ts";
4-
import { DurationPresentationType, FormattingWrapper, ParameterType, TimestampPresentationType, UserPresentationType, type AnyParameterValue, type UserParameter } from "./index.ts";
4+
import { DurationPresentationType, FormattingWrapper, ParameterType, TimestampPresentationType, UserPresentationType, type ParameterRecord, type UserParameter } from "./index.ts";
55
import { TokenType, type Token } from "./parsing.ts";
66

7-
export function format(params: Record<string, AnyParameterValue>, tokens: Token[]): string {
7+
export function formatTokens(params: ParameterRecord, tokens: Token[]): string {
88
let result = "";
99

1010
for (const token of tokens) {

backend/src/common/template/index.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
1-
import { format } from "./formatting.ts";
2-
import { parseTemplateTokens, type Token } from "./parsing.ts";
1+
/*
2+
* Parsing and formatting for templates for config values like 'Hello {{user#tag}}'.
3+
*
4+
* The message is split in to "tokens" and validated based on the passed schema.
5+
* This is a loose usage of the term, but in this case tokens consist of:
6+
* - Any literal text
7+
* - Parsed expressions inside {{}}
8+
*
9+
* These tokens are then rejoined in the formatting stage, based on the passed parameters.
10+
*/
311

4-
export class Template<P extends Record<string, ParameterType>> {
5-
private _tokens: Token[];
12+
import { formatTokens } from "./formatting.ts";
13+
import { parseTemplateTokens } from "./parsing.ts";
614

7-
public static parse<P extends Record<string, ParameterType>>(template: string, params: P): Template<P> | string {
8-
const tokens = parseTemplateTokens(template, params);
15+
/**
16+
* @returns A function to call to format data with the template,
17+
* or an error denoting something that went wrong with parsing.
18+
*/
19+
export function parseTemplate<S extends TemplateSchema>(template: string, schema: S): ((params: ParameterRecord<S>) => string) | string {
20+
// just a typed wrapper
21+
const tokens = parseTemplateTokens(template, schema);
922

10-
if (typeof tokens === "string")
11-
return tokens;
23+
if (typeof tokens === "string")
24+
return tokens;
1225

13-
return new Template(tokens);
14-
}
15-
16-
private constructor(tokens: Token[]) {
17-
this._tokens = tokens;
18-
}
19-
20-
public format(params: { readonly [K in keyof P]: ParameterValue<P[K]> }): string {
21-
return format(params, this._tokens);
22-
}
26+
return params => formatTokens(params, tokens);
2327
}
2428

29+
export type TemplateSchema = Record<string, ParameterType>;
30+
export type ParameterRecord<S extends Record<string, ParameterType> = any> = { readonly [K in keyof S]: ParameterValue<S[K]> };
31+
2532
export const enum FormattingWrapper {
2633
BlockQuote,
2734
InlineCodeblock,
@@ -65,8 +72,6 @@ export const enum TimestampPresentationType {
6572

6673
export type UserParameter = { id: string; tag: string; };
6774

68-
export type AnyParameterValue = ParameterValue<any>;
69-
7075
type ParameterValue<T extends ParameterType = any> =
7176
T extends ParameterType.User ? UserParameter :
7277
T extends ParameterType.RawString ? string :

backend/src/common/template/parsing.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface FormatGroups {
88
presentation?: string;
99
};
1010

11-
export function parseTemplateTokens(template: string, params: Record<string, ParameterType>): Token[] | string {
11+
export function parseTemplateTokens(template: string, schema: Record<string, ParameterType>): Token[] | string {
1212
const result: Token[] = [];
1313

1414
let match = FORMAT_PATTERN.exec(template);
@@ -23,7 +23,7 @@ export function parseTemplateTokens(template: string, params: Record<string, Par
2323
}
2424

2525
const groups = match.groups as unknown as FormatGroups;
26-
const token = parseFormatToken(groups, params);
26+
const token = parseFormatToken(groups, schema);
2727

2828
if (typeof token === "string")
2929
return token;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { pipe, rawTransform, string } from "valibot";
2+
import { parseTemplate, type TemplateSchema } from "../../common/template/index.ts";
3+
4+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
5+
export function template<S extends TemplateSchema>(schema: S) {
6+
return pipe(string(), rawTransform(({ dataset, addIssue, NEVER }) => {
7+
if (!dataset.typed)
8+
return NEVER;
9+
10+
const template = parseTemplate(dataset.value, schema);
11+
12+
if (typeof template === "string") {
13+
addIssue({ message: template });
14+
return NEVER;
15+
}
16+
17+
return template;
18+
}));
19+
}

0 commit comments

Comments
 (0)