sd-wildcards-utils
Version:
Parse Stable Diffusion wildcards source to a YAML object.
136 lines (115 loc) • 3.7 kB
text/typescript
import { isMatch } from 'picomatch';
import {
ICachesFindPath,
IFindPathEntry,
IOptionsFind,
IRecordWildcards,
IVisitPathsListReadonly,
IWildcardsYAMLDocument,
} from './types';
import { Document, isDocument } from 'yaml';
import { PicomatchOptions } from 'picomatch';
export function pathsToWildcardsPath(paths: IVisitPathsListReadonly, full?: boolean)
{
let s = paths.join('/');
if (full)
{
s = `__${s}__`
}
return s
}
export function pathsToDotPath(paths: IVisitPathsListReadonly)
{
return paths.join('.');
}
/**
* Recursively searches for a path in a nested object or array structure.
*/
export function findPath(data: IRecordWildcards | Document | IWildcardsYAMLDocument,
paths: string[],
findOpts?: IOptionsFind,
prefix: string[] = [],
list: IFindPathEntry[] = []
)
{
findOpts ??= {};
prefix ??= [];
list ??= [];
let _cache: ICachesFindPath = {
paths: paths.slice(),
findOpts,
prefix,
globOpts: findPathOptionsToGlobOptions(findOpts),
}
if (isDocument(data))
{
// @ts-ignore
_cache.data = data;
data = data.toJSON() as IRecordWildcards;
}
return _findPathCore(data, paths.slice(), findOpts, prefix, list, _cache)
}
export function findPathOptionsToGlobOptions(findOpts?: IOptionsFind): PicomatchOptions
{
return {
...findOpts?.globOpts,
ignore: findOpts?.ignore,
} satisfies PicomatchOptions
}
export function _findPathCore(data: IRecordWildcards, paths: string[], findOpts: IOptionsFind, prefix: string[], list: IFindPathEntry[], _cache: ICachesFindPath)
{
paths = paths.slice(); // Create a copy of the paths array to avoid modifying the original array.
const current = paths.shift(); // Remove the first element from the paths array.
const deep = paths.length > 0; // Check if there are remaining paths to search.
for (const key in data)
{
if (findOpts.onlyFirstMatchAll && list.length)
{
break;
}
// Create the current path.
const target = prefix.slice().concat(key);
const search = prefix.slice().concat(current);
// Check if the current key matches the current path element.
//const bool = isMatch(key, current);
const bool = isMatch(pathsToWildcardsPath(target), pathsToWildcardsPath(search), _cache.globOpts);
if (bool)
{
const value = data[key]; // Get the value at the current path.
const notArray = !Array.isArray(value); // Check if the value is not an array.
if (deep)
{
if (notArray && typeof value !== 'string')
{
_findPathCore(value, paths, findOpts, target, list, _cache); // Recursively search for the remaining paths in the nested object or array.
continue;
}
}
else if (!notArray)
{
list.push({
key: target,
value,
}); // Add the found path and its corresponding value to the list.
continue;
}
else if (!deep && _cache.findOpts.allowWildcardsAtEndMatchRecord && current.includes('*') && typeof value === 'object' && value)
{
list.push({
key: target,
value,
}); // Add the found path and its corresponding value to the list.
continue;
}
if (!current.includes('*') || notArray && !deep)
{
throw new TypeError(`Invalid Type. paths: [${target}], isMatch: ${bool}, deep: ${deep}, deep paths: [${paths}], notArray: ${notArray}, match: [${search}], value: ${value}, _cache : ${JSON.stringify(_cache)}`); // Throw an error if the value is not a string and there are remaining paths to search.
}
}
}
if (prefix.length === 0 && findOpts.throwWhenNotFound && !list.length)
{
throw new RangeError(`Invalid Paths. paths: [${[current, ...paths]}], _cache : ${JSON.stringify(_cache)}`);
}
return list; // Return the list of found paths and their corresponding values.
}