From ae60ee97c1742e99e8990beeab860ec7457ba143 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 14:56:00 +0500 Subject: [PATCH 1/6] +1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 535bdfb..261e3df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## `TODO` -The pack is feature-complete. +The pack is considered feature-complete. No other features planned. To do some fancy stuff with dictionaries, specialized node packs are recommended instead. # v1.0.3 From 58dbe9a70abd1f01ddb8a585fc8814c511dbed9c Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:02:19 +0500 Subject: [PATCH 2/6] All the secondary dict-related nodes are moved to a subcategory --- CHANGELOG.md | 4 ++++ __init__.py | 2 +- _meta.py | 1 + node_dict_add_any.py | 2 +- node_dict_add_string.py | 2 +- node_dict_preview.py | 2 +- node_validate_keys.py | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 261e3df..0f114b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ The pack is considered feature-complete. No other features planned. To do some fancy stuff with dictionaries, specialized node packs are recommended instead. +# v1.0.4 + +- All the secondary dict-related nodes are moved to a subcategory. + # v1.0.3 - [fix] Nodes made compatible with built-in preview nodes. diff --git a/__init__.py b/__init__.py index 0779f7d..36ce860 100644 --- a/__init__.py +++ b/__init__.py @@ -20,7 +20,7 @@ NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { 'StringConstructorValidateKeys': StringConstructorValidateKeys, } NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { - 'StringConstructorDictAddAny': "Add ANY to Format-Dict (Advanced)", + 'StringConstructorDictAddAny': "Add ANY to Format-Dict", 'StringConstructorDictAddString': "Add String to Format-Dict", 'StringConstructorDictFromText': "Format-Dict from Text", 'StringConstructorDictPreview': "Preview Format-Dict", diff --git a/_meta.py b/_meta.py index f716a0e..0714032 100644 --- a/_meta.py +++ b/_meta.py @@ -4,3 +4,4 @@ Metadata-related module. """ category: str = "String Constructor" +category_dict: str = f"{category}/Format-Dict" diff --git a/node_dict_add_any.py b/node_dict_add_any.py index b4bd4b6..68494a7 100644 --- a/node_dict_add_any.py +++ b/node_dict_add_any.py @@ -40,7 +40,7 @@ _input_types = _deepfreeze({ class StringConstructorDictAddAny: """Add/update a non-string item to the Format-Dict - to do some advanced formatting.""" NODE_NAME = 'StringConstructorDictAddAny' - CATEGORY = _meta.category + CATEGORY = _meta.category_dict DESCRIPTION = _format_docstring(_cleandoc(__doc__)) FUNCTION = 'main' diff --git a/node_dict_add_string.py b/node_dict_add_string.py index 1245797..6779a84 100644 --- a/node_dict_add_string.py +++ b/node_dict_add_string.py @@ -40,7 +40,7 @@ _input_types = _deepfreeze({ class StringConstructorDictAddString: """Add/update a string to the Format-Dict.""" NODE_NAME = 'StringConstructorDictAddString' - CATEGORY = _meta.category + CATEGORY = _meta.category_dict DESCRIPTION = _format_docstring(_cleandoc(__doc__)) FUNCTION = 'main' diff --git a/node_dict_preview.py b/node_dict_preview.py index 16c23a4..e599de3 100644 --- a/node_dict_preview.py +++ b/node_dict_preview.py @@ -63,7 +63,7 @@ class StringConstructorDictPreview: Show the contents of a Format-Dict. """ NODE_NAME = 'StringConstructorDictPreview' - CATEGORY = _meta.category + CATEGORY = _meta.category_dict DESCRIPTION = _format_docstring(_cleandoc(__doc__)) OUTPUT_NODE = True diff --git a/node_validate_keys.py b/node_validate_keys.py index 27f8988..7ada38a 100644 --- a/node_validate_keys.py +++ b/node_validate_keys.py @@ -31,7 +31,7 @@ class StringConstructorValidateKeys: but it might be handy to verify the dict right after it's created. """ NODE_NAME = 'StringConstructorValidateKeys' - CATEGORY = _meta.category + CATEGORY = _meta.category_dict DESCRIPTION = _format_docstring(_cleandoc(__doc__)) OUTPUT_NODE = True From 475a2e2ca776bbd7f012973622309942c1289040 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:40:24 +0500 Subject: [PATCH 3/6] Shorten node names: `Format-Dict` -> just `Dict` --- CHANGELOG.md | 1 + README.md | 14 +++++++------- __init__.py | 10 +++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f114b2..baea32d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ No other features planned. To do some fancy stuff with dictionaries, specialized # v1.0.4 - All the secondary dict-related nodes are moved to a subcategory. +- Default node name shortened: `Format-Dict` → just `Dict`. # v1.0.3 diff --git a/README.md b/README.md index 32026c6..cbf7c43 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,16 @@ When formatting the string, you can let chunks reference each other, which unloc > > With a sloppy use, you can create chunks that cross-reference each other in an infinite loop. The node will error out after reaching a high level of recursion (about 1k), so you're safe. But still, you've been warned! -## Supporting nodes for Dictionaries +## Helper nodes for Dictionaries At this point it should be clear that most of the work would be done around preparing the dictionary to use. The pack provides some utility nodes to build such dict: -- `Format-Dict from Text` - **this node would be enough 99% of the time**. It parses a single wall of text and splits it into chunks at empty lines. The first line in each chunk is extracted as key, the rest of the chunk (including new lines, if text continues) is the actual text of this chunk. -- `Add String to Format-Dict` - similar, but adds only one entry. Useful when you need a value or a key of the dictionary entry to come as input connection from somewhere else. -- `Add ANY to Format-Dict (Advanced)` similar, but for advanced formatting. It allows you to add not only a string, but literally anything (float, int, etc). The key still must follow the same restrictions. +- `Dict from Text` - **this node would be enough 99% of the time**. It parses a single wall of text and splits it into chunks at empty lines. The first line in each chunk is extracted as key, the rest of the chunk (including new lines, if text continues) is the actual text of this chunk. +- `Add String to Dict` - similar, but adds only one entry. Useful when you need a value or a key of the dictionary entry to come as input connection from somewhere else. +- `Add ANY to Dict` similar, but for advanced formatting. It allows you to add not only a string, but literally anything (float, int, etc). The key still must follow the same restrictions. - Any of these nodes can take another dictionary as input - then they extend/update it. -- `Validate Format-Dict` - a node that ensures that all the keys in the dictionary are named properly. Useful if you build the dictionary with nodes from other packs (see below) and want to ensure that everything is fine - before passing the dictionary down the line. +- `Validate Dict` - a node that ensures that all the keys in the dictionary are named properly. Useful if you build the dictionary with nodes from other packs (see below) and want to ensure that everything is fine - before passing the dictionary down the line. - You can design your dict to be intended for updating down the line. Change some keys → get a different prompt with the same template (for example, a more detailed description of a character). - Don't forget that with recursive formatting, template itself could be a part of the dictionary, too! @@ -95,9 +95,9 @@ These bundled nodes should be enough to start your journey. If you need to do a - [Eugene's Nodes](https://github.com/JEONG-JIWOO/ComfyUI_Eugene_Nodes) - [WAS Node Suite](https://github.com/ltdrdata/was-node-suite-comfyui). Note: this is a mega-pack, over-bloated to my taste. It has nodes to work with dictionaries, but also **A TON** of other unrelated stuff... and it's known to have dependencies conflicting with other custom nodes. -## Supporting nodes for Preview +## Helper nodes for Preview -To debug the dictionary you build, there's a `Preview Format-Dict` node. +To debug the dictionary you build, there's a `Preview Dict` node. You might also look into the built-in `Preview Any` node. diff --git a/__init__.py b/__init__.py index 36ce860..0f78e1b 100644 --- a/__init__.py +++ b/__init__.py @@ -20,12 +20,12 @@ NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { 'StringConstructorValidateKeys': StringConstructorValidateKeys, } NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { - 'StringConstructorDictAddAny': "Add ANY to Format-Dict", - 'StringConstructorDictAddString': "Add String to Format-Dict", - 'StringConstructorDictFromText': "Format-Dict from Text", - 'StringConstructorDictPreview': "Preview Format-Dict", + 'StringConstructorDictAddAny': "Add ANY to Dict", + 'StringConstructorDictAddString': "Add String to Dict", + 'StringConstructorDictFromText': "Dict from Text", + 'StringConstructorDictPreview': "Preview Dict", 'StringConstructorFormatter': "String Formatter", - 'StringConstructorValidateKeys': "Validate Format-Dict", + 'StringConstructorValidateKeys': "Validate Dict", } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] From 15469d928fd7cd64ee6dff622a443d0f3c81aaed Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 15:29:08 +0500 Subject: [PATCH 4/6] `StringConstructorValidateKeys` --- CHANGELOG.md | 1 + README.md | 1 + __init__.py | 3 ++ node_dict_add_any.py | 2 +- node_dict_add_string.py | 2 +- node_dict_key_extract.py | 74 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 node_dict_key_extract.py diff --git a/CHANGELOG.md b/CHANGELOG.md index baea32d..200d1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ No other features planned. To do some fancy stuff with dictionaries, specialized - All the secondary dict-related nodes are moved to a subcategory. - Default node name shortened: `Format-Dict` → just `Dict`. +- New node: `Extract String from Dict` - to be actually self-sufficient for the common use cases. # v1.0.3 diff --git a/README.md b/README.md index cbf7c43..eecdf92 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ The pack provides some utility nodes to build such dict: - `Add String to Dict` - similar, but adds only one entry. Useful when you need a value or a key of the dictionary entry to come as input connection from somewhere else. - `Add ANY to Dict` similar, but for advanced formatting. It allows you to add not only a string, but literally anything (float, int, etc). The key still must follow the same restrictions. - Any of these nodes can take another dictionary as input - then they extend/update it. +- `Extract String from Dict` - the opposite: extract a single string from the dict. With it, you can extract a single element to modify it - and put an updated version back to the dict. Technically, the same could be accomplished with the main `String Formatter` node, but this one is more compact. - `Validate Dict` - a node that ensures that all the keys in the dictionary are named properly. Useful if you build the dictionary with nodes from other packs (see below) and want to ensure that everything is fine - before passing the dictionary down the line. - You can design your dict to be intended for updating down the line. Change some keys → get a different prompt with the same template (for example, a more detailed description of a character). - Don't forget that with recursive formatting, template itself could be a part of the dictionary, too! diff --git a/__init__.py b/__init__.py index 0f78e1b..5fb9844 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,7 @@ import typing as _t from .node_dict_add_any import StringConstructorDictAddAny from .node_dict_add_string import StringConstructorDictAddString +from .node_dict_key_extract import StringConstructorDictExtractString from .node_dict_from_text import StringConstructorDictFromText from .node_dict_preview import StringConstructorDictPreview from .node_formatter import StringConstructorFormatter @@ -14,6 +15,7 @@ from .node_validate_keys import StringConstructorValidateKeys NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { 'StringConstructorDictAddAny': StringConstructorDictAddAny, 'StringConstructorDictAddString': StringConstructorDictAddString, + 'StringConstructorDictExtractString': StringConstructorDictExtractString, 'StringConstructorDictFromText': StringConstructorDictFromText, 'StringConstructorDictPreview': StringConstructorDictPreview, 'StringConstructorFormatter': StringConstructorFormatter, @@ -22,6 +24,7 @@ NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { 'StringConstructorDictAddAny': "Add ANY to Dict", 'StringConstructorDictAddString': "Add String to Dict", + 'StringConstructorDictExtractString': "Extract String from Dict", 'StringConstructorDictFromText': "Dict from Text", 'StringConstructorDictPreview': "Preview Dict", 'StringConstructorFormatter': "String Formatter", diff --git a/node_dict_add_any.py b/node_dict_add_any.py index 68494a7..61fbd2f 100644 --- a/node_dict_add_any.py +++ b/node_dict_add_any.py @@ -23,7 +23,7 @@ _dict = dict _input_types = _deepfreeze({ 'required': { 'name': (_IO.STRING, {'tooltip': ( - "Name of the non-string item inserted into the dict. " + "Name (key) of the non-string item inserted into the dict. " "It must comprise only of latin letters, digits and underscores + it can't start with a digit." )}), }, diff --git a/node_dict_add_string.py b/node_dict_add_string.py index 6779a84..0cf2fbd 100644 --- a/node_dict_add_string.py +++ b/node_dict_add_string.py @@ -20,7 +20,7 @@ from .funcs_common import _new_dict_with_updated_key, _T _input_types = _deepfreeze({ 'required': { 'name': (_IO.STRING, {'tooltip': ( - "Name of the substring inserted into the dict. " + "Name (key) of the substring inserted into the dict. " "It must comprise only of latin letters, digits and underscores + it can't start with a digit." )}), 'cleanup': (_IO.BOOLEAN, {'default': True, 'label_on': 'leading/trailing spaces', 'label_off': 'no', 'tooltip': ( diff --git a/node_dict_key_extract.py b/node_dict_key_extract.py new file mode 100644 index 0000000..2e099c9 --- /dev/null +++ b/node_dict_key_extract.py @@ -0,0 +1,74 @@ +# encoding: utf-8 +""" +""" + +import typing as _t + +from inspect import cleandoc as _cleandoc + +from frozendict import deepfreeze as _deepfreeze + +from comfy.comfy_types.node_typing import IO as _IO + +from . import _meta +from .docstring_formatter import format_docstring as _format_docstring +from .enums import DataTypes as _DataTypes +from .funcs_common import _show_text_on_node, _new_dict_with_updated_key, _T + + +_input_types = _deepfreeze({ + 'required': { + 'name': (_IO.STRING, {'tooltip': ( + "Name (key) of a string to extract from dictionary.\n" + "If the element with such key isn't a string, it will be turned to one.\n" + "If no such key exists in the dict, an empty string returned." + )}), + 'show_status': (_IO.BOOLEAN, {'default': False, 'label_on': 'value', 'label_off': 'no', 'tooltip': ( + "Show the extracted string on the node itself?" + )}), + }, + 'optional': { + 'dict': _DataTypes.input_dict(tooltip="The dictionary to extract the element from."), + }, + 'hidden': { + 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node + }, +}) + + +class StringConstructorDictExtractString: + """Extract a single string from the Format-Dict.""" + NODE_NAME = 'StringConstructorDictExtractString' + CATEGORY = _meta.category_dict + DESCRIPTION = _format_docstring(_cleandoc(__doc__)) + + FUNCTION = 'main' + RETURN_TYPES = (_IO.STRING, ) + RETURN_NAMES = ('string', ) + # OUTPUT_TOOLTIPS = tuple() + + @classmethod + def INPUT_TYPES(cls): + return _input_types + + @staticmethod + def main( + name: str, show_status: bool = False, dict: _t.Dict[str, _T] = None, + unique_id: str = None, + ) -> _t.Tuple[str]: + """Extract a single string from the Format-Dict.""" + # noinspection PyBroadException + try: + string: str = dict.get(name, '') + except Exception: + string = '' + if string is None: + string = '' + + if not isinstance(string, str): + string = repr(string) + + if show_status and unique_id: + _show_text_on_node(string, unique_id) + + return (string, ) From a3daf42c79ab0092ac49f94bf3eaa2106501c8a2 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 16:49:37 +0500 Subject: [PATCH 5/6] README: advanced topics --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index eecdf92..cc99266 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ There's already a plenty of string-formatting nodes for ComfyUI. But this node pack takes a different approach: - Build your dictionary of available text chunks once. Pass it further as a single line (bus/pipe design). -- Easily reuse these sub-strings to build many variations of a prompt as needed. +- Easily reuse these sub-strings to build as many variations of a prompt as needed. - It's especially handy for regional prompting (aka area composition). - [❗ Recursive formatting support](#recursive-formatting-) - ... @@ -58,17 +58,14 @@ The way you "paste" values from the dictionary into your text template is called - ❌ `wrong name with spaces`, `wrong-name.with:punctuation`, `4name`. - Don't worry, the node pack will warn you if you type an invalid name. -> [!NOTE] -> For advanced users: -> Internally, just a built-in `str.format()` is called with keyword arguments from the passed Format-Dict, which is literally just a dict with string keys. -> -> So any formatting patterns are available (like `{float_value:.3f}`) + `Add ANY to Format-Dict` node is there for exactly that. - ### Recursive formatting It's quite simple in principle yet the most powerful feature of the pack. -When formatting the string, you can let chunks reference each other, which unlocks immense possibilities - like building entire **HIERARCHIES** of descriptions with various granularity (tailored for different resolutions) or even conditional string formatting for the most advanced users. +When formatting the string, you can let chunks reference each other, which unlocks immense possibilities - like building entire **HIERARCHIES** of descriptions with various granularity (tailored for different resolutions) or even [conditional string formatting](#dynamic-pattern-aka-conditional-formatting) for the most advanced users. + +- You can design your dict to be intended for updating down the line. Change some keys → get a different prompt with the same template (for example, a more detailed description of a character). +- Don't forget that with recursive formatting, [template itself could be a part of the dictionary](#pattern-as-part-of-the-dictionary), too! ![image](img/recursive_screenshot_v2.png) @@ -82,14 +79,12 @@ When formatting the string, you can let chunks reference each other, which unloc At this point it should be clear that most of the work would be done around preparing the dictionary to use. The pack provides some utility nodes to build such dict: -- `Dict from Text` - **this node would be enough 99% of the time**. It parses a single wall of text and splits it into chunks at empty lines. The first line in each chunk is extracted as key, the rest of the chunk (including new lines, if text continues) is the actual text of this chunk. +- `Dict from Text` - **this node would be enough 99% of the time**. It parses a single wall of text and splits it into chunks at empty lines. The first line in each chunk is extracted as key, the rest of the chunk _(including any number of new lines, as long as they have some text)_ is the actual text of this chunk. - `Add String to Dict` - similar, but adds only one entry. Useful when you need a value or a key of the dictionary entry to come as input connection from somewhere else. - `Add ANY to Dict` similar, but for advanced formatting. It allows you to add not only a string, but literally anything (float, int, etc). The key still must follow the same restrictions. - Any of these nodes can take another dictionary as input - then they extend/update it. -- `Extract String from Dict` - the opposite: extract a single string from the dict. With it, you can extract a single element to modify it - and put an updated version back to the dict. Technically, the same could be accomplished with the main `String Formatter` node, but this one is more compact. +- `Extract String from Dict` - the opposite to `Add String to Dict`: extracts a single element. With these two nodes, you can extract a single string, modify it, and update the dict with the new version. Technically, the main `String Formatter` node can "extract" string, too - but this one is more compact. - `Validate Dict` - a node that ensures that all the keys in the dictionary are named properly. Useful if you build the dictionary with nodes from other packs (see below) and want to ensure that everything is fine - before passing the dictionary down the line. -- You can design your dict to be intended for updating down the line. Change some keys → get a different prompt with the same template (for example, a more detailed description of a character). -- Don't forget that with recursive formatting, template itself could be a part of the dictionary, too! These bundled nodes should be enough to start your journey. If you need to do a more advanced stuff with dictionaries, you can look into other node packs specializing on exactly that. Good candidates are: - [Basic Data Handling](https://github.com/StableLlama/ComfyUI-basic_data_handling) @@ -102,4 +97,42 @@ To debug the dictionary you build, there's a `Preview Dict` node. You might also look into the built-in `Preview Any` node. -Alternatively, [Crystools](https://github.com/crystian/ComfyUI-Crystools) pack has a `🪛 Show any to JSON` node, which produces a more readable output. Crystools pack isn't as bloated as WAS' one, but still, it's a multipurpose mega-pack, too. +Alternatively, [Crystools](https://github.com/crystian/ComfyUI-Crystools) pack has a `🪛 Show any to JSON` node _(I highly recommend it)_, which produces a more readable output. Crystools pack isn't as bloated as WAS' one, but still, it's a multipurpose mega-pack, too. + +## Advanced Topics + +### Pattern as part of the dictionary + +With [Recursive formatting](#recursive-formatting-), nothing stops you from putting the template itself into the dictionary, too. You can even have both your prompts there (positive/negative), and you unpack them with a couple of `String Formatter` nodes _(with their only text being something like `{pos_template}` / `{neg_template}`)_, just before KSampler. + +This single possibility can **drastically** change your approach to workflow creation. Since building a prompt for a specific KSampler becomes effectively free (with a properly configured dictionary) and string-formatting/text-encoding is almost free, too - you no longer need passing spaghetti of your pre-encoded conditionings all across your workflow. Instead, you just build them in-place by simply setting a couple of "special" keys to the desired toggle-like values, and pass only your all-in-one dictionary itself. + +### When you need curly braces themselves + +To have the literal curly-brace characters inside the **formatted** prompt, you need to "escape" them: whenever you need one, you type it twice (`'{{'` or `'}}'`). Then, after formatting, it will turn to `'{'` or `'}'`, respectively. + +Keep in mind though, that with [Recursive formatting](#recursive-formatting-), any `{{text}}` will become `{text}` after very first iteration, and thus on the next one, it still will be treated as a placeholder to put a string with a `text` key into. + +However, this might be exactly what you want for... + +### Dynamic pattern aka conditional formatting + +In other words, you build a prompt, where **keys themselves** are compiled from pieces. For example: +- Your main text template has {{character_`{active_char}`}} pattern somewhere inside it. +- You also have a string named `active_char` in your dict, which you simply set to a number. +- Also-also, you have strings named `character_1`, `character_2`, etc. +- Then, with recursive formatting, depending on just a **single** value you set the `active_char` element to, the following happens: + - At the first iteration, `{{` in the beginning and `}}` in the end are treated as escaped ones, so they're simply replaced to `{` and `}`. + - The middle `{active_char}` part, however, is treated as a real pattern to replace. + - Thus, after the first iteration, `{{character_{active_char}}}` becomes `{character_1}`... + - ... and in the next iteration, the actual value of `character_1` string is placed here instead. + +Clearly, to take advantage of this approach, you need to build your dictionary very cautiously and precisely. But if you do so, you effectively get a way of building entire **logic trees**, represented as data (not as code), all self-contained inside a single dictionary, but still letting you generate a **completely** different prompt by changing values of one or two such "switcher" strings. + +At this point, your imagination is your only limit. + +### Implementation details for programmers + +Internally, just a built-in `str.format_map()` is called with keyword arguments from the passed Format-Dict, which is literally just a dict _(and it's expected to have only string keys)_. + +So any "complex" formatting patterns are available, too (like `{float_value:.3f}`). `Add ANY to Format-Dict` node is there for exactly that. From 166ca35afafafcd1a2ceb96887ad68f096095d3e Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sat, 2 Aug 2025 17:28:36 +0500 Subject: [PATCH 6/6] `v1.0.4` --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bcc5557..0d6e1ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "string-constructor" description = "String formatting (compiling text from pieces) made for humans:\n• Turn parts of prompt into a dictionary - to share them all across the entire workflow as a single connection.\n• Compose your prompt in-place from these snippets and regular text - with just one text field.\n• Easily update the dictionary down the line - get different prompts from the same template.\n• Reference text chunks within each other to build complex hierarchies of self-updating prompts.\n• A true time-saver for regional prompting (aka area composition)." -version = "1.0.3" +version = "1.0.4" authors = [ {name = "Lex Darlog"} ]