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

GothenburgBitFactory / taskwarrior / 10152339701

29 Jul 2024 09:45PM UTC coverage: 84.437% (+0.07%) from 84.372%
10152339701

push

github

web-flow
Merge pull request #3566 from felixschurk/add-clang-format

Add clang-format to enforce style guide

12359 of 13760 new or added lines in 147 files covered. (89.82%)

123 existing lines in 42 files now uncovered.

19070 of 22585 relevant lines covered (84.44%)

19724.02 hits per line

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

97.82
/src/commands/CmdHistory.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
// cmake.h include header must come first
29

30
#include <CmdHistory.h>
31
#include <Context.h>
32
#include <Datetime.h>
33
#include <Filter.h>
34
#include <Table.h>
35
#include <format.h>
36
#include <main.h>
37
#include <util.h>
38

39
#include <sstream>
40

41
#define STRING_CMD_HISTORY_YEAR "Year"
42
#define STRING_CMD_HISTORY_MONTH "Month"
43
#define STRING_CMD_HISTORY_DAY "Day"
44
#define STRING_CMD_HISTORY_ADDED "Added"
45
#define STRING_CMD_HISTORY_COMP "Completed"
46
#define STRING_CMD_HISTORY_DEL "Deleted"
47

48
////////////////////////////////////////////////////////////////////////////////
49
template <class HistoryStrategy>
50
CmdHistoryBase<HistoryStrategy>::CmdHistoryBase() {
70,224✔
51
  _keyword = HistoryStrategy::keyword;
70,224✔
52
  _usage = HistoryStrategy::usage;
70,224✔
53
  _description = HistoryStrategy::description;
70,224✔
54

55
  _read_only = true;
70,224✔
56
  _displays_id = false;
70,224✔
57
  _needs_gc = false;
70,224✔
58
  _uses_context = true;
70,224✔
59
  _accepts_filter = true;
70,224✔
60
  _accepts_modifications = false;
70,224✔
61
  _accepts_miscellaneous = false;
70,224✔
62
  _category = Command::Category::graphs;
70,224✔
63
}
70,224✔
64

65
////////////////////////////////////////////////////////////////////////////////
66
template <class HistoryStrategy>
67
void CmdHistoryBase<HistoryStrategy>::outputGraphical(std::string& output) {
16✔
68
  auto widthOfBar = Context::getContext().getWidth() - HistoryStrategy::labelWidth;
16✔
69

70
  // Now build the view.
71
  Table view;
16✔
72
  setHeaderUnderline(view);
16✔
73
  view.width(Context::getContext().getWidth());
16✔
74

75
  HistoryStrategy::setupTableDates(view);
16✔
76

77
  view.add("Number Added/Completed/Deleted", true, false);  // Fixed.
16✔
78

79
  Color color_add(Context::getContext().config.get("color.history.add"));
16✔
80
  Color color_done(Context::getContext().config.get("color.history.done"));
16✔
81
  Color color_delete(Context::getContext().config.get("color.history.delete"));
16✔
82
  Color label(Context::getContext().config.get("color.label"));
16✔
83

84
  // Determine the longest line, and the longest "added" line.
85
  auto maxAddedLine = 0;
16✔
86
  auto maxRemovedLine = 0;
16✔
87
  for (auto& i : groups) {
48✔
88
    if (completedGroup[i.first] + deletedGroup[i.first] > maxRemovedLine)
32✔
89
      maxRemovedLine = completedGroup[i.first] + deletedGroup[i.first];
32✔
90

91
    if (addedGroup[i.first] > maxAddedLine) maxAddedLine = addedGroup[i.first];
32✔
92
  }
93

94
  auto maxLine = maxAddedLine + maxRemovedLine;
16✔
95
  if (maxLine > 0) {
16✔
96
    unsigned int leftOffset = (widthOfBar * maxAddedLine) / maxLine;
16✔
97

98
    time_t priorTime = 0;
16✔
99
    auto row = 0;
16✔
100
    for (auto& i : groups) {
48✔
101
      row = view.addRow();
32✔
102

103
      HistoryStrategy::insertRowDate(view, row, i.first, priorTime);
32✔
104
      priorTime = i.first;
32✔
105

106
      unsigned int addedBar = (widthOfBar * addedGroup[i.first]) / maxLine;
32✔
107
      unsigned int completedBar = (widthOfBar * completedGroup[i.first]) / maxLine;
32✔
108
      unsigned int deletedBar = (widthOfBar * deletedGroup[i.first]) / maxLine;
32✔
109

110
      std::string bar;
32✔
111
      if (Context::getContext().color()) {
32✔
112
        std::string aBar;
16✔
113
        if (addedGroup[i.first]) {
16✔
114
          aBar = format(addedGroup[i.first]);
16✔
115
          while (aBar.length() < addedBar) aBar = ' ' + aBar;
356✔
116
        }
117

118
        std::string cBar;
16✔
119
        if (completedGroup[i.first]) {
16✔
120
          cBar = format(completedGroup[i.first]);
16✔
121
          while (cBar.length() < completedBar) cBar = ' ' + cBar;
156✔
122
        }
123

124
        std::string dBar;
16✔
125
        if (deletedGroup[i.first]) {
16✔
126
          dBar = format(deletedGroup[i.first]);
8✔
127
          while (dBar.length() < deletedBar) dBar = ' ' + dBar;
120✔
128
        }
129

130
        bar += std::string(leftOffset - aBar.length(), ' ');
16✔
131
        bar += color_add.colorize(aBar);
16✔
132
        bar += color_done.colorize(cBar);
16✔
133
        bar += color_delete.colorize(dBar);
16✔
134
      } else {
16✔
135
        std::string aBar;
16✔
136
        while (aBar.length() < addedBar) aBar += '+';
372✔
137
        std::string cBar;
16✔
138
        while (cBar.length() < completedBar) cBar += 'X';
172✔
139
        std::string dBar;
16✔
140
        while (dBar.length() < deletedBar) dBar += '-';
136✔
141

142
        bar += std::string(leftOffset - aBar.length(), ' ');
16✔
143
        bar += aBar + cBar + dBar;
16✔
144
      }
145

146
      view.set(row, HistoryStrategy::dateFieldCount + 0, bar);
32✔
147
    }
148
  }
149

150
  std::stringstream out;
16✔
151
  if (view.rows()) {
16✔
152
    out << optionalBlankLine() << view.render() << '\n';
16✔
153

154
    if (Context::getContext().color())
16✔
155
      out << format("Legend: {1}, {2}, {3}", color_add.colorize(STRING_CMD_HISTORY_ADDED),
156
                    color_done.colorize(STRING_CMD_HISTORY_COMP),
157
                    color_delete.colorize(STRING_CMD_HISTORY_DEL))
158
          << optionalBlankLine() << '\n';
8✔
159
    else
160
      out << "Legend: + Added, X Completed, - Deleted\n";
8✔
161
  } else {
NEW
162
    Context::getContext().footnote("No tasks.");
×
UNCOV
163
    rc = 1;
×
164
  }
165

166
  output = out.str();
16✔
167
}
168

