diff --git a/README.md b/README.md index 134eb2d..fffcc5d 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,12 @@ Add delays and timing control to your workflows +🔀 Logic & Math +Boolean logic gates and mathematical operations + + 🔧 Utilities -Essential helpers for workflow optimization +Essential helpers for workflow optimization and list management @@ -142,6 +146,54 @@ Useful for dynamically adjusting prompts, modifying paths, and cleaning up gener +
+🔍 Text Regex (Search & Replace) - Pattern-based text manipulation + +**Category:** `DebugPadawan/Text` + +Searches for patterns using Regular Expressions and optionally replaces them. + +**📥 Inputs:** +- `text` *(String)*: The text to process +- `pattern` *(String)*: The regex pattern to search for +- `replace` *(String)*: The string to replace matches with + +**📤 Outputs:** +- `text` *(String)*: The modified text string +- `matches` *(List)*: All strings that matched the pattern +- `found` *(Boolean)*: True if at least one match was found + +Extract numbers, redact sensitive info, complex pattern replacement. + +
+ +
+💬 String Formatter - Create custom strings with placeholders + +**Category:** `DebugPadawan/Text` + +Formats a string using f-string-like syntax with up to 5 input strings. + +**📥 Inputs:** +- `format_string` *(String)*: The template string with placeholders (e.g., "Hello {input1}, your number is {input2}"). +- `input1` - `input5` *(String, optional)*: Up to five input strings to replace placeholders in the `format_string`. + +**📤 Outputs:** +- `formatted_string` *(String)*: The resulting formatted string. + +**💡 Example:** +``` +Format String: "The result of {input1} plus {input2} is {input3}" +Input1: "2" +Input2: "3" +Input3: "5" +Output: "The result of 2 plus 3 is 5" +``` + +Useful for constructing dynamic prompts, filenames, messages, or URLs from multiple pieces of data. + +
+ --- ### 📦 Data Conversion @@ -169,6 +221,35 @@ Use this node to quickly transform JSON-formatted text into a usable data struct +
+💬 JSONToText - Convert JSON to text + +**Category:** `DebugPadawan/JSON` + +Converts a JSON object back into a formatted JSON string. + +**📥 Inputs:** +- `json_data` *(JSON/Object)*: The JSON object to convert. +- `indent` *(Integer, optional)*: Number of spaces for indentation (0 for no indentation, default: `0`). + +**📤 Outputs:** +- `text` *(String)*: The JSON object as a string. + +**💡 Example:** +``` +Input: { "name": "Alice", "age": 30 } +Indent: 2 +Output: +{ + "name": "Alice", + "age": 30 +} +``` + +Use this node to convert JSON data into a readable string for display, logging, or integration with nodes that expect string input. + +
+ --- ### 🐛 Debug Tools @@ -296,6 +377,55 @@ Generates a random float or integer between a min and max value, driven by a spe --- +### 📝 List Manipulation + +
+📝 Get List Item - Retrieve an item from a list by index + +**Category:** `DebugPadawan/List` + +Retrieves a specific item from a list based on its index. Supports converting some iterable types to lists. + +**📥 Inputs:** +- `input_list` *(Any, List force input)*: The input list or iterable. +- `index` *(Integer)*: The zero-based index of the item to retrieve. + +**📤 Outputs:** +- `item` *(Any)*: The item at the specified index. + +**💡 Example:** +``` +Input List: ["apple", "banana", "orange"] +Index: 1 +Output: "banana" +``` + +Useful for extracting specific elements from lists, such as getting a particular image from a batch, a specific tag from a list, or individual components from a parsed string list. + +
+ +
+✂️ List Slicer - Extract a range of items from a list + +**Category:** `DebugPadawan/List` + +Extracts a sub-list from a larger list using start and end indices. + +**📥 Inputs:** +- `input_list` *(List)*: The input list. +- `start` *(Integer)*: Starting index (inclusive). +- `end` *(Integer)*: Ending index (exclusive, use 0 for "to the end"). + +**📤 Outputs:** +- `list_slice` *(List)*: The sub-list. +- `count` *(Integer)*: Number of elements in the slice. + +Get the first N images of a batch, skip the first item, or take a specific range of values. + +
+ +--- + ### 🔧 Utility Nodes
@@ -336,6 +466,24 @@ Conditional prompt modification, dynamic filename generation, workflow branching
+
+🔌 Logic Gate - Boolean operations + +**Category:** `DebugPadawan/Logic` + +Performs AND, OR, NOT, or XOR operations on boolean inputs. + +**📥 Inputs:** +- `a`, `b` *(Boolean)*: Boolean values. +- `operation` *(List)*: AND, OR, NOT (A), XOR. + +**📤 Outputs:** +- `result` *(Boolean)*: The resulting boolean value. + +Complex workflow branching and conditional logic. + +
+ --- ## 🎨 Quick Start Examples @@ -368,14 +516,15 @@ Data → Wait (2.0s) → Processing → Wait (1.0s) → Output | Category | Nodes | Purpose | |----------|-------|---------| -| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace | Text manipulation and processing | -| **DebugPadawan/JSON** | TextToJSON | Text to JSON conversion | +| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace, Text Regex, String Formatter | Text manipulation and processing | +| **DebugPadawan/JSON** | TextToJSON, JSONToText | Text to JSON conversion | | **DebugPadawan/Debug** | Debug Print | Debugging and monitoring | | **DebugPadawan/Timing** | Wait | Timing control and delays | | **DebugPadawan/Utilities** | List Info | Data analysis helpers | -| **DebugPadawan/Logic** | Conditional String | Conditional operations | +| **DebugPadawan/Logic** | Conditional String, Logic Gate | Conditional operations | | **DebugPadawan/Image** | Image Info | Image tensor analysis | | **DebugPadawan/Math** | Int Math Operation, Float Math Operation, Random Generator | Basic arithmetic and random number generation | +| **DebugPadawan/List** | Get List Item, List Slicer | List manipulation | --- @@ -399,4 +548,4 @@ This project is licensed under the [MIT License](LICENSE). **Made with ❤️ by [DebugPadawan](https://github.com/DebugPadawan)** - \ No newline at end of file + diff --git a/__init__.py b/__init__.py index 61a548a..9f8d34d 100644 --- a/__init__.py +++ b/__init__.py @@ -15,6 +15,13 @@ from .nodes.image import NODE_CLASS_MAPPINGS as IMAGE_NODES from .nodes.image import NODE_DISPLAY_NAME_MAPPINGS as IMAGE_DISPLAY_NAMES from .nodes.math_nodes import NODE_CLASS_MAPPINGS as MATH_NODES from .nodes.math_nodes import NODE_DISPLAY_NAME_MAPPINGS as MATH_DISPLAY_NAMES +from .nodes.list_nodes import NODE_CLASS_MAPPINGS as LIST_NODES +from .nodes.list_nodes import NODE_DISPLAY_NAME_MAPPINGS as LIST_DISPLAY_NAMES + +from .nodes.json_to_text import NODE_CLASS_MAPPINGS as JSON_TO_TEXT_NODES +from .nodes.json_to_text import NODE_DISPLAY_NAME_MAPPINGS as JSON_TO_TEXT_DISPLAY_NAMES +from .nodes.string_formatter import NODE_CLASS_MAPPINGS as STRING_FORMATTER_NODES +from .nodes.string_formatter import NODE_DISPLAY_NAME_MAPPINGS as STRING_FORMATTER_DISPLAY_NAMES # Combine all node mappings NODE_CLASS_MAPPINGS = { @@ -24,6 +31,9 @@ NODE_CLASS_MAPPINGS = { **JSON_NODES, **IMAGE_NODES, **MATH_NODES, + **LIST_NODES, + **JSON_TO_TEXT_NODES, + **STRING_FORMATTER_NODES, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -33,10 +43,13 @@ NODE_DISPLAY_NAME_MAPPINGS = { **JSON_DISPLAY_NAMES, **IMAGE_DISPLAY_NAMES, **MATH_DISPLAY_NAMES, + **LIST_DISPLAY_NAMES, + **JSON_TO_TEXT_DISPLAY_NAMES, + **STRING_FORMATTER_DISPLAY_NAMES, } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] # Version info -__version__ = "1.0.0" +__version__ = "1.1.0" __author__ = "DebugPadawan" \ No newline at end of file diff --git a/nodes/__init__.py b/nodes/__init__.py index cfda150..e69de29 100644 --- a/nodes/__init__.py +++ b/nodes/__init__.py @@ -1,3 +0,0 @@ -""" -Nodes package for DebugPadawan's ComfyUI Essentials -""" \ No newline at end of file diff --git a/nodes/json_to_text.py b/nodes/json_to_text.py new file mode 100644 index 0000000..c5ebd52 --- /dev/null +++ b/nodes/json_to_text.py @@ -0,0 +1,32 @@ +import json + +class JSONToText: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "json_data": ("JSON", {"forceInput": True}), + "indent": ("INT", {"default": 0, "min": 0, "max": 10, "step": 1}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "json_to_text" + CATEGORY = "DebugPadawan/JSON" + DESCRIPTION = "Converts a JSON object back into a formatted string" + + def json_to_text(self, json_data, indent): + if indent > 0: + return (json.dumps(json_data, indent=indent),) + else: + return (json.dumps(json_data),) + +# A dictionary that contains all nodes you want to export with their names +NODE_CLASS_MAPPINGS = { + "DP_JSONToText": JSONToText +} + +# A dictionary that contains the friendly names for the nodes +NODE_DISPLAY_NAME_MAPPINGS = { + "DP_JSONToText": "JSON To Text" +} diff --git a/nodes/list_nodes.py b/nodes/list_nodes.py new file mode 100644 index 0000000..9a20270 --- /dev/null +++ b/nodes/list_nodes.py @@ -0,0 +1,103 @@ +import torch + +class GetListItem: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "input_list": ("*", {"forceInput": True}), + "index": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("item",) + FUNCTION = "get_item" + CATEGORY = "DebugPadawan/List" + + def get_item(self, input_list, index): + if not isinstance(input_list, list): + # Attempt to convert to list if it's a ComfyUI tensor batch or similar + if isinstance(input_list, torch.Tensor): + input_list = input_list.tolist() + elif hasattr(input_list, '__iter__') and not isinstance(input_list, str): + input_list = list(input_list) + else: + # If it's a single item, wrap it in a list to allow indexing + input_list = [input_list] + + if not input_list: + raise ValueError("Input list is empty.") + if index < 0 or index >= len(input_list): + raise IndexError(f"Index {index} out of bounds for list of length {len(input_list)}.") + + return (input_list[index],) + + +class ListSlicer: + """ + Node for getting a slice of a list + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("LIST",), + "start": ("INT", {"default": 0, "min": 0}), + "end": ("INT", {"default": 0, "min": 0}), + } + } + + RETURN_TYPES = ("LIST", "INT") + RETURN_NAMES = ("list_slice", "count") + FUNCTION = "slice_list" + CATEGORY = "DebugPadawan/List" + + def slice_list(self, input_list, start, end): + if end == 0: + res = input_list[start:] + else: + res = input_list[start:end] + return (res, len(res)) + + +class ListInfo: + """ + Node for getting information about a list + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input_list": ("LIST",), + } + } + + RETURN_TYPES = ("INT", "STRING", "STRING") + RETURN_NAMES = ("count", "first_item", "last_item") + + FUNCTION = "get_list_info" + CATEGORY = "DebugPadawan/Utilities" + + def get_list_info(self, input_list): + count = len(input_list) + first_item = str(input_list[0]) if input_list else "" + last_item = str(input_list[-1]) if input_list else "" + + return (count, first_item, last_item) + + +NODE_CLASS_MAPPINGS = { + "DebugPadawan_GetListItem": GetListItem, + "DebugPadawan_ListSlicer": ListSlicer, + "DebugPadawan_ListInfo": ListInfo, + # Alias for backward compatibility + "DP_GetListItem": GetListItem, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DebugPadawan_GetListItem": "Get List Item", + "DebugPadawan_ListSlicer": "List Slicer", + "DebugPadawan_ListInfo": "List Info", + "DP_GetListItem": "Get List Item (Legacy)", +} diff --git a/nodes/string_formatter.py b/nodes/string_formatter.py new file mode 100644 index 0000000..c368099 --- /dev/null +++ b/nodes/string_formatter.py @@ -0,0 +1,54 @@ +class StringFormatter: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "format_string": ("STRING", {"multiline": True, "default": "Result: {input1}, {input2}"}), + }, + "optional": { + "input1": ("STRING", {"multiline": True, "default": ""}), + "input2": ("STRING", {"multiline": True, "default": ""}), + "input3": ("STRING", {"multiline": True, "default": ""}), + "input4": ("STRING", {"multiline": True, "default": ""}), + "input5": ("STRING", {"multiline": True, "default": ""}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "format_text" + CATEGORY = "DebugPadawan/Text" + DESCRIPTION = "Formats a string using f-string-like syntax with multiple inputs." + + def format_text(self, format_string, input1="", input2="", input3="", input4="", input5=""): + # Create a dictionary of inputs to be used in the format string + # Filter out empty inputs if necessary, though format() handles missing keys gracefully. + inputs = { + "input1": input1, + "input2": input2, + "input3": input3, + "input4": input4, + "input5": input5, + } + + # Use str.format_map for f-string-like behavior + # It's important to provide a default value for any potential missing keys in the format_string + # For simplicity, we are assuming keys in format_string will match inputX, + # or that missing ones will not cause an error with a default empty string. + try: + formatted_string = format_string.format(**inputs) + except KeyError as e: + # Handle cases where format_string contains keys not provided by inputs + # This can happen if user uses {input6} but only input1-5 are available + formatted_string = f"Error: Missing input for key {e} in format string." + except Exception as e: + formatted_string = f"An unexpected error occurred during formatting: {e}" + + return (formatted_string,) + +NODE_CLASS_MAPPINGS = { + "DP_StringFormatter": StringFormatter +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DP_StringFormatter": "String Formatter" +} diff --git a/nodes/text_processing.py b/nodes/text_processing.py index 088ba52..6168f08 100644 --- a/nodes/text_processing.py +++ b/nodes/text_processing.py @@ -1,4 +1,5 @@ import json +import re class TextSplitter: @@ -126,43 +127,56 @@ class TextReplace: return (text.replace(find, replace),) -class ListInfo: +class TextRegex: """ - Node for getting information about a list + Node for searching and replacing text using regular expressions """ @classmethod def INPUT_TYPES(cls): return { "required": { - "input_list": ("LIST",), + "text": ("STRING", { + "multiline": True, + "default": "My phone number is 123-456-7890" + }), + "pattern": ("STRING", { + "multiline": False, + "default": r"\d{3}-\d{3}-\d{4}" + }), + "replace": ("STRING", { + "multiline": False, + "default": "[REDACTED]" + }), } } - RETURN_TYPES = ("INT", "STRING", "STRING") - RETURN_NAMES = ("count", "first_item", "last_item") + RETURN_TYPES = ("STRING", "LIST", "BOOLEAN") + RETURN_NAMES = ("text", "matches", "found") - FUNCTION = "get_list_info" - CATEGORY = "DebugPadawan/Utilities" + FUNCTION = "regex_op" + CATEGORY = "DebugPadawan/Text" - def get_list_info(self, input_list): - count = len(input_list) - first_item = str(input_list[0]) if input_list else "" - last_item = str(input_list[-1]) if input_list else "" + def regex_op(self, text, pattern, replace): + if not pattern: + return (text, [], False) - return (count, first_item, last_item) + matches = re.findall(pattern, text) + result = re.sub(pattern, replace, text) + + return (result, matches, len(matches) > 0) NODE_CLASS_MAPPINGS = { "DebugPadawan_TextSplitter": TextSplitter, "DebugPadawan_TextJoiner": TextJoiner, "DebugPadawan_TextReplace": TextReplace, - "DebugPadawan_ListInfo": ListInfo, + "DebugPadawan_TextRegex": TextRegex, } NODE_DISPLAY_NAME_MAPPINGS = { "DebugPadawan_TextSplitter": "Text Splitter", "DebugPadawan_TextJoiner": "Text Joiner", "DebugPadawan_TextReplace": "Text Replace", - "DebugPadawan_ListInfo": "List Info", + "DebugPadawan_TextRegex": "Text Regex (Search & Replace)", } diff --git a/nodes/utilities.py b/nodes/utilities.py index 79ff114..8987ba4 100644 --- a/nodes/utilities.py +++ b/nodes/utilities.py @@ -90,12 +90,50 @@ class ConditionalString: """ return (true_string if condition else false_string,) + +class LogicGate: + """ + Node for boolean logic operations (AND, OR, NOT, XOR) + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("BOOLEAN", {"default": True}), + "b": ("BOOLEAN", {"default": False}), + "operation": (["AND", "OR", "NOT (A)", "XOR"],), + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("result",) + + FUNCTION = "logic_op" + CATEGORY = "DebugPadawan/Logic" + + def logic_op(self, a, b, operation): + if operation == "AND": + res = a and b + elif operation == "OR": + res = a or b + elif operation == "NOT (A)": + res = not a + elif operation == "XOR": + res = a != b + else: + res = False + return (res,) + + UTILITY_NODE_CLASS_MAPPINGS = { "DebugPadawan_DebugPrint": DebugPrint, "DebugPadawan_ConditionalString": ConditionalString, + "DebugPadawan_LogicGate": LogicGate, } UTILITY_NODE_DISPLAY_NAME_MAPPINGS = { "DebugPadawan_DebugPrint": "Debug Print", "DebugPadawan_ConditionalString": "Conditional String", -} \ No newline at end of file + "DebugPadawan_LogicGate": "Logic Gate", +}