• 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

91.16
/src/DOM.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 <DOM.h>
32
#include <Datetime.h>
33
#include <Duration.h>
34
#include <Lexer.h>
35
#include <Variant.h>
36
#include <format.h>
37
#include <shared.h>
38
#include <stdlib.h>
39
#include <util.h>
40

41
#include <map>
42
#include <sstream>
43

44
////////////////////////////////////////////////////////////////////////////////
45
// DOM Supported References:
46
//
47
// Configuration:
48
//   rc.<name>
49
//
50
// Taskwarrior:
51
//   tw.syncneeded
52
//   tw.program
53
//   tw.args
54
//   tw.width
55
//   tw.height
56
//   tw.version
57
//
58
// System:
59
//   context.program         // 2017-02-25 Deprecated in 2.6.0
60
//   context.args            // 2017-02-25 Deprecated in 2.6.0
61
//   context.width           // 2017-02-25 Deprecated in 2.6.0
62
//   context.height          // 2017-02-25 Deprecated in 2.6.0
63
//   system.version
64
//   system.os
65
//
66
bool getDOM(const std::string& name, Variant& value) {
5,649✔
67
  // Special case, blank refs cause problems.
68
  if (name == "") return false;
5,649✔
69

70
  auto len = name.length();
5,649✔
71

72
  // rc. --> context.config
73
  if (len > 3 && !name.compare(0, 3, "rc.", 3)) {
5,649✔
74
    auto key = name.substr(3);
4✔
75
    auto c = Context::getContext().config.find(key);
4✔
76
    if (c != Context::getContext().config.end()) {
4✔
77
      value = Variant(c->second);
3✔
78
      return true;
3✔
79
    }
80

81
    return false;
1✔
82
  }
4✔
83

84
  // tw.*
85
  if (len > 3 && !name.compare(0, 3, "tw.", 3)) {
5,645✔
86
    if (name == "tw.syncneeded") {
7✔
87
      value = Variant(0);
2✔
88
      if (Context::getContext().tdb2.num_local_changes() > 0) {
2✔
89
        value = Variant(1);
1✔
90
      }
91

92
      return true;
2✔
93
    } else if (name == "tw.program") {
5✔
94
      value = Variant(Context::getContext().cli2.getBinary());
1✔
95
      return true;
1✔
96
    } else if (name == "tw.args") {
4✔
97
      std::string commandLine;
1✔
98
      for (auto& arg : Context::getContext().cli2._original_args) {
4✔
99
        if (commandLine != "") commandLine += ' ';
3✔
100

101
        commandLine += arg.attribute("raw");
6✔
102
      }
103

104
      value = Variant(commandLine);
1✔
105
      return true;
1✔
106
    } else if (name == "tw.width") {
4✔
107
      value = Variant(static_cast<int>(Context::getContext().terminal_width
3✔
108
                                           ? Context::getContext().terminal_width
×
109
                                           : Context::getContext().getWidth()));
2✔
110
      return true;
1✔
111
    } else if (name == "tw.height") {
2✔
112
      value = Variant(static_cast<int>(Context::getContext().terminal_height
3✔
113
                                           ? Context::getContext().terminal_height
×
114
                                           : Context::getContext().getHeight()));
2✔
115
      return true;
1✔
116
    }
117

118
    else if (name == "tw.version") {
1✔
119
      value = Variant(VERSION);
1✔
120
      return true;
1✔
121
    }
122

123
    return false;
×
124
  }
125

126
  // context.*
127
  if (len > 8 && !name.compare(0, 8, "context.", 8)) {
5,638✔
128
    if (name == "context.program") {
39✔
129
      value = Variant(Context::getContext().cli2.getBinary());
2✔
130
      return true;
2✔
131
    } else if (name == "context.args") {
37✔
132
      std::string commandLine;
30✔
133
      for (auto& arg : Context::getContext().cli2._original_args) {
119✔
134
        if (commandLine != "") commandLine += ' ';
89✔
135

136
        commandLine += arg.attribute("raw");
178✔
137
      }
138

139
      value = Variant(commandLine);
30✔
140
      return true;
30✔
141
    } else if (name == "context.width") {
37✔
142
      value = Variant(static_cast<int>(Context::getContext().terminal_width
18✔
143
                                           ? Context::getContext().terminal_width
×
144
                                           : Context::getContext().getWidth()));
12✔
145
      return true;
6✔
146
    } else if (name == "context.height") {
1✔
147
      value = Variant(static_cast<int>(Context::getContext().terminal_height
3✔
148
                                           ? Context::getContext().terminal_height
×
149
                                           : Context::getContext().getHeight()));
2✔
150
      return true;
1✔
151
    }
152

153
    return false;
×
154
  }
155

156
  // system. --> Implement locally.
157
  if (len > 7 && !name.compare(0, 7, "system.", 7)) {
5,599✔
158
    // Taskwarrior version number.
159
    if (name == "system.version") {
2✔
160
      value = Variant(VERSION);
1✔
161
      return true;
1✔
162
    }
163

164
    // OS type.
165
    else if (name == "system.os") {
1✔
166
      value = Variant(osName());
1✔
167
      return true;
1✔
168
    }
169

170
    return false;
×
171
  }
172

173
  // Empty string if nothing is found.
174
  return false;
5,597✔
175
}
176

177
////////////////////////////////////////////////////////////////////////////////
178
// DOM Supported References:
179
//
180
// Relative or absolute attribute:
181
//   <attribute>
182
//   <id>.<attribute>
183
//   <uuid>.<attribute>
184
//
185
// Single tag:
186
//   tags.<word>
187
//
188
// Date type:
189
//   <date>.year
190
//   <date>.month
191
//   <date>.day
192
//   <date>.week
193
//   <date>.weekday
194
//   <date>.julian
195
//   <date>.hour
196
//   <date>.minute
197
//   <date>.second
198
//
199
// Annotations (entry is a date):
200
//   annotations.count
201
//   annotations.<N>.entry
202
//   annotations.<N>.description
203
//
204
// This code emphasizes speed, hence 'id' and 'urgency' being evaluated first
205
// as special cases.
206
//
207
// If task is NULL, then the contextual task will be determined from the DOM
208
// string, if any exists.
209
bool getDOM(const std::string& name, const Task* task, Variant& value) {
21,681✔
210
  // Special case, blank refs cause problems.
211
  if (name == "") return false;
21,681✔
212

213
  // Quickly deal with the most common cases.
214
  if (task && name == "id") {
21,681✔
215
    value = Variant(static_cast<int>(task->id));
9,266✔
216
    return true;
9,266✔
217
  }
218

219
  if (task && name == "urgency") {
12,415✔
220
    value = Variant(task->urgency_c());
32✔
221
    return true;
32✔
222
  }
223

224
  // split name on '.'
225
  auto elements = split(name, '.');
12,383✔
226
  Task loaded_task;
12,383✔
227

228
  // decide whether the reference is going to be the passed
229
  // "task" or whether it's going to be a newly loaded task (if id/uuid was
230
  // given).
231
  const Task* ref = task;
12,383✔
232
  Lexer lexer(elements[0]);
12,383✔
233
  std::string token;
12,383✔
234
  Lexer::Type type;
235

236
  // If this can be ID/UUID reference (the name contains '.'),
237
  // lex it to figure out. Otherwise don't lex, as lexing can be slow.
238
  if ((elements.size() > 1) and lexer.token(token, type)) {
12,383✔
239
    bool reloaded = false;
371✔
240

241
    if (type == Lexer::Type::uuid && token.length() == elements[0].length()) {
371✔
242
      if (!task || token != task->get("uuid")) {
16✔
243
        if (Context::getContext().tdb2.get(token, loaded_task)) reloaded = true;
16✔
244
      }
245

246
      // Eat elements[0]/UUID.
247
      elements.erase(elements.begin());
16✔
248
    } else if (type == Lexer::Type::number && token.find('.') == std::string::npos) {
355✔
249
      auto id = strtol(token.c_str(), nullptr, 10);
316✔
250
      if (id && (!task || id != task->id)) {
316✔
251
        if (Context::getContext().tdb2.get(id, loaded_task)) reloaded = true;
308✔
252
      }
253

254
      // Eat elements[0]/ID.
255
      elements.erase(elements.begin());
316✔
256
    }
257

258
    if (reloaded) ref = &loaded_task;
371✔
259
  }
260

261
  // The remainder of this method requires a contextual task, so if we do not
262
  // have one, delegate to the two-argument getDOM
263
  if (!ref) return getDOM(name, value);
12,383✔
264

265
  auto size = elements.size();
7,906✔
266

267
  std::string canonical;
7,906✔
268
  if ((size == 1 || size == 2) &&
15,791✔
269
      Context::getContext().cli2.canonicalize(canonical, "attribute", elements[0])) {
31,561✔
270
    // Now that 'ref' is the contextual task, and any ID/UUID is chopped off the
271
    // elements vector, DOM resolution is now simple.
272
    if (size == 1 && canonical == "id") {
6,738✔
273
      value = Variant(static_cast<int>(ref->id));
1✔
274
      return true;
1✔
275
    }
276

277
    if (size == 1 && canonical == "urgency") {
6,737✔
278
      value = Variant(ref->urgency_c());
50✔
279
      return true;
50✔
280
    }
281

282
    // Special handling of status required for virtual waiting status
283
    // implementation. Remove in 3.0.0.
284
    if (size == 1 && canonical == "status") {
6,687✔
285
      value = Variant(ref->statusToText(ref->getStatus()));
1,827✔
286
      return true;
1,827✔
287
    }
288

289
    Column* column = Context::getContext().columns[canonical];
4,860✔
290

291
    if (size == 1 && column) {
4,860✔
292
      if (column->is_uda() && !ref->has(canonical)) {
4,827✔
293
        value = Variant("");
89✔
294
        return true;
89✔
295
      }
296

297
      if (column->type() == "date") {
4,738✔
298
        auto numeric = ref->get_date(canonical);
198✔
299
        if (numeric == 0)
198✔
300
          value = Variant("");
51✔
301
        else
302
          value = Variant(numeric, Variant::type_date);
147✔
303
      } else if (column->type() == "duration" || canonical == "recur") {
4,540✔
304
        auto period = ref->get(canonical);
17✔
305

306
        Duration iso;
17✔
307
        std::string::size_type cursor = 0;
17✔
308
        if (iso.parse(period, cursor))
17✔
309
          value = Variant(iso.toTime_t(), Variant::type_duration);
17✔
310
        else
311
          value = Variant(Duration(ref->get(canonical)).toTime_t(), Variant::type_duration);
×
312
      } else if (column->type() == "numeric")
4,540✔
313
        value = Variant(ref->get_float(canonical));
7✔
314
      else
315
        value = Variant(ref->get(canonical));
4,516✔
316

317
      return true;
4,738✔
318
    }
319

320
    if (size == 2 && canonical == "tags") {
33✔
321
      value = Variant(ref->hasTag(elements[1]) ? elements[1] : "");
21✔
322
      return true;
16✔
323
    }
324

325
    if (size == 2 && column && column->type() == "date") {
17✔
326
      Datetime date(ref->get_date(canonical));
17✔
327
      if (elements[1] == "year") {
17✔
328
        value = Variant(static_cast<int>(date.year()));
8✔
329
        return true;
17✔
330
      } else if (elements[1] == "month") {
9✔
331
        value = Variant(static_cast<int>(date.month()));
1✔
332
        return true;
1✔
333
      } else if (elements[1] == "day") {
8✔
334
        value = Variant(static_cast<int>(date.day()));
1✔
335
        return true;
1✔
336
      } else if (elements[1] == "week") {
7✔
337
        value = Variant(static_cast<int>(date.week()));
1✔
338
        return true;
1✔
339
      } else if (elements[1] == "weekday") {
6✔
340
        value = Variant(static_cast<int>(date.dayOfWeek()));
1✔
341
        return true;
1✔
342
      } else if (elements[1] == "julian") {
5✔
343
        value = Variant(static_cast<int>(date.dayOfYear()));
2✔
344
        return true;
2✔
345
      } else if (elements[1] == "hour") {
3✔
346
        value = Variant(static_cast<int>(date.hour()));
1✔
347
        return true;
1✔
348
      } else if (elements[1] == "minute") {
2✔
349
        value = Variant(static_cast<int>(date.minute()));
1✔
350
        return true;
1✔
351
      } else if (elements[1] == "second") {
1✔
352
        value = Variant(static_cast<int>(date.second()));
1✔
353
        return true;
1✔
354
      }
355
    }
356
  }
357

358
  if (size == 2 && elements[0] == "annotations" && elements[1] == "count") {
1,168✔
359
    value = Variant(static_cast<int>(ref->getAnnotationCount()));
4✔
360
    return true;
4✔
361
  }
362

363
  if (size == 3 && elements[0] == "annotations") {
1,164✔
364
    auto annos = ref->getAnnotations();
20✔
365

366
    int a = strtol(elements[1].c_str(), nullptr, 10);
20✔
367
    int count = 0;
20✔
368

369
    // Count off the 'a'th annotation.
370
    for (const auto& i : annos) {
24✔
371
      if (++count == a) {
24✔
372
        if (elements[2] == "entry") {
20✔
373
          // annotation_1234567890
374
          // 0          ^11
375
          value =
376
              Variant((time_t)strtoll(i.first.substr(11).c_str(), NULL, 10), Variant::type_date);
5✔
377
          return true;
20✔
378
        } else if (elements[2] == "description") {
15✔
379
          value = Variant(i.second);
15✔
380
          return true;
15✔
381
        }
382
      }
383
    }
384
  }
20✔
385

386
  if (size == 4 && elements[0] == "annotations" && elements[2] == "entry") {
1,144✔
387
    auto annos = ref->getAnnotations();
1✔
388

389
    int a = strtol(elements[1].c_str(), nullptr, 10);
1✔
390
    int count = 0;
1✔
391

392
    // Count off the 'a'th annotation.
393
    for (const auto& i : annos) {
1✔
394
      if (++count == a) {
1✔
395
        // <annotations>.<N>.entry.year
396
        // <annotations>.<N>.entry.month
397
        // <annotations>.<N>.entry.day
398
        // <annotations>.<N>.entry.week
399
        // <annotations>.<N>.entry.weekday
400
        // <annotations>.<N>.entry.julian
401
        // <annotations>.<N>.entry.hour
402
        // <annotations>.<N>.entry.minute
403
        // <annotations>.<N>.entry.second
404
        Datetime date(i.first.substr(11));
1✔
405
        if (elements[3] == "year") {
1✔
406
          value = Variant(static_cast<int>(date.year()));
×
407
          return true;
1✔
408
        } else if (elements[3] == "month") {
1✔
409
          value = Variant(static_cast<int>(date.month()));
×
410
          return true;
×
411
        } else if (elements[3] == "day") {
1✔
412
          value = Variant(static_cast<int>(date.day()));
×
413
          return true;
×
414
        } else if (elements[3] == "week") {
1✔
415
          value = Variant(static_cast<int>(date.week()));
×
416
          return true;
×
417
        } else if (elements[3] == "weekday") {
1✔
418
          value = Variant(static_cast<int>(date.dayOfWeek()));
×
419
          return true;
×
420
        } else if (elements[3] == "julian") {
1✔
421
          value = Variant(static_cast<int>(date.dayOfYear()));
×
422
          return true;
×
423
        } else if (elements[3] == "hour") {
1✔
424
          value = Variant(static_cast<int>(date.hour()));
×
425
          return true;
×
426
        } else if (elements[3] == "minute") {
1✔
427
          value = Variant(static_cast<int>(date.minute()));
×
428
          return true;
×
429
        } else if (elements[3] == "second") {
1✔
430
          value = Variant(static_cast<int>(date.second()));
1✔
431
          return true;
1✔
432
        }
433
      }
434
    }
435
  }
1✔
436

437
  // Delegate to the context-free version of DOM::get.
438
  return getDOM(name, value);
1,143✔
439
}
12,383✔
440

