aboutsummaryrefslogtreecommitdiffstats
path: root/src/tagrules.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-12 11:47:40 +0200
committerDanilo M. <danix@danix.xyz>2026-08-12 11:47:40 +0200
commitd14c0224826f821b34c894037ab668ddfde29a6d (patch)
tree656b614f08b3a9eed9771ac4374e0d972a15eece /src/tagrules.h
parent49dac862685a50e1def6646349b61f4b36e73cc8 (diff)
downloadqtmaildir-d14c0224826f821b34c894037ab668ddfde29a6d.tar.gz
qtmaildir-d14c0224826f821b34c894037ab668ddfde29a6d.zip
feat(rules): read and write the shared rule store
The same ~/.config/mailrules/rules.json mailctl reads, parsed here with QJsonDocument and written atomically with QSaveFile. Fields this version does not understand round-trip untouched, which is what keeps the format neutral between the two tools. Mutation-checked: removing the unknown-field write fails unknownFieldsSurviveASave.
Diffstat (limited to 'src/tagrules.h')
-rw-r--r--src/tagrules.h87
1 files changed, 87 insertions, 0 deletions
diff --git a/src/tagrules.h b/src/tagrules.h
new file mode 100644
index 0000000..8f75b38
--- /dev/null
+++ b/src/tagrules.h
@@ -0,0 +1,87 @@
+/*
+ * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs
+ * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include <QJsonObject>
+#include <QList>
+#include <QString>
+#include <QStringList>
+
+/// One auto-tagging rule, as stored in ~/.config/mailrules/rules.json.
+///
+/// A rule carries NO scope. The notmuch post-new hook supplies `tag:new`, a
+/// dry run supplies nothing and counts against the whole corpus. That split is
+/// what lets one rule answer both "what would this tag on arrival" and "what
+/// does this match in all my mail".
+struct TagRule
+{
+ QString id; ///< Stable handle, [a-z0-9-]. Never the tag name: tags
+ ///< contain '/' and can be renamed.
+ QString query; ///< notmuch query, unscoped.
+ QString note; ///< Why the rule is shaped this way. Shown in the dialog.
+ QStringList add;
+ QStringList remove;
+ int stage = 50; ///< Ascending. Account tags 10, topic rules 50.
+ bool enabled = true;
+
+ /// Fields this version of qtmaildir does not understand, kept verbatim and
+ /// written back on save. Without this, one save from here silently strips
+ /// whatever a newer mailctl wrote, and the shared format would belong to
+ /// whichever tool saved last.
+ QJsonObject unknown;
+};
+
+/// Reads and writes the shared rule store.
+///
+/// Degrades rather than refusing, exactly as Config does: a malformed rule is
+/// dropped with a warning and the rest still load, because one typo must not
+/// cost every rule. qtmaildir must never fail to open because of this file.
+class TagRules
+{
+public:
+ /// $XDG_CONFIG_HOME/mailrules/rules.json, or ~/.config/... as fallback.
+ /// Deliberately not under qtmaildir's own config directory: mailctl reads
+ /// the same file and neither tool owns it.
+ static QString defaultPath();
+
+ /// Replaces the current contents. Never throws; see warnings().
+ void load(const QString &path = QString());
+
+ /// Atomic: QSaveFile writes a temporary and renames, so the hook can never
+ /// read a partial file. Returns false if the write failed.
+ bool save(const QString &path = QString()) const;
+
+ QList<TagRule> rules() const { return m_rules; }
+ void setRules(const QList<TagRule> &rules) { m_rules = rules; }
+
+ /// Enabled rules in execution order: stage ascending, ties in file order.
+ QList<TagRule> ordered() const;
+
+ QStringList warnings() const { return m_warnings; }
+
+ /// No file yet, as distinct from a file that would not load. A fresh
+ /// install is not an error and must not be reported as one.
+ bool missing() const { return m_missing; }
+
+private:
+ QList<TagRule> m_rules;
+ QStringList m_warnings;
+ QJsonObject m_unknown; ///< Unrecognised top-level keys.
+ bool m_missing = false;
+};