diff --git a/src/beanify/base/rules.py b/src/beanify/base/rules.py index 61b7737..b08903c 100644 --- a/src/beanify/base/rules.py +++ b/src/beanify/base/rules.py @@ -1,21 +1,39 @@ +import re from typing import Self import os 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.values = values + self.extended = extended def check(self, record: dict[str, str]) -> bool: """ Check whether this rule applies to the given record fields. """ - return all( - record.get(key, None) == value for key, value in self.conditions.items() - ) + if self.extended: + 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: @@ -71,6 +89,7 @@ class JSONRuleInterface(RuleInterface): rule = Rule( conditions=rule_data["record_fields"], values=rule_data["transaction_fields"], + extended=rule_data.get("extended", False), ) rules.append(rule) @@ -85,6 +104,7 @@ class JSONRuleInterface(RuleInterface): { "record_fields": rule.conditions, "transaction_fields": rule.values, + "extended": rule.extended, } ) diff --git a/src/beanify/gui/mainwindow.py b/src/beanify/gui/mainwindow.py index 708bcc7..18a9cf8 100644 --- a/src/beanify/gui/mainwindow.py +++ b/src/beanify/gui/mainwindow.py @@ -167,9 +167,9 @@ class MainWindow(ThemedTk): def rule_created(self, event): 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) self.ruleset.add_rule(rule) diff --git a/src/beanify/gui/roweditor.py b/src/beanify/gui/roweditor.py index 846e71e..edf88a8 100644 --- a/src/beanify/gui/roweditor.py +++ b/src/beanify/gui/roweditor.py @@ -59,13 +59,29 @@ class RowEditor(ttk.Frame): ) 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_toggle = ttk.Checkbutton( 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_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.grid(row=3, column=0, columnspan=2, sticky="s") @@ -236,6 +252,8 @@ class RowEditor(ttk.Frame): label.grid_remove() entry.grid(row=i, column=2, sticky="ew", padx=5) 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: # Disable match boxes for i, (box, _, _) in enumerate(self.txn_rows.values()): @@ -249,6 +267,8 @@ class RowEditor(ttk.Frame): entry.grid_remove() # Also disable rule button self.rule_button.configure(state="disabled") + self.extended_toggle.grid_remove() + self.extended_label.grid_remove() def do_save_txn(self): if not self.rows: @@ -299,7 +319,7 @@ class RowEditor(ttk.Frame): } 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]): if txns and not self.showing_txn: