feat: Add new list and text manipulation nodes (v1.5.0)

New List Nodes:
- Random List Selector: Pick random items from a list with seed control
- List Shuffler: Shuffle list order deterministically
- List Merger: Combine lists with concatenate/interleave modes
- List Deduplicator: Remove duplicates while preserving order

New Text Nodes:
- Text Case Converter: Convert between UPPER, lower, Title Case, snake_case, kebab-case, camelCase, PascalCase
- Text Trimmer: Advanced whitespace removal with multiple modes
- Text Prefix Suffix: Add prefix/suffix to text with optional spacing

Updated documentation in README.md with all new nodes.
This commit is contained in:
DebugPadawan Bot
2026-03-25 08:02:27 +01:00
parent d86efa63a5
commit 43cfdb3ac8
4 changed files with 547 additions and 26 deletions
+201 -3
View File
@@ -7,7 +7,7 @@
[![Python](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://python.org)
[![ComfyUI](https://img.shields.io/badge/ComfyUI-Compatible-green.svg)](https://github.com/comfyanonymous/ComfyUI)
[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Version](https://img.shields.io/badge/Version-1.4.0-informational.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/releases)
[![Version](https://img.shields.io/badge/Version-1.5.0-informational.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/releases)
[![GitHub Stars](https://img.shields.io/github/stars/DebugPadawan/DebugPadawans-ComfyUI-Essentials.svg)](https://github.com/DebugPadawan/DebugPadawans-ComfyUI-Essentials/stargazers)
</div>
@@ -301,6 +301,95 @@ The "all" mode removes all whitespace and collapses multiple spaces into single
</details>
<details>
<summary><b>🔤 Text Case Converter</b> - Transform text case styles</summary>
**Category:** `DebugPadawan/Text`
Converts text to different case styles for formatting prompts, filenames, and display text.
**📥 Inputs:**
- `text` *(String)*: The text to convert
- `case_mode` *(List)*: Case style - UPPER, lower, Title Case, Sentence case, snake_case, kebab-case, camelCase, PascalCase
**📤 Outputs:**
- `converted_text` *(String)*: The converted text
**💡 Example:**
```
Input: "hello world"
Mode: "Title Case"
Output: "Hello World"
Input: "Hello World"
Mode: "snake_case"
Output: "hello_world"
Input: "hello world"
Mode: "PascalCase"
Output: "HelloWorld"
```
Normalize prompts, format filenames, convert naming conventions between different systems.
</details>
<details>
<summary><b>✂️ Text Trimmer</b> - Advanced text trimming options</summary>
**Category:** `DebugPadawan/Text`
Trims text with various whitespace removal options.
**📥 Inputs:**
- `text` *(String)*: The text to trim
- `mode` *(List)*: Trim mode - both, start, end, all_whitespace, collapse_spaces
**📤 Outputs:**
- `trimmed_text` *(String)*: The trimmed text
**💡 Example:**
```
Input: " Hello World "
Mode: "collapse_spaces"
Output: "Hello World"
Mode: "all_whitespace"
Output: "HelloWorld"
```
Clean up prompt formatting, remove extra spaces, prepare text for further processing.
</details>
<details>
<summary><b>📎 Text Prefix Suffix</b> - Add prefix and/or suffix to text</summary>
**Category:** `DebugPadawan/Text`
Adds prefix and/or suffix to text with optional spacing.
**📥 Inputs:**
- `text` *(String)*: The base text
- `prefix` *(String, optional)*: Text to add at the beginning
- `suffix` *(String, optional)*: Text to add at the end
- `add_space` *(Boolean)*: Add space between parts (default: True)
**📤 Outputs:**
- `result_text` *(String)*: The modified text
**💡 Example:**
```
Text: "beautiful"
Prefix: "a"
Suffix: "sunset"
Output: "a beautiful sunset"
```
Build complex prompts from components, wrap text with markers, add modifiers to prompts.
</details>
---
### 📦 Data Conversion
@@ -551,6 +640,115 @@ Get the first N images of a batch, skip the first item, or take a specific range
</details>
<details>
<summary><b>🎲 Random List Selector</b> - Pick random items from a list</summary>
**Category:** `DebugPadawan/List`
Randomly selects one or more items from a list with seed-based reproducibility.
**📥 Inputs:**
- `input_list` *(List)*: The input list.
- `seed` *(Integer)*: Seed for reproducible random selection.
- `count` *(Integer)*: Number of items to select (default: 1).
- `allow_duplicates` *(Boolean)*: Allow selecting the same item multiple times.
**📤 Outputs:**
- `selected_items` *(List)*: The randomly selected items.
- `count` *(Integer)*: Number of items selected.
- `first_item` *(Any)*: The first selected item (convenient for single selections).
**💡 Example:**
```
Input List: ["style1", "style2", "style3", "style4", "style5"]
Seed: 42
Count: 2
Output: ["style3", "style1"]
```
Perfect for randomizing prompt styles, selecting random seeds, or picking random colors/tags from lists.
</details>
<details>
<summary><b>🔀 List Shuffler</b> - Shuffle list order deterministically</summary>
**Category:** `DebugPadawan/List`
Shuffles a list into random order using a seed for reproducibility.
**📥 Inputs:**
- `input_list` *(List)*: The input list.
- `seed` *(Integer)*: Seed for reproducible shuffling.
**📤 Outputs:**
- `shuffled_list` *(List)*: The shuffled list.
- `count` *(Integer)*: Number of items.
**💡 Example:**
```
Input: ["a", "b", "c", "d"]
Seed: 123
Output: ["c", "a", "d", "b"]
```
Randomize processing order, shuffle prompt components, or reorder batch elements.
</details>
<details>
<summary><b>🔗 List Merger</b> - Combine multiple lists</summary>
**Category:** `DebugPadawan/List`
Merges two lists together with different modes.
**📥 Inputs:**
- `list_a` *(List)*: First list.
- `list_b` *(List)*: Second list.
- `mode` *(List)*: Merge mode - "concatenate" or "interleave".
**📤 Outputs:**
- `merged_list` *(List)*: The merged list.
- `count` *(Integer)*: Number of items.
**💡 Example:**
```
List A: [1, 2, 3]
List B: ["a", "b", "c"]
Mode: "interleave"
Output: [1, "a", 2, "b", 3, "c"]
```
Combine tag lists, merge processing pipelines, or interleave prompt components.
</details>
<details>
<summary><b>🧹 List Deduplicator</b> - Remove duplicates from a list</summary>
**Category:** `DebugPadawan/List`
Removes duplicate items from a list while preserving order.
**📥 Inputs:**
- `input_list` *(List)*: The input list.
**📤 Outputs:**
- `deduplicated_list` *(List)*: List with duplicates removed.
- `count` *(Integer)*: Number of unique items.
- `removed_count` *(Integer)*: Number of duplicates removed.
**💡 Example:**
```
Input: ["apple", "banana", "apple", "orange", "banana"]
Output: ["apple", "banana", "orange"], count: 3, removed: 2
```
Clean up tag lists, remove redundant prompts, or filter unique values.
</details>
---
### 🔄 Type Conversion
@@ -1012,7 +1210,7 @@ Data → Wait (2.0s) → Processing → Wait (1.0s) → Output
| Category | Nodes | Purpose |
|----------|-------|---------|
| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace, Text Regex, String Formatter, Text Compare, Text Length, Text Case, Text Trim | Text manipulation and processing |
| **DebugPadawan/Text** | Text Splitter, Text Joiner, Text Replace, Text Regex, String Formatter, Text Compare, Text Length, Text Case, Text Trim, Text Case Converter, Text Trimmer, Text Prefix Suffix | 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 |
@@ -1020,7 +1218,7 @@ Data → Wait (2.0s) → Processing → Wait (1.0s) → Output
| **DebugPadawan/Logic** | Conditional String, Logic Gate | Conditional operations |
| **DebugPadawan/Image** | Image Info, Color Palette Extractor | Image tensor analysis and color extraction |
| **DebugPadawan/Math** | Int/Float Math Operation, Random Generator | Basic arithmetic and random number generation |
| **DebugPadawan/List** | Get List Item, List Slicer | List manipulation |
| **DebugPadawan/List** | Get List Item, List Slicer, Random List Selector, List Shuffler, List Merger, List Deduplicator | List manipulation |
| **DebugPadawan/Conversion** | Type Converter, Int to Float, Float to Int, String to Int, String to Float, String to Boolean, Number to String | Type conversion utilities |
| **DebugPadawan/Number** | Number Clamp, Number Round, Number Range, Number Compare, Number Absolute, Number Sign, Number Remap, Number Lerp | Number formatting and utilities |
+1 -1
View File
@@ -67,5 +67,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
# Version info
__version__ = "1.4.0"
__version__ = "1.5.0"
__author__ = "DebugPadawan"
+156
View File
@@ -1,4 +1,6 @@
import torch
import random
class GetListItem:
@classmethod
@@ -87,10 +89,160 @@ class ListInfo:
return (count, first_item, last_item)
class RandomListSelector:
"""
Node for randomly selecting one or more items from a list
Supports seeded randomness for reproducible selections
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_list": ("LIST",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"count": ("INT", {"default": 1, "min": 1}),
"allow_duplicates": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("LIST", "INT", "*")
RETURN_NAMES = ("selected_items", "count", "first_item")
FUNCTION = "select_random"
CATEGORY = "DebugPadawan/List"
def select_random(self, input_list, seed, count, allow_duplicates):
if not input_list:
raise ValueError("Input list is empty.")
rng = random.Random(seed)
list_len = len(input_list)
if count > list_len and not allow_duplicates:
count = list_len
if allow_duplicates:
selected = [rng.choice(input_list) for _ in range(count)]
else:
selected = rng.sample(input_list, min(count, list_len))
first_item = selected[0] if selected else None
return (selected, len(selected), first_item)
class ListShuffler:
"""
Node for shuffling a list with a deterministic seed
Useful for randomizing order while maintaining reproducibility
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_list": ("LIST",),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("LIST", "INT")
RETURN_NAMES = ("shuffled_list", "count")
FUNCTION = "shuffle_list"
CATEGORY = "DebugPadawan/List"
def shuffle_list(self, input_list, seed):
if not input_list:
return ([], 0)
# Create a copy to avoid modifying the original
shuffled = list(input_list)
rng = random.Random(seed)
rng.shuffle(shuffled)
return (shuffled, len(shuffled))
class ListMerger:
"""
Node for merging multiple lists together
Supports concatenation and interleaving modes
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list_a": ("LIST",),
"list_b": ("LIST",),
"mode": (["concatenate", "interleave"],),
}
}
RETURN_TYPES = ("LIST", "INT")
RETURN_NAMES = ("merged_list", "count")
FUNCTION = "merge_lists"
CATEGORY = "DebugPadawan/List"
def merge_lists(self, list_a, list_b, mode):
if mode == "concatenate":
result = list_a + list_b
else: # interleave
result = []
max_len = max(len(list_a), len(list_b))
for i in range(max_len):
if i < len(list_a):
result.append(list_a[i])
if i < len(list_b):
result.append(list_b[i])
return (result, len(result))
class ListDeduplicator:
"""
Node for removing duplicate items from a list
Preserves original order
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_list": ("LIST",),
}
}
RETURN_TYPES = ("LIST", "INT", "INT")
RETURN_NAMES = ("deduplicated_list", "count", "removed_count")
FUNCTION = "deduplicate"
CATEGORY = "DebugPadawan/List"
def deduplicate(self, input_list):
seen = []
result = []
for item in input_list:
# Convert to string for comparison (handles non-hashable types)
item_key = str(item)
if item_key not in seen:
seen.append(item_key)
result.append(item)
removed = len(input_list) - len(result)
return (result, len(result), removed)
NODE_CLASS_MAPPINGS = {
"DebugPadawan_GetListItem": GetListItem,
"DebugPadawan_ListSlicer": ListSlicer,
"DebugPadawan_ListInfo": ListInfo,
"DebugPadawan_RandomListSelector": RandomListSelector,
"DebugPadawan_ListShuffler": ListShuffler,
"DebugPadawan_ListMerger": ListMerger,
"DebugPadawan_ListDeduplicator": ListDeduplicator,
# Alias for backward compatibility
"DP_GetListItem": GetListItem,
}
@@ -99,5 +251,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"DebugPadawan_GetListItem": "Get List Item",
"DebugPadawan_ListSlicer": "List Slicer",
"DebugPadawan_ListInfo": "List Info",
"DebugPadawan_RandomListSelector": "Random List Selector",
"DebugPadawan_ListShuffler": "List Shuffler",
"DebugPadawan_ListMerger": "List Merger",
"DebugPadawan_ListDeduplicator": "List Deduplicator",
"DP_GetListItem": "Get List Item (Legacy)",
}
+189 -22
View File
@@ -6,7 +6,7 @@ class TextSplitter:
"""
Node for splitting text strings by a delimiter
"""
@classmethod
def INPUT_TYPES(cls):
return {
@@ -29,25 +29,25 @@ class TextSplitter:
}),
}
}
RETURN_TYPES = ("LIST", "INT")
RETURN_NAMES = ("text_list", "count")
FUNCTION = "split_text"
CATEGORY = "DebugPadawan/Text"
def split_text(self, text, delimiter, strip_whitespace=True, remove_empty=True):
"""
Split text by delimiter and return list with count
"""
result = text.split(delimiter)
if strip_whitespace:
result = [item.strip() for item in result]
if remove_empty:
result = [item for item in result if item]
return (result, len(result))
@@ -55,7 +55,7 @@ class TextJoiner:
"""
Node for joining a list of strings with a delimiter
"""
@classmethod
def INPUT_TYPES(cls):
return {
@@ -77,13 +77,13 @@ class TextJoiner:
}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("joined_text",)
FUNCTION = "join_text"
CATEGORY = "DebugPadawan/Text"
def join_text(self, text_list, delimiter, prefix="", suffix=""):
str_list = [str(item) for item in text_list]
result = delimiter.join(str_list)
@@ -95,7 +95,7 @@ class TextReplace:
"""
Node for replacing specific text in a string
"""
@classmethod
def INPUT_TYPES(cls):
return {
@@ -114,13 +114,13 @@ class TextReplace:
}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "replace_text"
CATEGORY = "DebugPadawan/Text"
def replace_text(self, text, find, replace):
if not find:
return (text,)
@@ -131,7 +131,7 @@ class TextRegex:
"""
Node for searching and replacing text using regular expressions
"""
@classmethod
def INPUT_TYPES(cls):
return {
@@ -150,28 +150,192 @@ class TextRegex:
}),
}
}
RETURN_TYPES = ("STRING", "LIST", "BOOLEAN")
RETURN_NAMES = ("text", "matches", "found")
FUNCTION = "regex_op"
CATEGORY = "DebugPadawan/Text"
def regex_op(self, text, pattern, replace):
if not pattern:
return (text, [], False)
matches = re.findall(pattern, text)
result = re.sub(pattern, replace, text)
return (result, matches, len(matches) > 0)
class TextCaseConverter:
"""
Node for converting text to different case styles
Useful for formatting prompts, filenames, and display text
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {
"multiline": True,
"default": "hello world"
}),
"case_mode": (["UPPER", "lower", "Title Case", "Sentence case", "snake_case", "kebab-case", "camelCase", "PascalCase"],),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("converted_text",)
FUNCTION = "convert_case"
CATEGORY = "DebugPadawan/Text"
def convert_case(self, text, case_mode):
"""
Convert text to the specified case style
"""
if case_mode == "UPPER":
return (text.upper(),)
elif case_mode == "lower":
return (text.lower(),)
elif case_mode == "Title Case":
return (text.title(),)
elif case_mode == "Sentence case":
# Capitalize first letter of each sentence
result = '. '.join(s.capitalize() for s in text.split('. '))
return (result,)
elif case_mode == "snake_case":
# Convert to snake_case
# First normalize spaces and existing separators
normalized = text.lower().replace('-', ' ').replace('_', ' ')
words = normalized.split()
return ('_'.join(words),)
elif case_mode == "kebab-case":
# Convert to kebab-case
normalized = text.lower().replace('_', ' ').replace('-', ' ')
words = normalized.split()
return ('-'.join(words),)
elif case_mode == "camelCase":
# Convert to camelCase
normalized = text.replace('-', ' ').replace('_', ' ')
words = normalized.split()
if not words:
return ("",)
result = words[0].lower() + ''.join(w.capitalize() for w in words[1:])
return (result,)
elif case_mode == "PascalCase":
# Convert to PascalCase
normalized = text.replace('-', ' ').replace('_', ' ')
words = normalized.split()
result = ''.join(w.capitalize() for w in words)
return (result,)
else:
return (text,)
class TextTrimmer:
"""
Node for trimming text with various options
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {
"multiline": True,
"default": " hello world "
}),
"mode": (["both", "start", "end", "all_whitespace", "collapse_spaces"],),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("trimmed_text",)
FUNCTION = "trim_text"
CATEGORY = "DebugPadawan/Text"
def trim_text(self, text, mode):
"""
Trim text based on the specified mode
"""
if mode == "both":
return (text.strip(),)
elif mode == "start":
return (text.lstrip(),)
elif mode == "end":
return (text.rstrip(),)
elif mode == "all_whitespace":
# Remove all whitespace characters
return (''.join(text.split()),)
elif mode == "collapse_spaces":
# Collapse multiple spaces into single spaces
return (' '.join(text.split()),)
else:
return (text,)
class TextPrefixSuffix:
"""
Node for adding prefix and/or suffix to text
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {
"multiline": True,
"default": "beautiful"
}),
},
"optional": {
"prefix": ("STRING", {
"multiline": False,
"default": ""
}),
"suffix": ("STRING", {
"multiline": False,
"default": ""
}),
"add_space": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("result_text",)
FUNCTION = "add_prefix_suffix"
CATEGORY = "DebugPadawan/Text"
def add_prefix_suffix(self, text, prefix="", suffix="", add_space=True):
result = text
if prefix:
if add_space and text and not text.startswith(' '):
result = prefix + ' ' + result
else:
result = prefix + result
if suffix:
if add_space and text and not text.endswith(' '):
result = result + ' ' + suffix
else:
result = result + suffix
return (result,)
NODE_CLASS_MAPPINGS = {
"DebugPadawan_TextSplitter": TextSplitter,
"DebugPadawan_TextJoiner": TextJoiner,
"DebugPadawan_TextJoiner": TextJoiner,
"DebugPadawan_TextReplace": TextReplace,
"DebugPadawan_TextRegex": TextRegex,
"DebugPadawan_TextCaseConverter": TextCaseConverter,
"DebugPadawan_TextTrimmer": TextTrimmer,
"DebugPadawan_TextPrefixSuffix": TextPrefixSuffix,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -179,4 +343,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"DebugPadawan_TextJoiner": "Text Joiner",
"DebugPadawan_TextReplace": "Text Replace",
"DebugPadawan_TextRegex": "Text Regex (Search & Replace)",
"DebugPadawan_TextCaseConverter": "Text Case Converter",
"DebugPadawan_TextTrimmer": "Text Trimmer",
"DebugPadawan_TextPrefixSuffix": "Text Prefix Suffix",
}