import { ResolvableTopLevelAlias, ResolvableValueLevelAlias, ResolvableModeLevelAlias, TokenState, } from '@specifyapp/specify-design-token-format'; import { SpecifyError, specifyErrors } from '../../../errors/index.js'; function sanitizeName(name: string) { return name.replace(/[\/\| \\~,@#%^&*(){}|[\]?<>"'+=!]/g, '-'); } enum Variable { MODE = 'mode', PATH = 'path', TOKEN = 'token', } export const DEFAULT_TEMPLATE = '{{path}}-{{token}}-{{mode}}'; export const DEFAULT_TEMPLATE_WITHOUT_MODE = '{{path}}-{{token}}'; const parse = (template: string) => (template.match(/{{(.*?)}}/g) ?? []).map(v => v.replace(/{|}/g, '')); export const validateTemplate = (template: string) => parse(template).forEach(variable => { if (![Variable.PATH, Variable.MODE, Variable.TOKEN].includes(variable as Variable)) throw new SpecifyError({ errorKey: specifyErrors.PARSERS_ENGINE_INVALID_OPTION.errorKey, publicMessage: `'${variable}' is not a valid variable for the 'template' option.`, }); }); export const dataOfToken = (token: TokenState, mode: string | undefined) => { const path = token.path.toArray().slice(0, token.path.length - 1); return { path, mode, token: token.name, }; }; type RendererData = { [Variable.PATH]: Array; [Variable.MODE]?: string; [Variable.TOKEN]: string; }; export const renderTemplate = (template: string, data: RendererData) => { const variables = parse(template); let rendered = template === DEFAULT_TEMPLATE && !data.mode ? DEFAULT_TEMPLATE_WITHOUT_MODE : template; for (let i = 0; i < variables.length; ++i) { const variable = variables[i]; const regexp = new RegExp(`{{${variable}}}`); const currentName = data[variable as Variable]; const name = Array.isArray(currentName) ? currentName.join('-') : currentName ?? ''; const sanitized = sanitizeName(name); rendered = rendered.replace(regexp, sanitized); } // Remove starting - if it exists return rendered.replace(/^-/, ''); }; export type templateRenderer = (data: RendererData) => string; export type aliasRenderer = ( alias: ResolvableTopLevelAlias | ResolvableModeLevelAlias | ResolvableValueLevelAlias, ) => string; export const makeRenderer = (template: string): templateRenderer => (data: RendererData) => { return renderTemplate(template, data); };