169
////////////////////////////////////////////////////////////////////////////////
170
template <class HistoryStrategy>
171
void CmdHistoryBase<HistoryStrategy>::outputTabular(std::string& output) {
8✔
172
  Table view;
8✔
173
  setHeaderUnderline(view);
8✔
174
  view.width(Context::getContext().getWidth());
8✔
175

176
  HistoryStrategy::setupTableDates(view);
8✔
177

178
  view.add(STRING_CMD_HISTORY_ADDED, false);
8✔
179
  view.add(STRING_CMD_HISTORY_COMP, false);
8✔
180
  view.add(STRING_CMD_HISTORY_DEL, false);
8✔
181
  view.add("Net", false);
8✔
182

183
  auto totalAdded = 0;
8✔
184
  auto totalCompleted = 0;
8✔
185
  auto totalDeleted = 0;
8✔
186

187
  auto row = 0;
8✔
188
  time_t lastTime = 0;
8✔
189
  for (auto& i : groups) {
24✔
190
    row = view.addRow();
16✔
191

192
    totalAdded += addedGroup[i.first];
16✔
193
    totalCompleted += completedGroup[i.first];
16✔
194
    totalDeleted += deletedGroup[i.first];
16✔
195

196
    HistoryStrategy::insertRowDate(view, row, i.first, lastTime);
16✔
197
    lastTime = i.first;
16✔
198

199
    auto net = 0;
16✔
200

201
    if (addedGroup.find(i.first) != addedGroup.end()) {
16✔
202
      view.set(row, HistoryStrategy::dateFieldCount + 0, addedGroup[i.first]);
16✔
203
      net += addedGroup[i.first];
16✔
204
    }
205

206
    if (completedGroup.find(i.first) != completedGroup.end()) {
16✔
207
      view.set(row, HistoryStrategy::dateFieldCount + 1, completedGroup[i.first]);
16✔
208
      net -= completedGroup[i.first];
16✔
209
    }
210

211
    if (deletedGroup.find(i.first) != deletedGroup.end()) {
16✔
212
      view.set(row, HistoryStrategy::dateFieldCount + 2, deletedGroup[i.first]);
16✔
213
      net -= deletedGroup[i.first];
16✔
214
    }
215

216
    Color net_color;
16✔
217
    if (Context::getContext().color() && net)
16✔
NEW
218
      net_color = net > 0 ? Color(Color::red) : Color(Color::green);
×
219

220
    view.set(row, HistoryStrategy::dateFieldCount + 3, net, net_color);
16✔
221
  }
222

223
  if (view.rows()) {
8✔
224
    row = view.addRow();
8✔
225
    view.set(row, 1, " ");
8✔
226
    row = view.addRow();
8✔
227

228
    Color row_color;
8✔
229
    if (Context::getContext().color())
8✔
NEW
230
      row_color = Color(Color::nocolor, Color::nocolor, false, true, false);
×
231

232
    view.set(row, HistoryStrategy::dateFieldCount - 1, "Average", row_color);
8✔
233
    view.set(row, HistoryStrategy::dateFieldCount + 0, totalAdded / (view.rows() - 2), row_color);
8✔
234
    view.set(row, HistoryStrategy::dateFieldCount + 1, totalCompleted / (view.rows() - 2),
8✔
235
             row_color);
236
    view.set(row, HistoryStrategy::dateFieldCount + 2, totalDeleted / (view.rows() - 2), row_color);
8✔
237
    view.set(row, HistoryStrategy::dateFieldCount + 3,
8✔
238
             (totalAdded - totalCompleted - totalDeleted) / (view.rows() - 2), row_color);
8✔
239
  }
240

241
  std::stringstream out;
8✔
242
  if (view.rows())
8✔
243
    out << optionalBlankLine() << view.render() << '\n';
8✔
244
  else {
NEW
245
    Context::getContext().footnote("No tasks.");
×
UNCOV
246
    rc = 1;
×
247
  }
248

249
  output = out.str();
8✔
250
}
251

252
////////////////////////////////////////////////////////////////////////////i
253
class MonthlyHistoryStrategy {
254
 public:
255
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfMonth(); }
16✔
256

257
  static void setupTableDates(Table& view) {
1✔
258
    view.add(STRING_CMD_HISTORY_YEAR, true);
1✔
259
    view.add(STRING_CMD_HISTORY_MONTH, true);
1✔
260
  }
1✔
261

262
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
2✔
263
    Datetime dt(rowTime);
2✔
264
    int m, d, y;
265
    dt.toYMD(y, m, d);
2✔
266

267
    Datetime last_dt(lastTime);
2✔
268
    int last_m, last_d, last_y;
269
    last_dt.toYMD(last_y, last_m, last_d);
2✔
270

271
    if (y != last_y) view.set(row, 0, y);
2✔
272

273
    view.set(row, 1, Datetime::monthName(m));
2✔
274
  }
2✔
275

276
  static constexpr const char* keyword = "history.monthly";
277
  static constexpr const char* usage = "task <filter> history.monthly";
278
  static constexpr const char* description = "Shows a report of task history, by month";
279
  static constexpr unsigned int dateFieldCount = 2;
280
  static constexpr bool graphical = false;
281
  static constexpr unsigned int labelWidth = 0;  // unused.
282
};
283

