From 2a0c87f9a491e9fc5cb7674b5ef5eaf4d7fb4cde Mon Sep 17 00:00:00 2001 From: StableLlama Date: Wed, 7 May 2025 18:13:42 +0200 Subject: [PATCH] Add BOOLEAN, FLOAT, INT and data lists. Bump to version 0.0.3 --- __init__.py | 4 - pyproject.toml | 10 +- src/__init__.py | 0 src/basic_data_handling/__init__.py | 16 +- src/basic_data_handling/boolean_nodes.py | 162 ++++++ src/basic_data_handling/float_nodes.py | 274 ++++++++++ src/basic_data_handling/int_nodes.py | 285 ++++++++++ src/basic_data_handling/list_nodes.py | 657 ++++++++++++++++++++++- src/basic_data_handling/nodes.py | 118 ---- src/basic_data_handling/string_nodes.py | 67 ++- 10 files changed, 1436 insertions(+), 157 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/basic_data_handling/boolean_nodes.py create mode 100644 src/basic_data_handling/float_nodes.py create mode 100644 src/basic_data_handling/int_nodes.py delete mode 100644 src/basic_data_handling/nodes.py diff --git a/__init__.py b/__init__.py index af7d5a7..48208d6 100644 --- a/__init__.py +++ b/__init__.py @@ -6,10 +6,6 @@ __all__ = [ "WEB_DIRECTORY", ] -__author__ = """Basic data handling""" -__email__ = "stablellama@tutanota.com" -__version__ = "0.0.1" - from .src.basic_data_handling import NODE_CLASS_MAPPINGS from .src.basic_data_handling import NODE_DISPLAY_NAME_MAPPINGS diff --git a/pyproject.toml b/pyproject.toml index b764ce5..f8235ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,18 +4,16 @@ build-backend = "setuptools.build_meta" [project] name = "basic_data_handling" -version = "0.0.2" -description = """Basic Python functions for manipulating data that every programmer -is used to. Currently supported: STRING.""" +version = "0.0.3" +description = """Basic Python functions for manipulating data that every programmer is used to. +Currently supported: BOOLEAN, FLOAT, INT, STRING and data lists.""" authors = [ {name = "Basic data handling"} ] readme = "README.md" license = {text = "MIT license"} classifiers = [] -dependencies = [ - -] +dependencies = [] [project.optional-dependencies] dev = [ diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/basic_data_handling/__init__.py b/src/basic_data_handling/__init__.py index b59b00b..da7ebf2 100644 --- a/src/basic_data_handling/__init__.py +++ b/src/basic_data_handling/__init__.py @@ -1,11 +1,17 @@ -from . import string_nodes, list_nodes, dict_nodes +from . import boolean_nodes, dict_nodes, float_nodes, int_nodes, list_nodes, string_nodes NODE_CLASS_MAPPINGS = {} -NODE_CLASS_MAPPINGS.update(string_nodes.NODE_CLASS_MAPPINGS) -NODE_CLASS_MAPPINGS.update(list_nodes.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(boolean_nodes.NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(dict_nodes.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(float_nodes.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(int_nodes.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(list_nodes.NODE_CLASS_MAPPINGS) +NODE_CLASS_MAPPINGS.update(string_nodes.NODE_CLASS_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS = {} -NODE_DISPLAY_NAME_MAPPINGS.update(string_nodes.NODE_DISPLAY_NAME_MAPPINGS) -NODE_DISPLAY_NAME_MAPPINGS.update(list_nodes.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(boolean_nodes.NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(dict_nodes.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(float_nodes.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(int_nodes.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(list_nodes.NODE_DISPLAY_NAME_MAPPINGS) +NODE_DISPLAY_NAME_MAPPINGS.update(string_nodes.NODE_DISPLAY_NAME_MAPPINGS) diff --git a/src/basic_data_handling/boolean_nodes.py b/src/basic_data_handling/boolean_nodes.py new file mode 100644 index 0000000..e49b217 --- /dev/null +++ b/src/basic_data_handling/boolean_nodes.py @@ -0,0 +1,162 @@ +from inspect import cleandoc + +class BooleanAnd: + """ + Returns the logical AND result of two boolean values. + + This node takes two boolean inputs and returns their logical AND result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input1": ("BOOLEAN", {"default": False}), + "input2": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "and_operation" + + def and_operation(self, input1: bool, input2: bool) -> tuple[bool]: + return (input1 and input2,) + + +class BooleanOr: + """ + Returns the logical OR result of two boolean values. + + This node takes two boolean inputs and returns their logical OR result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input1": ("BOOLEAN", {"default": False}), + "input2": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "or_operation" + + def or_operation(self, input1: bool, input2: bool) -> tuple[bool]: + return (input1 or input2,) + + +class BooleanNot: + """ + Returns the logical NOT result of a boolean value. + + This node takes one boolean input and returns its logical NOT result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "not_operation" + + def not_operation(self, input: bool) -> tuple[bool]: + return (not input,) + + +class BooleanXor: + """ + Returns the logical XOR result of two boolean values. + + This node takes two boolean inputs and returns their logical XOR result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input1": ("BOOLEAN", {"default": False}), + "input2": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "xor_operation" + + def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]: + return (input1 != input2,) + + +class BooleanNand: + """ + Returns the logical NAND result of two boolean values. + + This node takes two boolean inputs and returns their logical NAND result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input1": ("BOOLEAN", {"default": False}), + "input2": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "nand_operation" + + def nand_operation(self, input1: bool, input2: bool) -> tuple[bool]: + return (not (input1 and input2),) + + +class BooleanNor: + """ + Returns the logical NOR result of two boolean values. + + This node takes two boolean inputs and returns their logical NOR result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input1": ("BOOLEAN", {"default": False}), + "input2": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/BOOLEAN" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "nor_operation" + + def nor_operation(self, input1: bool, input2: bool) -> tuple[bool]: + return (not (input1 or input2),) + + +NODE_CLASS_MAPPINGS = { + "BooleanAnd": BooleanAnd, + "BooleanOr": BooleanOr, + "BooleanNot": BooleanNot, + "BooleanXor": BooleanXor, + "BooleanNand": BooleanNand, + "BooleanNor": BooleanNor, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "BooleanAnd": "and", + "BooleanOr": "or", + "BooleanNot": "not", + "BooleanXor": "xor", + "BooleanNand": "nand", + "BooleanNor": "nor", +} diff --git a/src/basic_data_handling/float_nodes.py b/src/basic_data_handling/float_nodes.py new file mode 100644 index 0000000..869311a --- /dev/null +++ b/src/basic_data_handling/float_nodes.py @@ -0,0 +1,274 @@ +from inspect import cleandoc + +class FloatAdd: + """ + Adds two floating-point numbers. + + This node takes two floats as input and returns their sum. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float1": ("FLOAT", {"default": 0.0}), + "float2": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "add" + + def add(self, float1: float, float2: float) -> tuple[float]: + return (float1 + float2,) + + +class FloatSubtract: + """ + Subtracts one floating-point number from another. + + This node takes two floats as input and returns the result of subtracting + the second float from the first. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float1": ("FLOAT", {"default": 0.0}), + "float2": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "subtract" + + def subtract(self, float1: float, float2: float) -> tuple[float]: + return (float1 - float2,) + + +class FloatMultiply: + """ + Multiplies two floating-point numbers. + + This node takes two floats as input and returns their product. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float1": ("FLOAT", {"default": 1.0}), + "float2": ("FLOAT", {"default": 1.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "multiply" + + def multiply(self, float1: float, float2: float) -> tuple[float]: + return (float1 * float2,) + + +class FloatDivide: + """ + Divides one floating-point number by another. + + This node takes two floats as input and returns the result of division. + It raises a ValueError if the divisor is 0. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float1": ("FLOAT", {"default": 1.0}), + "float2": ("FLOAT", {"default": 1.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "divide" + + def divide(self, float1: float, float2: float) -> tuple[float]: + if float2 == 0.0: + raise ValueError("Cannot divide by zero.") + return (float1 / float2,) + + +class FloatPower: + """ + Raises one floating-point number to the power of another. + + This node takes two floats as input and returns the result of raising + the first float to the power of the second. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "base": ("FLOAT", {"default": 1.0}), + "exponent": ("FLOAT", {"default": 1.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "power" + + def power(self, base: float, exponent: float) -> tuple[float]: + return (base ** exponent,) + + +class FloatRound: + """ + Rounds a floating-point number to the specified number of decimal places. + + This node takes a float and an integer for decimal places as inputs, + and returns the rounded result. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 0.0}), + "decimal_places": ("INT", {"default": 2, "min": 0}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "round" + + def round(self, float_value: float, decimal_places: int) -> tuple[float]: + return (round(float_value, decimal_places),) + + +class FloatIsInteger: + """ + Checks if a floating-point number is an integer. + + This node takes a floating-point number as input and returns True if the number + is an integer, otherwise False. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "is_integer" + + def is_integer(self, float_value: float) -> tuple[bool]: + return (float_value.is_integer(),) + + +class FloatAsIntegerRatio: + """ + Returns the integer ratio of a floating-point number. + + This node takes a floating-point number and returns two integers, + which represent the ratio as numerator and denominator. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("INT", "INT") + RETURN_NAMES = ("numerator", "denominator") + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "as_integer_ratio" + + def as_integer_ratio(self, float_value: float) -> tuple[int, int]: + # Decompose the float into numerator and denominator + numerator, denominator = float_value.as_integer_ratio() + return numerator, denominator + + +class FloatHex: + """ + Converts a floating-point number to its hexadecimal representation. + + This node takes a float as input and returns its hexadecimal string representation. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "float_value": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("STRING",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "to_hex" + + def to_hex(self, float_value: float) -> tuple[str]: + return (float_value.hex(),) + + +class FloatFromHex: + """ + Converts a hexadecimal string to its corresponding floating-point number. + + This node takes a hexadecimal float string as input and returns the float. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "hex_value": ("STRING", {"default": "0x0.0p+0"}), + } + } + + RETURN_TYPES = ("FLOAT",) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "from_hex" + + def from_hex(self, hex_value: str) -> tuple[float]: + return (float.fromhex(hex_value),) + + +NODE_CLASS_MAPPINGS = { + "FloatAdd": FloatAdd, + "FloatSubtract": FloatSubtract, + "FloatMultiply": FloatMultiply, + "FloatDivide": FloatDivide, + "FloatPower": FloatPower, + "FloatRound": FloatRound, + "FloatIsInteger": FloatIsInteger, + "FloatAsIntegerRatio": FloatAsIntegerRatio, + "FloatHex": FloatHex, + "FloatFromHex": FloatFromHex, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "FloatAdd": "add", + "FloatSubtract": "subtract", + "FloatMultiply": "multiply", + "FloatDivide": "divide", + "FloatPower": "power", + "FloatRound": "round", + "FloatIsInteger": "is_integer", + "FloatAsIntegerRatio": "as_integer_ratio", + "FloatHex": "to_hex", + "FloatFromHex": "from_hex", +} diff --git a/src/basic_data_handling/int_nodes.py b/src/basic_data_handling/int_nodes.py new file mode 100644 index 0000000..3287d91 --- /dev/null +++ b/src/basic_data_handling/int_nodes.py @@ -0,0 +1,285 @@ +from inspect import cleandoc +from typing import Literal + + +class IntAdd: + """ + Adds two integers. + + This node takes two integers as input and returns their sum. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": ("INT", {"default": 0}), + "int2": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "add" + + def add(self, int1: int, int2: int) -> tuple[int]: + return (int1 + int2,) + + +class IntSubtract: + """ + Subtracts one integer from another. + + This node takes two integers as input and returns the result of subtracting + the second integer from the first. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": ("INT", {"default": 0}), + "int2": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "subtract" + + def subtract(self, int1: int, int2: int) -> tuple[int]: + return (int1 - int2,) + + +class IntMultiply: + """ + Multiplies two integers. + + This node takes two integers as input and returns their product. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": ("INT", {"default": 1}), + "int2": ("INT", {"default": 1}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "multiply" + + def multiply(self, int1: int, int2: int) -> tuple[int]: + return (int1 * int2,) + + +class IntDivide: + """ + Divides one integer by another. + + This node takes two integers as input and returns the result of integer + division. It raises a ValueError if the divisor is 0. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": ("INT", {"default": 1}), + "int2": ("INT", {"default": 1}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "divide" + + def divide(self, int1: int, int2: int) -> tuple[int]: + if int2 == 0: + raise ValueError("Cannot divide by zero.") + return (int1 // int2,) + + +class IntModulus: + """ + Returns the modulus of two integers. + + This node takes two integers as input and returns the remainder when the + first integer is divided by the second. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int1": ("INT", {"default": 0}), + "int2": ("INT", {"default": 1}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "modulus" + + def modulus(self, int1: int, int2: int) -> tuple[int]: + if int2 == 0: + raise ValueError("Cannot perform modulus operation by zero.") + return (int1 % int2,) + + +class IntPower: + """ + Raises one integer to the power of another. + + This node takes two integers as input and returns the result of raising + the first integer to the power of the second. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "base": ("INT", {"default": 1}), + "exponent": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "power" + + def power(self, base: int, exponent: int) -> tuple[int]: + return (base**exponent,) + + +class IntBitLength: + """ + Returns the number of bits required to represent an integer in binary. + + This node takes an integer as input and returns the number of bits needed + to represent it, excluding the sign and leading zeros. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int_value": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Additional Methods/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "bit_length" + + def bit_length(self, int_value: int) -> tuple[int]: + return (int_value.bit_length(),) + + +class IntToBytes: + """ + Converts an integer to its byte representation. + + This node takes an integer, byte length, and byte order as inputs and + returns the bytes object representation of the integer. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int_value": ("INT", {"default": 0}), + "length": ("INT", {"default": 4, "min": 1}), + "byteorder": (["big", "little"], {"default": "big"}), + "signed": (["True", "False"], {"default": "False"}), + } + } + + RETURN_TYPES = ("BYTES",) + CATEGORY = "Additional Methods/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "to_bytes" + + def to_bytes(self, int_value: int, length: int, byteorder: Literal["big"|"little"], signed: Literal["True"|"False"]) -> tuple[int]: + signed_bool = (signed == "True") + return (int_value.to_bytes(length, byteorder=byteorder, signed=signed_bool),) + + +class IntFromBytes: + """ + Converts a bytes object to an integer. + + This class method takes bytes, byte order, and signed flag as inputs and + returns an integer. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "bytes_value": ("BYTES", {}), + "byteorder": (["big", "little"], {"default": "big"}), + "signed": (["True", "False"], {"default": "False"}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Additional Methods/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "from_bytes" + + def from_bytes(self, bytes_value, byteorder: Literal["big"|"little"], signed: Literal["True"|"False"]) -> tuple[int]: + signed_bool = (signed == "True") + return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),) + + +class IntBitCount: + """ + Returns the number of 1 bits in the binary representation of an integer. + + This node takes an integer as input and returns the count of set bits. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int_value": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("INT",) + CATEGORY = "Additional Methods/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "bit_count" + + def bit_count(self, int_value: int) -> tuple[int]: + # Introduced in Python 3.10 + return (int_value.bit_count(),) + + +NODE_CLASS_MAPPINGS = { + "IntAdd": IntAdd, + "IntSubtract": IntSubtract, + "IntMultiply": IntMultiply, + "IntDivide": IntDivide, + "IntModulus": IntModulus, + "IntPower": IntPower, + "IntBitLength": IntBitLength, + "IntToBytes": IntToBytes, + "IntFromBytes": IntFromBytes, + "IntBitCount": IntBitCount, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "IntAdd": "add", + "IntSubtract": "subtract", + "IntMultiply": "multiply", + "IntDivide": "divide", + "IntModulus": "modulus", + "IntPower": "power", + "IntBitLength": "bit_length", + "IntToBytes": "to_bytes", + "IntFromBytes": "from_bytes", + "IntBitCount": "bit_count", +} + diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index a4c9a54..8542f52 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -1,3 +1,5 @@ +from typing import Any + from inspect import cleandoc class ListAppend: @@ -10,20 +12,389 @@ class ListAppend: @classmethod def INPUT_TYPES(cls): return { - "required": { - "input_list": ("LIST",), - "item": ("ANY",), + "optional": { + "list":("*",{}), + "item":("*",{}), } } - RETURN_TYPES = ("LIST",) - CATEGORY = "Basic/LIST" + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "append" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def append(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + result = kwargs.get('list', []).copy() + item = kwargs.get('item', []) + if len(item) > 0: + result.append(item[0]) + return (result,) + + +class ListExtend: + """ + Extends a list by appending elements from another list. + + This node takes two lists as input and returns a new list that contains + all elements from both lists. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "list_a": ("*",{}), + "list_b": ("*",{}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "extend" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def extend(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + return (kwargs.get('list_a', []) + kwargs.get('list_b', []),) + + +class ListInsert: + """ + Inserts an item at a specified position in a list. + + This node takes a list, an index, and any item as inputs, then returns a new + list with the item inserted at the specified index. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + "index": ("INT", {"default": 0}), + "item": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "insert" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def insert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + result = kwargs.get('list', []).copy() + result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0]) + return (result,) + + +class ListRemove: + """ + Removes the first occurrence of a specified value from a list. + + This node takes a list and a value as inputs, then returns a new list with + the first occurrence of the value removed. Raises a ValueError if the value is not present. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + "value": ("*",), + } + } + + RETURN_TYPES = ("*", "BOOLEAN",) + RETURN_NAMES = ("list", "success",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "remove" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True, False,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def remove(self, **kwargs: dict[str, list]) -> tuple[list[Any], bool]: + result = kwargs.get('list', []).copy() + value = kwargs.get('value', []) + try: + result.remove(value[0]) + return result, True + except ValueError: + return result, False + + +class ListPop: + """ + Removes and returns an item at a specified position in a list. + + This node takes a list and an index as inputs, then returns both the new list + with the item removed and the removed item. If no index is specified, + removes and returns the last item. + When the list is empty, the item is None. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + }, + "optional": { + "index": ("INT", {"default": -1}), + } + } + + RETURN_TYPES = ("*", "*") + RETURN_NAMES = ("list", "item") + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "pop" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True, False) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + if input_types["index"] not in ("INT"): + return "index must be an INT type" + return True + + def pop(self, **kwargs: dict[str, list]) -> tuple[list[Any], Any]: + result = kwargs.get('list', []).copy() + index = kwargs.get('index', [-1])[0] + try: + item = result.pop(index) + return result, item + except IndexError: + 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 { + "optional": { + "list": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "clear" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def clear(self, **kwargs: dict[str, list]) -> tuple[[]]: + # Return a new empty list rather than modifying the input + return ([],) + + +class ListIndex: + """ + Returns the index of the first occurrence of a value in a list. + + This node takes a list and a value as inputs, then returns the index of the first + occurrence of the value. Optional start and end parameters limit the search to a slice + of the list. Returns -1 if the value is not present. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + "value": ("*",), + }, + "optional": { + "start": ("INT", {"default": 0}), + "end": ("INT", {"default": -1}), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("index",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "list_index" + INPUT_IS_LIST = True + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def list_index(self, **kwargs: dict[str, list]) -> tuple[int]: + input_list = kwargs.get('list', []) + value = kwargs.get('value', [None])[0] + start = kwargs.get('start', [0])[0] + end = kwargs.get('end', [-1])[0] + if end == -1: + end = len(input_list) + + try: + return (input_list.index(value, start, end),) + except ValueError: + return (-1,) + + +class ListCount: + """ + Counts the number of occurrences of a value in a list. + + This node takes a list and a value as inputs, then returns the number of times + the value appears in the list. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + "value": ("*",), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("count",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "count" + INPUT_IS_LIST = True + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def count(self, **kwargs: dict[str, list]) -> tuple[int]: + value = kwargs.get('value', [None])[0] + return (kwargs.get('list', []).count(value),) + + +class ListSort: + """ + Sorts the items in a list. + + This node takes a list as input and returns a new sorted list. + Options include sorting in reverse order and using a key function. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + }, + "optional": { + "reverse": (["False", "True"], {"default": "False"}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "sort" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def sort(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + # Convert string to boolean + reverse = kwargs.get('reverse', ["False"])[0] == "True" + + result = sorted(kwargs.get('list', []), reverse=reverse) + return (result,) + + +class ListReverse: + """ + Reverses the order of items in a list. + + This node takes a list as input and returns a new list with the items in reversed order. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "reverse" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def reverse(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + result = kwargs.get('list', []).copy() + result.reverse() + return (result,) + + +class ListCopy: + """ + Creates a shallow copy of a list. + + This node takes a list as input and returns a new list that is a shallow copy of the original. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "copy" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def copy(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + return (kwargs.get('list', []).copy(),) - def append(self, input_list, item): - input_list.append(item) - return (input_list,) class ListLength: """ @@ -35,24 +406,286 @@ class ListLength: def INPUT_TYPES(cls): return { "required": { - "input_list": ("LIST",), + "list": ("*",), } } RETURN_TYPES = ("INT",) - CATEGORY = "Basic/LIST" + RETURN_NAMES = ("length",) + CATEGORY = "Basic/list" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "length" + INPUT_IS_LIST = True + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def length(self, **kwargs: dict[str, list]) -> tuple[int]: + return (len(kwargs.get('list', [])),) + + +class ListSlice: + """ + Creates a slice of a list. + + This node takes a list and start/stop/step parameters, and returns a new list + containing the specified slice of the original list. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + }, + "optional": { + "start": ("INT", {"default": 0}), + "stop": ("INT", {"default": -1}), + "step": ("INT", {"default": 1}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "slice" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def slice(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + input_list = kwargs.get('list', []) + start = kwargs.get('start', [0])[0] + stop = kwargs.get('stop', [-1])[0] + step = kwargs.get('step', [1])[0] + + if stop == -1: + stop = len(input_list) + + return (input_list[start:stop:step],) + + +class ListGetItem: + """ + Retrieves an item at a specified position in a list. + + This node takes a list and an index as inputs, then returns the item at the specified index. + Negative indices count from the end of the list. + Out of range indices return None. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("*",), + "index": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("item",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "get_item" + INPUT_IS_LIST = True + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def get_item(self, **kwargs: dict[str, list]) -> tuple[Any]: + index = kwargs.get('index', [0])[0] + try: + return (kwargs.get('list', [])[index],) + except IndexError: + return (None,) + + +class ListSetItem: + """ + Sets an item at a specified position in a list. + + This node takes a list, an index, and a value, then returns a new list with + the item at the specified index replaced by the value. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("*",), + "index": ("INT", {"default": 0}), + "value": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "set_item" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def set_item(self, **kwargs: dict[str, list]) -> tuple[Any]: + input_list = kwargs.get('list', []) + index = kwargs.get('index', [0])[0] + value = kwargs.get('value', [None])[0] + try: + result = input_list.copy() + result[index] = value + return (result,) + except IndexError: + raise IndexError(f"Index {index} out of range for list of length {len(input_list)}") + + +class ListContains: + """ + Checks if a list contains a specified value. + + This node takes a list and a value as inputs, then returns True if the value + is present in the list, and False otherwise. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("*",), + "value": ("*",), + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("contains",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "contains" + INPUT_IS_LIST = True + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def contains(self, **kwargs: dict[str, list]) -> tuple[bool]: + value = kwargs.get('value', []) + if len(value) == 0: + return (False,) + return (value[0] in kwargs.get('list', []),) + + +class ListCreateEmpty: + """ + Creates a new empty list. + + This node creates and returns an empty list. + """ + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_empty" + OUTPUT_IS_LIST = (True,) + + def create_empty(self) -> tuple[[]]: + return ([],) + + +class ListZip: + """ + Combines multiple lists element-wise. + + This node takes multiple data lists as input and returns a new data list + where each item is a list containing the corresponding elements from the input lists. + The length of the output list will be equal to the length of the shortest input list. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list1": ("*",), + "list2": ("*",), + }, + "optional": { + "list3": ("*",), + "list4": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/list" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "zip_lists" + INPUT_IS_LIST = True + OUTPUT_IS_LIST = (True,) + + @classmethod + def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool: + return True + + def zip_lists(self, **kwargs: dict[str, list]) -> tuple[list[Any]]: + lists = [kwargs.get('list1', []), kwargs.get('list2', [])] + + if 'list3' in kwargs: + lists.append(kwargs['list3']) + + if 'list4' in kwargs: + lists.append(kwargs['list4']) + + # Zip the lists together and convert each tuple to a list + result = [list(item) for item in zip(*lists)] + return (result,) - def length(self, input_list): - return (len(input_list),) NODE_CLASS_MAPPINGS = { "ListAppend": ListAppend, + "ListExtend": ListExtend, + "ListInsert": ListInsert, + "ListRemove": ListRemove, + "ListPop": ListPop, + "ListClear": ListClear, + "ListIndex": ListIndex, + "ListCount": ListCount, + "ListSort": ListSort, + "ListReverse": ListReverse, + "ListCopy": ListCopy, "ListLength": ListLength, + "ListSlice": ListSlice, + "ListGetItem": ListGetItem, + "ListSetItem": ListSetItem, + "ListContains": ListContains, + "ListCreateEmpty": ListCreateEmpty, + "ListZip": ListZip, } NODE_DISPLAY_NAME_MAPPINGS = { "ListAppend": "append", + "ListExtend": "extend", + "ListInsert": "insert", + "ListRemove": "remove", + "ListPop": "pop", + "ListClear": "clear", + "ListIndex": "index", + "ListCount": "count", + "ListSort": "sort", + "ListReverse": "reverse", + "ListCopy": "copy", "ListLength": "length", + "ListSlice": "slice", + "ListGetItem": "get_item", + "ListSetItem": "set_item", + "ListContains": "contains", + "ListCreateEmpty": "create_empty", + "ListZip": "zip", } diff --git a/src/basic_data_handling/nodes.py b/src/basic_data_handling/nodes.py deleted file mode 100644 index fab5cff..0000000 --- a/src/basic_data_handling/nodes.py +++ /dev/null @@ -1,118 +0,0 @@ -from inspect import cleandoc -class Example: - """ - A example node - - Class methods - ------------- - INPUT_TYPES (dict): - Tell the main program input parameters of nodes. - IS_CHANGED: - optional method to control when the node is re executed. - - Attributes - ---------- - RETURN_TYPES (`tuple`): - The type of each element in the output tulple. - RETURN_NAMES (`tuple`): - Optional: The name of each output in the output tulple. - FUNCTION (`str`): - The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() - OUTPUT_NODE ([`bool`]): - If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. - The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. - Assumed to be False if not present. - CATEGORY (`str`): - The category the node should appear in the UI. - execute(s) -> tuple || None: - The entry point method. The name of this method must be the same as the value of property `FUNCTION`. - For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. - """ - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - """ - Return a dictionary which contains config for all input fields. - Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". - Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. - The type can be a list for selection. - - Returns: `dict`: - - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` - - Value input_fields (`dict`): Contains input fields config: - * Key field_name (`string`): Name of a entry-point method's argument - * Value field_config (`tuple`): - + First value is a string indicate the type of field or a list for selection. - + Secound value is a config for type "INT", "STRING" or "FLOAT". - """ - return { - "required": { - "image": ("Image", { "tooltip": "This is an image"}), - "int_field": ("INT", { - "default": 0, - "min": 0, #Minimum value - "max": 4096, #Maximum value - "step": 64, #Slider's step - "display": "number" # Cosmetic only: display as "number" or "slider" - }), - "float_field": ("FLOAT", { - "default": 1.0, - "min": 0.0, - "max": 10.0, - "step": 0.01, - "round": 0.001, #The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. - "display": "number"}), - "print_to_screen": (["enable", "disable"],), - "string_field": ("STRING", { - "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node - "default": "Hello World!" - }), - }, - } - - RETURN_TYPES = ("IMAGE",) - #RETURN_NAMES = ("image_output_name",) - DESCRIPTION = cleandoc(__doc__) - FUNCTION = "test" - - #OUTPUT_NODE = False - #OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node - - CATEGORY = "Example" - - def test(self, image, string_field, int_field, float_field, print_to_screen): - if print_to_screen == "enable": - print(f"""Your input contains: - string_field aka input text: {string_field} - int_field: {int_field} - float_field: {float_field} - """) - #do some processing on the image, in this example I just invert it - image = 1.0 - image - return (image,) - - """ - The node will always be re executed if any of the inputs change but - this method can be used to force the node to execute again even when the inputs don't change. - You can make this node return a number or a string. This value will be compared to the one returned the last time the node was - executed, if it is different the node will be executed again. - This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash - changes between executions the LoadImage node is executed again. - """ - #@classmethod - #def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen): - # return "" - - -# A dictionary that contains all nodes you want to export with their names -# NOTE: names should be globally unique -NODE_CLASS_MAPPINGS = { - "Example": Example -} - -# A dictionary that contains the friendly/humanly readable titles for the nodes -NODE_DISPLAY_NAME_MAPPINGS = { - "Example": "Example Node" -} diff --git a/src/basic_data_handling/string_nodes.py b/src/basic_data_handling/string_nodes.py index c21c3ac..1996252 100644 --- a/src/basic_data_handling/string_nodes.py +++ b/src/basic_data_handling/string_nodes.py @@ -914,10 +914,11 @@ class StringRsplit: } } - RETURN_TYPES = ("LIST",) + RETURN_TYPES = ("STRING",) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rsplit" + OUTPUT_IS_LIST = (True,) def rsplit(self, string, sep=None, maxsplit=-1): if sep == "": @@ -1205,10 +1206,11 @@ class StringRsplit: } } - RETURN_TYPES = ("LIST",) + RETURN_TYPES = ("STRING",) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rsplit" + OUTPUT_IS_LIST = (True,) def rsplit(self, string, sep=None, maxsplit=-1): if sep == "": @@ -1216,6 +1218,7 @@ class StringRsplit: return (string.rsplit(sep, maxsplit),) + class StringRstrip: """ Removes trailing characters from the string. @@ -1246,6 +1249,34 @@ class StringRstrip: return (string.rstrip(chars),) +class StringJoin: + """ + Joins strings from a data 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": { + "sep": ("STRING", {"default": " ", "defaultInput": True}), + "strings": ("STRING", {"forceInput": True}), + } + } + + RETURN_TYPES = ("STRING",) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "join" + INPUT_IS_LIST = True # The "strings" input accepts a data list + + def join(self, sep, strings): + separator = sep[0] # everything comes as a list, so sep is list[str] + return (separator.join(strings),) + + class StringLjust: """ Left-aligns the string within a field of a given width. @@ -1465,10 +1496,11 @@ class StringRsplit: } } - RETURN_TYPES = ("LIST",) + RETURN_TYPES = ("STRING",) # Changed from "LIST" to "STRING" CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "rsplit" + OUTPUT_IS_LIST = (True,) # This indicates that the output is a data list def rsplit(self, string, sep=None, maxsplit=-1): if sep == "": @@ -1510,8 +1542,10 @@ class StringSplit: """ Splits the string at the specified separator. - This node returns a list of strings by splitting the input string at the specified separator. - If maxsplit is provided, at most maxsplit splits are done. + 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. """ @classmethod @@ -1526,15 +1560,19 @@ class StringSplit: } } - RETURN_TYPES = ("LIST",) + RETURN_TYPES = ("STRING",) + FUNCTION = "split" CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "split" + OUTPUT_IS_LIST = (True,) # This indicates that the output is a data list def split(self, string, sep=None, maxsplit=-1): if sep == "": sep = None - return (string.split(sep, maxsplit),) + + parts = string.split(sep, maxsplit) + # Return a data list where each item is a separate string + return (parts,) class StringSplitlines: @@ -1551,17 +1589,20 @@ class StringSplitlines: "string": ("STRING", {"default": "", "defaultInput": True}), }, "optional": { - "keepends": ("BOOLEAN", {"default": False}), + "keepends": (["False", "True"], {"default": "False"}), } } - RETURN_TYPES = ("LIST",) + RETURN_TYPES = ("STRING",) CATEGORY = "Basic/STRING" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "splitlines" + OUTPUT_IS_LIST = (True,) - def splitlines(self, string, keepends=False): - return (string.splitlines(keepends),) + def splitlines(self, string, keepends="False"): + keepends_bool = (keepends == "True") + lines = string.splitlines(keepends_bool) + return (lines,) class StringStartswith: @@ -1750,6 +1791,7 @@ NODE_CLASS_MAPPINGS = { "StringIsSpace": StringIsSpace, "StringIsTitle": StringIsTitle, "StringIsUpper": StringIsUpper, + "StringJoin": StringJoin, "StringLjust": StringLjust, "StringLower": StringLower, "StringLstrip": StringLstrip, @@ -1795,6 +1837,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "StringIsSpace": "isspace", "StringIsTitle": "istitle", "StringIsUpper": "isupper", + "StringJoin": "join", "StringLjust": "ljust", "StringLower": "lower", "StringLstrip": "lstrip",