From 7fe4478f3efecb21f60183d3f84a3c9fe2d77fbe Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sun, 11 May 2025 21:52:29 +0200 Subject: [PATCH] Bum version to 0.2.0 as most nodes should be stable now. Consolidate all nodes. --- pyproject.toml | 2 +- src/basic_data_handling/casting_nodes.py | 54 -- src/basic_data_handling/data_list_nodes.py | 64 ++- src/basic_data_handling/float_nodes.py | 100 ++-- src/basic_data_handling/int_nodes.py | 110 ++-- src/basic_data_handling/list_nodes.py | 141 ++--- src/basic_data_handling/math_formula_node.py | 63 ++- src/basic_data_handling/math_nodes.py | 156 +++--- src/basic_data_handling/path_nodes.py | 140 +++-- src/basic_data_handling/regex_nodes.py | 132 ++++- src/basic_data_handling/set_nodes.py | 82 +-- src/basic_data_handling/string_nodes.py | 525 ++++++++++++------- 12 files changed, 905 insertions(+), 664 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9bc8971..cc803a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "basic_data_handling" -version = "0.1.0" +version = "0.2.0" description = """NOTE: Still in development! Expect breaking changes! Basic Python functions for manipulating data that every programmer is used to. Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists. diff --git a/src/basic_data_handling/casting_nodes.py b/src/basic_data_handling/casting_nodes.py index 226733c..8feb940 100644 --- a/src/basic_data_handling/casting_nodes.py +++ b/src/basic_data_handling/casting_nodes.py @@ -116,31 +116,6 @@ class CastToList(ComfyNodeABC): return ([input],) -class CastDataListToList(ComfyNodeABC): - """ - Converts a ComfyUI data list into a LIST object. - - This node takes a data list input (which is typically a list of items with the same type) - and converts it to a LIST object (a Python list as a single variable). - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "list": (IO.ANY, {}), - } - } - - RETURN_TYPES = ("LIST",) - CATEGORY = "Basic/cast" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "convert" - INPUT_IS_LIST = True - - def convert(self, **kwargs: list[Any]) -> tuple[list[Any]]: - return (kwargs.get('list', []).copy(),) - - class CastToSet(ComfyNodeABC): """ Converts any input to a SET. Non-set inputs are converted into a set. If input is a ComfyUI data list, @@ -165,31 +140,6 @@ class CastToSet(ComfyNodeABC): return ({input,} if not isinstance(input, list) else set(input),) -class CastDataListToSet(ComfyNodeABC): - """ - Converts a ComfyUI data list into a LIST object. - - This node takes a data list input (which is typically a list of items with the same type) - and converts it to a LIST object (a Python list as a single variable). - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "list": (IO.ANY, {}), - } - } - - RETURN_TYPES = ("SET",) - CATEGORY = "Basic/cast" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "convert" - INPUT_IS_LIST = True - - def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]: - return (set(kwargs.get('list', [])),) - - class CastToDict(ComfyNodeABC): """ Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs. @@ -220,9 +170,7 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: CastToFloat": CastToFloat, "Basic data handling: CastToBoolean": CastToBoolean, "Basic data handling: CastToList": CastToList, - "Basic data handling: CastDataListToList": CastDataListToList, "Basic data handling: CastToSet": CastToSet, - "Basic data handling: CastDataListToSet": CastDataListToSet, "Basic data handling: CastToDict": CastToDict, } @@ -232,8 +180,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: CastToFloat": "to FLOAT", "Basic data handling: CastToBoolean": "to BOOLEAN", "Basic data handling: CastToList": "to LIST", - "Basic data handling: CastDataListToList": "data list to LIST", "Basic data handling: CastToSet": "to SET", - "Basic data handling: CastDataListToSet": "data list to SET", "Basic data handling: CastToDict": "to DICT", } diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index 26c58f1..928de26 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -2,6 +2,8 @@ from typing import Any from inspect import cleandoc from comfy.comfy_types.node_typing import IO, ComfyNodeABC +INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes + class DataListAppend(ComfyNodeABC): """ Adds an item to the end of a list. @@ -337,7 +339,7 @@ class DataListSlice(ComfyNodeABC): }, "optional": { "start": (IO.INT, {"default": 0}), - "stop": (IO.INT, {"default": -1}), + "stop": (IO.INT, {"default": INT_MAX}), "step": (IO.INT, {"default": 1}), } } @@ -353,11 +355,9 @@ class DataListSlice(ComfyNodeABC): def slice(self, **kwargs: list[Any]) -> tuple[list[Any]]: input_list = kwargs.get('list', []) start = kwargs.get('start', [0])[0] - stop = kwargs.get('stop', [-1])[0] + stop = kwargs.get('stop', [INT_MAX])[0] step = kwargs.get('step', [1])[0] - - if stop == -1: - stop = len(input_list) + print(f"start: {start}, stop: {stop}, step: {step}; input_list: {input_list}") return (input_list[start:stop:step],) @@ -635,6 +635,56 @@ class DataListMax(ComfyNodeABC): return (None,) +class DataListToList(ComfyNodeABC): + """ + Converts a ComfyUI data list into a LIST object. + + This node takes a data list input (which is typically a list of items with the same type) + and converts it to a LIST object (a Python list as a single variable). + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": (IO.ANY, {}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/data list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "convert" + INPUT_IS_LIST = True + + def convert(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return (kwargs.get('list', []).copy(),) + + +class DataListToSet(ComfyNodeABC): + """ + Converts a ComfyUI data list into a LIST object. + + This node takes a data list input (which is typically a list of items with the same type) + and converts it to a LIST object (a Python list as a single variable). + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": (IO.ANY, {}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/data list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "convert" + INPUT_IS_LIST = True + + def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set(kwargs.get('list', [])),) + + NODE_CLASS_MAPPINGS = { "Basic data handling: DataListAppend": DataListAppend, "Basic data handling: DataListExtend": DataListExtend, @@ -655,6 +705,8 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: DataListFilter": DataListFilter, "Basic data handling: DataListMin": DataListMin, "Basic data handling: DataListMax": DataListMax, + "Basic data handling: DataListToList": DataListToList, + "Basic data handling: DataListToSet": DataListToSet, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -677,4 +729,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DataListFilter": "filter", "Basic data handling: DataListMin": "min", "Basic data handling: DataListMax": "max", + "Basic data handling: DataListToList": "convert to LIST", + "Basic data handling: DataListToSet": "convert to SET", } diff --git a/src/basic_data_handling/float_nodes.py b/src/basic_data_handling/float_nodes.py index 60e1d88..eb7060c 100644 --- a/src/basic_data_handling/float_nodes.py +++ b/src/basic_data_handling/float_nodes.py @@ -1,6 +1,7 @@ from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class FloatAdd: +class FloatAdd(ComfyNodeABC): """ Adds two floating-point numbers. @@ -10,12 +11,12 @@ class FloatAdd: def INPUT_TYPES(cls): return { "required": { - "float1": ("FLOAT", {"default": 0.0}), - "float2": ("FLOAT", {"default": 0.0}), + "float1": (IO.FLOAT, {"default": 0.0}), + "float2": (IO.FLOAT, {"default": 0.0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "add" @@ -24,7 +25,7 @@ class FloatAdd: return (float1 + float2,) -class FloatSubtract: +class FloatSubtract(ComfyNodeABC): """ Subtracts one floating-point number from another. @@ -35,12 +36,12 @@ class FloatSubtract: def INPUT_TYPES(cls): return { "required": { - "float1": ("FLOAT", {"default": 0.0}), - "float2": ("FLOAT", {"default": 0.0}), + "float1": (IO.FLOAT, {"default": 0.0}), + "float2": (IO.FLOAT, {"default": 0.0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "subtract" @@ -49,7 +50,7 @@ class FloatSubtract: return (float1 - float2,) -class FloatMultiply: +class FloatMultiply(ComfyNodeABC): """ Multiplies two floating-point numbers. @@ -59,12 +60,12 @@ class FloatMultiply: def INPUT_TYPES(cls): return { "required": { - "float1": ("FLOAT", {"default": 1.0}), - "float2": ("FLOAT", {"default": 1.0}), + "float1": (IO.FLOAT, {"default": 1.0}), + "float2": (IO.FLOAT, {"default": 1.0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "multiply" @@ -73,7 +74,7 @@ class FloatMultiply: return (float1 * float2,) -class FloatDivide: +class FloatDivide(ComfyNodeABC): """ Divides one floating-point number by another. @@ -84,12 +85,12 @@ class FloatDivide: def INPUT_TYPES(cls): return { "required": { - "float1": ("FLOAT", {"default": 1.0}), - "float2": ("FLOAT", {"default": 1.0}), + "float1": (IO.FLOAT, {"default": 1.0}), + "float2": (IO.FLOAT, {"default": 1.0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "divide" @@ -100,7 +101,34 @@ class FloatDivide: return (float1 / float2,) -class FloatPower: +class FloatDivideSafe(ComfyNodeABC): + """ + Divides one floating-point number by another. + + This node takes two floats as input and returns the result of the division. + It returns positive or negative infinity if the divisor is 0 (assumed to be +0.0). + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float1": (IO.FLOAT, {"default": 1.0}), + "float2": (IO.FLOAT, {"default": 1.0}), + } + } + + RETURN_TYPES = (IO.FLOAT,) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "divide" + + def divide(self, float1: float, float2: float) -> tuple[float]: + if float2 == 0.0: + return (float('inf') if float1 > 0 else float('-inf'),) + return (float1 / float2,) + + +class FloatPower(ComfyNodeABC): """ Raises one floating-point number to the power of another. @@ -111,12 +139,12 @@ class FloatPower: def INPUT_TYPES(cls): return { "required": { - "base": ("FLOAT", {"default": 1.0}), - "exponent": ("FLOAT", {"default": 1.0}), + "base": (IO.FLOAT, {"default": 1.0}), + "exponent": (IO.FLOAT, {"default": 1.0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "power" @@ -125,7 +153,7 @@ class FloatPower: return (base ** exponent,) -class FloatRound: +class FloatRound(ComfyNodeABC): """ Rounds a floating-point number to the specified number of decimal places. @@ -136,12 +164,12 @@ class FloatRound: def INPUT_TYPES(cls): return { "required": { - "float_value": ("FLOAT", {"default": 0.0}), - "decimal_places": ("INT", {"default": 2, "min": 0}), + "float_value": (IO.FLOAT, {"default": 0.0}), + "decimal_places": (IO.INT, {"default": 2, "min": 0}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "round" @@ -150,7 +178,7 @@ class FloatRound: return (round(float_value, decimal_places),) -class FloatIsInteger: +class FloatIsInteger(ComfyNodeABC): """ Checks if a floating-point number is an integer. @@ -161,7 +189,7 @@ class FloatIsInteger: def INPUT_TYPES(cls): return { "required": { - "float_value": ("FLOAT", {"default": 0.0}), + "float_value": (IO.FLOAT, {"default": 0.0}), } } @@ -174,7 +202,7 @@ class FloatIsInteger: return (float_value.is_integer(),) -class FloatAsIntegerRatio: +class FloatAsIntegerRatio(ComfyNodeABC): """ Returns the integer ratio of a floating-point number. @@ -185,11 +213,11 @@ class FloatAsIntegerRatio: def INPUT_TYPES(cls): return { "required": { - "float_value": ("FLOAT", {"default": 0.0}), + "float_value": (IO.FLOAT, {"default": 0.0}), } } - RETURN_TYPES = ("INT", "INT") + RETURN_TYPES = (IO.INT, IO.INT) RETURN_NAMES = ("numerator", "denominator") CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") @@ -201,7 +229,7 @@ class FloatAsIntegerRatio: return numerator, denominator -class FloatHex: +class FloatHex(ComfyNodeABC): """ Converts a floating-point number to its hexadecimal representation. @@ -211,11 +239,11 @@ class FloatHex: def INPUT_TYPES(cls): return { "required": { - "float_value": ("FLOAT", {"default": 0.0}), + "float_value": (IO.FLOAT, {"default": 0.0}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "to_hex" @@ -224,7 +252,7 @@ class FloatHex: return (float_value.hex(),) -class FloatFromHex: +class FloatFromHex(ComfyNodeABC): """ Converts a hexadecimal string to its corresponding floating-point number. @@ -234,11 +262,11 @@ class FloatFromHex: def INPUT_TYPES(cls): return { "required": { - "hex_value": ("STRING", {"default": "0x0.0p+0"}), + "hex_value": (IO.STRING, {"default": "0x0.0p+0"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/FLOAT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "from_hex" @@ -252,6 +280,7 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: FloatSubtract": FloatSubtract, "Basic data handling: FloatMultiply": FloatMultiply, "Basic data handling: FloatDivide": FloatDivide, + "Basic data handling: FloatDivideSafe": FloatDivideSafe, "Basic data handling: FloatPower": FloatPower, "Basic data handling: FloatRound": FloatRound, "Basic data handling: FloatIsInteger": FloatIsInteger, @@ -265,6 +294,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: FloatSubtract": "subtract", "Basic data handling: FloatMultiply": "multiply", "Basic data handling: FloatDivide": "divide", + "Basic data handling: FloatDivideSafe": "divide (division by zero safe)", "Basic data handling: FloatPower": "power", "Basic data handling: FloatRound": "round", "Basic data handling: FloatIsInteger": "is_integer", diff --git a/src/basic_data_handling/int_nodes.py b/src/basic_data_handling/int_nodes.py index 3a16c94..669e136 100644 --- a/src/basic_data_handling/int_nodes.py +++ b/src/basic_data_handling/int_nodes.py @@ -1,8 +1,8 @@ from inspect import cleandoc from typing import Literal +from comfy.comfy_types.node_typing import IO, ComfyNodeABC - -class IntAdd: +class IntAdd(ComfyNodeABC): """ Adds two integers. @@ -12,12 +12,12 @@ class IntAdd: def INPUT_TYPES(cls): return { "required": { - "int1": ("INT", {"default": 0}), - "int2": ("INT", {"default": 0}), + "int1": (IO.INT, {"default": 0}), + "int2": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "add" @@ -26,7 +26,7 @@ class IntAdd: return (int1 + int2,) -class IntSubtract: +class IntSubtract(ComfyNodeABC): """ Subtracts one integer from another. @@ -37,12 +37,12 @@ class IntSubtract: def INPUT_TYPES(cls): return { "required": { - "int1": ("INT", {"default": 0}), - "int2": ("INT", {"default": 0}), + "int1": (IO.INT, {"default": 0}), + "int2": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "subtract" @@ -51,7 +51,7 @@ class IntSubtract: return (int1 - int2,) -class IntMultiply: +class IntMultiply(ComfyNodeABC): """ Multiplies two integers. @@ -61,12 +61,12 @@ class IntMultiply: def INPUT_TYPES(cls): return { "required": { - "int1": ("INT", {"default": 1}), - "int2": ("INT", {"default": 1}), + "int1": (IO.INT, {"default": 1}), + "int2": (IO.INT, {"default": 1}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "multiply" @@ -75,7 +75,7 @@ class IntMultiply: return (int1 * int2,) -class IntDivide: +class IntDivide(ComfyNodeABC): """ Divides one integer by another. @@ -86,12 +86,12 @@ class IntDivide: def INPUT_TYPES(cls): return { "required": { - "int1": ("INT", {"default": 1}), - "int2": ("INT", {"default": 1}), + "int1": (IO.INT, {"default": 1}), + "int2": (IO.INT, {"default": 1}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "divide" @@ -102,7 +102,35 @@ class IntDivide: return (int1 // int2,) -class IntModulus: +class IntDivideSafe(ComfyNodeABC): + """ + Divides one integer by another. + + This node takes two integers as input and returns the result of the integer + division. It returns the positive or negative infinity value if the divisor is 0. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": (IO.INT, {"default": 1}), + "int2": (IO.INT, {"default": 1}), + "infinity": (IO.INT, {"default": 9223372036854775807}), # 2**63 - 1 + } + } + + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "divide" + + def divide(self, int1: int, int2: int, infinity: int) -> tuple[int]: + if int2 == 0: + return (infinity if int1 > 0 else -infinity,) + return (int1 // int2,) + + +class IntModulus(ComfyNodeABC): """ Returns the modulus of two integers. @@ -113,12 +141,12 @@ class IntModulus: def INPUT_TYPES(cls): return { "required": { - "int1": ("INT", {"default": 0}), - "int2": ("INT", {"default": 1}), + "int1": (IO.INT, {"default": 0}), + "int2": (IO.INT, {"default": 1}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "modulus" @@ -129,7 +157,7 @@ class IntModulus: return (int1 % int2,) -class IntPower: +class IntPower(ComfyNodeABC): """ Raises one integer to the power of another. @@ -140,12 +168,12 @@ class IntPower: def INPUT_TYPES(cls): return { "required": { - "base": ("INT", {"default": 1}), - "exponent": ("INT", {"default": 0}), + "base": (IO.INT, {"default": 1}), + "exponent": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "power" @@ -154,7 +182,7 @@ class IntPower: return (base**exponent,) -class IntBitLength: +class IntBitLength(ComfyNodeABC): """ Returns the number of bits required to represent an integer in binary. @@ -165,12 +193,12 @@ class IntBitLength: def INPUT_TYPES(cls): return { "required": { - "int_value": ("INT", {"default": 0}), + "int_value": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("INT",) - CATEGORY = "Additional Methods/INT" + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "bit_length" @@ -178,7 +206,7 @@ class IntBitLength: return (int_value.bit_length(),) -class IntToBytes: +class IntToBytes(ComfyNodeABC): """ Converts an integer to its byte representation. @@ -189,15 +217,15 @@ class IntToBytes: def INPUT_TYPES(cls): return { "required": { - "int_value": ("INT", {"default": 0}), - "length": ("INT", {"default": 4, "min": 1}), + "int_value": (IO.INT, {"default": 0}), + "length": (IO.INT, {"default": 4, "min": 1}), "byteorder": (["big", "little"], {"default": "big"}), "signed": (["True", "False"], {"default": "False"}), } } RETURN_TYPES = ("BYTES",) - CATEGORY = "Additional Methods/INT" + CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "to_bytes" @@ -206,7 +234,7 @@ class IntToBytes: return (int_value.to_bytes(length, byteorder=byteorder, signed=signed_bool),) -class IntFromBytes: +class IntFromBytes(ComfyNodeABC): """ Converts a bytes object to an integer. @@ -223,8 +251,8 @@ class IntFromBytes: } } - RETURN_TYPES = ("INT",) - CATEGORY = "Additional Methods/INT" + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "from_bytes" @@ -233,7 +261,7 @@ class IntFromBytes: return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),) -class IntBitCount: +class IntBitCount(ComfyNodeABC): """ Returns the number of 1 bits in the binary representation of an integer. @@ -243,12 +271,12 @@ class IntBitCount: def INPUT_TYPES(cls): return { "required": { - "int_value": ("INT", {"default": 0}), + "int_value": (IO.INT, {"default": 0}), } } - RETURN_TYPES = ("INT",) - CATEGORY = "Additional Methods/INT" + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "bit_count" @@ -262,6 +290,7 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: IntSubtract": IntSubtract, "Basic data handling: IntMultiply": IntMultiply, "Basic data handling: IntDivide": IntDivide, + "Basic data handling: IntDivideSafe": IntDivideSafe, "Basic data handling: IntModulus": IntModulus, "Basic data handling: IntPower": IntPower, "Basic data handling: IntBitLength": IntBitLength, @@ -275,6 +304,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: IntSubtract": "subtract", "Basic data handling: IntMultiply": "multiply", "Basic data handling: IntDivide": "divide", + "Basic data handling: IntDivideSafe": "divide (division by zero safe)", "Basic data handling: IntModulus": "modulus", "Basic data handling: IntPower": "power", "Basic data handling: IntBitLength": "bit_length", diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index a781745..0938a14 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -1,7 +1,10 @@ from typing import Any from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class ListAppend: +INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes + +class ListAppend(ComfyNodeABC): """ Adds an item to the end of a LIST. @@ -13,7 +16,7 @@ class ListAppend: return { "required": { "list": ("LIST", {}), - "item": ("*", {}), + "item": (IO.ANY, {}), } } @@ -28,7 +31,26 @@ class ListAppend: return (result,) -class ListExtend: +class ListEmpty(ComfyNodeABC): + """ + Create an empty LIST. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": {} + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create" + + def create(self) -> tuple[list[Any]]: + return ([],) + + +class ListExtend(ComfyNodeABC): """ Extends a LIST by appending elements from another LIST. @@ -55,7 +77,7 @@ class ListExtend: return (result,) -class ListInsert: +class ListInsert(ComfyNodeABC): """ Inserts an item at a specified position in a LIST. @@ -68,7 +90,7 @@ class ListInsert: "required": { "list": ("LIST", {}), "index": ("INT", {"default": 0}), - "item": ("*", {}), + "item": (IO.ANY, {}), } } @@ -83,7 +105,7 @@ class ListInsert: return (result,) -class ListRemove: +class ListRemove(ComfyNodeABC): """ Removes the first occurrence of a specified value from a LIST. @@ -96,7 +118,7 @@ class ListRemove: return { "required": { "list": ("LIST", {}), - "value": ("*", {}), + "value": (IO.ANY, {}), } } @@ -115,7 +137,7 @@ class ListRemove: return result, False -class ListPop: +class ListPop(ComfyNodeABC): """ Removes and returns an item at a specified position in a LIST. @@ -135,7 +157,7 @@ class ListPop: } } - RETURN_TYPES = ("LIST", "*") + RETURN_TYPES = ("LIST", IO.ANY) RETURN_NAMES = ("list", "item") CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") @@ -150,30 +172,7 @@ class ListPop: return result, None -class ListClear: - """ - Removes all items from a LIST. - - This node takes a LIST as input and returns a new empty LIST. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "list": ("LIST", {}), - } - } - - RETURN_TYPES = ("LIST",) - CATEGORY = "Basic/LIST" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "clear" - - def clear(self, list: list[Any]) -> tuple[list[Any]]: - return ([],) - - -class ListIndex: +class ListIndex(ComfyNodeABC): """ Returns the index of the first occurrence of a value in a LIST. @@ -186,7 +185,7 @@ class ListIndex: return { "required": { "list": ("LIST", {}), - "value": ("*", {}), + "value": (IO.ANY, {}), }, "optional": { "start": ("INT", {"default": 0}), @@ -210,7 +209,7 @@ class ListIndex: return (-1,) -class ListCount: +class ListCount(ComfyNodeABC): """ Counts the number of occurrences of a value in a LIST. @@ -222,7 +221,7 @@ class ListCount: return { "required": { "list": ("LIST", {}), - "value": ("*", {}), + "value": (IO.ANY, {}), } } @@ -236,7 +235,7 @@ class ListCount: return (list.count(value),) -class ListSort: +class ListSort(ComfyNodeABC): """ Sorts the items in a LIST. @@ -272,7 +271,7 @@ class ListSort: return (list.copy(),) -class ListReverse: +class ListReverse(ComfyNodeABC): """ Reverses the order of items in a LIST. @@ -297,7 +296,7 @@ class ListReverse: return (result,) -class ListLength: +class ListLength(ComfyNodeABC): """ Returns the number of items in a LIST. @@ -321,7 +320,7 @@ class ListLength: return (len(list),) -class ListSlice: +class ListSlice(ComfyNodeABC): """ Creates a slice of a LIST. @@ -336,7 +335,7 @@ class ListSlice: }, "optional": { "start": ("INT", {"default": 0}), - "stop": ("INT", {"default": -1}), + "stop": ("INT", {"default": INT_MAX}), "step": ("INT", {"default": 1}), } } @@ -346,11 +345,12 @@ class ListSlice: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "slice" - def slice(self, list: list[Any], start: int = 0, stop: int = -1, step: int = 1) -> tuple[list[Any]]: + def slice(self, list: list[Any], start: int = 0, stop: int = INT_MAX, + step: int = 1) -> tuple[list[Any]]: return (list[start:stop:step],) -class ListGetItem: +class ListGetItem(ComfyNodeABC): """ Retrieves an item at a specified position in a LIST. @@ -367,7 +367,7 @@ class ListGetItem: } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("item",) CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") @@ -380,7 +380,7 @@ class ListGetItem: return (None,) -class ListSetItem: +class ListSetItem(ComfyNodeABC): """ Sets an item at a specified position in a LIST. @@ -393,7 +393,7 @@ class ListSetItem: "required": { "list": ("LIST", {}), "index": ("INT", {"default": 0}), - "value": ("*", {}), + "value": (IO.ANY, {}), } } @@ -411,7 +411,7 @@ class ListSetItem: raise IndexError(f"Index {index} out of range for LIST of length {len(list)}") -class ListContains: +class ListContains(ComfyNodeABC): """ Checks if a LIST contains a specified value. @@ -423,7 +423,7 @@ class ListContains: return { "required": { "list": ("LIST", {}), - "value": ("*", {}), + "value": (IO.ANY, {}), } } @@ -437,7 +437,7 @@ class ListContains: return (value in list,) -class ListToDataList: +class ListToDataList(ComfyNodeABC): """ Converts a LIST object into a ComfyUI data list. @@ -453,7 +453,7 @@ class ListToDataList: } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert" @@ -463,7 +463,7 @@ class ListToDataList: return (list,) -class ListToSet: +class ListToSet(ComfyNodeABC): """ Converts a LIST into a SET. @@ -487,32 +487,7 @@ class ListToSet: return (set(list),) -class AnyToList: - """ - Converts any input datatype into a LIST. - - This node takes any input value and wraps it in a LIST object, - allowing it to be processed by nodes that expect LIST inputs. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "input": ("*", {}), - } - } - - RETURN_TYPES = ("LIST",) - CATEGORY = "Basic/LIST" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "convert" - - def convert(self, input: Any) -> tuple[list[Any]]: - # Create a new list containing the input value - return ([input],) - - -class ListMin: +class ListMin(ComfyNodeABC): """ Returns the minimum value in a LIST. @@ -527,7 +502,7 @@ class ListMin: } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("min_value",) CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") @@ -544,7 +519,7 @@ class ListMin: return (None,) -class ListMax: +class ListMax(ComfyNodeABC): """ Returns the maximum value in a LIST. @@ -559,7 +534,7 @@ class ListMax: } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("max_value",) CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") @@ -578,11 +553,11 @@ class ListMax: NODE_CLASS_MAPPINGS = { "Basic data handling: ListAppend": ListAppend, + "Basic data handling: ListEmpty": ListEmpty, "Basic data handling: ListExtend": ListExtend, "Basic data handling: ListInsert": ListInsert, "Basic data handling: ListRemove": ListRemove, "Basic data handling: ListPop": ListPop, - "Basic data handling: ListClear": ListClear, "Basic data handling: ListIndex": ListIndex, "Basic data handling: ListCount": ListCount, "Basic data handling: ListSort": ListSort, @@ -594,18 +569,17 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: ListContains": ListContains, "Basic data handling: ListToDataList": ListToDataList, "Basic data handling: ListToSet": ListToSet, - "Basic data handling: AnyToList": AnyToList, "Basic data handling: ListMin": ListMin, "Basic data handling: ListMax": ListMax, } NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: ListAppend": "append", + "Basic data handling: ListEmpty": "empty", "Basic data handling: ListExtend": "extend", "Basic data handling: ListInsert": "insert", "Basic data handling: ListRemove": "remove", "Basic data handling: ListPop": "pop", - "Basic data handling: ListClear": "clear", "Basic data handling: ListIndex": "index", "Basic data handling: ListCount": "count", "Basic data handling: ListSort": "sort", @@ -617,7 +591,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: ListContains": "contains", "Basic data handling: ListToDataList": "convert to data list", "Basic data handling: ListToSet": "convert to SET", - "Basic data handling: AnyToList": "any to LIST", "Basic data handling: ListMin": "min", "Basic data handling: ListMax": "max", } diff --git a/src/basic_data_handling/math_formula_node.py b/src/basic_data_handling/math_formula_node.py index a0a2c02..14058ad 100644 --- a/src/basic_data_handling/math_formula_node.py +++ b/src/basic_data_handling/math_formula_node.py @@ -1,6 +1,7 @@ from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class MathFormula: +class MathFormula(ComfyNodeABC): """ A node that evaluates a mathematical formula provided as a string without using eval. @@ -13,21 +14,23 @@ class MathFormula: The calculation would be: result = 2 + abs(-3 * 4 + 5) = 2 + abs(-7) = 2 + 7 = 9 """ + EXPERIMENTAL = True + @classmethod def INPUT_TYPES(cls): return { "required": { - "formula": ("STRING", {"default": "a + b"}), - "a": ("FLOAT", {"default": 0.0}), + "formula": (IO.STRING, {"default": "a + b"}), + "a": (IO.NUMBER, {"default": 0.0}), }, "optional": { - "b": ("FLOAT", {"default": 0.0}), - "c": ("FLOAT", {"default": 0.0}), - "d": ("FLOAT", {"default": 0.0}), + "b": (IO.NUMBER, {"default": 0.0}), + "c": (IO.NUMBER, {"default": 0.0}), + "d": (IO.NUMBER, {"default": 0.0}), }, } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.NUMBER,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "evaluate" @@ -36,17 +39,6 @@ class MathFormula: SUPPORTED_FUNCTIONS = {"abs", "floor", "ceil", "round"} VALID_CHARS = set("0123456789.+-*/%(), ") - @classmethod - def VALIDATE_INPUTS(cls, input_types): - for input, value in input_types.items(): - if input == "formula": - if value == "STRING": - continue - return f"input '{input}' must be a STRING type" - if value not in ("INT", "FLOAT"): - return f"input '{input}' must be an INT or FLOAT type" - return True - def evaluate(self, formula: str, a: float, b: float = 0.0, c: float = 0.0, d: float = 0.0) -> tuple[float]: # Tokenize the formula without replacement print(f'formula: "{formula}"') @@ -137,34 +129,57 @@ class MathFormula: postfix = [] # Output list previous_token = None # Tracks the previous token to distinguish unary vs binary operators + # Track function-to-parenthesis relationships + function_stack = [] # Stack to track pending functions + for token in tokens: if self.is_number(token): # Numbers postfix.append(float(token)) elif token in self.SUPPORTED_FUNCTIONS: # Functions (e.g., abs) + # Push function onto stack - will be processed when matching parenthesis is found + function_stack.append(len(stack)) # Record stack depth for this function stack.append(token) elif token == "(": # Left parentheses stack.append(token) elif token == ")": # Right parentheses + # Pop operators until left parenthesis while stack and stack[-1] != "(": postfix.append(stack.pop()) + if not stack: raise ValueError("Mismatched parentheses in expression.") - stack.pop() # Pops the left parenthesis + + stack.pop() # Discard the left parenthesis + + # Check if this closing parenthesis matches a function call + if function_stack and len(stack) == function_stack[-1]: + # We've found our function, add it to postfix + if stack and stack[-1] in self.SUPPORTED_FUNCTIONS: + postfix.append(stack.pop()) + function_stack.pop() # Remove the function marker elif token in self.OPERATORS: # Operators (+, -, *, etc.) # Check for unary negation if token == "-" and (previous_token is None or previous_token in self.OPERATORS or previous_token == "("): - # Treat as unary minus - postfix.append(0.0) - stack.append(token) + # Handle negative numbers directly - place negative value in postfix + if tokens.index(token) + 1 < len(tokens) and self.is_number(tokens[tokens.index(token) + 1]): + next_token = tokens[tokens.index(token) + 1] + # Skip this token and the next number, add negative number to postfix + postfix.append(-float(next_token)) + previous_token = next_token # Skip to after the number + continue + else: + # Traditional unary minus using 0 - operand + postfix.append(0.0) + stack.append(token) else: # Handle regular operators - while stack and precedence.get(stack[-1], 0) >= precedence[token]: + while stack and stack[-1] not in ["("] and precedence.get(stack[-1], 0) >= precedence[token]: postfix.append(stack.pop()) stack.append(token) else: raise ValueError(f"Unexpected token in infix expression: {token}") - # Update previous token (e.g., to track variable, number, etc.) + # Update previous token previous_token = token # Pop any remaining operators from the stack diff --git a/src/basic_data_handling/math_nodes.py b/src/basic_data_handling/math_nodes.py index c91ffe1..2ff4b66 100644 --- a/src/basic_data_handling/math_nodes.py +++ b/src/basic_data_handling/math_nodes.py @@ -1,9 +1,9 @@ import math from inspect import cleandoc from typing import Literal, Union +from comfy.comfy_types.node_typing import IO, ComfyNodeABC - -class MathSin: +class MathSin(ComfyNodeABC): """ Calculates the sine of an angle in radians or degrees. @@ -15,12 +15,12 @@ class MathSin: def INPUT_TYPES(cls): return { "required": { - "angle": ("FLOAT", {"default": 0.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -32,7 +32,7 @@ class MathSin: return (math.sin(angle),) -class MathCos: +class MathCos(ComfyNodeABC): """ Calculates the cosine of an angle in radians or degrees. @@ -44,12 +44,12 @@ class MathCos: def INPUT_TYPES(cls): return { "required": { - "angle": ("FLOAT", {"default": 0.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -61,7 +61,7 @@ class MathCos: return (math.cos(angle),) -class MathTan: +class MathTan(ComfyNodeABC): """ Calculates the tangent of an angle in radians or degrees. @@ -73,12 +73,12 @@ class MathTan: def INPUT_TYPES(cls): return { "required": { - "angle": ("FLOAT", {"default": 0.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -95,7 +95,7 @@ class MathTan: return (math.tan(angle),) -class MathAsin: +class MathAsin(ComfyNodeABC): """ Calculates the arc sine (inverse sine) of a value. @@ -106,12 +106,12 @@ class MathAsin: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0, "min": -1.0, "max": 1.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -123,7 +123,7 @@ class MathAsin: return (result,) -class MathAcos: +class MathAcos(ComfyNodeABC): """ Calculates the arc cosine (inverse cosine) of a value. @@ -134,12 +134,12 @@ class MathAcos: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 1.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -151,7 +151,7 @@ class MathAcos: return (result,) -class MathAtan: +class MathAtan(ComfyNodeABC): """ Calculates the arc tangent (inverse tangent) of a value. @@ -162,12 +162,12 @@ class MathAtan: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -179,7 +179,7 @@ class MathAtan: return (result,) -class MathAtan2: +class MathAtan2(ComfyNodeABC): """ Calculates the arc tangent of y/x, considering the quadrant. @@ -191,13 +191,13 @@ class MathAtan2: def INPUT_TYPES(cls): return { "required": { - "y": ("FLOAT", {"default": 0.0}), - "x": ("FLOAT", {"default": 1.0}), - "unit": (["radians", "degrees"], {"default": "radians"}), + "y": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "x": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "unit": (["radians", "degrees"], {"default": "degrees"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -209,7 +209,7 @@ class MathAtan2: return (result,) -class MathSqrt: +class MathSqrt(ComfyNodeABC): """ Calculates the square root of a non-negative number. @@ -219,11 +219,11 @@ class MathSqrt: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0, "min": 0.0}), + "value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -232,7 +232,7 @@ class MathSqrt: return (math.sqrt(value),) -class MathExp: +class MathExp(ComfyNodeABC): """ Calculates the exponential of a number (e^x). @@ -242,11 +242,11 @@ class MathExp: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -255,7 +255,7 @@ class MathExp: return (math.exp(value),) -class MathLog: +class MathLog(ComfyNodeABC): """ Calculates the natural logarithm (base e) of a number. @@ -266,14 +266,14 @@ class MathLog: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 1.0, "min": 0.0000001}), + "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "FLOAT"}), }, "optional": { - "base": ("FLOAT", {"default": math.e, "min": 0.0000001}), + "base": (IO.NUMBER, {"default": math.e, "min": 0.0000001, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -285,7 +285,7 @@ class MathLog: return (math.log(value, base),) -class MathLog10: +class MathLog10(ComfyNodeABC): """ Calculates the base-10 logarithm of a number. @@ -295,11 +295,11 @@ class MathLog10: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 1.0, "min": 0.0000001}), + "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -308,7 +308,7 @@ class MathLog10: return (math.log10(value),) -class MathDegrees: +class MathDegrees(ComfyNodeABC): """ Converts an angle from radians to degrees. @@ -318,11 +318,11 @@ class MathDegrees: def INPUT_TYPES(cls): return { "required": { - "radians": ("FLOAT", {"default": 0.0}), + "radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -331,7 +331,7 @@ class MathDegrees: return (math.degrees(radians),) -class MathRadians: +class MathRadians(ComfyNodeABC): """ Converts an angle from degrees to radians. @@ -341,11 +341,11 @@ class MathRadians: def INPUT_TYPES(cls): return { "required": { - "degrees": ("FLOAT", {"default": 0.0}), + "degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -354,7 +354,7 @@ class MathRadians: return (math.radians(degrees),) -class MathFloor: +class MathFloor(ComfyNodeABC): """ Returns the floor of a number. @@ -364,11 +364,11 @@ class MathFloor: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -377,7 +377,7 @@ class MathFloor: return (math.floor(value),) -class MathCeil: +class MathCeil(ComfyNodeABC): """ Returns the ceiling of a number. @@ -387,11 +387,11 @@ class MathCeil: def INPUT_TYPES(cls): return { "required": { - "value": ("FLOAT", {"default": 0.0}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -400,7 +400,7 @@ class MathCeil: return (math.ceil(value),) -class MathAbs: +class MathAbs(ComfyNodeABC): """ Returns the absolute value of a number. @@ -410,26 +410,20 @@ class MathAbs: def INPUT_TYPES(cls): return { "required": { - "value": (["FLOAT", "INT"], {}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str: - if input_types["value"] not in ("FLOAT", "INT"): - return "value must be a FLOAT or INT type" - return True - def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]: return (abs(value),) -class MathPi: +class MathPi(ComfyNodeABC): """ Returns the mathematical constant π (pi). @@ -439,7 +433,7 @@ class MathPi: def INPUT_TYPES(cls): return {"required": {}} - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -448,7 +442,7 @@ class MathPi: return (math.pi,) -class MathE: +class MathE(ComfyNodeABC): """ Returns the mathematical constant e. @@ -458,7 +452,7 @@ class MathE: def INPUT_TYPES(cls): return {"required": {}} - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = (IO.FLOAT,) CATEGORY = "Basic/maths" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" @@ -467,7 +461,7 @@ class MathE: return (math.e,) -class MathMin: +class MathMin(ComfyNodeABC): """ Returns the minimum value among the inputs. @@ -478,8 +472,8 @@ class MathMin: def INPUT_TYPES(cls): return { "required": { - "value1": (["FLOAT", "INT"], {"default": 0}), - "value2": (["FLOAT", "INT"], {"default": 0}), + "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } @@ -488,18 +482,11 @@ class MathMin: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str: - for key, value in input_types.items(): - if key.startswith("value") and value not in ("FLOAT", "INT"): - return f"{key} must be a FLOAT or INT type" - return True - def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]: return (min(value1, value2),) -class MathMax: +class MathMax(ComfyNodeABC): """ Returns the maximum value among the inputs. @@ -510,8 +497,8 @@ class MathMax: def INPUT_TYPES(cls): return { "required": { - "value1": (["FLOAT", "INT"], {"default": 0}), - "value2": (["FLOAT", "INT"], {"default": 0}), + "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), } } @@ -520,13 +507,6 @@ class MathMax: DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - @classmethod - def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str: - for key, value in input_types.items(): - if key.startswith("value") and value not in ("FLOAT", "INT"): - return f"{key} must be a FLOAT or INT type" - return True - def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]: return (max(value1, value2),) diff --git a/src/basic_data_handling/path_nodes.py b/src/basic_data_handling/path_nodes.py index 29e3f4d..dc19a70 100644 --- a/src/basic_data_handling/path_nodes.py +++ b/src/basic_data_handling/path_nodes.py @@ -1,9 +1,10 @@ from inspect import cleandoc import os import glob +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class PathJoin: +class PathJoin(ComfyNodeABC): """ Joins multiple path components into a single path. @@ -15,14 +16,14 @@ class PathJoin: def INPUT_TYPES(cls): return { "required": { - "path1": ("STRING", {"default": ""}), + "path1": (IO.STRING, {"default": ""}), }, "optional": { - "path2": ("STRING", {"default": ""}), + "path2": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("path",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -33,7 +34,7 @@ class PathJoin: return (str(os.path.join(*paths)),) -class PathAbspath: +class PathAbspath(ComfyNodeABC): """ Returns the absolute path of a file or directory. @@ -44,11 +45,11 @@ class PathAbspath: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("absolute_path",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -58,7 +59,7 @@ class PathAbspath: return (os.path.abspath(path),) -class PathExists: +class PathExists(ComfyNodeABC): """ Checks if a path exists in the filesystem. @@ -69,11 +70,11 @@ class PathExists: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("exists",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -83,7 +84,7 @@ class PathExists: return (os.path.exists(path),) -class PathIsFile: +class PathIsFile(ComfyNodeABC): """ Checks if a path points to a file. @@ -94,11 +95,11 @@ class PathIsFile: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_file",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -108,7 +109,7 @@ class PathIsFile: return (os.path.isfile(path),) -class PathIsDir: +class PathIsDir(ComfyNodeABC): """ Checks if a path points to a directory. @@ -119,11 +120,11 @@ class PathIsDir: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_dir",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -133,7 +134,7 @@ class PathIsDir: return (os.path.isdir(path),) -class PathGetSize: +class PathGetSize(ComfyNodeABC): """ Returns the size of a file in bytes. @@ -144,11 +145,11 @@ class PathGetSize: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("size_bytes",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -162,7 +163,7 @@ class PathGetSize: return (os.path.getsize(path),) -class PathSplit: +class PathSplit(ComfyNodeABC): """ Splits a path into directory and filename components. @@ -173,11 +174,11 @@ class PathSplit: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING", "STRING") + RETURN_TYPES = (IO.STRING, IO.STRING) RETURN_NAMES = ("directory", "filename") CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -187,7 +188,7 @@ class PathSplit: return os.path.split(path) -class PathSplitExt: +class PathSplitExt(ComfyNodeABC): """ Splits a path into name and extension components. @@ -198,11 +199,11 @@ class PathSplitExt: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING", "STRING") + RETURN_TYPES = (IO.STRING, IO.STRING) RETURN_NAMES = ("path_without_ext", "extension") CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -212,7 +213,7 @@ class PathSplitExt: return os.path.splitext(path) -class PathBasename: +class PathBasename(ComfyNodeABC): """ Returns the base name of a path. @@ -223,11 +224,11 @@ class PathBasename: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("basename",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -237,7 +238,7 @@ class PathBasename: return (os.path.basename(path),) -class PathDirname: +class PathDirname(ComfyNodeABC): """ Returns the directory name of a path. @@ -248,11 +249,11 @@ class PathDirname: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("dirname",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -262,7 +263,7 @@ class PathDirname: return (os.path.dirname(path),) -class PathGetExtension: +class PathGetExtension(ComfyNodeABC): """ Returns the extension of a file. @@ -273,11 +274,11 @@ class PathGetExtension: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("extension",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -287,7 +288,7 @@ class PathGetExtension: return (os.path.splitext(path)[1],) -class PathNormalize: +class PathNormalize(ComfyNodeABC): """ Normalizes a path. @@ -299,11 +300,11 @@ class PathNormalize: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("normalized_path",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -313,7 +314,7 @@ class PathNormalize: return (os.path.normpath(path),) -class PathRelative: +class PathRelative(ComfyNodeABC): """ Returns a relative path. @@ -324,14 +325,14 @@ class PathRelative: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("STRING", {"default": ""}), + "start": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("relative_path",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -343,7 +344,7 @@ class PathRelative: return (os.path.relpath(path, start),) -class PathGlob: +class PathGlob(ComfyNodeABC): """ Finds paths matching a pattern. @@ -358,30 +359,25 @@ class PathGlob: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {"default": "*.txt"}), + "pattern": (IO.STRING, {"default": "*.txt"}), }, "optional": { - "recursive": (["False", "True"], {"default": "False"}), + "recursive": (IO.BOOLEAN, {"default": False}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("matching_paths",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "glob_paths" OUTPUT_IS_LIST = (True,) - def glob_paths(self, pattern: str, recursive: str = "False") -> tuple[list[str]]: - is_recursive = (recursive == "True") - if is_recursive: - # If recursive, use ** pattern with glob.glob's recursive option - return (glob.glob(pattern, recursive=True),) - else: - return (glob.glob(pattern),) + def glob_paths(self, pattern: str, recursive: bool = False) -> tuple[list[str]]: + return (glob.glob(pattern, recursive=recursive),) -class PathExpandVars: +class PathExpandVars(ComfyNodeABC): """ Expands environment variables in a path. @@ -392,11 +388,11 @@ class PathExpandVars: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("expanded_path",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -406,7 +402,7 @@ class PathExpandVars: return (os.path.expandvars(path),) -class PathGetCwd: +class PathGetCwd(ComfyNodeABC): """ Returns the current working directory. @@ -416,7 +412,7 @@ class PathGetCwd: def INPUT_TYPES(cls): return {"required": {}} - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("current_directory",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -426,7 +422,7 @@ class PathGetCwd: return (os.getcwd(),) -class PathListDir: +class PathListDir(ComfyNodeABC): """ Lists the contents of a directory. @@ -439,22 +435,22 @@ class PathListDir: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), }, "optional": { - "files_only": (["False", "True"], {"default": "False"}), - "dirs_only": (["False", "True"], {"default": "False"}), + "files_only": (IO.BOOLEAN, {"default": False}), + "dirs_only": (IO.BOOLEAN, {"default": False}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("entries",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "list_directory" OUTPUT_IS_LIST = (True,) - def list_directory(self, path: str, files_only: str = "False", dirs_only: str = "False") -> tuple[list[str]]: + def list_directory(self, path: str, files_only: str = False, dirs_only: str = False) -> tuple[list[str]]: if not path: path = os.getcwd() @@ -465,15 +461,15 @@ class PathListDir: entries = os.listdir(path) - if files_only == "True": + if files_only: entries = [e for e in entries if os.path.isfile(os.path.join(path, e))] - elif dirs_only == "True": + elif dirs_only: entries = [e for e in entries if os.path.isdir(os.path.join(path, e))] return (entries,) -class PathIsAbsolute: +class PathIsAbsolute(ComfyNodeABC): """ Checks if a path is absolute. @@ -484,11 +480,11 @@ class PathIsAbsolute: def INPUT_TYPES(cls): return { "required": { - "path": ("STRING", {"default": ""}), + "path": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_absolute",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") @@ -498,7 +494,7 @@ class PathIsAbsolute: return (os.path.isabs(path),) -class PathCommonPrefix: +class PathCommonPrefix(ComfyNodeABC): """ Finds the common prefix of multiple paths. @@ -508,14 +504,14 @@ class PathCommonPrefix: def INPUT_TYPES(cls): return { "required": { - "path1": ("STRING", {"default": ""}), + "path1": (IO.STRING, {"default": ""}), }, "optional": { - "path2": ("STRING", {"default": ""}), + "path2": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("common_prefix",) CATEGORY = "Basic/path" DESCRIPTION = cleandoc(__doc__ or "") diff --git a/src/basic_data_handling/regex_nodes.py b/src/basic_data_handling/regex_nodes.py index 2b1bdd2..7ff3cfa 100644 --- a/src/basic_data_handling/regex_nodes.py +++ b/src/basic_data_handling/regex_nodes.py @@ -1,7 +1,8 @@ from inspect import cleandoc import re +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class RegexSearchGroups: +class RegexSearchGroupsDataList(ComfyNodeABC): """ Searches the string for a match to the pattern and returns a LIST of match groups. If no match is found, it returns an empty LIST. @@ -10,8 +11,35 @@ class RegexSearchGroups: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING/regex" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "search_groups" + OUTPUT_IS_LIST = (True,) + + def search_groups(self, pattern: str, string: str) -> tuple[list[str]]: + match = re.search(pattern, string) + if match: + return (list(match.groups()),) + return ([],) + + +class RegexSearchGroupsList(ComfyNodeABC): + """ + Searches the string for a match to the pattern and returns a data list of match groups. + If no match is found, it returns an empty data list. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), } } @@ -27,7 +55,7 @@ class RegexSearchGroups: return ([],) -class RegexGroupDict: +class RegexGroupDict(ComfyNodeABC): """ Searches the string with the given pattern and returns a DICT of named groups. If no match is found, it returns an empty DICT. @@ -36,8 +64,8 @@ class RegexGroupDict: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), } } @@ -53,7 +81,7 @@ class RegexGroupDict: return ({},) -class RegexFindall: +class RegexFindallDataList(ComfyNodeABC): """ Returns all non-overlapping matches of a pattern in the string as a list of strings. """ @@ -61,8 +89,31 @@ class RegexFindall: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING/regex" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "findall" + OUTPUT_IS_LIST = (True,) + + def findall(self, pattern: str, string: str) -> tuple[list[str]]: + return (re.findall(pattern, string),) + + +class RegexFindallList(ComfyNodeABC): + """ + Returns all non-overlapping matches of a pattern in the string as a list of strings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), } } @@ -75,7 +126,7 @@ class RegexFindall: return (re.findall(pattern, string),) -class RegexSplit: +class RegexSplitDataList(ComfyNodeABC): """ Splits the string at each match of the pattern and returns a list of substrings. """ @@ -83,8 +134,31 @@ class RegexSplit: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING/regex" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "split" + OUTPUT_IS_LIST = (True,) + + def split(self, pattern: str, string: str) -> tuple[list[str]]: + return (re.split(pattern, string),) + + +class RegexSplitList(ComfyNodeABC): + """ + Splits the string at each match of the pattern and returns a list of substrings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), } } @@ -97,7 +171,7 @@ class RegexSplit: return (re.split(pattern, string),) -class RegexSub: +class RegexSub(ComfyNodeABC): """ Substitutes matches of the pattern in the string with a replacement string. """ @@ -105,14 +179,14 @@ class RegexSub: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "repl": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), + "repl": (IO.STRING, {}), "count": ("INT", {"default": 0}), # 0 means replace all occurrences } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING/regex" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "sub" @@ -121,7 +195,7 @@ class RegexSub: return (re.sub(pattern, repl, string, count),) -class RegexTest: +class RegexTest(ComfyNodeABC): """ Tests whether a given regex pattern matches any part of the input string. Returns True if a match is found, otherwise False. @@ -130,8 +204,8 @@ class RegexTest: def INPUT_TYPES(cls): return { "required": { - "pattern": ("STRING", {}), - "string": ("STRING", {}), + "string": (IO.STRING, {}), + "pattern": (IO.STRING, {}), } } @@ -146,19 +220,25 @@ class RegexTest: NODE_CLASS_MAPPINGS = { - "Basic data handling: RegexSearchGroups": RegexSearchGroups, + "Basic data handling: RegexSearchGroupsDataList": RegexSearchGroupsDataList, + "Basic data handling: RegexSearchGroupsList": RegexSearchGroupsList, "Basic data handling: RegexGroupDict": RegexGroupDict, - "Basic data handling: RegexFindall": RegexFindall, - "Basic data handling: RegexSplit": RegexSplit, + "Basic data handling: RegexFindallDataList": RegexFindallDataList, + "Basic data handling: RegexFindallList": RegexFindallList, + "Basic data handling: RegexSplitDataList": RegexSplitDataList, + "Basic data handling: RegexSplitList": RegexSplitList, "Basic data handling: RegexSub": RegexSub, "Basic data handling: RegexTest": RegexTest, } NODE_DISPLAY_NAME_MAPPINGS = { - "Basic data handling: RegexSearchGroups": "search groups", + "Basic data handling: RegexSearchGroupsDataList": "search groups (data list)", + "Basic data handling: RegexSearchGroupsList": "search groups (LIST)", "Basic data handling: RegexGroupDict": "search named groups", - "Basic data handling: RegexFindall": "find all", - "Basic data handling: RegexSplit": "split", + "Basic data handling: RegexFindallDataList": "find all (data list)", + "Basic data handling: RegexFindallList": "find all (LIST)", + "Basic data handling: RegexSplitDataList": "split (data list)", + "Basic data handling: RegexSplitList": "split (LIST)", "Basic data handling: RegexSub": "substitute", "Basic data handling: RegexTest": "test", } diff --git a/src/basic_data_handling/set_nodes.py b/src/basic_data_handling/set_nodes.py index ffc853a..27fc2e2 100644 --- a/src/basic_data_handling/set_nodes.py +++ b/src/basic_data_handling/set_nodes.py @@ -1,7 +1,8 @@ from typing import Any from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class SetAdd: +class SetAdd(ComfyNodeABC): """ Adds an item to a SET. @@ -13,7 +14,7 @@ class SetAdd: return { "required": { "set": ("SET", {}), - "item": ("*", {}), + "item": (IO.ANY, {}), } } @@ -28,7 +29,7 @@ class SetAdd: return (result,) -class SetRemove: +class SetRemove(ComfyNodeABC): """ Removes an item from a SET. @@ -41,11 +42,11 @@ class SetRemove: return { "required": { "set": ("SET", {}), - "item": ("*", {}), + "item": (IO.ANY, {}), } } - RETURN_TYPES = ("SET", "BOOLEAN") + RETURN_TYPES = ("SET", IO.BOOLEAN) RETURN_NAMES = ("set", "success") CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -60,7 +61,7 @@ class SetRemove: return result, False -class SetDiscard: +class SetDiscard(ComfyNodeABC): """ Removes an item from a SET if it is present. @@ -72,7 +73,7 @@ class SetDiscard: return { "required": { "set": ("SET", {}), - "item": ("*", {}), + "item": (IO.ANY, {}), } } @@ -87,7 +88,7 @@ class SetDiscard: return (result,) -class SetPop: +class SetPop(ComfyNodeABC): """ Removes and returns an arbitrary item from a SET. @@ -103,7 +104,7 @@ class SetPop: } } - RETURN_TYPES = ("SET", "*") + RETURN_TYPES = ("SET", IO.ANY) RETURN_NAMES = ("set", "item") CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -118,30 +119,7 @@ class SetPop: return result, None -class SetClear: - """ - Removes all items from a SET. - - This node takes a SET as input and returns a new empty SET. - """ - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "set": ("SET", {}), - } - } - - RETURN_TYPES = ("SET",) - CATEGORY = "Basic/SET" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "clear" - - def clear(self, set: set[Any]) -> tuple[set[Any]]: - return (set.__class__(),) - - -class SetUnion: +class SetUnion(ComfyNodeABC): """ Returns the union of two or more SETs. @@ -179,7 +157,7 @@ class SetUnion: return (result,) -class SetIntersection: +class SetIntersection(ComfyNodeABC): """ Returns the intersection of two or more SETs. @@ -217,7 +195,7 @@ class SetIntersection: return (result,) -class SetDifference: +class SetDifference(ComfyNodeABC): """ Returns the difference between two SETs. @@ -244,7 +222,7 @@ class SetDifference: return (result,) -class SetSymmetricDifference: +class SetSymmetricDifference(ComfyNodeABC): """ Returns the symmetric difference between two SETs. @@ -271,7 +249,7 @@ class SetSymmetricDifference: return (result,) -class SetIsSubset: +class SetIsSubset(ComfyNodeABC): """ Checks if set1 is a subset of set2. @@ -287,7 +265,7 @@ class SetIsSubset: } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_subset",) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -297,7 +275,7 @@ class SetIsSubset: return (set1.issubset(set2),) -class SetIsSuperset: +class SetIsSuperset(ComfyNodeABC): """ Checks if set1 is a superset of set2. @@ -313,7 +291,7 @@ class SetIsSuperset: } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_superset",) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -323,7 +301,7 @@ class SetIsSuperset: return (set1.issuperset(set2),) -class SetIsDisjoint: +class SetIsDisjoint(ComfyNodeABC): """ Checks if two SETs have no elements in common. @@ -338,7 +316,7 @@ class SetIsDisjoint: } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("is_disjoint",) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -348,7 +326,7 @@ class SetIsDisjoint: return (set1.isdisjoint(set2),) -class SetContains: +class SetContains(ComfyNodeABC): """ Checks if a SET contains a specified value. @@ -360,11 +338,11 @@ class SetContains: return { "required": { "set": ("SET", {}), - "value": ("*", {}), + "value": (IO.ANY, {}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("contains",) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -374,7 +352,7 @@ class SetContains: return (value in set,) -class SetLength: +class SetLength(ComfyNodeABC): """ Returns the number of items in a SET. @@ -388,7 +366,7 @@ class SetLength: } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("length",) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") @@ -398,7 +376,7 @@ class SetLength: return (len(set),) -class ListToSet: +class ListToSet(ComfyNodeABC): """ Converts a LIST into a SET. @@ -422,7 +400,7 @@ class ListToSet: return (set(list),) -class SetToDataList: +class SetToDataList(ComfyNodeABC): """ Converts a SET object into a ComfyUI data list. @@ -438,7 +416,7 @@ class SetToDataList: } } - RETURN_TYPES = ("*",) + RETURN_TYPES = (IO.ANY,) CATEGORY = "Basic/SET" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert" @@ -448,7 +426,7 @@ class SetToDataList: return (list(set),) -class SetToList: +class SetToList(ComfyNodeABC): """ Converts a SET into a LIST. @@ -478,7 +456,6 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: SetRemove": SetRemove, "Basic data handling: SetDiscard": SetDiscard, "Basic data handling: SetPop": SetPop, - "Basic data handling: SetClear": SetClear, "Basic data handling: SetUnion": SetUnion, "Basic data handling: SetIntersection": SetIntersection, "Basic data handling: SetDifference": SetDifference, @@ -497,7 +474,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: SetRemove": "remove", "Basic data handling: SetDiscard": "discard", "Basic data handling: SetPop": "pop", - "Basic data handling: SetClear": "clear", "Basic data handling: SetUnion": "union", "Basic data handling: SetIntersection": "intersection", "Basic data handling: SetDifference": "difference", diff --git a/src/basic_data_handling/string_nodes.py b/src/basic_data_handling/string_nodes.py index dc358bd..1ff636b 100644 --- a/src/basic_data_handling/string_nodes.py +++ b/src/basic_data_handling/string_nodes.py @@ -1,16 +1,17 @@ from inspect import cleandoc +from comfy.comfy_types.node_typing import IO, ComfyNodeABC -class StringCapitalize: +class StringCapitalize(ComfyNodeABC): """Converts the first character of the input string to uppercase and all other characters to lowercase.""" @classmethod def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "capitalize" @@ -19,7 +20,7 @@ class StringCapitalize: return (string.capitalize(),) -class StringCasefold: +class StringCasefold(ComfyNodeABC): """ Converts text to lowercase in a way optimized for case-insensitive comparisons. @@ -31,11 +32,11 @@ class StringCasefold: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "casefold" @@ -44,7 +45,7 @@ class StringCasefold: return (string.casefold(),) -class StringCenter: +class StringCenter(ComfyNodeABC): """ Centers text within a specified width. @@ -56,15 +57,15 @@ class StringCenter: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "width": ("INT", {"default": 20, "min": 0, "max": 1000}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "width": (IO.INT, {"default": 20, "min": 0, "max": 1000}), }, "optional": { - "fillchar": ("STRING", {"default": " "}), + "fillchar": (IO.STRING, {"default": " "}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "center" @@ -79,18 +80,18 @@ class StringCenter: return (string.center(width, fillchar),) -class StringConcat: +class StringConcat(ComfyNodeABC): """Combines two text strings together, joining them end-to-end.""" @classmethod def INPUT_TYPES(cls): return { "required": { - "string_a": ("STRING", {"default": "", "defaultInput": True}), - "string_b": ("STRING", {"default": "", "defaultInput": True}), + "string_a": (IO.STRING, {"default": "", "defaultInput": True}), + "string_b": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "concat" @@ -99,7 +100,7 @@ class StringConcat: return (string_a + string_b,) -class StringCount: +class StringCount(ComfyNodeABC): """ Counts occurrences of a substring within a string. @@ -110,16 +111,16 @@ class StringCount: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "substring": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "substring": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": 0, "min": 0}), + "start": (IO.INT, {"default": 0, "min": 0}), + "end": (IO.INT, {"default": 0, "min": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "count" @@ -132,7 +133,7 @@ class StringCount: return (string.count(substring, start, end),) -class StringDecode: +class StringDecode(ComfyNodeABC): """ Converts a bytes-like string representation back to a text string. @@ -144,13 +145,13 @@ class StringDecode: def INPUT_TYPES(cls): return { "required": { - "bytes_string": ("STRING", {"default": "b''", "defaultInput": True}), + "bytes_string": (IO.STRING, {"default": "b''", "defaultInput": True}), "encoding": (["utf-8", "ascii", "latin-1", "utf-16", "utf-32", "cp1252"],), "errors": (["strict", "ignore", "replace", "backslashreplace"],), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "decode" @@ -181,7 +182,7 @@ class StringDecode: return (f"Decoding error: {str(e)}",) -class StringEncode: +class StringEncode(ComfyNodeABC): """ Converts a string to bytes using the specified encoding. @@ -193,13 +194,13 @@ class StringEncode: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), "encoding": (["utf-8", "ascii", "latin-1", "utf-16", "utf-32", "cp1252"],), "errors": (["strict", "ignore", "replace", "xmlcharrefreplace", "backslashreplace"],), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "encode" @@ -217,7 +218,7 @@ class StringEncode: return (f"Encoding error: {str(e)}",) -class StringEndswith: +class StringEndswith(ComfyNodeABC): """ Checks if a string ends with the specified suffix. @@ -229,16 +230,16 @@ class StringEndswith: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "suffix": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "suffix": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": 0, "min": 0}), + "start": (IO.INT, {"default": 0, "min": 0}), + "end": (IO.INT, {"default": 0, "min": 0}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "endswith" @@ -251,7 +252,7 @@ class StringEndswith: return (string.endswith(suffix, start, end),) -class StringExpandtabs: +class StringExpandtabs(ComfyNodeABC): """ Replaces tab characters with spaces. @@ -263,14 +264,14 @@ class StringExpandtabs: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "tabsize": ("INT", {"default": 8, "min": 1, "max": 100}), + "tabsize": (IO.INT, {"default": 8, "min": 1, "max": 100}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "expandtabs" @@ -279,7 +280,7 @@ class StringExpandtabs: return (string.expandtabs(tabsize),) -class StringFind: +class StringFind(ComfyNodeABC): """ Finds the first occurrence of a substring in a string. @@ -291,16 +292,16 @@ class StringFind: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "substring": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "substring": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": 0, "min": 0}), + "start": (IO.INT, {"default": 0, "min": 0}), + "end": (IO.INT, {"default": 0, "min": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "find" @@ -313,7 +314,7 @@ class StringFind: return (string.find(substring, start, end),) -class StringIn: +class StringIn(ComfyNodeABC): """ Checks if a string contains a specified substring. @@ -325,12 +326,12 @@ class StringIn: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "substring": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "substring": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "contains" @@ -339,7 +340,7 @@ class StringIn: return (substring in string,) -class StringFormatMap: +class StringFormatMap(ComfyNodeABC): """ Formats a string using values from a dictionary. @@ -352,12 +353,12 @@ class StringFormatMap: def INPUT_TYPES(cls): return { "required": { - "template": ("STRING", {"default": "Hello, {key}", "defaultInput": True}), + "template": (IO.STRING, {"default": "Hello, {key}", "defaultInput": True}), "mapping": ("DICT", {"default": {}}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "format_map" @@ -372,7 +373,7 @@ class StringFormatMap: return (f"Formatting error: {str(e)}",) -class StringIsAlnum: +class StringIsAlnum(ComfyNodeABC): """ Checks if all characters in the string are alphanumeric. @@ -383,11 +384,11 @@ class StringIsAlnum: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isalnum" @@ -396,7 +397,7 @@ class StringIsAlnum: return (string.isalnum(),) -class StringIsAlpha: +class StringIsAlpha(ComfyNodeABC): """ Checks if all characters in the string are alphabetic. @@ -407,11 +408,11 @@ class StringIsAlpha: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isalpha" @@ -420,7 +421,7 @@ class StringIsAlpha: return (string.isalpha(),) -class StringIsAscii: +class StringIsAscii(ComfyNodeABC): """ Checks if all characters in the string are ASCII characters. @@ -431,11 +432,11 @@ class StringIsAscii: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isascii" @@ -444,7 +445,7 @@ class StringIsAscii: return (string.isascii(),) -class StringIsDecimal: +class StringIsDecimal(ComfyNodeABC): """ Checks if all characters in the string are decimal characters. @@ -457,11 +458,11 @@ class StringIsDecimal: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isdecimal" @@ -470,7 +471,7 @@ class StringIsDecimal: return (string.isdecimal(),) -class StringIsDigit: +class StringIsDigit(ComfyNodeABC): """ Checks if all characters in the string are digits. @@ -481,11 +482,11 @@ class StringIsDigit: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isdigit" @@ -494,7 +495,7 @@ class StringIsDigit: return (string.isdigit(),) -class StringIsIdentifier: +class StringIsIdentifier(ComfyNodeABC): """ Checks if the string is a valid identifier in Python. @@ -506,11 +507,11 @@ class StringIsIdentifier: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isidentifier" @@ -519,7 +520,7 @@ class StringIsIdentifier: return (string.isidentifier(),) -class StringIsLower: +class StringIsLower(ComfyNodeABC): """ Checks if all characters in the string are lowercase. @@ -530,11 +531,11 @@ class StringIsLower: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "islower" @@ -543,7 +544,7 @@ class StringIsLower: return (string.islower(),) -class StringIsNumeric: +class StringIsNumeric(ComfyNodeABC): """ Checks if all characters in the string are numeric. @@ -555,11 +556,11 @@ class StringIsNumeric: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isnumeric" @@ -568,7 +569,7 @@ class StringIsNumeric: return (string.isnumeric(),) -class StringIsPrintable: +class StringIsPrintable(ComfyNodeABC): """ Checks if all characters in the string are printable. @@ -579,11 +580,11 @@ class StringIsPrintable: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isprintable" @@ -592,7 +593,7 @@ class StringIsPrintable: return (string.isprintable(),) -class StringIsSpace: +class StringIsSpace(ComfyNodeABC): """ Checks if all characters in the string are whitespace. @@ -603,11 +604,11 @@ class StringIsSpace: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isspace" @@ -616,7 +617,7 @@ class StringIsSpace: return (string.isspace(),) -class StringIsTitle: +class StringIsTitle(ComfyNodeABC): """ Checks if the string is titlecased. @@ -628,11 +629,11 @@ class StringIsTitle: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "istitle" @@ -641,7 +642,7 @@ class StringIsTitle: return (string.istitle(),) -class StringIsUpper: +class StringIsUpper(ComfyNodeABC): """ Checks if all characters in the string are uppercase. @@ -652,11 +653,11 @@ class StringIsUpper: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING/is" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "isupper" @@ -665,7 +666,7 @@ class StringIsUpper: return (string.isupper(),) -class StringLjust: +class StringLjust(ComfyNodeABC): """ Left-aligns the string within a field of a given width. @@ -676,15 +677,15 @@ class StringLjust: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "width": ("INT", {"default": 10, "min": 0}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "width": (IO.INT, {"default": 10, "min": 0}), }, "optional": { - "fillchar": ("STRING", {"default": " "}), + "fillchar": (IO.STRING, {"default": " "}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "ljust" @@ -696,7 +697,7 @@ class StringLjust: return (string.ljust(width, fillchar),) -class StringLstrip: +class StringLstrip(ComfyNodeABC): """ Removes leading characters from the string. @@ -708,14 +709,14 @@ class StringLstrip: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "chars": ("STRING", {"default": ""}), + "chars": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "lstrip" @@ -726,7 +727,7 @@ class StringLstrip: return (string.lstrip(chars),) -class StringRemoveprefix: +class StringRemoveprefix(ComfyNodeABC): """ Removes prefix from the string if present. @@ -737,12 +738,12 @@ class StringRemoveprefix: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "prefix": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "prefix": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "removeprefix" @@ -751,7 +752,7 @@ class StringRemoveprefix: return (string.removeprefix(prefix),) -class StringRemovesuffix: +class StringRemovesuffix(ComfyNodeABC): """ Removes suffix from the string if present. @@ -762,12 +763,12 @@ class StringRemovesuffix: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "suffix": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "suffix": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "removesuffix" @@ -776,27 +777,28 @@ class StringRemovesuffix: return (string.removesuffix(suffix),) -class StringRsplit: +class StringRsplitDataList(ComfyNodeABC): """ Splits the string at the specified separator from the right. This node returns a list of strings by splitting the input string at the specified separator, starting from the right. If maxsplit is provided, at most maxsplit splits are done. If the separator is not specified or is None, any whitespace string is a separator. + Creates a data list of strings. """ @classmethod def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "sep": ("STRING", {"default": ""}), - "maxsplit": ("INT", {"default": -1, "min": -1}), + "sep": (IO.STRING, {"default": ""}), + "maxsplit": (IO.INT, {"default": -1, "min": -1}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rsplit" @@ -808,7 +810,39 @@ class StringRsplit: return (string.rsplit(sep, maxsplit),) -class StringLower: +class StringRsplitList(ComfyNodeABC): + """ + Splits the string at the specified separator from the right. + + This node returns a list of strings by splitting the input string at the specified separator, + starting from the right. If maxsplit is provided, at most maxsplit splits are done. + If the separator is not specified or is None, any whitespace string is a separator. + Creates a LIST of strings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": "", "defaultInput": True}), + }, + "optional": { + "sep": (IO.STRING, {"default": ""}), + "maxsplit": (IO.INT, {"default": -1, "min": -1}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "rsplit" + + def rsplit(self, string, sep=None, maxsplit=-1): + if sep == "": + sep = None + return (string.rsplit(sep, maxsplit),) + + +class StringLower(ComfyNodeABC): """ Converts the string to lowercase. @@ -818,11 +852,11 @@ class StringLower: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "lower" @@ -831,7 +865,7 @@ class StringLower: return (string.lower(),) -class StringReplace: +class StringReplace(ComfyNodeABC): """ Replaces occurrences of a substring with another substring. @@ -843,16 +877,16 @@ class StringReplace: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "old": ("STRING", {"default": ""}), - "new": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "old": (IO.STRING, {"default": ""}), + "new": (IO.STRING, {"default": ""}), }, "optional": { - "count": ("INT", {"default": -1, "min": -1}), + "count": (IO.INT, {"default": -1, "min": -1}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "replace" @@ -861,7 +895,7 @@ class StringReplace: return (string.replace(old, new, count),) -class StringRjust: +class StringRjust(ComfyNodeABC): """ Right-aligns the string within a field of a given width. @@ -872,15 +906,15 @@ class StringRjust: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "width": ("INT", {"default": 10, "min": 0}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "width": (IO.INT, {"default": 10, "min": 0}), }, "optional": { - "fillchar": ("STRING", {"default": " "}), + "fillchar": (IO.STRING, {"default": " "}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rjust" @@ -892,7 +926,7 @@ class StringRjust: return (string.rjust(width, fillchar),) -class StringJoin: +class StringDataListJoin(ComfyNodeABC): """ Joins strings from a data list with a specified separator. @@ -904,12 +938,12 @@ class StringJoin: def INPUT_TYPES(cls): return { "required": { - "sep": ("STRING", {"default": " ", "defaultInput": True}), - "strings": ("STRING", {"forceInput": True}), + "strings": (IO.STRING, {"forceInput": True}), + "sep": (IO.STRING, {"default": " "}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "join" @@ -920,7 +954,34 @@ class StringJoin: return (separator.join(strings),) -class StringRfind: +class StringListJoin(ComfyNodeABC): + """ + Joins strings from a LIST with a specified separator. + + This node takes a data list of strings and concatenates them, with the specified + separator string between each element. The separator is inserted between the strings, + not at the beginning or end of the result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "strings": ("LIST", {"forceInput": True}), + "sep": (IO.STRING, {"default": " "}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "join" + + def join(self, sep, strings): + separator = sep[0] # everything comes as a list, so sep is list[str] + return (separator.join(strings),) + + +class StringRfind(ComfyNodeABC): """ Finds the highest index of the substring in the string. @@ -932,16 +993,16 @@ class StringRfind: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "substring": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "substring": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": 0, "min": 0}), + "start": (IO.INT, {"default": 0, "min": 0}), + "end": (IO.INT, {"default": 0, "min": 0}), } } - RETURN_TYPES = ("INT",) + RETURN_TYPES = (IO.INT,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rfind" @@ -954,7 +1015,7 @@ class StringRfind: return (string.rfind(substring, start, end),) -class StringRstrip: +class StringRstrip(ComfyNodeABC): """ Removes trailing characters from the string. @@ -966,14 +1027,14 @@ class StringRstrip: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "chars": ("STRING", {"default": ""}), + "chars": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rstrip" @@ -984,7 +1045,7 @@ class StringRstrip: return (string.rstrip(chars),) -class StringSplit: +class StringSplitDataList(ComfyNodeABC): """ Splits the string at the specified separator. @@ -993,20 +1054,21 @@ class StringSplit: splits are done. If the separator is not specified or is None, any whitespace string is a separator. + Creates a data list of strings. """ @classmethod def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "sep": ("STRING", {"default": ""}), - "maxsplit": ("INT", {"default": -1, "min": -1}), + "sep": (IO.STRING, {"default": ""}), + "maxsplit": (IO.INT, {"default": -1, "min": -1}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) FUNCTION = "split" CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") @@ -1021,37 +1083,103 @@ class StringSplit: return (parts,) -class StringSplitlines: +class StringSplitList(ComfyNodeABC): """ - Splits the string at line boundaries. + Splits the string at the specified separator. - This node returns a list of the lines in the string, breaking at line boundaries. - If keepends is True, line breaks are included in the resulting list. + This node splits the input string at the specified separator and returns a + data list containing all parts. If maxsplit is provided, at most maxsplit + splits are done. + + If the separator is not specified or is None, any whitespace string is a separator. + Creates a LIST of strings. """ @classmethod def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "keepends": (["False", "True"], {"default": "False"}), + "sep": (IO.STRING, {"default": ""}), + "maxsplit": (IO.INT, {"default": -1, "min": -1}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = ("LIST",) + FUNCTION = "split" + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + + def split(self, string, sep=None, maxsplit=-1): + if sep == "": + sep = None + + parts = string.split(sep, maxsplit) + # Return a data list where each item is a separate string + return (parts,) + + +class StringSplitlinesDataList(ComfyNodeABC): + """ + Splits the string at line boundaries. + + This node returns a list of the lines in the string, breaking at line boundaries. + If keepends is True, line breaks are included in the resulting list. + Creates a data list of strings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": "", "defaultInput": True}), + }, + "optional": { + "keepends": (IO.BOOLEAN, {"default": False}), + } + } + + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "splitlines" OUTPUT_IS_LIST = (True,) - def splitlines(self, string, keepends="False"): - keepends_bool = (keepends == "True") - lines = string.splitlines(keepends_bool) + def splitlines(self, string, keepends=False): + lines = string.splitlines(keepends) return (lines,) -class StringStartswith: +class StringSplitlinesList(ComfyNodeABC): + """ + Splits the string at line boundaries. + + This node returns a list of the lines in the string, breaking at line boundaries. + If keepends is True, line breaks are included in the resulting list. + Creates a LIST of strings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": "", "defaultInput": True}), + }, + "optional": { + "keepends": (IO.BOOLEAN, {"default": False}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "splitlines" + + def splitlines(self, string, keepends=False): + lines = string.splitlines(keepends) + return (lines,) + + +class StringStartswith(ComfyNodeABC): """ Checks if a string starts with the specified prefix. @@ -1063,16 +1191,16 @@ class StringStartswith: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "prefix": ("STRING", {"default": ""}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "prefix": (IO.STRING, {"default": ""}), }, "optional": { - "start": ("INT", {"default": 0, "min": 0}), - "end": ("INT", {"default": 0, "min": 0}), + "start": (IO.INT, {"default": 0, "min": 0}), + "end": (IO.INT, {"default": 0, "min": 0}), } } - RETURN_TYPES = ("BOOLEAN",) + RETURN_TYPES = (IO.BOOLEAN,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "startswith" @@ -1085,7 +1213,7 @@ class StringStartswith: return (string.startswith(prefix, start, end),) -class StringStrip: +class StringStrip(ComfyNodeABC): """ Removes leading and trailing characters from the string. @@ -1097,14 +1225,14 @@ class StringStrip: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), }, "optional": { - "chars": ("STRING", {"default": ""}), + "chars": (IO.STRING, {"default": ""}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "strip" @@ -1115,7 +1243,7 @@ class StringStrip: return (string.strip(chars),) -class StringSwapcase: +class StringSwapcase(ComfyNodeABC): """ Swaps case of all characters in the string. @@ -1126,11 +1254,11 @@ class StringSwapcase: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "swapcase" @@ -1139,7 +1267,7 @@ class StringSwapcase: return (string.swapcase(),) -class StringTitle: +class StringTitle(ComfyNodeABC): """ Converts the string to titlecase. @@ -1150,11 +1278,11 @@ class StringTitle: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "title" @@ -1163,7 +1291,7 @@ class StringTitle: return (string.title(),) -class StringUpper: +class StringUpper(ComfyNodeABC): """ Converts the string to uppercase. @@ -1173,11 +1301,11 @@ class StringUpper: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "upper" @@ -1186,7 +1314,7 @@ class StringUpper: return (string.upper(),) -class StringZfill: +class StringZfill(ComfyNodeABC): """ Pads the string with zeros on the left. @@ -1198,12 +1326,12 @@ class StringZfill: def INPUT_TYPES(cls): return { "required": { - "string": ("STRING", {"default": "", "defaultInput": True}), - "width": ("INT", {"default": 10, "min": 0}), + "string": (IO.STRING, {"default": "", "defaultInput": True}), + "width": (IO.INT, {"default": 10, "min": 0}), } } - RETURN_TYPES = ("STRING",) + RETURN_TYPES = (IO.STRING,) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "zfill" @@ -1212,6 +1340,29 @@ class StringZfill: return (string.zfill(width),) +class StringLength(ComfyNodeABC): + """ + Returns the length of a string. + + This node calculates and returns the number of characters in the input string. + It works the same way as Python's built-in len() function for strings. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": "", "defaultInput": True}), + } + } + + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "length" + + def length(self, string): + return (len(string),) + NODE_CLASS_MAPPINGS = { "Basic data handling: StringCapitalize": StringCapitalize, @@ -1237,7 +1388,8 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: StringIsSpace": StringIsSpace, "Basic data handling: StringIsTitle": StringIsTitle, "Basic data handling: StringIsUpper": StringIsUpper, - "Basic data handling: StringJoin": StringJoin, + "Basic data handling: StringDataListJoin": StringDataListJoin, + "Basic data handling: StringListJoin": StringListJoin, "Basic data handling: StringLjust": StringLjust, "Basic data handling: StringLower": StringLower, "Basic data handling: StringLstrip": StringLstrip, @@ -1246,16 +1398,20 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: StringReplace": StringReplace, "Basic data handling: StringRfind": StringRfind, "Basic data handling: StringRjust": StringRjust, - "Basic data handling: StringRsplit": StringRsplit, + "Basic data handling: StringRsplitDataList": StringRsplitDataList, + "Basic data handling: StringRsplitList": StringRsplitList, "Basic data handling: StringRstrip": StringRstrip, - "Basic data handling: StringSplit": StringSplit, - "Basic data handling: StringSplitlines": StringSplitlines, + "Basic data handling: StringSplitDataList": StringSplitDataList, + "Basic data handling: StringSplitList": StringSplitList, + "Basic data handling: StringSplitlinesDataList": StringSplitlinesDataList, + "Basic data handling: StringSplitlinesList": StringSplitlinesList, "Basic data handling: StringStartswith": StringStartswith, "Basic data handling: StringStrip": StringStrip, "Basic data handling: StringSwapcase": StringSwapcase, "Basic data handling: StringTitle": StringTitle, "Basic data handling: StringUpper": StringUpper, "Basic data handling: StringZfill": StringZfill, + "Basic data handling: StringLength": StringLength, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -1283,7 +1439,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: StringIsSpace": "isspace", "Basic data handling: StringIsTitle": "istitle", "Basic data handling: StringIsUpper": "isupper", - "Basic data handling: StringJoin": "join", + "Basic data handling: StringDataListJoin": "join (from data list)", + "Basic data handling: StringListJoin": "join (from LIST)", "Basic data handling: StringLjust": "ljust", "Basic data handling: StringLower": "lower", "Basic data handling: StringLstrip": "lstrip", @@ -1292,14 +1449,18 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: StringReplace": "replace", "Basic data handling: StringRfind": "rfind", "Basic data handling: StringRjust": "rjust", - "Basic data handling: StringRsplit": "rsplit", + "Basic data handling: StringRsplitDataList": "rsplit (from data list)", + "Basic data handling: StringRsplitList": "rsplit (from LIST)", "Basic data handling: StringRstrip": "rstrip", - "Basic data handling: StringSplit": "split", - "Basic data handling: StringSplitlines": "splitlines", + "Basic data handling: StringSplitDataList": "split (to data list)", + "Basic data handling: StringSplitList": "split (to LIST)", + "Basic data handling: StringSplitlinesDataList": "splitlines (from data list)", + "Basic data handling: StringSplitlinesList": "splitlines (to LIST)", "Basic data handling: StringStartswith": "startswith", "Basic data handling: StringStrip": "strip", "Basic data handling: StringSwapcase": "swapcase", "Basic data handling: StringTitle": "title", "Basic data handling: StringUpper": "upper", "Basic data handling: StringZfill": "zfill", + "Basic data handling: StringLength": "length", }