import { SpecifyDesignTokenTypeName, TokenState } from '@specifyapp/specify-design-token-format'; import { extname } from 'node:path'; import { matchJsonValue } from './jsonValueMatcher.js'; import { parseMustacheTemplate } from './parseMustacheTemplate.js'; import { SpecifyError, specifyErrors } from '../../errors/index.js'; export const DEFAULT_FILENAME_TEMPLATE_WITH_MODES = '{{parents}}/{{name}}-{{mode}}{{extension}}'; export const DEFAULT_FILENAME_TEMPLATE_WITHOUT_MODES = '{{parents}}/{{name}}{{extension}}'; export const filenameTemplateVariables = ['parents', 'name', 'mode', 'extension'] as const; export const filenameTemplateVariablesByTokenType = new Map>([ ['textStyle', ['font.family', 'font.style', 'font.weight']], ['font', ['family', 'style', 'weight']], ['bitmap', ['format', 'width', 'height', 'variationLabel']], ]); type MapMatcher = Map< string, (tokenState: TokenState, mode: string, url: string) => string | number | undefined >; const commonMatchers: MapMatcher = new Map([ [ 'parents', tokenState => tokenState.path .toArray() .slice(0, tokenState.path.length - 1) .join('/'), ], ['name', tokenState => tokenState.name], ['mode', (_, mode) => mode], ['extension', (_, __, url) => extname(url)], ]); function getMatchers(tokenState: TokenState) { const variables = filenameTemplateVariablesByTokenType.get(tokenState.type) ?? []; return new Map([ ...commonMatchers, ...variables.reduce((matchers, variable) => { return matchers.set(variable, (tokenState, mode) => matchJsonValue( { // @ts-ignore [tokenState.type]: value => variable.split('.').reduce((acc, key) => acc[key], value), }, _ => '', tokenState, mode, ), ); }, new Map() as MapMatcher), ]); } export function validateFilenameTemplateVariables( variables: Array, tokenType?: SpecifyDesignTokenTypeName, ): Array { const availableVariables = [ ...filenameTemplateVariables, ...(filenameTemplateVariablesByTokenType.get(tokenType ?? '') ?? []), ]; return variables.map(variable => { if (availableVariables.includes(variable) === false) { let publicMessage = `"${variable}" is not a valid variable for the filenameTemplate option.`; if (tokenType) { publicMessage += ` Available variables for token type "${tokenType}": ${availableVariables.join( ', ', )}`; } throw new SpecifyError({ errorKey: specifyErrors.PARSERS_ENGINE_INVALID_OPTION.errorKey, publicMessage, }); } return variable; }); } export function makeFilename( filenameTemplate: string, tokenState: TokenState, mode: string, url: string, ) { const templateVariables = validateFilenameTemplateVariables( parseMustacheTemplate(filenameTemplate), tokenState.type, ); // @ts-ignore - Expression produces a union type that is too complex to represent. const matchers = getMatchers(tokenState); return templateVariables.reduce((filenameTemplate, variable) => { const value = matchers.get(variable)?.(tokenState, mode, url); return filenameTemplate.replaceAll(`{{${variable}}}`, `${value ?? ''}`); }, filenameTemplate); }