441
////////////////////////////////////////////////////////////////////////////////
442
// DOM Class
443
//
444
// References are paths into a tree structure. For example:
445
//
446
//   1.due.month.number
447
//   1.due.day.number
448
//
449
// Are represented internally as:
450
//
451
//   1
452
//   +- due
453
//     +- day
454
//     | +- number
455
//     +- month
456
//       +- number
457
//
458
// The tree is augmented by other elements:
459
//
460
//   1
461
//   +- due
462
//   | +- day
463
//   | | +- number
464
//   | +- month
465
//   |   +- number
466
//   +- system
467
//      +- os
468
//
469
// Each node in the tree has a name ("due", "system", "day"), and each node may
470
// have a data source attached to it.
471
//
472
// The DOM class is independent of the project, in that it knows nothing about
473
// the internal data or program structure. It knows only that certain DOM path
474
// elements have handlers which will provide the data.
475
//
476
// The DOM class is therefore responsible for maintaining a tree of named nodes
477
// with associated proividers. When a reference value is requested, the DOM
478
// class will decompose the reference path, and navigate the tree to the lowest
479
// level provider, and call it.
480
//
481
// This makes the DOM class a reusible object.
482

483
////////////////////////////////////////////////////////////////////////////////
484
DOM::~DOM() { delete _node; }
1✔
485

