import { SDTF_PATH_SEPARATOR, TokenState } from '@specifyapp/specify-design-token-format'; import { isEmpty, merge } from 'lodash-es'; type nestedValueWithToken = { [tokenName: string]: T }; /** * `path` is expected to be a object key path separated by `.`, * rather than working with nested object, everything will be nested at the end. * Useful and when working with groups * Example: * - `hello` -> `{ hello: { ... } }` * - `hello.world` -> `{ hello: { world: { ... } } }` */ type flatNestedObject = { [path: string]: nestedValueWithToken | T; }; export function toNestedObject(flatObject: flatNestedObject) { return Object.entries(flatObject).reduce((acc, [path, token]) => { const nestedObject = path .split(SDTF_PATH_SEPARATOR) .reverse() .reduce((acc, key) => (isEmpty(acc) ? { [key]: token } : { [key]: acc }), {}); return merge(acc, nestedObject); }, {}); } export function makeDefaultPath(token: TokenState) { return token.path .toArray() .slice(0, token.path.length - 1) .join(SDTF_PATH_SEPARATOR); } type convertTokenReturn = { value: T; path?: string; name?: string; }; export function tokensToFlatObject( tokens: Array, convertToken: ( token: TokenState, ) => convertTokenReturn | Array> | undefined, ): flatNestedObject { return tokens.reduce((acc, token) => { const convertedToken = convertToken(token); if (convertedToken === undefined || convertedToken === null) return acc; const defaultPath = makeDefaultPath(token); if (Array.isArray(convertedToken)) { for (let i = 0; i < convertedToken.length; i++) { const { value, path, name } = convertedToken[i]; const resolvedPath = path ?? defaultPath; if (resolvedPath !== '') { acc[resolvedPath] ??= {}; (acc[path ?? defaultPath] as nestedValueWithToken)[name ?? token.name] = value; } else { acc[name ?? token.name] = value; } } } else { const resolvedPath = convertedToken.path ?? defaultPath; if (resolvedPath !== '') { acc[convertedToken.path ?? defaultPath] ??= {}; (acc[convertedToken.path ?? defaultPath] as nestedValueWithToken)[ convertedToken.name ?? token.name ] = convertedToken.value; } else { acc[convertedToken.name ?? token.name] = convertedToken.value; } } return acc; }, {} as flatNestedObject); }