Add full sd-webui-prompt-postprocessor tag support: - <ppp:set> with evaluate, add, ifundefined modifiers - <ppp:echo> with fallback - <ppp:if> with eq/ne/gt/lt/ge/le/contains/in operators and logical combinators - <ppp:stn> for sending content to negative prompt (start/end positions) - <ppp:ext> for generating <lora:...> and hypernet tags Create PromptWithWildcards node: - Auto-infers model family (sd1, sdxl, sd3, flux) from MODEL input - Supports variant override (pony, illustrious, anima, noobai) - Provides _is_* system variables for <ppp:if> conditions - Merges <ppp:stn> content into negative output Add prompt cleanup utilities: - ignore_repeats: deduplicate keywords case-insensitively - cleanup_extra_spaces: normalize whitespace and trim brackets - cleanup_empty_constructs: remove empty keywords and attention tags Fixes: - Handle all-zero variant weights (fallback to uniform random selection) - Resolve ___name__ wildcard syntax with leading underscores Docs: - Update README with PPP syntax reference and node documentation - Add ROADMAP.md with ComfyUI encoding behavior notes Tests: 190 passing (35 new cleanup tests, 64 PPP/feature tests)
158 lines
4.8 KiB
Python
158 lines
4.8 KiB
Python
from src.parser.parser import parse
|
|
from src.parser.ast_nodes import Template, Text, PPPSet, PPPEcho, PPPIf, Variant, PPPSendToNegative, PPPExtraNetwork
|
|
|
|
|
|
def test_ppp_set_parses():
|
|
result = parse("<ppp:set x>hello<ppp:/set>")
|
|
assert len(result.parts) == 1
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPSet)
|
|
assert node.var_name == "x"
|
|
assert node.modifiers == []
|
|
assert node.content.parts[0].value == "hello"
|
|
|
|
|
|
def test_ppp_set_with_modifiers():
|
|
result = parse("<ppp:set x evaluate add>hello<ppp:/set>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPSet)
|
|
assert node.var_name == "x"
|
|
assert node.modifiers == ["evaluate", "add"]
|
|
|
|
|
|
def test_ppp_echo_self_closing():
|
|
result = parse("<ppp:echo x/>")
|
|
assert len(result.parts) == 1
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPEcho)
|
|
assert node.var_name == "x"
|
|
assert node.default is None
|
|
|
|
|
|
def test_ppp_echo_with_default():
|
|
result = parse("<ppp:echo x>fallback<ppp:/echo>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPEcho)
|
|
assert node.var_name == "x"
|
|
assert node.default is not None
|
|
assert node.default.parts[0].value == "fallback"
|
|
|
|
|
|
def test_ppp_if_simple():
|
|
result = parse("<ppp:if x>matched<ppp:/if>")
|
|
assert len(result.parts) == 1
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPIf)
|
|
assert len(node.branches) == 1
|
|
assert node.branches[0][0] == "x"
|
|
assert node.branches[0][1].parts[0].value == "matched"
|
|
assert node.else_content is None
|
|
|
|
|
|
def test_ppp_if_with_else():
|
|
result = parse("<ppp:if x>a<ppp:else>b<ppp:/if>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPIf)
|
|
assert len(node.branches) == 1
|
|
assert node.else_content is not None
|
|
assert node.else_content.parts[0].value == "b"
|
|
|
|
|
|
def test_ppp_if_with_elif():
|
|
result = parse("<ppp:if x eq 1>a<ppp:elif x eq 2>b<ppp:/if>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPIf)
|
|
assert len(node.branches) == 2
|
|
assert node.branches[0][0] == "x eq 1"
|
|
assert node.branches[1][0] == "x eq 2"
|
|
assert node.else_content is None
|
|
|
|
|
|
def test_ppp_if_with_elif_and_else():
|
|
result = parse("<ppp:if x eq 1>a<ppp:elif x eq 2>b<ppp:else>c<ppp:/if>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPIf)
|
|
assert len(node.branches) == 2
|
|
assert node.else_content is not None
|
|
assert node.else_content.parts[0].value == "c"
|
|
|
|
|
|
def test_ppp_tag_in_text():
|
|
result = parse("before <ppp:set x>hello<ppp:/set> after")
|
|
assert len(result.parts) == 3
|
|
assert isinstance(result.parts[0], Text)
|
|
assert isinstance(result.parts[1], PPPSet)
|
|
assert isinstance(result.parts[2], Text)
|
|
|
|
|
|
def test_ppp_nested_in_variant():
|
|
result = parse("{a|<ppp:if x>b<ppp:/if>}")
|
|
v = result.parts[0]
|
|
assert isinstance(v, Variant)
|
|
assert len(v.options) == 2
|
|
assert isinstance(v.options[1].node.parts[0], PPPIf)
|
|
|
|
|
|
def test_ppp_stn_paired():
|
|
result = parse("<ppp:stn e>content<ppp:/stn>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPSendToNegative)
|
|
assert node.position == "e"
|
|
assert node.content.parts[0].value == "content"
|
|
assert not node.is_insertion_point
|
|
|
|
|
|
def test_ppp_stn_self_closing():
|
|
result = parse("<ppp:stn i0/>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPSendToNegative)
|
|
assert node.position == "i0"
|
|
assert node.is_insertion_point
|
|
|
|
|
|
def test_ppp_stn_no_position():
|
|
result = parse("<ppp:stn>content<ppp:/stn>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPSendToNegative)
|
|
assert node.position is None
|
|
assert node.content.parts[0].value == "content"
|
|
|
|
|
|
def test_ppp_ext_paired():
|
|
result = parse("<ppp:ext lora test_lora 0.5>trigger<ppp:/ext>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPExtraNetwork)
|
|
assert node.ext_type == "lora"
|
|
assert node.name == "test_lora"
|
|
assert node.weight == "0.5"
|
|
assert node.triggers.parts[0].value == "trigger"
|
|
assert not node.is_mapping
|
|
|
|
|
|
def test_ppp_ext_self_closing():
|
|
result = parse("<ppp:ext lora test_lora/>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPExtraNetwork)
|
|
assert node.ext_type == "lora"
|
|
assert node.name == "test_lora"
|
|
assert node.weight is None
|
|
assert node.triggers is None
|
|
|
|
|
|
def test_ppp_ext_with_condition():
|
|
result = parse('<ppp:ext lora test_lora if _is_pony>trigger<ppp:/ext>')
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPExtraNetwork)
|
|
assert node.ext_type == "lora"
|
|
assert node.name == "test_lora"
|
|
assert node.condition == "_is_pony"
|
|
assert node.triggers.parts[0].value == "trigger"
|
|
|
|
|
|
def test_ppp_ext_quoted_name():
|
|
result = parse("<ppp:ext lora 'test lora' 0.5/>")
|
|
node = result.parts[0]
|
|
assert isinstance(node, PPPExtraNetwork)
|
|
assert node.name == "test lora"
|
|
assert node.weight == "0.5"
|