123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
import re
|
|
from typing import Any, List
|
|
|
|
|
|
_PLACEHOLDER_RE = re.compile(
|
|
r"%(?:"
|
|
r"%|" # escaped percent
|
|
r"(?:[-+#0 ]*)?" # flags
|
|
r"(?:\d+|\*)?" # width
|
|
r"(?:\.(?:\d+|\*))?" # precision
|
|
r"[hlL]?" # length modifier (ignored by Python for most)
|
|
r"[diuoxXeEfFgGcrsa]" # conversion type
|
|
r")"
|
|
)
|
|
|
|
|
|
class CNPFormat:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"format": (
|
|
"STRING",
|
|
{
|
|
"default": "Size: %u x %u | Prompt: %s",
|
|
"multiline": True,
|
|
},
|
|
),
|
|
"arg_1": ("*", {}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("text",)
|
|
FUNCTION = "format_text"
|
|
CATEGORY = "ComfyNodesPlus/Text"
|
|
|
|
@classmethod
|
|
def VALIDATE_INPUTS(cls, input_types):
|
|
return True
|
|
|
|
def _ordered_args(self, kwargs: dict[str, Any]) -> List[Any]:
|
|
args: List[Any] = []
|
|
index = 1
|
|
while True:
|
|
key = f"arg_{index}"
|
|
if key not in kwargs:
|
|
break
|
|
args.append(kwargs[key])
|
|
index += 1
|
|
return args
|
|
|
|
def _normalise_value(self, value: Any, specifier: str) -> Any:
|
|
if value is None:
|
|
return "" if specifier == "s" else 0
|
|
|
|
if specifier == "s":
|
|
return str(value)
|
|
|
|
if specifier == "c":
|
|
if isinstance(value, int):
|
|
return value
|
|
text = str(value)
|
|
if len(text) != 1:
|
|
raise ValueError(f"%c expects a single character, got {value!r}")
|
|
return text
|
|
|
|
if specifier in {"d", "i", "u", "o", "x", "X"}:
|
|
if isinstance(value, bool):
|
|
return int(value)
|
|
if isinstance(value, (int, float)):
|
|
return int(value)
|
|
text = str(value).strip()
|
|
if text == "":
|
|
return 0
|
|
return int(float(text))
|
|
|
|
if specifier in {"e", "E", "f", "F", "g", "G"}:
|
|
if isinstance(value, bool):
|
|
return 1.0 if value else 0.0
|
|
if isinstance(value, (int, float)):
|
|
return float(value)
|
|
text = str(value).strip()
|
|
if text == "":
|
|
return 0.0
|
|
return float(text)
|
|
|
|
return value
|
|
|
|
def _extract_specifiers(self, fmt: str) -> List[str]:
|
|
specifiers: List[str] = []
|
|
for match in _PLACEHOLDER_RE.finditer(fmt):
|
|
token = match.group(0)
|
|
if token == "%%":
|
|
continue
|
|
specifiers.append(token[-1])
|
|
return specifiers
|
|
|
|
def format_text(self, format: str, **kwargs):
|
|
args = self._ordered_args(kwargs)
|
|
specifiers = self._extract_specifiers(format)
|
|
|
|
if len(args) < len(specifiers):
|
|
raise ValueError(
|
|
f"CNPFormat: format string expects {len(specifiers)} argument(s), "
|
|
f"but only {len(args)} were connected."
|
|
)
|
|
|
|
converted = []
|
|
for index, specifier in enumerate(specifiers):
|
|
converted.append(self._normalise_value(args[index], specifier))
|
|
|
|
try:
|
|
if len(converted) == 0:
|
|
result = format % () if "%%" in format else format
|
|
elif len(converted) == 1:
|
|
result = format % converted[0]
|
|
else:
|
|
result = format % tuple(converted)
|
|
except Exception as exc:
|
|
raise ValueError(f"CNPFormat failed to format string: {exc}") from exc
|
|
|
|
return (str(result),) |