diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index 58de2b6..c3ffa58 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -811,7 +811,6 @@ class DataListSlice(ComfyNodeABC): start = kwargs.get('start', [0])[0] stop = kwargs.get('stop', [INT_MAX])[0] step = kwargs.get('step', [1])[0] - print(f"start: {start}, stop: {stop}, step: {step}; input_list: {input_list}") return (input_list[start:stop:step],) diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index fd32d25..cb35268 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -91,8 +91,8 @@ class ListCreateFromFloat(ComfyNodeABC): FUNCTION = "create_list" def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: - values = [float(value) for value in kwargs.values()] - return (values[:-1],) + values = [float(value) for value in list(kwargs.values())[:-1]] + return (values,) class ListCreateFromInt(ComfyNodeABC): @@ -117,8 +117,8 @@ class ListCreateFromInt(ComfyNodeABC): FUNCTION = "create_list" def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: - values = [int(value) for value in kwargs.values()] - return (values[:-1],) + values = [int(value) for value in list(kwargs.values())[:-1]] + return (values,) class ListCreateFromString(ComfyNodeABC): @@ -143,8 +143,8 @@ class ListCreateFromString(ComfyNodeABC): FUNCTION = "create_list" def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: - values = [str(value) for value in kwargs.values()] - return (values[:-1],) + values = [str(value) for value in list(kwargs.values())[:-1]] + return (values,) class ListAppend(ComfyNodeABC): diff --git a/src/basic_data_handling/set_nodes.py b/src/basic_data_handling/set_nodes.py index 33909c6..bb52ef8 100644 --- a/src/basic_data_handling/set_nodes.py +++ b/src/basic_data_handling/set_nodes.py @@ -37,8 +37,8 @@ class SetCreate(ComfyNodeABC): FUNCTION = "create_set" def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: - values = kwargs.values() - return (set(values[:-1]),) + values = list(kwargs.values())[:-1] + return (set(values),) class SetCreateFromBoolean(ComfyNodeABC): @@ -63,8 +63,8 @@ class SetCreateFromBoolean(ComfyNodeABC): FUNCTION = "create_set" def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: - values = [bool(value) for value in kwargs.values()] - return (set(values[:-1]),) + values = [bool(value) for value in list(kwargs.values())[:-1]] + return (set(values),) class SetCreateFromFloat(ComfyNodeABC): @@ -89,8 +89,8 @@ class SetCreateFromFloat(ComfyNodeABC): FUNCTION = "create_set" def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: - values = [float(value) for value in kwargs.values()] - return (set(values[:-1]),) + values = [float(value) for value in list(kwargs.values())[:-1]] + return (set(values),) class SetCreateFromInt(ComfyNodeABC): @@ -115,8 +115,8 @@ class SetCreateFromInt(ComfyNodeABC): FUNCTION = "create_set" def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: - values = [int(value) for value in kwargs.values()] - return (set(values[:-1]),) + values = [int(value) for value in list(kwargs.values())[:-1]] + return (set(values),) class SetCreateFromString(ComfyNodeABC): @@ -141,8 +141,8 @@ class SetCreateFromString(ComfyNodeABC): FUNCTION = "create_set" def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: - values = [str(value) for value in kwargs.values()] - return (set(values[:-1]),) + values = [str(value) for value in list(kwargs.values())[:-1]] + return (set(values),) class SetAdd(ComfyNodeABC): diff --git a/src/basic_data_handling/string_nodes.py b/src/basic_data_handling/string_nodes.py index 8e59d29..3fa6f26 100644 --- a/src/basic_data_handling/string_nodes.py +++ b/src/basic_data_handling/string_nodes.py @@ -890,6 +890,74 @@ class StringRemovesuffix(ComfyNodeABC): return (string.removesuffix(suffix),) +class StringUnescape(ComfyNodeABC): + """ + Unescapes a string by converting escape sequences to their actual characters. + + This node converts escape sequences like '\n' (two characters) to actual newlines (one character), + '\t' to tabs, '\\' to backslashes, etc. Useful for processing strings where escape sequences + are represented literally rather than interpreted. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": ""}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "unescape" + + def unescape(self, string): + # Decode escaped sequences only for control characters + result = ( + string + .replace(r'\\', '\u0000') + .replace(r'\n', '\n') + .replace(r'\t', '\t') + .replace(r'\"', '"') + .replace(r'\'', "'") + .replace('\u0000', '\\') + ) + return (result,) + + +class StringEscape(ComfyNodeABC): + """ + Escapes a string by converting special characters to escape sequences. + + This node converts characters like newlines, tabs, quotes, and backslashes to their escaped + representation (like '\n', '\t', '\"', '\\'). Useful when you need to prepare strings + for formats that require escaped sequences instead of literal special characters. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string": (IO.STRING, {"default": ""}), + } + } + + RETURN_TYPES = (IO.STRING,) + CATEGORY = "Basic/STRING" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "escape" + + def escape(self, string): + result = ( + string + .replace('\\', r'\\') + .replace('\n', r'\n') + .replace('\t', r'\t') + .replace('"', r'\"') + .replace("'", r"\'") + ) + return (result,) + + class StringReplace(ComfyNodeABC): """ Replaces occurrences of a substring with another substring. @@ -1384,6 +1452,7 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: StringDecode": StringDecode, "Basic data handling: StringEncode": StringEncode, "Basic data handling: StringEndswith": StringEndswith, + "Basic data handling: StringEscape": StringEscape, "Basic data handling: StringExpandtabs": StringExpandtabs, "Basic data handling: StringFind": StringFind, "Basic data handling: StringFormatMap": StringFormatMap, @@ -1422,6 +1491,7 @@ NODE_CLASS_MAPPINGS = { "Basic data handling: StringStrip": StringStrip, "Basic data handling: StringSwapcase": StringSwapcase, "Basic data handling: StringTitle": StringTitle, + "Basic data handling: StringUnescape": StringUnescape, "Basic data handling: StringUpper": StringUpper, "Basic data handling: StringZfill": StringZfill, } @@ -1435,6 +1505,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: StringDecode": "decode", "Basic data handling: StringEncode": "encode", "Basic data handling: StringEndswith": "endswith", + "Basic data handling: StringEscape": "escape", "Basic data handling: StringExpandtabs": "expandtabs", "Basic data handling: StringFind": "find", "Basic data handling: StringFormatMap": "format_map", @@ -1473,6 +1544,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: StringStrip": "strip", "Basic data handling: StringSwapcase": "swapcase", "Basic data handling: StringTitle": "title", + "Basic data handling: StringUnescape": "unescape", "Basic data handling: StringUpper": "upper", "Basic data handling: StringZfill": "zfill", } diff --git a/tests/test_dict_nodes.py b/tests/test_dict_nodes.py index 7b21d50..f314a0d 100644 --- a/tests/test_dict_nodes.py +++ b/tests/test_dict_nodes.py @@ -55,7 +55,7 @@ def test_dict_set(): def test_dict_create_from_boolean(): node = DictCreateFromBoolean() # Test with dynamic inputs - result = node.create(key_0="key1", value_0=True, key_1="key2", value_1=False) + result = node.create(key_0="key1", value_0=True, key_1="key2", value_1=False, key_2="", value_2="") assert result == ({"key1": True, "key2": False},) # Test with empty inputs assert node.create() == ({},) @@ -64,7 +64,7 @@ def test_dict_create_from_boolean(): def test_dict_create_from_float(): node = DictCreateFromFloat() # Test with dynamic inputs - result = node.create(key_0="key1", value_0=1.5, key_1="key2", value_1=2.5) + result = node.create(key_0="key1", value_0=1.5, key_1="key2", value_1=2.5, key_2="", value_2="") assert result == ({"key1": 1.5, "key2": 2.5},) # Test with empty inputs assert node.create() == ({},) @@ -73,7 +73,7 @@ def test_dict_create_from_float(): def test_dict_create_from_int(): node = DictCreateFromInt() # Test with dynamic inputs - result = node.create(key_0="key1", value_0=1, key_1="key2", value_1=2) + result = node.create(key_0="key1", value_0=1, key_1="key2", value_1=2, key_2="", value_2="") assert result == ({"key1": 1, "key2": 2},) # Test with empty inputs assert node.create() == ({},) @@ -82,7 +82,7 @@ def test_dict_create_from_int(): def test_dict_create_from_string(): node = DictCreateFromString() # Test with dynamic inputs - result = node.create(key_0="key1", value_0="value1", key_1="key2", value_1="value2") + result = node.create(key_0="key1", value_0="value1", key_1="key2", value_1="value2", key_2="", value_2="") assert result == ({"key1": "value1", "key2": "value2"},) # Test with empty inputs assert node.create() == ({},) diff --git a/tests/test_list_nodes.py b/tests/test_list_nodes.py index 2c8abbf..668fba8 100644 --- a/tests/test_list_nodes.py +++ b/tests/test_list_nodes.py @@ -176,36 +176,36 @@ def test_list_max(): def test_list_create(): node = ListCreate() - assert node.create_list(item_0=1, item_1=2, item_2=3) == ([1, 2, 3],) + assert node.create_list(item_0=1, item_1=2, item_2=3, item_3="") == ([1, 2, 3],) assert node.create_list() == ([],) # Empty list - assert node.create_list(item_0="test") == (["test"],) + assert node.create_list(item_0="test", item_1="") == (["test"],) def test_list_create_from_boolean(): node = ListCreateFromBoolean() - assert node.create_list(item_0=True, item_1=False, item_2=True) == ([True, False, True],) - assert node.create_list(item_0=True) == ([True],) + assert node.create_list(item_0=True, item_1=False, item_2=True, item_3="") == ([True, False, True],) + assert node.create_list(item_0=True, item_1="") == ([True],) assert node.create_list() == ([],) def test_list_create_from_float(): node = ListCreateFromFloat() - assert node.create_list(item_0=1.1, item_1=2.2, item_2=3.3) == ([1.1, 2.2, 3.3],) - assert node.create_list(item_0=0.0) == ([0.0],) + assert node.create_list(item_0=1.1, item_1=2.2, item_2=3.3, item_3="") == ([1.1, 2.2, 3.3],) + assert node.create_list(item_0=0.0, item_1="") == ([0.0],) assert node.create_list() == ([],) def test_list_create_from_int(): node = ListCreateFromInt() - assert node.create_list(item_0=1, item_1=2, item_2=3) == ([1, 2, 3],) - assert node.create_list(item_0=0) == ([0],) + assert node.create_list(item_0=1, item_1=2, item_2=3, item_3="") == ([1, 2, 3],) + assert node.create_list(item_0=0, item_1="") == ([0],) assert node.create_list() == ([],) def test_list_create_from_string(): node = ListCreateFromString() - assert node.create_list(item_0="a", item_1="b", item_2="c") == (["a", "b", "c"],) - assert node.create_list(item_0="test") == (["test"],) + assert node.create_list(item_0="a", item_1="b", item_2="c", item_3="") == (["a", "b", "c"],) + assert node.create_list(item_0="test", item_1="") == (["test"],) assert node.create_list() == ([],) diff --git a/tests/test_set_nodes.py b/tests/test_set_nodes.py index 83a91d9..6ad37a1 100644 --- a/tests/test_set_nodes.py +++ b/tests/test_set_nodes.py @@ -26,29 +26,29 @@ from src.basic_data_handling.set_nodes import ( def test_set_create(): node = SetCreate() # Testing with kwargs to simulate dynamic inputs - assert node.create_set(item_0=1, item_1=2, item_2=3) == ({1, 2, 3},) - assert node.create_set(item_0="a", item_1="b") == ({"a", "b"},) + assert node.create_set(item_0=1, item_1=2, item_2=3, item_3="") == ({1, 2, 3},) + assert node.create_set(item_0="a", item_1="b", item_2="") == ({"a", "b"},) assert node.create_set() == (set(),) # Empty set with no arguments # Mixed types - assert node.create_set(item_0=1, item_1="b", item_2=True) == ({1, "b", True},) + assert node.create_set(item_0=1, item_1="b", item_2=True, item_3="") == ({1, "b", True},) def test_set_create_from_int(): node = SetCreateFromInt() - assert node.create_set(item_0=1, item_1=2, item_2=3) == ({1, 2, 3},) - assert node.create_set(item_0=5) == ({5},) # Single item set - assert node.create_set(item_0=1, item_1=1) == ({1},) # Duplicate items become single item + assert node.create_set(item_0=1, item_1=2, item_2=3, item_3="") == ({1, 2, 3},) + assert node.create_set(item_0=5, item_1="") == ({5},) # Single item set + assert node.create_set(item_0=1, item_1=1, item_2="") == ({1},) # Duplicate items become single item assert node.create_set() == (set(),) # Empty set with no arguments def test_set_create_from_string(): node = SetCreateFromString() - result = node.create_set(item_0="apple", item_1="banana") + result = node.create_set(item_0="apple", item_1="banana", item_2="") assert isinstance(result[0], set) assert result[0] == {"apple", "banana"} # Duplicate strings - result = node.create_set(item_0="apple", item_1="apple") + result = node.create_set(item_0="apple", item_1="apple", item_2="") assert result[0] == {"apple"} # Empty set @@ -57,19 +57,19 @@ def test_set_create_from_string(): def test_set_create_from_float(): node = SetCreateFromFloat() - assert node.create_set(item_0=1.5, item_1=2.5) == ({1.5, 2.5},) - assert node.create_set(item_0=3.14) == ({3.14},) # Single item set - assert node.create_set(item_0=1.0, item_1=1.0) == ({1.0},) # Duplicate items + assert node.create_set(item_0=1.5, item_1=2.5, item_2="") == ({1.5, 2.5},) + assert node.create_set(item_0=3.14, item_1="") == ({3.14},) # Single item set + assert node.create_set(item_0=1.0, item_1=1.0, item_2="") == ({1.0},) # Duplicate items assert node.create_set() == (set(),) # Empty set with no arguments def test_set_create_from_boolean(): node = SetCreateFromBoolean() - assert node.create_set(item_0=True, item_1=False) == ({True, False},) - assert node.create_set(item_0=True, item_1=True) == ({True},) # Duplicate booleans + assert node.create_set(item_0=True, item_1=False, item_2="") == ({True, False},) + assert node.create_set(item_0=True, item_1=True, item_2="") == ({True},) # Duplicate booleans assert node.create_set() == (set(),) # Empty set with no arguments # Test conversion from non-boolean values - assert node.create_set(item_0=1, item_1=0) == ({True, False},) + assert node.create_set(item_0=1, item_1=0, item_2="") == ({True, False},) def test_set_add(): diff --git a/tests/test_string_nodes.py b/tests/test_string_nodes.py index 61897d3..eec010f 100644 --- a/tests/test_string_nodes.py +++ b/tests/test_string_nodes.py @@ -10,6 +10,7 @@ from src.basic_data_handling.string_nodes import ( StringDecode, StringEncode, StringEndswith, + StringEscape, StringExpandtabs, StringFind, StringFormatMap, @@ -47,6 +48,7 @@ from src.basic_data_handling.string_nodes import ( StringStrip, StringSwapcase, StringTitle, + StringUnescape, StringUpper, StringZfill, ) @@ -364,6 +366,38 @@ def test_zfill(): assert node.zfill("123", 2) == ("123",) # Width smaller than string length assert node.zfill("", 3) == ("000",) # Empty string +def test_unescape(): + node = StringUnescape() + # Test control characters + assert node.unescape(r"Hello\nWorld") == ("Hello\nWorld",) # Newline + assert node.unescape(r"Hello\tWorld") == ("Hello\tWorld",) # Tab + assert node.unescape(r"C:\\Program Files\\App") == (r"C:\Program Files\App",) # Backslash + assert node.unescape(r"Quote: \"Text\"") == ("Quote: \"Text\"",) # Double quote + assert node.unescape(r"Quote: \'Text\'") == ("Quote: 'Text'",) # Single quote + + # Test that normal Unicode characters remain unchanged + assert node.unescape("German: äöüß") == ("German: äöüß",) # Umlauts should be preserved + assert node.unescape("Hello äöü\nWorld") == ("Hello äöü\nWorld",) # Umlauts with control char + + # Test complex string with multiple escape sequences + assert node.unescape(r"Path: C:\\folder\\file.txt\nLine1\tLine2") == ("Path: C:\\folder\\file.txt\nLine1\tLine2",) + +def test_escape(): + node = StringEscape() + # Test control characters + assert node.escape("Hello\nWorld") == (r"Hello\nWorld",) # Newline + assert node.escape("Hello\tWorld") == (r"Hello\tWorld",) # Tab + assert node.escape(r"C:\Program Files\App") == (r"C:\\Program Files\\App",) # Backslash + assert node.escape('Quote: "Text"') == (r'Quote: \"Text\"',) # Double quote + assert node.escape("Quote: 'Text'") == (r"Quote: \'Text\'",) # Single quote + + # Test that normal Unicode characters remain unchanged + assert node.escape("German: äöüß") == (r"German: äöüß",) # Umlauts should be preserved + assert node.escape("Hello äöü\nWorld") == (r"Hello äöü\nWorld",) # Umlauts with control char + + # Test complex string with multiple special characters + assert node.escape("Path: C:\\folder\\file.txt\nLine1\tLine2") == (r"Path: C:\\folder\\file.txt\nLine1\tLine2",) + def test_length(): node = StringLength() assert node.length("hello") == (5,)