1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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;
};
|