diff --git a/README.md b/README.md index 7dd9a44..b936e31 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ - Output: cleaned `STRING` - Removes a trailing `_` iteration suffix before the file extension, for example `asset_003.png -> asset.png`. +- `Dumas Slugify String` + - Input: `text` + - Output: slug `STRING` + - Converts text into a lowercase URL/file-safe slug, for example `Crème brûlée 2026 -> creme-brulee-2026`. + - `Dumas JSON Get Value` - Inputs: `json_object`, `path` - Output: `JSON` @@ -167,6 +172,8 @@ decr -> use index - 1 `Dumas Strip Iteration Suffix` only strips a final numeric suffix. Names like `my_asset_final.png` are left untouched, while `my_asset_012.png` becomes `my_asset.png`. +`Dumas Slugify String` lowercases text, strips accents, replaces non-alphanumeric runs with `-`, and trims leading or trailing dashes. + ## Roadmap This repo is intended to grow into a broader set of Dumas-branded generic utility nodes, including JSON helpers and adjacent data-manipulation tools. diff --git a/dumas_json_nodes.py b/dumas_json_nodes.py index f8e76e8..7c257cc 100644 --- a/dumas_json_nodes.py +++ b/dumas_json_nodes.py @@ -2,6 +2,8 @@ from functools import lru_cache from itertools import islice import json import os +import re +import unicodedata def _clone_container(value): @@ -259,6 +261,16 @@ def _strip_iteration_suffix(value): return f"{prefix}{extension}" +def _slugify_string(value): + if not isinstance(value, str): + return "" + + normalized = unicodedata.normalize("NFKD", value) + ascii_value = normalized.encode("ascii", "ignore").decode("ascii") + slug = re.sub(r"[^a-zA-Z0-9]+", "-", ascii_value.lower()) + return slug.strip("-") + + class DumasJSONStringToObjectNode: CATEGORY = "Dumas/JSON" RETURN_TYPES = ("JSON",) @@ -298,6 +310,23 @@ class DumasStripIterationSuffixNode: return (_strip_iteration_suffix(filename),) +class DumasSlugifyStringNode: + CATEGORY = "Dumas/String" + RETURN_TYPES = ("STRING",) + FUNCTION = "slugify_string" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"default": "", "multiline": False}), + } + } + + def slugify_string(self, text): + return (_slugify_string(text),) + + class DumasJSONObjectToStringNode: CATEGORY = "Dumas/JSON" RETURN_TYPES = ("STRING",) @@ -630,6 +659,7 @@ class DumasJSONUnflattenNode: NODE_CLASS_MAPPINGS = { "DumasJSONStringToObject": DumasJSONStringToObjectNode, "DumasStripIterationSuffix": DumasStripIterationSuffixNode, + "DumasSlugifyString": DumasSlugifyStringNode, "DumasJSONObjectToString": DumasJSONObjectToStringNode, "DumasJSONGetValue": DumasJSONGetValueNode, "DumasJSONSetValue": DumasJSONSetValueNode, @@ -650,6 +680,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "DumasJSONStringToObject": "Dumas JSON String to Object", "DumasStripIterationSuffix": "Dumas Strip Iteration Suffix", + "DumasSlugifyString": "Dumas Slugify String", "DumasJSONObjectToString": "Dumas JSON Object to String", "DumasJSONGetValue": "Dumas JSON Get Value", "DumasJSONSetValue": "Dumas JSON Set Value", diff --git a/tests/test_dumas_json_nodes.py b/tests/test_dumas_json_nodes.py index 31410b9..34f0274 100644 --- a/tests/test_dumas_json_nodes.py +++ b/tests/test_dumas_json_nodes.py @@ -17,6 +17,7 @@ from dumas_json_nodes import ( DumasJSONObjectToStringNode, DumasJSONStringToObjectNode, DumasJSONSetValueNode, + DumasSlugifyStringNode, DumasStripIterationSuffixNode, ) @@ -63,6 +64,20 @@ class DumasJSONNodeTests(unittest.TestCase): self.assertEqual(result, ("my_asset_final.png",)) + def test_slugify_string_normalizes_text(self): + node = DumasSlugifyStringNode() + + result = node.slugify_string("Hello, World! This is Dumas.") + + self.assertEqual(result, ("hello-world-this-is-dumas",)) + + def test_slugify_string_strips_accents_and_repeated_symbols(self): + node = DumasSlugifyStringNode() + + result = node.slugify_string("Crème brûlée___2026") + + self.assertEqual(result, ("creme-brulee-2026",)) + def test_get_value_reads_nested_paths(self): node = DumasJSONGetValueNode()