486
////////////////////////////////////////////////////////////////////////////////
487
void DOM::addSource(const std::string& reference, bool (*provider)(const std::string&, Variant&)) {
4✔
488
  if (_node == nullptr) _node = new DOM::Node();
4✔
489

490
  _node->addSource(reference, provider);
4✔
491
}
4✔
492

493
////////////////////////////////////////////////////////////////////////////////
494
bool DOM::valid(const std::string& reference) const {
5✔
495
  return _node && _node->find(reference) != nullptr;
5✔
496
}
497

498
////////////////////////////////////////////////////////////////////////////////
499
Variant DOM::get(const std::string& reference) const {
5✔
500
  Variant v("");
5✔
501

502
  if (_node) {
5✔
503
    auto node = _node->find(reference);
5✔
504
    if (node != nullptr && node->_provider != nullptr) {
5✔
505
      if (node->_provider(reference, v)) return v;
4✔
506
    }
507
  }
508

509
  return v;
1✔
UNCOV
510
}
×
511

512
////////////////////////////////////////////////////////////////////////////////
513
int DOM::count() const {
2✔
514
  if (_node) return _node->count();
2✔
515

516
  return 0;
1✔
517
}
518

519
////////////////////////////////////////////////////////////////////////////////
520
std::vector<std::string> DOM::decomposeReference(const std::string& reference) {
14✔
521
  return split(reference, '.');
14✔
522
}
523

