sd-wildcards-utils
Version:
Parse Stable Diffusion wildcards source to a YAML object.
245 lines (210 loc) • 6.24 kB
text/typescript
import { array_unique_overwrite, defaultChecker } from 'array-hyper-unique';
import { Document, isDocument, isMap, isPair, isScalar, isSeq, Node, ParsedNode, visit, visitor, YAMLMap } from 'yaml';
import {
IOptionsParseDocument,
IOptionsVisitor,
IResultDeepFindSingleRootAt,
IVisitorFnKey,
IVisitPathsList,
IVisitPathsNodeList,
IWildcardsYAMLDocument,
IWildcardsYAMLMapRoot,
IWildcardsYAMLPair,
IWildcardsYAMLScalar,
} from './types';
import { formatPrompts } from './format';
import { isWildcardsYAMLDocument, isWildcardsYAMLMap } from './is';
import { _checkValue } from './valid';
export function visitWildcardsYAML(node: Node | Document | null, visitorOptions: IOptionsVisitor)
{
return visit(node, visitorOptions as visitor)
}
export function defaultCheckerIgnoreCase(a: unknown, b: unknown)
{
if (typeof a === 'string' && typeof b === 'string')
{
a = a.toLowerCase();
b = b.toLowerCase();
}
return defaultChecker(a, b)
}
export function uniqueSeqItemsChecker(a: Node, b: Node)
{
if (isScalar(a) && isScalar(b))
{
return defaultCheckerIgnoreCase(a.value, b.value)
}
return defaultCheckerIgnoreCase(a, b)
}
export function uniqueSeqItems<T extends Node>(items: (T | unknown)[])
{
return array_unique_overwrite(items, {
// @ts-ignore
checker: uniqueSeqItemsChecker,
}) as T[];
}
/**
* This function is used to find a single root node in a YAML structure.
* It traverses the YAML structure and returns the first node that has only one child.
* If the node is a Document, it will start from its contents.
*
* @param node - The YAML node to start the search from.
* @param result - An optional object to store the result.
* @returns - An object containing the paths, key, value, and parent of the found single root node.
* If no single root node is found, it returns the input `result` object.
* @throws - Throws a TypeError if the Document Node is passed as a child node.
*/
export function deepFindSingleRootAt(node: ParsedNode | Document.Parsed | IWildcardsYAMLMapRoot | IWildcardsYAMLDocument,
result?: IResultDeepFindSingleRootAt,
)
{
if (isMap(node) && node.items.length === 1)
{
let child = node.items[0] as IWildcardsYAMLPair;
let key = child.key.value;
let paths = result?.paths?.slice() ?? [];
(paths as any as string[]).push(key);
let value = child.value;
if (isSeq(value))
{
return result
}
return deepFindSingleRootAt(value, {
paths,
key,
value,
parent: node as IWildcardsYAMLMapRoot,
} as const satisfies IResultDeepFindSingleRootAt)
}
else if (isDocument(node))
{
if (result)
{
throw new TypeError(`The Document Node should not as Child Node`)
}
let value = node.contents as IWildcardsYAMLMapRoot;
return deepFindSingleRootAt(value, {
paths: [] as const,
key: void 0,
value,
parent: node as IWildcardsYAMLDocument,
} as const satisfies IResultDeepFindSingleRootAt)
}
return result;
}
export function _handleVisitPathsCore(nodePaths: IVisitPathsNodeList): IWildcardsYAMLPair[]
{
return nodePaths.filter(p => isPair(p)) as any
}
export function convertPairsToPathsList(nodePaths: IWildcardsYAMLPair[])
{
return nodePaths.map(p => p.key.value) as IVisitPathsList
}
/**
* [ 'root', 'root2', 'sub2', 'sub2-2' ]
*/
export function handleVisitPaths(nodePaths: IVisitPathsNodeList)
{
return convertPairsToPathsList(_handleVisitPathsCore(nodePaths))
}
/**
* full paths
*
* [ 'root', 'root2', 'sub2', 'sub2-2', 1 ]
*/
export function handleVisitPathsFull<T>(key: IVisitorFnKey | null,
_node: T,
nodePaths: IVisitPathsNodeList,
)
{
const paths = handleVisitPaths(nodePaths);
if (typeof key === 'number')
{
paths.push(key)
}
return paths
}
/**
* This function is used to find all paths of sequences in a given YAML structure.
* It traverses the YAML structure and collects the paths of all sequences (Seq nodes).
*
* @param node - The YAML node to start the search from. It can be a Node, Document.
* @returns - An array of arrays, where each inner array represents a path of sequence nodes.
* Each path is represented as an array of paths, where each path is a key or index.
*/
export function findWildcardsYAMLPathsAll(node: Node | Document)
{
const ls: IVisitPathsList[] = [];
visitWildcardsYAML(node, {
Seq(...args)
{
const paths = handleVisitPathsFull(...args);
ls.push(paths)
}
});
return ls;
}
const RE_UNSAFE_QUOTE = /['"]/;
const RE_UNSAFE_VALUE = /^\s*-|[{$~!@}\n|:?#'"%]/;
const RE_UNSAFE_PLAIN = /-/
export function _visitNormalizeScalar(key: IVisitorFnKey, node: IWildcardsYAMLScalar, runtime: {
checkUnsafeQuote: boolean,
options: IOptionsParseDocument,
})
{
let value = node.value as string;
if (typeof value === 'string')
{
if (runtime.checkUnsafeQuote && RE_UNSAFE_QUOTE.test(value))
{
throw new SyntaxError(`Invalid SYNTAX [UNSAFE_QUOTE]. key: ${key}, node: ${node}`)
}
else if (node.type === 'QUOTE_DOUBLE' || node.type === 'QUOTE_SINGLE' && !value.includes('\\'))
{
node.type = 'PLAIN';
}
value = formatPrompts(value, runtime.options);
if (!value.length)
{
throw new SyntaxError(`Invalid SYNTAX [EMPTY_VALUE]. key: ${key}, node: ${node}`)
}
else if (RE_UNSAFE_VALUE.test(value))
{
if (node.type === 'PLAIN')
{
node.type = 'BLOCK_LITERAL'
}
else if (node.type === 'BLOCK_FOLDED' && /#/.test(value))
{
node.type = 'BLOCK_LITERAL'
}
}
else if (node.type === 'PLAIN' && RE_UNSAFE_PLAIN.test(value))
{
node.type = 'QUOTE_DOUBLE'
}
let res = _checkValue(value);
if (res?.error)
{
throw new SyntaxError(`${res.error}. key: ${key}, node: ${node}`)
}
node.value = value;
}
}
export function getTopRootContents<T extends IWildcardsYAMLDocument | Document | IWildcardsYAMLMapRoot | YAMLMap>(doc: T)
{
if (isWildcardsYAMLDocument(doc))
{
// @ts-ignore
doc = doc.contents as IWildcardsYAMLMapRoot
}
if (isWildcardsYAMLMap(doc))
{
return doc
}
throw new TypeError(`Input document is not a YAML Document or a YAML Map. Please provide a valid YAML structure.`)
}
export function getTopRootNodes<T extends IWildcardsYAMLDocument | Document | IWildcardsYAMLMapRoot | YAMLMap>(doc: T)
{
return getTopRootContents(doc).items
}