diff --git a/docs/SYNTAX.md b/docs/SYNTAX.md index 1ade95a..29cb11b 100644 --- a/docs/SYNTAX.md +++ b/docs/SYNTAX.md @@ -4,7 +4,7 @@ The extension uses a format for its commands similar to an extranetwork, but it has a "ppp:" prefix followed by the command, and then a space and any parameters (if any). -`` +`` When a command is associated with any content, it will be between an opening and a closing command: @@ -66,7 +66,7 @@ The generic format is: `__parameters$$wildcard'filter'(var=value)__` The parameters, the filter, and the setting of a variable are optional. The parameters follow the same format as for the choices. -The wildcard identifier can have a relative path and contain globbing formatting, to read multiple wildcards and merge their choices. Note that if there are no parameters specified, the globbing will use the ones from the first wildcard that matches and have parameters (sorted by keys), so if you don't want that you might want to specify them. Also note that, unlike with *Dynamic Prompts*, the wildcard name has to be specified with its full path (unless you use globbing). +The wildcard identifier can have a relative path and contain globbing formatting, to read multiple wildcards and merge their choices. Note that if there are no parameters specified, the globbing will use the ones from the first wildcard that matches and have parameters (sorted by keys), so if you don't want that you might want to specify them. Also note that, unlike with *Dynamic Prompts*, the wildcard name has to be specified with its full path (unless you use globbing). You can use variables here, with the `${name:default}` format, to build a dynamic identifier. The filter can be used to filter specific choices from the wildcard. The filtering works before applying the choice conditions (if any). The surrounding quotes can be single or double. The filter is a comma separated list of an integer (positional choice index; zero-based) or choice label. You can also compound them with `+`. That is, the comma separated items act as an OR and the `+` inside them as an AND. Using labels can simplify the definitions of complex wildcards where you want to have direct access to specific choices on occasion (you don't need to create wildcards for each individual choice). There are some additional formats when using filters. You can specify `^wildcard` as a filter to use the filter of a previous wildcard in the chain. You can start the filter (regular or inherited) with `#` and it will not be applied to the current wildcard choices, but the filter will remain in memory to use by other descendant wildcards. You use `#` and `^` when you want to pass a filter to inner wildcards (see the test files). @@ -169,7 +169,7 @@ The format is: | Construct | | --------- | -| `` | +| `` | | `default` | The *Dynamic Prompts* format is: @@ -244,7 +244,7 @@ This command is a shortcut to add an extranetwork (usually a lora), and its trig The full format is: `[triggers]` -`` +`` The `type` is the kind of extranetwork, like `lora` or `hypernet`. @@ -265,7 +265,7 @@ If the condition passes (or if there is no condition) the extranetwork tag will ```text test sd1x test pony - + test sdxl ``` @@ -298,7 +298,7 @@ extnettype: Used like this: ```text - + inline triggers ``` @@ -321,7 +321,7 @@ The new format for this command is like this: | Construct | Meaning | | --------- | ------- | | `content` | send to negative prompt | -| `` | insertion point to be used in the negative prompt as destination for the pN position | +| `` | insertion point to be used in the negative prompt as destination for the pN position | Where position is optional (defaults to the start) and can be: diff --git a/grammar.lark b/grammar.lark index 34a5b29..1b8bfbd 100644 --- a/grammar.lark +++ b/grammar.lark @@ -92,7 +92,7 @@ extranetworktag: "<" /(?!ppp:)\w+:/ encontent ">" // command: stn (send to negative) commandstn: "" content_negtag "" -commandstni: "" +commandstni: "" | ">" ) // command: if commandif.2: commandif_if commandif_elif* commandif_else? "" @@ -121,11 +121,11 @@ commandsetmodifiers: (_WHITESPACE /evaluate|ifundefined|add/ )+ ?commandsetcontent.3: content // command: echo -commandecho: "" [ commandechodefault "" ] +commandecho: "" [ commandechodefault "" ] | "/>" ) ?commandechodefault.3: content // command: ext -commandext: "" [ commandexttriggers "" ] +commandext: "" [ commandexttriggers "" ] | "/>" ) commandexttype: [/\$/] IDENTIFIER ?commandextid: STRING | CNAME ?commandextparams: STRING | SIGNED_NUMBER diff --git a/ppp.py b/ppp.py index b4705b4..ca950c6 100644 --- a/ppp.py +++ b/ppp.py @@ -719,6 +719,10 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in for k in var_keys: ev = self.echoed_variables.get(k) if ev is None: + ev = self.user_variables.get(k) + if ev is None or not isinstance(ev, str): + if self.debug_level == DEBUG_LEVEL.full: + self.logger.debug(self.format_output(f"Completing variable: {k}")) ev = p_processor.get_final_user_variable(k) all_variables[k] = ev if self.debug_level == DEBUG_LEVEL.full: @@ -1517,13 +1521,13 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in """ Process a DP use variable command in the tree. """ - self.__varecho("variableuse", str(tree.children[0]), tree.children[1]) + self.__varecho("variableuse", str(tree.children[0]), tree.children[1] if len(tree.children) > 1 else None) def commandecho(self, tree: lark.Tree): """ Process an echo command in the tree. """ - self.__varecho("commandecho", str(tree.children[0]), tree.children[1]) + self.__varecho("commandecho", str(tree.children[0]), tree.children[1] if len(tree.children) > 1 else None) def commandif(self, tree: lark.Tree): """ @@ -2096,6 +2100,32 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in ) return (options, choice_values) + def replace_variables(self, s: str) -> str: + """ + Replace variables in the given string. + + Args: + s (str): The input string. + Returns: + str: The string with variables replaced. + """ + result = "" + idx = 0 + pattern = re.compile(r"\$\{([a-zA-Z_][a-zA-Z0-9_]*)(?::([^}]+))?\}") + while True: + match = pattern.search(s, idx) + if not match: + result += s[idx:] + break + result += s[idx : match.start()] + var_name = match.group(1) + var_value = self.__get_user_variable_value(var_name, True, False) + if var_value is None: + var_value = match.group(2) if match.group(2) is not None else "" + result += str(var_value) + idx = match.end() + return result + def wildcard(self, tree: lark.Tree): """ Process a wildcard construct in the tree. @@ -2105,6 +2135,10 @@ class PromptPostProcessor: # pylint: disable=too-few-public-methods,too-many-in start_result = self.result applied_options = self.__convert_choices_options(tree.children[0]) wildcard_key: str = tree.children[1].value # should be a token + # TODO implement proper parsing of variables in the grammar instead of the hack using replace_variables + if "${" in wildcard_key: + self.__ppp.logger.debug(f"Replacing variables in wildcard key '{wildcard_key}'") + wildcard_key = self.replace_variables(wildcard_key) wc = self.__get_original_node_content(tree, f"?__{wildcard_key}__") if self.__ppp.wil_process_wildcards: if self.__ppp.debug_level == DEBUG_LEVEL.full: diff --git a/ppp_comfyui.py b/ppp_comfyui.py index 6d8433d..7cc62fc 100644 --- a/ppp_comfyui.py +++ b/ppp_comfyui.py @@ -2,7 +2,7 @@ import os # pylint: disable=import-error import folder_paths # type: ignore -import nodes +import nodes # type: ignore from .ppp import PromptPostProcessor from .ppp_hosts import SUPPORTED_APPS diff --git a/tests/tests.py b/tests/tests.py index 00c34c2..38f1650 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -189,7 +189,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "flowersred, green, blueyellow, purpleblack", - "normal quality, worse quality", + "normal quality, worse quality", ), PromptPair("flowers", "red, green, yellow, normal quality, purple, worse quality, black, blue"), ) @@ -198,7 +198,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "red ((pink)), flowers purple, mauveblue, yellow green", - "normal quality, , bad quality, worse quality", + "normal quality, , bad quality, worse quality", ), PromptPair( "flowers", @@ -210,7 +210,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "red ((pink)), flowers purple, mauveblue, yellow green", - "normal quality, , bad quality, worse quality", + "normal quality, , bad quality, worse quality", ), PromptPair( " (()), flowers , , ", @@ -264,7 +264,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "[neg5] this \\(is\\): a (([complex|simpleneg6|regular] testneg1)(test:2.0):1.5) \nBREAK, BREAK with [abcneg4:defneg2(neg3:1.6):5]:0.5 AND loratrigger AND AND hypernettrigger :0.3", - "normal quality, ", + "normal quality, ", ), PromptPair( "this \\(is\\): a (([complex|simple|regular] test)(test:2):1.5)\nBREAK with [abc:def:5]:0.5 AND loratrigger AND hypernettrigger :0.3", @@ -276,7 +276,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "[neg5] this \\(is\\): a (([complex|simpleneg6|regular] testneg1)(test:2.0):1.5) \nBREAK, BREAK with [abcneg4:defneg2(neg3:1.6):5]:0.5 AND loratrigger AND AND hypernettrigger :0.3", - "normal quality, ", + "normal quality, ", ), PromptPair( "this \\(is\\): a (([complex|simple|regular] test)(test:2):1.5)\nBREAK with [abc:def:5]:0.5 AND loratrigger AND hypernettrigger :0.3", @@ -420,7 +420,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "[neg5] this \\(is\\): a (([complex|simpleneg6|regular] testneg1)(test:2.0):1.5) \nBREAK, BREAK with [abcneg4:defneg2(neg3:1.6):5]:0.5 AND loratrigger AND AND hypernettrigger :0.3", - "normal quality, ", + "normal quality, ", ), PromptPair( "this \\(is\\): a (([complex|simple|regular] test)(test:2):1.5)\nBREAK with [abc:def:5]:0.5 AND loratrigger AND hypernettrigger :0.3", @@ -482,7 +482,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): def test_cmd_set_if_echo_nested(self): # nested set, if and echo commands self.process( PromptPair( - "1OKnot OK NOK OK", + "1OKnot OK NOK OK", "", ), PromptPair("OK OK OK", ""), @@ -646,6 +646,15 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): ), ) + def test_cmd_ext_map_notrigger(self): # ext mapping, no trigger + self.process( + PromptPair( + "", + "", + ), + PromptPair("triggergeneric1, triggergeneric2, two, triggergeneric1, triggergeneric2, two", ""), + ) + def test_cmd_ext_map1(self): # ext mapping, no lora self.process( PromptPair( @@ -851,7 +860,7 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): self.process( PromptPair( "[neg5] this is: __bad_wildcard__ a (([complex|simpleneg6|regular] testneg1)(test:2.0):1.5) \nBREAK, BREAK with [abcneg4:defneg2(neg3:1.6):5] ", - "normal quality, {option1|option2}", + "normal quality, {option1|option2}", ), PromptPair( "this is: a (([complex|simple|regular] test)(test:2):1.5)\nBREAK with [abc:def:5]", @@ -1185,6 +1194,13 @@ class TestPromptPostProcessor(TestPromptPostProcessorBase): interrupted=True, ) + def test_wc_dynamicwildcard(self): # wildcard built from variables + self.process( + PromptPair("the choices are: ${x={1|2|3}} __yaml/wildcard${x}__", ""), + PromptPair("the choices are: choice1-choice3-choice1", ""), + ppp=self.nocupppp, + ) + # Model variants tests def test_variants(self): diff --git a/tests/wildcards2/text/wildcard3.txt b/tests/wildcards2/text/wildcard3.txt index 67ed0e4..32a0421 100644 --- a/tests/wildcards2/text/wildcard3.txt +++ b/tests/wildcards2/text/wildcard3.txt @@ -1,3 +1,3 @@ -# wildcard3 +# wildcard3 (with windows-1252 encoding) __2$$,$$text/wildcard2__ 0::áüç \ No newline at end of file diff --git a/workflows/PPP example.jpg b/workflows/PPP example.jpg new file mode 100644 index 0000000..1d5f284 Binary files /dev/null and b/workflows/PPP example.jpg differ