524
////////////////////////////////////////////////////////////////////////////////
525
std::string DOM::dump() const {
1✔
526
  if (_node) return _node->dump();
1✔
527

528
  return "";
×
529
}
530

531
////////////////////////////////////////////////////////////////////////////////
532
DOM::Node::~Node() {
5✔
533
  for (auto& branch : _branches) delete branch;
9✔
534
}
5✔
535

536
////////////////////////////////////////////////////////////////////////////////
537
void DOM::Node::addSource(const std::string& reference,
4✔
538
                          bool (*provider)(const std::string&, Variant&)) {
539
  auto cursor = this;
4✔
540
  for (const auto& element : DOM::decomposeReference(reference)) {
10✔
541
    auto found{false};
6✔
542
    for (auto& branch : cursor->_branches) {
8✔
543
      if (branch->_name == element) {
4✔
544
        cursor = branch;
2✔
545
        found = true;
2✔
546
        break;
2✔
547
      }
548
    }
549

550
    if (!found) {
6✔
551
      auto branch = new DOM::Node();
4✔
552
      branch->_name = element;
4✔
553
      cursor->_branches.push_back(branch);
4✔
554
      cursor = branch;
4✔
555
    }
556
  }
4✔
557

558
  cursor->_provider = provider;
4✔
559
}
4✔
560

561
////////////////////////////////////////////////////////////////////////////////
562
// A valid reference is one that has a provider function.
563
bool DOM::Node::valid(const std::string& reference) const { return find(reference) != nullptr; }
×
564

565
////////////////////////////////////////////////////////////////////////////////
566
const DOM::Node* DOM::Node::find(const std::string& reference) const {
10✔
567
  auto cursor = this;
10✔
568
  for (const auto& element : DOM::decomposeReference(reference)) {
22✔
569
    auto found{false};
14✔
570
    for (auto& branch : cursor->_branches) {
22✔
571
      if (branch->_name == element) {
20✔
572
        cursor = branch;
12✔
573
        found = true;
12✔
574
        break;
12✔
575
      }
576
    }
577

578
    if (!found) break;
14✔
579
  }
10✔
580

581
  if (reference.length() && cursor != this) return cursor;
10✔
582

583
  return nullptr;
2✔
584
}
585

586
////////////////////////////////////////////////////////////////////////////////
587
int DOM::Node::count() const {
10✔
588
  // Recurse and count the branches.
589
  int total{0};
10✔
590
  for (auto& branch : _branches) {
18✔
591
    if (branch->_provider) ++total;
8✔
592
    total += branch->count();
8✔
593
  }
594

595
  return total;
10✔
596
}
597

598
////////////////////////////////////////////////////////////////////////////////
599
std::string DOM::Node::dumpNode(const DOM::Node* node, int depth) const {
5✔
600
  std::stringstream out;
5✔
601

602
  // Indent.
603
  out << std::string(depth * 2, ' ');
5✔
604

605
  out << "\033[31m" << node->_name << "\033[0m";
5✔
606

607
  if (node->_provider) out << " 0x" << std::hex << (long long)(void*)node->_provider;
5✔
608

609
  out << '\n';
5✔
610

611
  // Recurse for branches.
612
  for (auto& b : node->_branches) out << dumpNode(b, depth + 1);
9✔
613

614
  return out.str();
10✔
615
}
5✔
616

617
////////////////////////////////////////////////////////////////////////////////
618
std::string DOM::Node::dump() const {
1✔
619
  std::stringstream out;
1✔
620
  out << "DOM::Node (" << count() << " nodes)\n" << dumpNode(this, 1);
1✔
621

622
  return out.str();
2✔
623
}
1✔
624

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