284
////////////////////////////////////////////////////////////////////////////////
285
template <class HistoryStrategy>
286
int CmdHistoryBase<HistoryStrategy>::execute(std::string& output) {
24✔
287
  rc = 0;
24✔
288

289
  // TODO is this necessary?
290
  groups.clear();
24✔
291
  addedGroup.clear();
24✔
292
  deletedGroup.clear();
24✔
293
  completedGroup.clear();
24✔
294

295
  // Apply filter.
296
  handleUntil();
24✔
297
  handleRecurrence();
24✔
298
  Filter filter;
24✔
299
  std::vector<Task> filtered;
24✔
300
  filter.subset(filtered);
24✔
301

302
  for (auto& task : filtered) {
240✔
303
    Datetime entry(task.get_date("entry"));
216✔
304

305
    Datetime end;
216✔
306
    if (task.has("end")) end = Datetime(task.get_date("end"));
216✔
307

308
    auto epoch = HistoryStrategy::getRelevantDate(entry).toEpoch();
216✔
309
    groups[epoch] = 0;
216✔
310

311
    // Every task has an entry date, but exclude templates.
312
    if (task.getStatus() != Task::recurring) ++addedGroup[epoch];
216✔
313

314
    // All deleted tasks have an end date.
315
    if (task.getStatus() == Task::deleted) {
216✔
316
      epoch = HistoryStrategy::getRelevantDate(end).toEpoch();
72✔
317
      groups[epoch] = 0;
72✔
318
      ++deletedGroup[epoch];
72✔
319
    }
320

321
    // All completed tasks have an end date.
322
    else if (task.getStatus() == Task::completed) {
144✔
323
      epoch = HistoryStrategy::getRelevantDate(end).toEpoch();
96✔
324
      groups[epoch] = 0;
96✔
325
      ++completedGroup[epoch];
96✔
326
    }
327
  }
328

329
  // Now build the view.
330
  if (HistoryStrategy::graphical)
331
    this->outputGraphical(output);
16✔
332
  else
333
    this->outputTabular(output);
8✔
334

335
  return rc;
24✔
336
}
24✔
337

338
////////////////////////////////////////////////////////////////////////////i
339
class MonthlyGHistoryStrategy {
340
 public:
341
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfMonth(); }
32✔
342

343
  static void setupTableDates(Table& view) {
2✔
344
    view.add(STRING_CMD_HISTORY_YEAR, true);
2✔
345
    view.add(STRING_CMD_HISTORY_MONTH, true);
2✔
346
  }
2✔
347

348
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
4✔
349
    Datetime dt(rowTime);
4✔
350
    int m, d, y;
351
    dt.toYMD(y, m, d);
4✔
352

353
    Datetime last_dt(lastTime);
4✔
354
    int last_m, last_d, last_y;
355
    last_dt.toYMD(last_y, last_m, last_d);
4✔
356

357
    if (y != last_y) view.set(row, 0, y);
4✔
358

359
    view.set(row, 1, Datetime::monthName(m));
4✔
360
  }
4✔
361

362
  static constexpr const char* keyword = "ghistory.monthly";
363
  static constexpr const char* usage = "task <filter> ghistory.monthly";
364
  static constexpr const char* description = "Shows a graphical report of task history, by month";
365
  static constexpr unsigned int dateFieldCount = 2;
366
  static constexpr bool graphical = true;
367
  static constexpr unsigned int labelWidth = 15;  // length '2017 September ' = 15
368
};
369

370
////////////////////////////////////////////////////////////////////////////i
371
class AnnualGHistoryStrategy {
372
 public:
373
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfYear(); }
32✔
374

375
  static void setupTableDates(Table& view) { view.add(STRING_CMD_HISTORY_YEAR, true); }
2✔
376

377
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
4✔
378
    Datetime dt(rowTime);
4✔
379
    int m, d, y;
380
    dt.toYMD(y, m, d);
4✔
381

382
    Datetime last_dt(lastTime);
4✔
383
    int last_m, last_d, last_y;
384
    last_dt.toYMD(last_y, last_m, last_d);
4✔
385

386
    if (y != last_y) view.set(row, 0, y);
4✔
387
  }
4✔
388

389
  static constexpr const char* keyword = "ghistory.annual";
390
  static constexpr const char* usage = "task <filter> ghistory.annual";
391
  static constexpr const char* description = "Shows a graphical report of task history, by year";
392
  static constexpr unsigned int dateFieldCount = 1;
393
  static constexpr bool graphical = true;
394
  static constexpr unsigned int labelWidth = 5;  // length '2017 ' = 5
395
};
396

397
////////////////////////////////////////////////////////////////////////////i
398
class AnnualHistoryStrategy {
399
 public:
400
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfYear(); }
16✔
401

402
  static void setupTableDates(Table& view) { view.add(STRING_CMD_HISTORY_YEAR, true); }
1✔
403

404
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
2✔
405
    Datetime dt(rowTime);
2✔
406
    int m, d, y;
407
    dt.toYMD(y, m, d);
2✔
408

409
    Datetime last_dt(lastTime);
2✔
410
    int last_m, last_d, last_y;
411
    last_dt.toYMD(last_y, last_m, last_d);
2✔
412

413
    if (y != last_y) view.set(row, 0, y);
2✔
414
  }
2✔
415

416
  static constexpr const char* keyword = "history.annual";
417
  static constexpr const char* usage = "task <filter> history.annual";
418
  static constexpr const char* description = "Shows a report of task history, by year";
419
  static constexpr unsigned int dateFieldCount = 1;
420
  static constexpr bool graphical = false;
421
  static constexpr unsigned int labelWidth = 0;  // unused.
422
};
423

424
////////////////////////////////////////////////////////////////////////////i
425
class DailyHistoryStrategy {
426
 public:
427
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfDay(); }
16✔
428

429
  static void setupTableDates(Table& view) {
1✔
430
    view.add(STRING_CMD_HISTORY_YEAR, true);
1✔
431
    view.add(STRING_CMD_HISTORY_MONTH, true);
1✔
432
    view.add(STRING_CMD_HISTORY_DAY, false);
1✔
433
  }
1✔
434

435
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
2✔
436
    Datetime dt(rowTime);
2✔
437
    int m, d, y;
438
    dt.toYMD(y, m, d);
2✔
439

440
    Datetime last_dt(lastTime);
2✔
441
    int last_m, last_d, last_y;
442
    last_dt.toYMD(last_y, last_m, last_d);
2✔
443

444
    bool y_changed = (y != last_y) || (lastTime == 0);
2✔
445
    bool m_changed = (m != last_m) || (lastTime == 0);
2✔
446

447
    if (y_changed) view.set(row, 0, y);
2✔
448

449
    if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m));
2✔
450

451
    view.set(row, 2, d);
2✔
452
  }
2✔
453

454
  static constexpr const char* keyword = "history.daily";
455
  static constexpr const char* usage = "task <filter> history.daily";
456
  static constexpr const char* description = "Shows a report of task history, by day";
457
  static constexpr unsigned int dateFieldCount = 3;
458
  static constexpr bool graphical = false;
459
  static constexpr unsigned int labelWidth = 0;  // unused.
460
};
461

462
////////////////////////////////////////////////////////////////////////////i
463
class DailyGHistoryStrategy {
464
 public:
465
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfDay(); }
32✔
466

467
  static void setupTableDates(Table& view) {
2✔
468
    view.add(STRING_CMD_HISTORY_YEAR, true);
2✔
469
    view.add(STRING_CMD_HISTORY_MONTH, true);
2✔
470
    view.add(STRING_CMD_HISTORY_DAY, false);
2✔
471
  }
2✔
472

473
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
4✔
474
    Datetime dt(rowTime);
4✔
475
    int m, d, y;
476
    dt.toYMD(y, m, d);
4✔
477

478
    Datetime last_dt(lastTime);
4✔
479
    int last_m, last_d, last_y;
480
    last_dt.toYMD(last_y, last_m, last_d);
4✔
481

482
    bool y_changed = (y != last_y) || (lastTime == 0);
4✔
483
    bool m_changed = (m != last_m) || (lastTime == 0);
4✔
484

485
    if (y_changed) view.set(row, 0, y);
4✔
486

487
    if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m));
4✔
488

489
    view.set(row, 2, d);
4✔
490
  }
4✔
491

492
  static constexpr const char* keyword = "ghistory.daily";
493
  static constexpr const char* usage = "task <filter> ghistory.daily";
