Skip to content

Instantly share code, notes, and snippets.

@JTRNS
Last active June 28, 2024 13:09
Show Gist options
  • Save JTRNS/0a5745e5089c819ef2f96abb9938edd9 to your computer and use it in GitHub Desktop.
Save JTRNS/0a5745e5089c819ef2f96abb9938edd9 to your computer and use it in GitHub Desktop.
Type Safe Templates
type Delimiter = ",\n"|"," | "." | "?" | "!" | "\n"
type IsTemplateTag<Word> = Word extends `{${infer TagName}}${Delimiter}${string}` ? TagName : never;
// type IsTemplateTag<Word> = Word extends `{${infer TagName}}` ? TagName : isTemplateTagWithComma<Word>;
// type isTemplateTagWithComma<Word> = Word extends `{${infer Tagname}},` ? Tagname : isTemplateTagWithPeriod<Word>;
// type isTemplateTagWithPeriod<Word> = Word extends `{${infer Tagname}}.` ? Tagname : never;
type TemplateTags<TemplateString> = TemplateString extends
`${infer PartA} ${infer PartB}` ? IsTemplateTag<PartA> | TemplateTags<PartB>
: IsTemplateTag<TemplateString>;
type TemplateParams<T extends string> = {
[Key in TemplateTags<T>]: {
toString(): string;
};
};
type TemplateRenderFunc<T extends string> = (
params: TemplateParams<T>,
) => string;
export function parseTemplateString<T extends string>(
str: T,
): TemplateRenderFunc<T> {
const tags = [...str.matchAll(/{\w+}/g)].flat();
function render<U extends TemplateParams<T>>(params: U): string {
// check for missing params
if (
tags.some((tag) => !Object.keys(params).includes(tag.slice(1, -1)))
) {
throw new Error(
`Missing template parameter. Required: ${tags.join(", ")} Received: ${
Object.keys(params).join(", ")
}`,
);
}
let result: string = str;
for (const key in params) {
const value = params[key];
result = result.replaceAll(`{${key}}`, value.toString());
}
return result;
}
return render;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment