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

GothenburgBitFactory / taskwarrior / 11420355627

19 Oct 2024 08:00PM UTC coverage: 84.853% (+0.6%) from 84.223%
11420355627

push

github

web-flow
Pass rc.weekstart to libshared for ISO8601 weeknum parsing if "monday" (#3654)

* libshared: bump for weekstart, epoch defines, eopww fix

mainly those visible changes, and miscellaneous others

see GothenburgBitFactory/taskwarrior#3623 (weekstart)
see GothenburgBitFactory/taskwarrior#3651 (epoch limit defines)
see GothenburgBitFactory/libshared#73 (eopww fix)

* Initialize libshared's weekstart from user's rc.weekstart config

This enables use of newer libshared code that can parse week numbers
according to ISO8601 instead of existing code which is always using
Sunday-based weeks.  To get ISO behavior, set rc.weekstart=monday.
Default is still Sunday / old algorithm, as before, since Sunday is in
the hardcoded default rcfile.

Weekstart does not yet fix week-relative shortcuts, which will still
always use Monday.

See #3623 for further details.

4 of 6 new or added lines in 2 files covered. (66.67%)

993 existing lines in 25 files now uncovered.

19019 of 22414 relevant lines covered (84.85%)

23067.98 hits per line

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

91.3
/src/feedback.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 <Context.h>
31
#include <Datetime.h>
32
#include <Duration.h>
33
#include <Lexer.h>
34
#include <format.h>
35
#include <inttypes.h>
36
#include <main.h>
37
#include <shared.h>
38
#include <stdlib.h>
39

40
#include <algorithm>
41
#include <iostream>
42
#include <sstream>
43
#include <string>
44
#include <vector>
45

46
static void countTasks(const std::vector<Task>&, const std::string&, int&, int&);
47

48
////////////////////////////////////////////////////////////////////////////////
49
std::string renderAttribute(const std::string& name, const std::string& value,
493✔
50
                            const std::string& format /* = "" */) {
51
  if (Context::getContext().columns.find(name) != Context::getContext().columns.end()) {
493✔
52
    Column* col = Context::getContext().columns[name];
490✔
53
    if (col && col->type() == "date" && value != "") {
490✔
54
      Datetime d((time_t)strtoll(value.c_str(), nullptr, 10));
152✔
55
      if (format == "") return d.toString(Context::getContext().config.get("dateformat"));
304✔
56

57
      return d.toString(format);
×
58
    }
59
  }
60

61
  return value;
341✔
62
}
63

64
////////////////////////////////////////////////////////////////////////////////
65
// Implements:
66
//    <string>
67
void feedback_affected(const std::string& effect) {
6✔
68
  if (Context::getContext().verbose("affected")) std::cout << effect << "\n";
6✔
69
}
6✔
70

71
////////////////////////////////////////////////////////////////////////////////
72
// Implements:
73
//    Deleted 3 tasks
74
//
75
// The 'effect' string should contain:
76
//    {1}    Quantity
77
void feedback_affected(const std::string& effect, int quantity) {
447✔
78
  if (Context::getContext().verbose("affected")) std::cout << format(effect, quantity) << "\n";
447✔
79
}
447✔
80

81
////////////////////////////////////////////////////////////////////////////////
82
// Implements:
83
//    Deleting task 123 'This is a test'
84
//
85
// The 'effect' string should contain:
86
//    {1}    ID
87
//    {2}    Description
88
void feedback_affected(const std::string& effect, const Task& task) {
456✔
89
  if (Context::getContext().verbose("affected")) {
456✔
90
    std::cout << format(effect, task.identifier(true), task.get("description")) << "\n";
446✔
91
  }
92
}
456✔
93

94
////////////////////////////////////////////////////////////////////////////////
95
// Implements feedback and error when adding a reserved tag name.
96
void feedback_reserved_tags(const std::string& tag) {
194✔
97
  // Note: This list must match that in Task::hasTag.
98
  // Note: This list must match that in CmdInfo::execute.
99
  if (tag == "ACTIVE" || tag == "ANNOTATED" || tag == "BLOCKED" || tag == "BLOCKING" ||
582✔
100
      tag == "CHILD" ||  // Deprecated 2.6.0
388✔
101
      tag == "COMPLETED" || tag == "DELETED" || tag == "DUE" || tag == "DUETODAY" ||
582✔
102
      tag == "INSTANCE" || tag == "LATEST" || tag == "MONTH" || tag == "ORPHAN" ||
582✔
103
      tag == "OVERDUE" || tag == "PARENT" ||  // Deprecated 2.6.0
582✔
104
      tag == "PENDING" || tag == "PRIORITY" || tag == "PROJECT" || tag == "QUARTER" ||
582✔
105
      tag == "READY" || tag == "SCHEDULED" || tag == "TAGGED" || tag == "TEMPLATE" ||
582✔
106
      tag == "TODAY" || tag == "TOMORROW" || tag == "UDA" || tag == "UNBLOCKED" || tag == "UNTIL" ||
582✔
107
      tag == "WAITING" || tag == "WEEK" || tag == "YEAR" || tag == "YESTERDAY") {
582✔
UNCOV
108
    throw format("Virtual tags (including '{1}') are reserved and may not be added or removed.",
×
109
                 tag);
×
110
  }
111
}
194✔
112

113
////////////////////////////////////////////////////////////////////////////////
114
// Implements feedback when adding special tags to a task.
115
void feedback_special_tags(const Task& task, const std::string& tag) {
197✔
116
  if (Context::getContext().verbose("special")) {
197✔
117
    std::string msg;
183✔
118
    if (tag == "nocolor")
183✔
119
      msg = "The 'nocolor' special tag will disable color rules for this task.";
1✔
120
    else if (tag == "nonag")
182✔
121
      msg = "The 'nonag' special tag will prevent nagging when this task is modified.";
4✔
122
    else if (tag == "nocal")
178✔
123
      msg = "The 'nocal' special tag will keep this task off the 'calendar' report.";
×
124
    else if (tag == "next")
178✔
125
      msg =
126
          "The 'next' special tag will boost the urgency of this task so it appears on the 'next' "
127
          "report.";
3✔
128

129
    if (msg.length()) {
183✔
130
      std::cout << format(msg, task.identifier()) << "\n";
8✔
131
    }
132
  }
183✔
133
}
197✔
134

135
////////////////////////////////////////////////////////////////////////////////
136
// Called on completion, deletion and update.  If this task is blocking another
137
// task, then if this was the *only* blocking task, that other task is now
138
// unblocked.  Mention it.
139
//
140
// Implements:
141
//    Unblocked <id> '<description>'
142
void feedback_unblocked(const Task& task) {
311✔
143
  if (Context::getContext().verbose("affected")) {
311✔
144
    // Get a list of tasks that depended on this task.
145
    auto blocked = task.getBlockedTasks();
308✔
146

147
    // Scan all the tasks that were blocked by this task
148
    for (auto& i : blocked) {
322✔
149
      auto blocking = i.getDependencyTasks();
14✔
150
      if (blocking.size() == 0) {
14✔
151
        if (i.id)
8✔
152
          std::cout << format("Unblocked {1} '{2}'.", i.id, i.get("description")) << "\n";
8✔
153
        else {
154
          std::string uuid = i.get("uuid");
×
155
          std::cout << format("Unblocked {1} '{2}'.", i.get("uuid"), i.get("description")) << "\n";
×
156
        }
157
      }
158
    }
14✔
159
  }
308✔
160
}
311✔
161

162
///////////////////////////////////////////////////////////////////////////////
163
void feedback_backlog() {
648✔
164
  // If non-local sync is not set up, do not provide this feedback.
165
  if (Context::getContext().config.get("sync.encryption_secret") == "") {
648✔
166
    return;
648✔
167
  }
168

169
  if (Context::getContext().verbose("sync")) {
×
170
    int count = Context::getContext().tdb2.num_local_changes();
×
171
    if (count)
×
172
      Context::getContext().footnote(format(count > 1
×
173
                                                ? "There are {1} local changes.  Sync required."
174
                                                : "There is {1} local change.  Sync required.",
175
                                            count));
176
  }
177
}
178

179
///////////////////////////////////////////////////////////////////////////////
180
std::string onProjectChange(Task& task, bool scope /* = true */) {
1,585✔
181
  std::stringstream msg;
1,585✔
182
  std::string project = task.get("project");
3,170✔
183

184
  if (project != "") {
1,585✔
185
    if (scope) msg << format("The project '{1}' has changed.", project) << "  ";
261✔
186

187
    // Count pending and done tasks, for this project.
188
    int count_pending = 0;
261✔
189
    int count_done = 0;
261✔
190
    std::vector<Task> all = Context::getContext().tdb2.all_tasks();
261✔
191
    countTasks(all, project, count_pending, count_done);
261✔
192

193
    // count_done  count_pending  percentage
194
    // ----------  -------------  ----------
195
    //          0              0          0%
196
    //         >0              0        100%
197
    //          0             >0          0%
198
    //         >0             >0  calculated
199
    int percentage = 0;
261✔
200
    if (count_done == 0)
261✔
201
      percentage = 0;
249✔
202
    else if (count_pending == 0)
12✔
203
      percentage = 100;
5✔
204
    else
205
      percentage = (count_done * 100 / (count_done + count_pending));
7✔
206

207
    msg << format("Project '{1}' is {2}% complete", project, percentage) << ' ';
261✔
208

209
    if (count_pending == 1 && count_done == 0)
261✔
210
      msg << format("({1} task remaining).", count_pending);
190✔
211
    else
212
      msg << format("({1} of {2} tasks remaining).", count_pending, count_pending + count_done);
71✔
213
  }
261✔
214

215
  return msg.str();
3,170✔
216
}
1,585✔
217

218
///////////////////////////////////////////////////////////////////////////////
219
std::string onProjectChange(Task& task1, Task& task2) {
119✔
220
  if (task1.get("project") == task2.get("project")) return onProjectChange(task1, false);
119✔
221

222
  std::string messages1 = onProjectChange(task1);
15✔
223
  std::string messages2 = onProjectChange(task2);
15✔
224

225
  if (messages1.length() && messages2.length()) return messages1 + '\n' + messages2;
19✔
226

227
  return messages1 + messages2;
11✔
228
}
15✔
229

230
///////////////////////////////////////////////////////////////////////////////
231
std::string onExpiration(Task& task) {
4✔
232
  std::stringstream msg;
4✔
233

234
  if (Context::getContext().verbose("affected"))
4✔
235
    msg << format("Task {1} '{2}' expired and was deleted.", task.identifier(true),
6✔
236
                  task.get("description"));
9✔
237

238
  return msg.str();
8✔
239
}
4✔
240

241
///////////////////////////////////////////////////////////////////////////////
242
static void countTasks(const std::vector<Task>& all, const std::string& project, int& count_pending,
261✔
243
                       int& count_done) {
244
  for (auto& it : all) {
873✔
245
    if (it.get("project") == project) {
612✔
246
      switch (it.getStatus()) {
342✔
247
        case Task::pending:
304✔
248
        case Task::waiting:
249
          ++count_pending;
304✔
250
          break;
304✔
251

252
        case Task::completed:
13✔
253
          ++count_done;
13✔
254
          break;
13✔
255

256
        case Task::deleted:
25✔
257
        case Task::recurring:
258
        default:
259
          break;
25✔
260
      }
261
    }
262
  }
263
}
261✔
264

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

© 2025 Coveralls, Inc