494
  static constexpr const char* description = "Shows a graphical report of task history, by day";
495
  static constexpr unsigned int dateFieldCount = 3;
496
  static constexpr bool graphical = true;
497
  static constexpr unsigned int labelWidth = 19;  // length '2017 September Day ' = 19
498
};
499

500
////////////////////////////////////////////////////////////////////////////i
501
class WeeklyHistoryStrategy {
502
 public:
503
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfWeek(); }
16✔
504

505
  static void setupTableDates(Table& view) {
1✔
506
    view.add(STRING_CMD_HISTORY_YEAR, true);
1✔
507
    view.add(STRING_CMD_HISTORY_MONTH, true);
1✔
508
    view.add(STRING_CMD_HISTORY_DAY, false);
1✔
509
  }
1✔
510

511
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
2✔
512
    Datetime dt(rowTime);
2✔
513
    int m, d, y;
514
    dt.toYMD(y, m, d);
2✔
515

516
    Datetime last_dt(lastTime);
2✔
517
    int last_m, last_d, last_y;
518
    last_dt.toYMD(last_y, last_m, last_d);
2✔
519

520
    bool y_changed = (y != last_y) || (lastTime == 0);
2✔
521
    bool m_changed = (m != last_m) || (lastTime == 0);
2✔
522

523
    if (y_changed) view.set(row, 0, y);
2✔
524

525
    if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m));
2✔
526

527
    view.set(row, 2, d);
2✔
528
  }
2✔
529

530
  static constexpr const char* keyword = "history.weekly";
531
  static constexpr const char* usage = "task <filter> history.weekly";
532
  static constexpr const char* description = "Shows a report of task history, by week";
533
  static constexpr unsigned int dateFieldCount = 3;
534
  static constexpr bool graphical = false;
535
  static constexpr unsigned int labelWidth = 0;  // unused.
536
};
537

538
////////////////////////////////////////////////////////////////////////////i
539
class WeeklyGHistoryStrategy {
540
 public:
541
  static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfWeek(); }
32✔
542

543
  static void setupTableDates(Table& view) {
2✔
544
    view.add(STRING_CMD_HISTORY_YEAR, true);
2✔
545
    view.add(STRING_CMD_HISTORY_MONTH, true);
2✔
546
    view.add(STRING_CMD_HISTORY_DAY, false);
2✔
547
  }
2✔
548

549
  static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) {
4✔
550
    Datetime dt(rowTime);
4✔
551
    int m, d, y;
552
    dt.toYMD(y, m, d);
4✔
553

554
    Datetime last_dt(lastTime);
4✔
555
    int last_m, last_d, last_y;
556
    last_dt.toYMD(last_y, last_m, last_d);
4✔
557

558
    bool y_changed = (y != last_y) || (lastTime == 0);
4✔
559
    bool m_changed = (m != last_m) || (lastTime == 0);
4✔
560

561
    if (y_changed) view.set(row, 0, y);
4✔
562

563
    if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m));
4✔
564

565
    view.set(row, 2, d);
4✔
566
  }
4✔
567

568
  static constexpr const char* keyword = "ghistory.weekly";
569
  static constexpr const char* usage = "task <filter> ghistory.weekly";
570
  static constexpr const char* description = "Shows a graphical report of task history, by week";
571
  static constexpr unsigned int dateFieldCount = 3;
572
  static constexpr bool graphical = true;
573
  static constexpr unsigned int labelWidth = 19;  // length '2017 September Day ' = 19
574
};
575

576
// Explicit instantiations, avoiding cpp-inclusion or implementation in header
577
template class CmdHistoryBase<DailyHistoryStrategy>;
578
template class CmdHistoryBase<WeeklyHistoryStrategy>;
579
template class CmdHistoryBase<MonthlyHistoryStrategy>;
580
template class CmdHistoryBase<AnnualHistoryStrategy>;
581
template class CmdHistoryBase<DailyGHistoryStrategy>;
582
template class CmdHistoryBase<WeeklyGHistoryStrategy>;
583
template class CmdHistoryBase<MonthlyGHistoryStrategy>;
584
template class CmdHistoryBase<AnnualGHistoryStrategy>;
585

586
////////////////////////////////////////////////////////////////////////////////
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