import { specifyErrors, SpecifyError } from '../../../errors/index.js'; import { ResolvableTopLevelAlias, ResolvableValueLevelAlias, ResolvableModeLevelAlias, TokenState, } from '@specifyapp/specify-design-token-format'; import { capitalize, lowerFirst, upperFirst } from 'lodash-es'; function sanitizeName(name: string) { return name.replace(/[\/\| \\~,@#%^&*(){}|[\]?<>"'+=!-]/g, '_'); } enum Variable { MODE = 'mode', PATH = 'path', TOKEN = 'token', } export const DEFAULT_TEMPLATE = '{{path}}{{token}}{{mode}}'; 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) .map((v, i) => (i === 0 ? v.toLowerCase() : capitalize(v))); 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; let isFirstApplied = true; for (let i = 0; i < variables.length; ++i) { const variable = variables[i]; const regexp = new RegExp(`{{${variable}}}`); const currentName = data[variable as Variable]; let name; let isPath = false; if (i === 0 && Array.isArray(currentName) && currentName.length > 0) { name = lowerFirst(currentName.join('')); isPath = true; } else if (Array.isArray(currentName)) { name = upperFirst(currentName.join('')); isPath = true; } else if (i === 0) { name = currentName?.toLowerCase(); } else { name = capitalize(currentName); } const sanitized = sanitizeName(name ?? ''); rendered = rendered.replace( regexp, isFirstApplied && !isPath ? sanitized.toLowerCase() : sanitized, ); if (name !== '') { isFirstApplied = false; } } return rendered; }; 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); };