feat(rules): Add extended mode

This commit is contained in:
2026-01-09 00:22:13 +10:00
parent 5ef9b7b05a
commit 0c19f0a87a
3 changed files with 50 additions and 10 deletions

View File

@@ -1,21 +1,39 @@
import re
from typing import Self from typing import Self
import os import os
class Rule: class Rule:
__slots__ = ("conditions", "values") __slots__ = ("conditions", "values", "extended")
def __init__(self, conditions: dict[str, str], values: dict[str, str]): def __init__(
self, conditions: dict[str, str], values: dict[str, str], extended=False
):
self.conditions = conditions self.conditions = conditions
self.values = values self.values = values
self.extended = extended
def check(self, record: dict[str, str]) -> bool: def check(self, record: dict[str, str]) -> bool:
""" """
Check whether this rule applies to the given record fields. Check whether this rule applies to the given record fields.
""" """
return all( if self.extended:
record.get(key, None) == value for key, value in self.conditions.items() match = True
) for key, pattern in self.conditions.items():
actualvalue = record.get(key, None)
if isinstance(pattern, str) and isinstance(actualvalue, str):
match = match and bool(re.match(pattern, actualvalue))
else:
# Fallback to equality
match = match and (pattern == actualvalue)
if not match:
break
else:
match = all(
record.get(key, None) == value for key, value in self.conditions.items()
)
return match
class RuleInterface: class RuleInterface:
@@ -71,6 +89,7 @@ class JSONRuleInterface(RuleInterface):
rule = Rule( rule = Rule(
conditions=rule_data["record_fields"], conditions=rule_data["record_fields"],
values=rule_data["transaction_fields"], values=rule_data["transaction_fields"],
extended=rule_data.get("extended", False),
) )
rules.append(rule) rules.append(rule)
@@ -85,6 +104,7 @@ class JSONRuleInterface(RuleInterface):
{ {
"record_fields": rule.conditions, "record_fields": rule.conditions,
"transaction_fields": rule.values, "transaction_fields": rule.values,
"extended": rule.extended,
} }
) )

View File

@@ -167,9 +167,9 @@ class MainWindow(ThemedTk):
def rule_created(self, event): def rule_created(self, event):
logger.debug(f"Received RowEditor rulecreated event {event!r}") logger.debug(f"Received RowEditor rulecreated event {event!r}")
conditions, values = self.editor.make_rule() conditions, values, extended = self.editor.make_rule()
rule = Rule(conditions, values) rule = Rule(conditions, values, extended)
affected = sum(rule.check(record.match_fields()) for record in self.rows) affected = sum(rule.check(record.match_fields()) for record in self.rows)
self.ruleset.add_rule(rule) self.ruleset.add_rule(rule)

View File

@@ -59,13 +59,29 @@ class RowEditor(ttk.Frame):
) )
self.txn_frame.columnconfigure(2, weight=1) self.txn_frame.columnconfigure(2, weight=1)
self.matchopt_frame = ttk.Frame(self)
self.matchopt_frame.grid(row=0, column=1, sticky="ew")
self.matchopt_frame.columnconfigure(0, weight=0)
self.matchopt_frame.columnconfigure(1, weight=1)
self.matchopt_frame.columnconfigure(2, weight=0)
self.matchopt_frame.columnconfigure(3, weight=0)
self.match_var = BooleanVar() self.match_var = BooleanVar()
self.match_toggle = ttk.Checkbutton( self.match_toggle = ttk.Checkbutton(
self, variable=self.match_var, command=self._display_matchboxes self, variable=self.match_var, command=self._display_matchboxes
) )
self.match_label = ttk.Label(self, text="Rule creation mode")
self.match_toggle.grid(row=0, column=0, padx=11, pady=10, sticky="nw") self.match_toggle.grid(row=0, column=0, padx=11, pady=10, sticky="nw")
self.match_label.grid(row=0, column=1, padx=7, pady=10, sticky="nw")
self.match_label = ttk.Label(self.matchopt_frame, text="Rule creation mode")
self.match_label.grid(row=0, column=0, padx=7, pady=10, sticky="nw")
self.extended_var = BooleanVar()
self.extended_toggle = ttk.Checkbutton(
self.matchopt_frame, variable=self.extended_var
)
self.extended_label = ttk.Label(self.matchopt_frame, text="Extended")
self.extended_toggle.grid(row=0, column=2, padx=11, pady=10, sticky="nw")
self.extended_label.grid(row=0, column=3, padx=7, pady=10, sticky="nw")
self.button_frame = ttk.Frame(self, padding=(2, 2, 3, 3)) self.button_frame = ttk.Frame(self, padding=(2, 2, 3, 3))
self.button_frame.grid(row=3, column=0, columnspan=2, sticky="s") self.button_frame.grid(row=3, column=0, columnspan=2, sticky="s")
@@ -236,6 +252,8 @@ class RowEditor(ttk.Frame):
label.grid_remove() label.grid_remove()
entry.grid(row=i, column=2, sticky="ew", padx=5) entry.grid(row=i, column=2, sticky="ew", padx=5)
self.rule_button.configure(state="enabled") self.rule_button.configure(state="enabled")
self.extended_toggle.grid(row=0, column=2, padx=11, pady=10, sticky="nw")
self.extended_label.grid(row=0, column=3, padx=7, pady=10, sticky="nw")
else: else:
# Disable match boxes # Disable match boxes
for i, (box, _, _) in enumerate(self.txn_rows.values()): for i, (box, _, _) in enumerate(self.txn_rows.values()):
@@ -249,6 +267,8 @@ class RowEditor(ttk.Frame):
entry.grid_remove() entry.grid_remove()
# Also disable rule button # Also disable rule button
self.rule_button.configure(state="disabled") self.rule_button.configure(state="disabled")
self.extended_toggle.grid_remove()
self.extended_label.grid_remove()
def do_save_txn(self): def do_save_txn(self):
if not self.rows: if not self.rows:
@@ -299,7 +319,7 @@ class RowEditor(ttk.Frame):
} }
update_fields = txn.parse_input(input_fields) update_fields = txn.parse_input(input_fields)
return (rule_record_fields, update_fields) return (rule_record_fields, update_fields, self.extended_var.get())
def _display_txns(self, txns: list[PartialTXN]): def _display_txns(self, txns: list[PartialTXN]):
if txns and not self.showing_txn: if txns and not self.showing_txn: