• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 10119318781

27 Jul 2024 12:30AM UTC coverage: 84.363% (+0.08%) from 84.286%
10119318781

push

github

web-flow
Make `task news` nag configurable and deterministic (#3567)

This patch fixes #3497.

8 of 10 new or added lines in 2 files covered. (80.0%)

9 existing lines in 4 files now uncovered.

19347 of 22933 relevant lines covered (84.36%)

20359.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.61
/src/rules.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included
13
// in all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
// SOFTWARE.
22
//
23
// https://www.opensource.org/licenses/mit-license.php
24
//
25
////////////////////////////////////////////////////////////////////////////////
26

27
#include <cmake.h>
28
#include <stdlib.h>
29
#include <Context.h>
30
#include <Datetime.h>
31
#include <shared.h>
32
#include <main.h>
33

34
static std::map <std::string, Color> gsColor;
35
static std::vector <std::string> gsPrecedence;
36
static Datetime now;
37

38
////////////////////////////////////////////////////////////////////////////////
39
void initializeColorRules ()
4,389✔
40
{
41
  // If color is not enable/supported, short circuit.
42
  if (! Context::getContext ().color ())
4,389✔
43
    return;
4,206✔
44

45
  try
46
  {
47
    gsColor.clear ();
183✔
48
    gsPrecedence.clear ();
183✔
49

50
    // Load all the configuration values, filter to only the ones that begin with
51
    // "color.", then store name/value in gsColor, and name in rules.
52
    std::vector <std::string> rules;
183✔
53
    for (const auto& v : Context::getContext ().config)
45,432✔
54
    {
55
      if (! v.first.compare (0, 6, "color.", 6))
45,249✔
56
      {
57
        Color c (v.second);
8,876✔
58
        gsColor[v.first] = c;
8,876✔
59

60
        rules.push_back (v.first);
8,876✔
61
      }
62
    }
63

64
    // Load the rule.precedence.color list, split it, then autocomplete against
65
    // the 'rules' vector loaded above.
66
    std::vector <std::string> results;
183✔
67
    auto precedence = split (Context::getContext ().config.get ("rule.precedence.color"), ',');
366✔
68

69
    for (const auto& p : precedence)
2,928✔
70
    {
71
      // Add the leading "color." string.
72
      std::string rule = "color." + p;
2,745✔
73
      autoComplete (rule, rules, results, 3); // Hard-coded 3.
2,745✔
74

75
      for (auto& r : results)
6,126✔
76
        gsPrecedence.push_back (r);
3,381✔
77
    }
2,745✔
78
  }
183✔
79

80
  catch (const std::string& e)
×
81
  {
82
    Context::getContext ().error (e);
×
83
  }
84
}
85

86
////////////////////////////////////////////////////////////////////////////////
87
static void applyColor (const Color& base, Color& c, bool merge)
42✔
88
{
89
  if (merge)
42✔
90
    c.blend (base);
40✔
91
  else
92
    c = base;
2✔
93
}
42✔
94

95
////////////////////////////////////////////////////////////////////////////////
96
static void colorizeBlocked (Task& task, const Color& base, Color& c, bool merge)
38✔
97
{
98
  if (task.is_blocked)
38✔
99
    applyColor (base, c, merge);
1✔
100
}
38✔
101

102
////////////////////////////////////////////////////////////////////////////////
103
static void colorizeBlocking (Task& task, const Color& base, Color& c, bool merge)
38✔
104
{
105
  if (task.is_blocking)
38✔
106
    applyColor (base, c, merge);
1✔
107
}
38✔
108

109
////////////////////////////////////////////////////////////////////////////////
110
static void colorizeTagged (Task& task, const Color& base, Color& c, bool merge)
14✔
111
{
112
  if (task.getTagCount ())
14✔
113
    applyColor (base, c, merge);
5✔
114
}
14✔
115

116
////////////////////////////////////////////////////////////////////////////////
117
static void colorizeActive (Task& task, const Color& base, Color& c, bool merge)
38✔
118
{
119
  // TODO: Not consistent with the implementation of the +ACTIVE virtual tag
120
  if (task.has ("start") &&
77✔
121
      !task.has ("end"))
39✔
122
    applyColor (base, c, merge);
1✔
123
}
38✔
124

125
////////////////////////////////////////////////////////////////////////////////
126
static void colorizeScheduled (Task& task, const Color& base, Color& c, bool merge)
38✔
127
{
128
  // TODO: Not consistent with the implementation of the +SCHEDULED virtual tag
129
  if (task.has ("scheduled") &&
76✔
130
      Datetime (task.get_date ("scheduled")) <= now)
38✔
131
    applyColor (base, c, merge);
×
132
}
38✔
133

134
////////////////////////////////////////////////////////////////////////////////
135
static void colorizeUntil (Task& task, const Color& base, Color& c, bool merge)
×
136
{
137
  if (task.has ("until"))
×
138
    applyColor (base, c, merge);
×
139
}
140

141
////////////////////////////////////////////////////////////////////////////////
142
static void colorizeTag (Task& task, const std::string& rule, const Color& base, Color& c, bool merge)
72✔
143
{
144
  if (task.hasTag (rule.substr (10)))
72✔
145
    applyColor (base, c, merge);
12✔
146
}
72✔
147

148
////////////////////////////////////////////////////////////////////////////////
149
static void colorizeProject (Task& task, const std::string& rule, const Color& base, Color& c, bool merge)
24✔
150
{
151
  // Observe the case sensitivity setting.
152
  bool sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive");
24✔
153

154
  auto project = task.get ("project");
48✔
155
  auto rule_trunc = rule.substr (14);
24✔
156

157
  // Match project names leftmost.
158
  if (rule_trunc.length () <= project.length ())
24✔
159
    if (compare (rule_trunc, project.substr (0, rule_trunc.length ()), sensitive))
1✔
160
      applyColor (base, c, merge);
1✔
161
}
24✔
162

163
////////////////////////////////////////////////////////////////////////////////
164
static void colorizeProjectNone (Task& task, const Color& base, Color& c, bool merge)
1✔
165
{
166
  if(!task.has ("project"))
1✔
167
    applyColor (base, c, merge);
1✔
168
}
1✔
169

170
////////////////////////////////////////////////////////////////////////////////
171
static void colorizeTagNone (Task& task, const Color& base, Color& c, bool merge)
1✔
172
{
173
  if (task.getTagCount () == 0)
1✔
174
    applyColor (base, c, merge);
1✔
175
}
1✔
176

177
////////////////////////////////////////////////////////////////////////////////
178
static void colorizeKeyword (Task& task, const std::string& rule, const Color& base, Color& c, bool merge)
25✔
179
{
180
  // Observe the case sensitivity setting.
181
  auto sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive");
25✔
182

183
  // The easiest thing to check is the description, because it is just one
184
  // attribute.
185
  if (find (task.get ("description"), rule.substr (14), sensitive) != std::string::npos)
25✔
186
    applyColor (base, c, merge);
2✔
187

188
  // Failing the description check, look at all annotations, returning on the
189
  // first match.
190
  else
191
  {
192
    for (const auto& att : task.getAnnotations ())
23✔
193
    {
194
      if (find (att.second, rule.substr (14), sensitive) != std::string::npos)
×
195
      {
196
        applyColor (base, c, merge);
×
197
        return;
×
198
      }
199
    }
23✔
200
  }
201
}
202

203
////////////////////////////////////////////////////////////////////////////////
204
static void colorizeUDA (Task& task, const std::string& rule, const Color& base, Color& c, bool merge)
161✔
205
{
206
  // Is the rule color.uda.name.value or color.uda.name?
207
  auto pos = rule.find ('.', 10);
161✔
208
  if (pos == std::string::npos)
161✔
209
  {
210
    if (task.has (rule.substr (10)))
23✔
211
      applyColor (base, c, merge);
1✔
212
  }
213
  else
214
  {
215
    auto uda = rule.substr (10, pos - 10);
138✔
216
    auto val = rule.substr (pos + 1);
138✔
217
    if ((val == "none" && ! task.has (uda)) ||
276✔
218
        task.get (uda) == val)
276✔
219
      applyColor (base, c, merge);
4✔
220
  }
138✔
221
}
161✔
222

223
////////////////////////////////////////////////////////////////////////////////
224
static void colorizeDue (Task& task, const Color& base, Color& c, bool merge)
38✔
225
{
226
  if (task.is_due ())
38✔
227
    applyColor (base, c, merge);
7✔
228
}
38✔
229

230
////////////////////////////////////////////////////////////////////////////////
231
static void colorizeDueToday (Task& task, const Color& base, Color& c, bool merge)
38✔
232
{
233
  if (task.is_duetoday ())
38✔
UNCOV
234
    applyColor (base, c, merge);
×
235
}
38✔
236

237
////////////////////////////////////////////////////////////////////////////////
238
static void colorizeOverdue (Task& task, const Color& base, Color& c, bool merge)
38✔
239
{
240
  if (task.is_overdue ())
38✔
241
    applyColor (base, c, merge);
3✔
242
}
38✔
243

244
////////////////////////////////////////////////////////////////////////////////
245
static void colorizeRecurring (Task& task, const Color& base, Color& c, bool merge)
38✔
246
{
247
  if (task.has ("recur"))
38✔
248
    applyColor (base, c, merge);
2✔
249
}
38✔
250

251
////////////////////////////////////////////////////////////////////////////////
252
static void colorizeCompleted (Task& task, const Color& base, Color& c, bool merge)
×
253
{
254
  if (task.getStatus () == Task::completed)
×
255
    applyColor (base, c, merge);
×
256
}
257

258
////////////////////////////////////////////////////////////////////////////////
259
static void colorizeDeleted (Task& task, const Color& base, Color& c, bool merge)
×
260
{
261
  if (task.getStatus () == Task::deleted)
×
262
    applyColor (base, c, merge);
×
263
}
264

265
////////////////////////////////////////////////////////////////////////////////
266
void autoColorize (Task& task, Color& c)
1,373✔
267
{
268
  // The special tag 'nocolor' overrides all auto and specific colorization.
269
  if (! Context::getContext ().color () ||
1,424✔
270
      task.hasTag ("nocolor"))
1,424✔
271
  {
272
    c = Color ();
1,324✔
273
    return;
1,324✔
274
  }
275

276
  auto merge = Context::getContext ().config.getBoolean ("rule.color.merge");
49✔
277

278
  // Note: c already contains colors specifically assigned via command.
279
  // Note: These rules form a hierarchy - the last rule is King, hence the
280
  //       reverse iterator.
281

282
  for (auto r = gsPrecedence.rbegin (); r != gsPrecedence.rend (); ++r)
1,014✔
283
  {
284
    Color base = gsColor[*r];
965✔
285
    if (base.nontrivial ())
965✔
286
    {
287
           if (*r == "color.blocked")                      colorizeBlocked      (task, base, c, merge);
602✔
288
      else if (*r == "color.blocking")                     colorizeBlocking     (task, base, c, merge);
564✔
289
      else if (*r == "color.tagged")                       colorizeTagged       (task, base, c, merge);
526✔
290
      else if (*r == "color.active")                       colorizeActive       (task, base, c, merge);
512✔
291
      else if (*r == "color.scheduled")                    colorizeScheduled    (task, base, c, merge);
474✔
292
      else if (*r == "color.until")                        colorizeUntil        (task, base, c, merge);
436✔
293
      else if (*r == "color.project.none")                 colorizeProjectNone  (task, base, c, merge);
436✔
294
      else if (*r == "color.tag.none")                     colorizeTagNone      (task, base, c, merge);
435✔
295
      else if (*r == "color.due")                          colorizeDue          (task, base, c, merge);
434✔
296
      else if (*r == "color.due.today")                    colorizeDueToday     (task, base, c, merge);
396✔
297
      else if (*r == "color.overdue")                      colorizeOverdue      (task, base, c, merge);
358✔
298
      else if (*r == "color.recurring")                    colorizeRecurring    (task, base, c, merge);
320✔
299
      else if (*r == "color.completed")                    colorizeCompleted    (task, base, c, merge);
282✔
300
      else if (*r == "color.deleted")                      colorizeDeleted      (task, base, c, merge);
282✔
301

302
      // Wildcards
303
      else if (! r->compare (0, 10, "color.tag.", 10))     colorizeTag          (task, *r, base, c, merge);
282✔
304
      else if (! r->compare (0, 14, "color.project.", 14)) colorizeProject      (task, *r, base, c, merge);
210✔
305
      else if (! r->compare (0, 14, "color.keyword.", 14)) colorizeKeyword      (task, *r, base, c, merge);
186✔
306
      else if (! r->compare (0, 10, "color.uda.", 10))     colorizeUDA          (task, *r, base, c, merge);
161✔
307
    }
308
  }
309

310
}
311

312
////////////////////////////////////////////////////////////////////////////////
313
std::string colorizeHeader (const std::string& input)
364✔
314
{
315
  if (gsColor["color.header"].nontrivial ())
364✔
316
    return gsColor["color.header"].colorize (input);
480✔
317

318
  return input;
124✔
319
}
320

321
////////////////////////////////////////////////////////////////////////////////
322
std::string colorizeFootnote (const std::string& input)
72✔
323
{
324
  if (gsColor["color.footnote"].nontrivial ())
72✔
325
    return gsColor["color.footnote"].colorize (input);
112✔
326

327
  return input;
16✔
328
}
329

330
////////////////////////////////////////////////////////////////////////////////
331
std::string colorizeError (const std::string& input)
1✔
332
{
333
  if (gsColor["color.error"].nontrivial ())
1✔
334
    return gsColor["color.error"].colorize (input);
2✔
335

336
  return input;
×
337
}
338

339
////////////////////////////////////////////////////////////////////////////////
340
std::string colorizeDebug (const std::string& input)
8✔
341
{
342
  if (gsColor["color.debug"].nontrivial ())
8✔
343
    return gsColor["color.debug"].colorize (input);
16✔
344

345
  return input;
×
346
}
347

348
////////////////////////////////////////////////////////////////////////////////
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc