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

GothenburgBitFactory / taskwarrior / 12343201393

15 Dec 2024 11:30PM UTC coverage: 84.419% (-1.1%) from 85.522%
12343201393

Pull #3724

github

web-flow
Merge 532931b9f into ddae5c4ba
Pull Request #3724: Support importing Taskwarrior v2.x data files

15 of 145 new or added lines in 4 files covered. (10.34%)

183 existing lines in 48 files now uncovered.

19289 of 22849 relevant lines covered (84.42%)

23168.82 hits per line

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

92.17
/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,
821✔
50
                            const std::string& format /* = "" */) {
51
  if (Context::getContext().columns.find(name) != Context::getContext().columns.end()) {
821✔
52
    Column* col = Context::getContext().columns[name];
816✔
53
    if (col && col->type() == "date" && value != "") {
816✔
54
      Datetime d((time_t)strtoll(value.c_str(), nullptr, 10));
265✔
55
      if (format == "") return d.toString(Context::getContext().config.get("dateformat"));
571✔
56

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

61
  return value;
556✔
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";
18✔
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";
1,341✔
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")) {
1,368✔
90
    std::cout << format(effect, task.identifier(true), task.get("description")) << "\n";
892✔
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) {
192✔
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" ||
384✔
100
      tag == "CHILD" ||  // Deprecated 2.6.0
192✔
101
      tag == "COMPLETED" || tag == "DELETED" || tag == "DUE" || tag == "DUETODAY" ||
192✔
102
      tag == "INSTANCE" || tag == "LATEST" || tag == "MONTH" || tag == "ORPHAN" ||
192✔
103
      tag == "OVERDUE" || tag == "PARENT" ||  // Deprecated 2.6.0
192✔
104
      tag == "PENDING" || tag == "PRIORITY" || tag == "PROJECT" || tag == "QUARTER" ||
192✔
105
      tag == "READY" || tag == "SCHEDULED" || tag == "TAGGED" || tag == "TEMPLATE" ||
192✔
106
      tag == "TODAY" || tag == "TOMORROW" || tag == "UDA" || tag == "UNBLOCKED" || tag == "UNTIL" ||
192✔
107
      tag == "WAITING" || tag == "WEEK" || tag == "YEAR" || tag == "YESTERDAY") {
384✔
108
    throw format("Virtual tags (including '{1}') are reserved and may not be added or removed.",
109
                 tag);
×
110
  }
111
}
192✔
112

113
////////////////////////////////////////////////////////////////////////////////
114
// Implements feedback when adding special tags to a task.
115
void feedback_special_tags(const Task& task, const std::string& tag) {
195✔
116
  if (Context::getContext().verbose("special")) {
585✔
117
    std::string msg;
181✔
118
    if (tag == "nocolor")
181✔
119
      msg = "The 'nocolor' special tag will disable color rules for this task.";
1✔
120
    else if (tag == "nonag")
180✔
121
      msg = "The 'nonag' special tag will prevent nagging when this task is modified.";
4✔
122
    else if (tag == "nocal")
176✔
123
      msg = "The 'nocal' special tag will keep this task off the 'calendar' report.";
×
124
    else if (tag == "next")
176✔
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()) {
181✔
130
      std::cout << format(msg, task.identifier()) << "\n";
8✔
131
    }
132
  }
181✔
133
}
195✔
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) {
309✔
143
  if (Context::getContext().verbose("affected")) {
927✔
144
    // Get a list of tasks that depended on this task.
145
    auto blocked = task.getBlockedTasks();
306✔
146

147
    // Scan all the tasks that were blocked by this task
148
    for (auto& i : blocked) {
320✔
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";
32✔
153
        else {
154
          std::string uuid = i.get("uuid");
×
155
          std::cout << format("Unblocked {1} '{2}'.", i.get("uuid"), i.get("description")) << "\n";
×
UNCOV
156
        }
×
157
      }
158
    }
14✔
159
  }
306✔
160
}
309✔
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") == "") {
1,944✔
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,582✔
181
  std::stringstream msg;
1,582✔
182
  std::string project = task.get("project");
1,582✔
183

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

187
    // Count pending and done tasks, for this project.
188
    int count_pending = 0;
255✔
189
    int count_done = 0;
255✔
190
    std::vector<Task> all = Context::getContext().tdb2.all_tasks();
255✔
191
    countTasks(all, project, count_pending, count_done);
255✔
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;
255✔
200
    if (count_done == 0)
255✔
201
      percentage = 0;
243✔
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) << ' ';
765✔
208

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

215
  return msg.str();
3,164✔
216
}
1,582✔
217

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

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

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

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

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

234
  if (Context::getContext().verbose("affected"))
12✔
235
    msg << format("Task {1} '{2}' expired and was deleted.", task.identifier(true),
12✔
236
                  task.get("description"));
12✔
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,
255✔
243
                       int& count_done) {
244
  for (auto& it : all) {
861✔
245
    if (it.get("project") == project) {
1,212✔
246
      switch (it.getStatus()) {
338✔
247
        case Task::pending:
300✔
248
        case Task::waiting:
249
          ++count_pending;
300✔
250
          break;
300✔
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
}
255✔
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