aboutsummaryrefslogtreecommitdiffstats
path: root/mailctl.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-12 11:42:14 +0200
committerDanilo M. <danix@danix.xyz>2026-08-12 11:42:14 +0200
commitf1890fc7b5fe1094445ce8b83c2d2f15258fceec (patch)
tree6caf922da23b5a17ee758402b5687fe0a305750c /mailctl.py
parentf2feb50773b945aafed896b7d75e66035315aa5b (diff)
downloadmailctl-f1890fc7b5fe1094445ce8b83c2d2f15258fceec.tar.gz
mailctl-f1890fc7b5fe1094445ce8b83c2d2f15258fceec.zip
feat(rules): read-only rules list and show
Editing stays out of this tool: a rule edit affects every future sync and its gate is not designed yet. qtmaildir has the editor. The install step now copies mailrules.py beside mailctl.py. The import resolves from the script's own directory, so copying only mailctl.py would break every command, not just the new ones.
Diffstat (limited to 'mailctl.py')
-rwxr-xr-xmailctl.py50
1 files changed, 50 insertions, 0 deletions
diff --git a/mailctl.py b/mailctl.py
index be4cbcf..ea31286 100755
--- a/mailctl.py
+++ b/mailctl.py
@@ -59,6 +59,8 @@ from email.message import EmailMessage
from email.utils import make_msgid, formatdate
from pathlib import Path
+import mailrules
+
AUDIT_LOG = Path.home() / ".local" / "state" / "mailctl" / "audit.log"
MAIL_ROOT = Path(os.environ.get("MAILCTL_MAIL_ROOT", Path.home() / "Mail"))
@@ -472,6 +474,41 @@ def log_mutation(scope, query, tag_expr, count):
f"change={' '.join(tag_expr)}\tcount={count}\n")
+def cmd_rules_list(args):
+ store = mailrules.load()
+ for warning in store.warnings:
+ print(f"warning: {warning}", file=sys.stderr)
+ if store.missing:
+ print(f"No rules file at {mailrules.default_path()}", file=sys.stderr)
+ return
+ for rule in mailrules.ordered(store.rules) if args.enabled_only \
+ else store.rules:
+ state = " " if rule.enabled else "-"
+ tags = " ".join(mailrules.tag_arguments(rule))
+ print(f"{state} {rule.stage:>4} {rule.id:<28.28} {tags}")
+ total = len(store.rules)
+ disabled = sum(1 for r in store.rules if not r.enabled)
+ print(f"\n{total} rule(s), {disabled} disabled", file=sys.stderr)
+
+
+def cmd_rules_show(args):
+ store = mailrules.load()
+ for rule in store.rules:
+ if rule.id != args.id:
+ continue
+ print(f"id: {rule.id}")
+ print(f"stage: {rule.stage}")
+ print(f"enabled: {rule.enabled}")
+ print(f"add: {', '.join(rule.add) or '(none)'}")
+ print(f"remove: {', '.join(rule.remove) or '(none)'}")
+ print(f"query: {rule.query}")
+ if rule.note:
+ print(f"note: {rule.note}")
+ return
+ print(f"No rule with id '{args.id}'", file=sys.stderr)
+ sys.exit(1)
+
+
def build_parser():
p = argparse.ArgumentParser(
prog="mailctl",
@@ -550,6 +587,19 @@ def build_parser():
sp.add_argument("--body-file", help="read body from a file instead")
sp.set_defaults(func=cmd_draft)
+ sp = sub.add_parser("rules", help="inspect the shared tagging rules "
+ "(read-only)")
+ rules_sub = sp.add_subparsers(dest="rules_command", required=True)
+
+ rp = rules_sub.add_parser("list", help="list every rule in stage order")
+ rp.add_argument("--enabled-only", action="store_true",
+ help="only the rules the hook would run")
+ rp.set_defaults(func=cmd_rules_list)
+
+ rp = rules_sub.add_parser("show", help="show one rule in full")
+ rp.add_argument("id")
+ rp.set_defaults(func=cmd_rules_show)
+
return p