From f479318ea8f890122f2f28a7a224802dbf4e870b Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Wed, 12 Aug 2026 11:43:36 +0200 Subject: feat(rules): dry-run counts what a rule matches Two numbers per rule: what it matches across all mail, and what it would tag on the next sync. The first is what decides whether a rule is still earning its place. --- mailctl.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'mailctl.py') diff --git a/mailctl.py b/mailctl.py index ea31286..550e56c 100755 --- a/mailctl.py +++ b/mailctl.py @@ -509,6 +509,27 @@ def cmd_rules_show(args): sys.exit(1) +def cmd_rules_dry_run(args): + """What each rule matches right now. Two numbers, because they answer + different questions: the corpus count says whether a rule is still doing + anything and whether it would bury real correspondence, the pending count + says what the next sync would tag.""" + store = mailrules.load() + rules = [r for r in store.rules if not args.id or r.id == args.id] + if not rules: + print(f"No rule with id '{args.id}'", file=sys.stderr) + sys.exit(1) + + print(f"{'rule':<28} {'corpus':>8} {'pending':>8}") + for rule in rules: + corpus = run_notmuch(["count", mailrules.scoped_query(rule, None)]) + pending = run_notmuch(["count", + mailrules.scoped_query(rule, "tag:new")]) + state = "" if rule.enabled else " (disabled)" + print(f"{rule.id:<28.28} {corpus.strip():>8} " + f"{pending.strip():>8}{state}") + + def build_parser(): p = argparse.ArgumentParser( prog="mailctl", @@ -600,6 +621,12 @@ def build_parser(): rp.add_argument("id") rp.set_defaults(func=cmd_rules_show) + rp = rules_sub.add_parser("dry-run", + help="count what each rule matches, changing " + "nothing") + rp.add_argument("id", nargs="?", help="one rule, default is all") + rp.set_defaults(func=cmd_rules_dry_run) + return p -- cgit v1.2.3