MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.6% 219 / 0 / 234
Functions: 100.0% 11 / 0 / 11
Branches: 74.8% 354 / 0 / 473

presentation/RuleConsoleInput.cpp
Line Branch Exec Source
1 #include "RuleConsoleInput.h"
2
3 #include <QCoreApplication>
4 #include <QRegularExpression>
5 #include <QVariantList>
6 #include <algorithm>
7
8 namespace RuleConsoleInput {
9
10 namespace {
11
12 // Splits on whitespace; "quoted parts" stay together (quotes removed).
13 64 QStringList tokenize(const QString &text) {
14 64 QStringList tokens;
15 64 QString current;
16 64 bool quoted = false;
17 64 bool hadQuote = false;
18
2/2
✓ Branch 26 → 4 taken 1168 times.
✓ Branch 26 → 27 taken 64 times.
1232 for (const QChar c : text) {
19
2/2
✓ Branch 7 → 8 taken 28 times.
✓ Branch 7 → 9 taken 1140 times.
1168 if (c == QLatin1Char('"')) {
20 28 quoted = !quoted;
21 28 hadQuote = true;
22 28 continue;
23 }
24
6/6
✓ Branch 10 → 11 taken 94 times.
✓ Branch 10 → 13 taken 1046 times.
✓ Branch 11 → 12 taken 92 times.
✓ Branch 11 → 13 taken 2 times.
✓ Branch 14 → 15 taken 92 times.
✓ Branch 14 → 24 taken 1048 times.
1140 if (c.isSpace() && !quoted) {
25
5/6
✓ Branch 16 → 17 taken 24 times.
✓ Branch 16 → 18 taken 68 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 24 times.
✓ Branch 20 → 21 taken 68 times.
✓ Branch 20 → 22 taken 24 times.
92 if (!current.isEmpty() || hadQuote)
26
1/1
✓ Branch 21 → 22 taken 68 times.
68 tokens.append(current);
27 92 current.clear();
28 92 hadQuote = false;
29 92 continue;
30 }
31
1/1
✓ Branch 24 → 25 taken 1048 times.
1048 current.append(c);
32 }
33
5/6
✓ Branch 28 → 29 taken 40 times.
✓ Branch 28 → 30 taken 24 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 40 times.
✓ Branch 32 → 33 taken 24 times.
✓ Branch 32 → 34 taken 40 times.
64 if (!current.isEmpty() || hadQuote)
34
1/1
✓ Branch 33 → 34 taken 24 times.
24 tokens.append(current);
35 64 return tokens;
36 64 }
37
38 // The first arrow outside double quotes (S106-325: an address or folder in
39 // quotes may contain one); "→" is preferred over "->" and "=>" as before.
40 38 int arrowIndex(const QString &text, int *length) {
41
2/2
✓ Branch 38 → 16 taken 47 times.
✓ Branch 38 → 39 taken 3 times.
316 for (const QString &arrow : {QStringLiteral("→"), QStringLiteral("->"), QStringLiteral("=>")}) {
42 47 bool quoted = false;
43
2/2
✓ Branch 36 → 17 taken 1261 times.
✓ Branch 36 → 37 taken 12 times.
1273 for (int index = 0; index < text.size(); ++index) {
44
2/2
✓ Branch 21 → 22 taken 32 times.
✓ Branch 21 → 23 taken 1229 times.
1261 if (text.at(index) == QLatin1Char('"'))
45 32 quoted = !quoted;
46
6/6
✓ Branch 23 → 24 taken 1124 times.
✓ Branch 23 → 30 taken 105 times.
✓ Branch 28 → 29 taken 35 times.
✓ Branch 28 → 30 taken 1089 times.
✓ Branch 31 → 32 taken 35 times.
✓ Branch 31 → 34 taken 1194 times.
1229 else if (!quoted && QStringView(text).mid(index).startsWith(arrow)) {
47 35 *length = int(arrow.size());
48 35 return index;
49 }
50 }
51 152 }
52 3 return -1;
53 }
54
55 43 bool hasWildcard(const QString &value) {
56
5/6
✓ Branch 4 → 5 taken 43 times.
✓ Branch 5 → 6 taken 42 times.
✓ Branch 5 → 10 taken 1 time.
✓ Branch 8 → 9 taken 42 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 42 times.
43 return value.contains(QLatin1Char('*')) || value.contains(QLatin1Char('?'));
57 }
58
59 43 QVariantMap textCondition(const QString &field, const QString &value, bool negated) {
60 43 QString op = QStringLiteral("contains");
61
3/3
✓ Branch 6 → 7 taken 43 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 15 taken 42 times.
43 if (hasWildcard(value))
62 1 op = QStringLiteral("matches");
63
7/7
✓ Branch 17 → 18 taken 16 times.
✓ Branch 17 → 21 taken 26 times.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 30 taken 15 times.
✓ Branch 23 → 24 taken 27 times.
✓ Branch 24 → 25 taken 23 times.
✓ Branch 24 → 30 taken 4 times.
65 else if ((field == QLatin1String("from") || field == QLatin1String("toOrCc")) && value.contains(QLatin1Char('@')) &&
64
5/5
✓ Branch 27 → 28 taken 23 times.
✓ Branch 28 → 29 taken 13 times.
✓ Branch 28 → 30 taken 10 times.
✓ Branch 31 → 32 taken 13 times.
✓ Branch 31 → 39 taken 29 times.
65 !value.startsWith(QLatin1Char('@')))
65 13 op = QStringLiteral("is");
66 129 return {{QStringLiteral("field"), field}, {QStringLiteral("op"), op},
67
4/8
✓ Branch 59 → 60 taken 172 times.
✓ Branch 59 → 61 taken 43 times.
✓ Branch 65 → 66 taken 43 times.
✓ Branch 65 → 67 taken 43 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 97 not taken.
387 {QStringLiteral("values"), QStringList{value}}, {QStringLiteral("negated"), negated}};
68 258 }
69
70 2 bool parseSize(const QString &spec, quint64 *bytes) {
71
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 15 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
3 static const QRegularExpression pattern(QStringLiteral("^(\\d+)\\s*([kKmMgG]?)[bB]?$"));
72
1/1
✓ Branch 16 → 17 taken 2 times.
2 const QRegularExpressionMatch match = pattern.match(spec);
73
3/3
✓ Branch 17 → 18 taken 2 times.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 1 time.
2 if (!match.hasMatch())
74 1 return false;
75
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
1 quint64 value = match.captured(1).toULongLong();
76
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
1 const QString unit = match.captured(2).toLower();
77
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 1 time.
1 if (unit == QLatin1String("k"))
78 ✗ value *= 1024;
79
1/2
✓ Branch 32 → 33 taken 1 time.
✗ Branch 32 → 34 not taken.
1 else if (unit == QLatin1String("m"))
80 1 value *= 1024 * 1024;
81 ✗ else if (unit == QLatin1String("g"))
82 ✗ value *= 1024ULL * 1024 * 1024;
83 1 *bytes = value;
84 1 return value > 0;
85 2 }
86
87 // One condition token; false with `error` set when it is not understood.
88 46 bool conditionFor(const QString &token, QVariantMap *condition, QString *error) {
89 46 bool negated = false;
90 46 QString word = token;
91
7/8
✓ Branch 5 → 6 taken 46 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 12 taken 45 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 18 taken 45 times.
46 if (word.startsWith(QLatin1Char('-')) && word.contains(QLatin1Char(':'))) {
92 1 negated = true;
93
1/1
✓ Branch 14 → 15 taken 1 time.
1 word = word.mid(1);
94 }
95
1/1
✓ Branch 19 → 20 taken 46 times.
46 if (word.startsWith(QLatin1String("size>"), Qt::CaseInsensitive) ||
96
2/3
✓ Branch 22 → 23 taken 45 times.
✓ Branch 23 → 24 taken 45 times.
✗ Branch 23 → 34 not taken.
45 word.startsWith(QLatin1String("size<"), Qt::CaseInsensitive) ||
97
9/13
✓ Branch 20 → 21 taken 45 times.
✓ Branch 20 → 34 taken 1 time.
✓ Branch 27 → 28 taken 45 times.
✓ Branch 28 → 29 taken 44 times.
✓ Branch 28 → 34 taken 1 time.
✓ Branch 42 → 43 taken 45 times.
✓ Branch 42 → 44 taken 1 time.
✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 115 taken 44 times.
✗ Branch 281 → 282 not taken.
✗ Branch 281 → 283 not taken.
✗ Branch 285 → 286 not taken.
✗ Branch 285 → 287 not taken.
227 word.startsWith(QStringLiteral("größe>"), Qt::CaseInsensitive) ||
98
8/13
✓ Branch 32 → 33 taken 44 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 44 times.
✓ Branch 36 → 37 taken 44 times.
✓ Branch 36 → 38 taken 2 times.
✓ Branch 38 → 39 taken 44 times.
✓ Branch 38 → 40 taken 2 times.
✓ Branch 40 → 41 taken 45 times.
✓ Branch 40 → 42 taken 1 time.
✗ Branch 273 → 274 not taken.
✗ Branch 273 → 275 not taken.
✗ Branch 277 → 278 not taken.
✗ Branch 277 → 279 not taken.
90 word.startsWith(QStringLiteral("größe<"), Qt::CaseInsensitive)) {
99
2/2
✓ Branch 49 → 50 taken 2 times.
✓ Branch 50 → 51 taken 2 times.
2 const int at = word.indexOf(QRegularExpression(QStringLiteral("[<>]")));
100 2 quint64 bytes = 0;
101
4/4
✓ Branch 54 → 55 taken 2 times.
✓ Branch 55 → 56 taken 2 times.
✓ Branch 57 → 58 taken 1 time.
✓ Branch 57 → 62 taken 1 time.
2 if (!parseSize(word.mid(at + 1), &bytes)) {
102
1/1
✓ Branch 58 → 59 taken 1 time.
1 *error = QCoreApplication::translate("RuleConsoleInput", "Write sizes like size>1M or size<100K.");
103 1 return false;
104 }
105 3 *condition = {{QStringLiteral("field"), QStringLiteral("size")},
106
3/10
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 80 not taken.
✗ Branch 103 → 104 not taken.
✓ Branch 103 → 105 taken 1 time.
✓ Branch 105 → 106 taken 1 time.
✗ Branch 105 → 107 not taken.
✗ Branch 323 → 324 not taken.
✗ Branch 323 → 325 not taken.
✗ Branch 327 → 328 not taken.
✗ Branch 327 → 329 not taken.
3 {QStringLiteral("op"), word.at(at) == QLatin1Char('>') ? QStringLiteral("over") : QStringLiteral("under")},
107
2/4
✓ Branch 96 → 97 taken 4 times.
✓ Branch 96 → 98 taken 1 time.
✗ Branch 306 → 307 not taken.
✗ Branch 306 → 308 not taken.
7 {QStringLiteral("size"), bytes}, {QStringLiteral("negated"), negated}};
108 1 return true;
109 }
110 44 const int colon = word.indexOf(QLatin1Char(':'));
111
1/2
✗ Branch 118 → 119 not taken.
✓ Branch 118 → 123 taken 44 times.
44 if (colon <= 0) {
112 ✗ *error = QCoreApplication::translate("RuleConsoleInput", "Start each condition with from:, to:, subject:, list:, header:, body: or size.");
113 ✗ return false;
114 }
115
2/2
✓ Branch 123 → 124 taken 44 times.
✓ Branch 124 → 125 taken 44 times.
44 const QString prefix = word.left(colon).toLower();
116
1/1
✓ Branch 126 → 127 taken 44 times.
44 const QString value = word.mid(colon + 1);
117
1/2
✗ Branch 128 → 129 not taken.
✓ Branch 128 → 133 taken 44 times.
44 if (value.isEmpty()) {
118 ✗ *error = QCoreApplication::translate("RuleConsoleInput", "A condition has no value.");
119 ✗ return false;
120 }
121
6/6
✓ Branch 135 → 136 taken 19 times.
✓ Branch 135 → 139 taken 25 times.
✓ Branch 138 → 139 taken 1 time.
✓ Branch 138 → 140 taken 18 times.
✓ Branch 141 → 142 taken 26 times.
✓ Branch 141 → 151 taken 18 times.
44 if (prefix == QLatin1String("from") || prefix == QLatin1String("von"))
122
1/1
✓ Branch 145 → 146 taken 26 times.
52 *condition = textCondition(QStringLiteral("from"), value, negated);
123
5/6
✓ Branch 153 → 154 taken 17 times.
✓ Branch 153 → 157 taken 1 time.
✗ Branch 156 → 157 not taken.
✓ Branch 156 → 158 taken 17 times.
✓ Branch 159 → 160 taken 1 time.
✓ Branch 159 → 169 taken 17 times.
18 else if (prefix == QLatin1String("to") || prefix == QLatin1String("an"))
124
1/1
✓ Branch 163 → 164 taken 1 time.
2 *condition = textCondition(QStringLiteral("toOrCc"), value, negated);
125
6/6
✓ Branch 171 → 172 taken 4 times.
✓ Branch 171 → 175 taken 13 times.
✓ Branch 174 → 175 taken 1 time.
✓ Branch 174 → 176 taken 3 times.
✓ Branch 177 → 178 taken 14 times.
✓ Branch 177 → 187 taken 3 times.
17 else if (prefix == QLatin1String("subject") || prefix == QLatin1String("betreff"))
126
1/1
✓ Branch 181 → 182 taken 14 times.
28 *condition = textCondition(QStringLiteral("subject"), value, negated);
127
5/6
✓ Branch 189 → 190 taken 2 times.
✓ Branch 189 → 193 taken 1 time.
✗ Branch 192 → 193 not taken.
✓ Branch 192 → 194 taken 2 times.
✓ Branch 195 → 196 taken 1 time.
✓ Branch 195 → 205 taken 2 times.
3 else if (prefix == QLatin1String("list") || prefix == QLatin1String("liste"))
128
1/1
✓ Branch 199 → 200 taken 1 time.
2 *condition = textCondition(QStringLiteral("list"), value, negated);
129
1/2
✗ Branch 207 → 208 not taken.
✓ Branch 207 → 217 taken 2 times.
2 else if (prefix == QLatin1String("body"))
130 ✗ *condition = textCondition(QStringLiteral("body"), value, negated);
131
2/2
✓ Branch 219 → 220 taken 1 time.
✓ Branch 219 → 258 taken 1 time.
2 else if (prefix == QLatin1String("header")) {
132 1 const int equals = value.indexOf(QLatin1Char('='));
133
3/6
✓ Branch 223 → 224 taken 1 time.
✗ Branch 223 → 226 not taken.
✗ Branch 225 → 226 not taken.
✓ Branch 225 → 227 taken 1 time.
✗ Branch 228 → 229 not taken.
✓ Branch 228 → 233 taken 1 time.
1 if (equals <= 0 || equals == value.size() - 1) {
134 ✗ *error = QCoreApplication::translate("RuleConsoleInput", "Write header conditions like header:X-Tag=value.");
135 ✗ return false;
136 }
137
2/2
✓ Branch 233 → 234 taken 1 time.
✓ Branch 237 → 238 taken 1 time.
2 *condition = textCondition(QStringLiteral("header"), value.mid(equals + 1), negated);
138
3/5
✓ Branch 249 → 250 taken 1 time.
✓ Branch 255 → 256 taken 1 time.
✓ Branch 255 → 257 taken 1 time.
✗ Branch 423 → 424 not taken.
✗ Branch 423 → 425 not taken.
3 condition->insert(QStringLiteral("headers"), QStringList{value.left(equals)});
139 } else {
140
1/1
✓ Branch 258 → 259 taken 1 time.
1 *error = QCoreApplication::translate("RuleConsoleInput", "Unknown condition. Use from:, to:, subject:, list:, header:, body: or size.");
141 1 return false;
142 }
143 43 return true;
144 52 }
145
146 33 QVariantMap action(const QString &kind, const QString &target = {}) {
147
2/4
✓ Branch 8 → 9 taken 33 times.
✓ Branch 8 → 10 taken 33 times.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
99 QVariantMap map{{QStringLiteral("kind"), kind}};
148
2/2
✓ Branch 13 → 14 taken 21 times.
✓ Branch 13 → 44 taken 12 times.
33 if (!target.isEmpty())
149
7/11
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 27 taken 20 times.
✓ Branch 30 → 31 taken 21 times.
✓ Branch 32 → 33 taken 20 times.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 20 times.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
42 map.insert(kind == QLatin1String("label") ? QStringLiteral("flags") : QStringLiteral("target"),
150
8/14
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 20 taken 20 times.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 20 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 43 taken 20 times.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 43 taken 1 time.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 84 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
44 kind == QLatin1String("label") ? QVariant(QStringList{target}) : QVariant(target));
151 33 return map;
152 67 }
153
154 } // namespace
155
156 10 QString seedFromSender(const QString &address) {
157
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 10 times.
10 if (address.isEmpty())
158 ✗ return {};
159 // S106-325: a sender with an arrow or a space stays one quoted value.
160
2/3
✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 10 times.
✗ Branch 8 → 29 not taken.
20 const bool quote = !address.contains(QLatin1Char('"')) &&
161
9/20
✓ Branch 12 → 13 taken 10 times.
✓ Branch 13 → 14 taken 9 times.
✓ Branch 13 → 28 taken 1 time.
✓ Branch 17 → 18 taken 9 times.
✓ Branch 18 → 19 taken 9 times.
✗ Branch 18 → 28 not taken.
✓ Branch 36 → 37 taken 9 times.
✓ Branch 36 → 38 taken 1 time.
✓ Branch 38 → 39 taken 10 times.
✗ Branch 38 → 40 not taken.
✓ Branch 40 → 41 taken 10 times.
✗ Branch 40 → 42 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
✗ Branch 89 → 90 not taken.
✗ Branch 89 → 91 not taken.
29 (address.contains(QStringLiteral("→")) || address.contains(QStringLiteral("->")) ||
162
6/11
✓ Branch 22 → 23 taken 9 times.
✓ Branch 23 → 24 taken 9 times.
✗ Branch 23 → 28 not taken.
✓ Branch 32 → 33 taken 9 times.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 34 → 35 taken 9 times.
✓ Branch 34 → 36 taken 1 time.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
19 address.contains(QStringLiteral("=>")) ||
163
4/5
✓ Branch 26 → 27 taken 9 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 9 times.
✓ Branch 30 → 31 taken 9 times.
✓ Branch 30 → 32 taken 1 time.
160 std::any_of(address.cbegin(), address.cend(), [](QChar c) { return c.isSpace(); }));
164
6/8
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 49 taken 9 times.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 48 → 50 taken 1 time.
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 52 taken 9 times.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 97 not taken.
10 const QString value = quote ? QLatin1Char('"') + address + QLatin1Char('"') : address;
165
2/2
✓ Branch 58 → 59 taken 10 times.
✓ Branch 59 → 60 taken 10 times.
20 return QStringLiteral("from:") + value + QStringLiteral(" → ");
166 10 }
167
168 34 Result parse(const QString &text, bool stopByDefault) {
169 34 Result result;
170 34 int arrowLength = 0;
171
1/1
✓ Branch 2 → 3 taken 34 times.
34 const int arrow = arrowIndex(text, &arrowLength);
172
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 8 taken 32 times.
34 if (arrow < 0) {
173
1/1
✓ Branch 4 → 5 taken 2 times.
2 result.error = QCoreApplication::translate("RuleConsoleInput", "Add → and what should happen, e.g. → Dev/CI read.");
174 2 return result;
175 }
176
2/2
✓ Branch 8 → 9 taken 32 times.
✓ Branch 9 → 10 taken 32 times.
32 const QStringList conditionTokens = tokenize(text.left(arrow));
177
2/2
✓ Branch 11 → 12 taken 32 times.
✓ Branch 12 → 13 taken 32 times.
32 const QStringList actionTokens = tokenize(text.mid(arrow + arrowLength));
178 32 QVariantList conditions;
179 32 bool sawAnd = false;
180 32 bool sawOr = false;
181
2/2
✓ Branch 57 → 16 taken 49 times.
✓ Branch 57 → 58 taken 30 times.
79 for (const QString &token : conditionTokens) {
182
1/1
✓ Branch 17 → 18 taken 49 times.
49 const QString lower = token.toLower();
183
6/6
✓ Branch 20 → 21 taken 48 times.
✓ Branch 20 → 24 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 47 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 47 times.
49 if (lower == QLatin1String("or") || lower == QLatin1String("oder")) {
184 2 sawOr = true;
185 2 continue;
186 }
187
5/6
✓ Branch 30 → 31 taken 46 times.
✓ Branch 30 → 34 taken 1 time.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 46 times.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 38 taken 46 times.
47 if (lower == QLatin1String("and") || lower == QLatin1String("und")) {
188 1 sawAnd = true;
189 1 continue;
190 }
191 46 QVariantMap condition;
192
3/3
✓ Branch 38 → 39 taken 46 times.
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 41 taken 44 times.
46 if (!conditionFor(token, &condition, &result.error))
193 2 return result;
194
1/1
✓ Branch 42 → 43 taken 44 times.
44 conditions.append(condition);
195 51 }
196
4/4
✓ Branch 58 → 59 taken 2 times.
✓ Branch 58 → 64 taken 28 times.
✓ Branch 59 → 60 taken 1 time.
✓ Branch 59 → 64 taken 1 time.
30 if (sawOr && sawAnd) {
197
1/1
✓ Branch 60 → 61 taken 1 time.
1 result.error = QCoreApplication::translate("RuleConsoleInput", "Use the editor for mixed and/or conditions.");
198 1 return result;
199 }
200
2/2
✓ Branch 65 → 66 taken 1 time.
✓ Branch 65 → 70 taken 28 times.
29 if (conditions.isEmpty()) {
201
1/1
✓ Branch 66 → 67 taken 1 time.
1 result.error = QCoreApplication::translate("RuleConsoleInput", "Add at least one condition before →.");
202 1 return result;
203 }
204 28 QVariantList actions;
205 28 QString name;
206 28 bool stop = stopByDefault;
207
2/2
✓ Branch 297 → 72 taken 39 times.
✓ Branch 297 → 298 taken 27 times.
66 for (const QString &token : actionTokens) {
208
1/1
✓ Branch 73 → 74 taken 39 times.
39 const QString lower = token.toLower();
209 39 const int colon = token.indexOf(QLatin1Char(':'));
210
3/3
✓ Branch 77 → 78 taken 8 times.
✓ Branch 77 → 79 taken 31 times.
✓ Branch 78 → 80 taken 8 times.
39 const QString prefix = colon > 0 ? lower.left(colon) : QString();
211
3/3
✓ Branch 80 → 81 taken 8 times.
✓ Branch 80 → 82 taken 31 times.
✓ Branch 81 → 83 taken 8 times.
39 const QString value = colon > 0 ? token.mid(colon + 1) : QString();
212
5/6
✓ Branch 85 → 86 taken 29 times.
✓ Branch 85 → 89 taken 10 times.
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 90 taken 29 times.
✓ Branch 91 → 92 taken 10 times.
✓ Branch 91 → 105 taken 29 times.
39 if (lower == QLatin1String("read") || lower == QLatin1String("gelesen"))
213
2/2
✓ Branch 96 → 97 taken 10 times.
✓ Branch 98 → 99 taken 10 times.
20 actions.append(action(QStringLiteral("read")));
214
5/6
✓ Branch 107 → 108 taken 29 times.
✗ Branch 107 → 111 not taken.
✓ Branch 110 → 111 taken 1 time.
✓ Branch 110 → 112 taken 28 times.
✓ Branch 113 → 114 taken 1 time.
✓ Branch 113 → 127 taken 28 times.
29 else if (lower == QLatin1String("important") || lower == QLatin1String("wichtig"))
215
2/2
✓ Branch 118 → 119 taken 1 time.
✓ Branch 120 → 121 taken 1 time.
2 actions.append(action(QStringLiteral("important")));
216
1/2
✗ Branch 129 → 130 not taken.
✓ Branch 129 → 143 taken 28 times.
28 else if (lower == QLatin1String("junk"))
217 ✗ actions.append(action(QStringLiteral("junk")));
218
5/6
✓ Branch 145 → 146 taken 27 times.
✓ Branch 145 → 149 taken 1 time.
✗ Branch 148 → 149 not taken.
✓ Branch 148 → 150 taken 27 times.
✓ Branch 151 → 152 taken 1 time.
✓ Branch 151 → 165 taken 27 times.
28 else if (lower == QLatin1String("trash") || lower == QLatin1String("papierkorb"))
219
2/2
✓ Branch 156 → 157 taken 1 time.
✓ Branch 158 → 159 taken 1 time.
2 actions.append(action(QStringLiteral("trash")));
220
5/6
✓ Branch 167 → 168 taken 26 times.
✓ Branch 167 → 171 taken 1 time.
✗ Branch 170 → 171 not taken.
✓ Branch 170 → 172 taken 26 times.
✓ Branch 173 → 174 taken 1 time.
✓ Branch 173 → 175 taken 26 times.
27 else if (lower == QLatin1String("stop") || lower == QLatin1String("stopp")) {
221 1 stop = true;
222 1 result.explicitStop = true;
223
5/6
✓ Branch 177 → 178 taken 23 times.
✓ Branch 177 → 181 taken 3 times.
✗ Branch 180 → 181 not taken.
✓ Branch 180 → 182 taken 23 times.
✓ Branch 183 → 184 taken 3 times.
✓ Branch 183 → 185 taken 23 times.
26 } else if (lower == QLatin1String("+now") || lower == QLatin1String("+jetzt"))
224 3 result.applyNow = true;
225
5/6
✓ Branch 187 → 188 taken 23 times.
✗ Branch 187 → 191 not taken.
✓ Branch 190 → 191 taken 1 time.
✓ Branch 190 → 192 taken 22 times.
✓ Branch 193 → 194 taken 1 time.
✓ Branch 193 → 205 taken 22 times.
23 else if (prefix == QLatin1String("copy") || prefix == QLatin1String("kopie"))
226
2/2
✓ Branch 197 → 198 taken 1 time.
✓ Branch 199 → 200 taken 1 time.
1 actions.append(action(QStringLiteral("copy"), value));
227
2/2
✓ Branch 207 → 208 taken 1 time.
✓ Branch 207 → 235 taken 21 times.
22 else if (prefix == QLatin1String("label")) {
228 1 bool numeric = false;
229
1/1
✓ Branch 208 → 209 taken 1 time.
1 const int number = value.toInt(&numeric);
230
2/2
✓ Branch 222 → 223 taken 1 time.
✓ Branch 224 → 225 taken 1 time.
1 actions.append(action(QStringLiteral("label"),
231
6/15
✓ Branch 209 → 210 taken 1 time.
✗ Branch 209 → 218 not taken.
✓ Branch 210 → 211 taken 1 time.
✗ Branch 210 → 218 not taken.
✓ Branch 211 → 212 taken 1 time.
✗ Branch 211 → 218 not taken.
✓ Branch 216 → 217 taken 1 time.
✓ Branch 230 → 231 taken 1 time.
✗ Branch 230 → 232 not taken.
✓ Branch 232 → 233 taken 1 time.
✗ Branch 232 → 234 not taken.
✗ Branch 463 → 464 not taken.
✗ Branch 463 → 465 not taken.
✗ Branch 467 → 468 not taken.
✗ Branch 467 → 469 not taken.
3 numeric && number >= 1 && number <= 5 ? QStringLiteral("$label%1").arg(number) : value));
232
5/6
✓ Branch 237 → 238 taken 16 times.
✓ Branch 237 → 241 taken 5 times.
✗ Branch 240 → 241 not taken.
✓ Branch 240 → 242 taken 16 times.
✓ Branch 243 → 244 taken 5 times.
✓ Branch 243 → 258 taken 16 times.
21 } else if (prefix == QLatin1String("forward") || prefix == QLatin1String("weiter")) {
233
2/2
✓ Branch 247 → 248 taken 5 times.
✓ Branch 249 → 250 taken 5 times.
5 actions.append(action(QStringLiteral("forwardCopy"), value));
234
2/2
✓ Branch 254 → 255 taken 5 times.
✓ Branch 255 → 256 taken 5 times.
5 result.forwardTargets.append(value.toLower());
235
2/2
✓ Branch 260 → 261 taken 1 time.
✓ Branch 260 → 262 taken 15 times.
16 } else if (prefix == QLatin1String("name"))
236 1 name = value;
237
1/2
✓ Branch 263 → 264 taken 15 times.
✗ Branch 263 → 282 not taken.
15 else if (!token.isEmpty()) {
238
2/2
✓ Branch 265 → 266 taken 1 time.
✓ Branch 265 → 270 taken 14 times.
15 if (!result.folder.isEmpty()) {
239
1/1
✓ Branch 266 → 267 taken 1 time.
1 result.error = QCoreApplication::translate("RuleConsoleInput", "Only one target folder per rule; use copy:Folder for more.");
240 1 return result;
241 }
242 14 result.folder = token;
243
2/2
✓ Branch 274 → 275 taken 14 times.
✓ Branch 276 → 277 taken 14 times.
14 actions.prepend(action(QStringLiteral("move"), token));
244 }
245 41 }
246
2/2
✓ Branch 299 → 300 taken 8 times.
✓ Branch 299 → 304 taken 19 times.
27 if (actions.isEmpty()) {
247
1/1
✓ Branch 300 → 301 taken 8 times.
8 result.error = QCoreApplication::translate("RuleConsoleInput", "Add what should happen after →, e.g. a folder or read.");
248 8 return result;
249 }
250 38 result.draft = {{QStringLiteral("name"), name},
251 38 {QStringLiteral("enabled"), true},
252 38 {QStringLiteral("matchAll"), !sawOr},
253 38 {QStringLiteral("stop"), stop},
254 38 {QStringLiteral("blockIndex"), -1},
255 38 {QStringLiteral("conditions"), conditions},
256
2/4
✓ Branch 336 → 337 taken 133 times.
✓ Branch 336 → 338 taken 19 times.
✗ Branch 513 → 514 not taken.
✗ Branch 513 → 515 not taken.
171 {QStringLiteral("actions"), actions}};
257 19 result.ok = true;
258 19 return result;
259 203 }
260
261 4 QString completeFolder(const QString &text, const QStringList &folders) {
262 4 int arrowLength = 0;
263
1/1
✓ Branch 2 → 3 taken 4 times.
4 const int arrow = arrowIndex(text, &arrowLength);
264
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 3 times.
4 if (arrow < 0)
265 1 return {};
266 3 const int start = text.lastIndexOf(QLatin1Char(' ')) + 1;
267
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3 times.
3 if (start <= arrow)
268 ✗ return {};
269
1/1
✓ Branch 10 → 11 taken 3 times.
3 const QString partial = text.mid(start);
270 3 QString prefix;
271 3 QString stem = partial;
272 3 const int colon = partial.indexOf(QLatin1Char(':'));
273
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 23 taken 2 times.
3 if (colon > 0) {
274
1/1
✓ Branch 16 → 17 taken 1 time.
1 prefix = partial.left(colon + 1);
275
1/1
✓ Branch 19 → 20 taken 1 time.
1 stem = partial.mid(colon + 1);
276 }
277
8/18
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 34 taken 2 times.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 34 taken 1 time.
✗ Branch 30 → 31 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 3 times.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 2 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 3 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
3 if (!prefix.isEmpty() && prefix.toLower() != QLatin1String("copy:") && prefix.toLower() != QLatin1String("kopie:"))
278 ✗ return {};
279
1/2
✓ Branch 74 → 43 taken 10 times.
✗ Branch 74 → 75 not taken.
10 for (const QString &folder : folders)
280
6/7
✓ Branch 44 → 45 taken 10 times.
✓ Branch 45 → 46 taken 3 times.
✓ Branch 45 → 49 taken 7 times.
✓ Branch 47 → 48 taken 3 times.
✗ Branch 47 → 49 not taken.
✓ Branch 50 → 51 taken 3 times.
✓ Branch 50 → 72 taken 7 times.
10 if (folder.startsWith(stem, Qt::CaseInsensitive) && folder.compare(stem, Qt::CaseInsensitive) != 0) {
281
7/9
✓ Branch 53 → 54 taken 3 times.
✓ Branch 54 → 55 taken 1 time.
✓ Branch 54 → 61 taken 2 times.
✓ Branch 59 → 60 taken 1 time.
✓ Branch 60 → 62 taken 1 time.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 64 taken 2 times.
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 95 not taken.
3 const QString value = folder.contains(QLatin1Char(' ')) ? QLatin1Char('"') + folder + QLatin1Char('"') : folder;
282
3/3
✓ Branch 64 → 65 taken 3 times.
✓ Branch 65 → 66 taken 3 times.
✓ Branch 66 → 67 taken 3 times.
3 return text.left(start) + prefix + value;
283 3 }
284 ✗ return {};
285 3 }
286
287 } // namespace RuleConsoleInput
288