• 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

96.88
/src/Filter.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 <Eval.h>
33
#include <Filter.h>
34
#include <Timer.h>
35
#include <Variant.h>
36
#include <format.h>
37
#include <shared.h>
38

39
#include <algorithm>
40

41
////////////////////////////////////////////////////////////////////////////////
42
// Take an input set of tasks and filter into a subset.
43
void Filter::subset(const std::vector<Task>& input, std::vector<Task>& output) {
79✔
44
  Timer timer;
79✔
45
  _startCount = (int)input.size();
79✔
46

47
  Context::getContext().cli2.prepareFilter();
79✔
48

49
  std::vector<std::pair<std::string, Lexer::Type>> precompiled;
79✔
50
  for (auto& a : Context::getContext().cli2._args)
585✔
51
    if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype);
506✔
52

53
  if (precompiled.size()) {
79✔
54
    Eval eval;
2✔
55
    eval.addSource(domSource);
2✔
56

57
    // Debug output from Eval during compilation is useful.  During evaluation
58
    // it is mostly noise.
59
    eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false);
2✔
60
    eval.compileExpression(precompiled);
2✔
61

62
    for (auto& task : input) {
6✔
63
      // Set up context for any DOM references.
64
      auto currentTask = Context::getContext().withCurrentTask(&task);
4✔
65

66
      Variant var;
4✔
67
      eval.evaluateCompiledExpression(var);
4✔
68
      if (var.get_bool()) output.push_back(task);
4✔
69
    }
4✔
70

71
    eval.debug(false);
2✔
72
  } else
2✔
73
    output = input;
77✔
74

75
  _endCount = (int)output.size();
79✔
76
  Context::getContext().debug(
158✔
77
      format("Filtered {1} tasks --> {2} tasks [list subset]", _startCount, _endCount));
158✔
78
  Context::getContext().time_filter_us += timer.total_us();
79✔
79
}
79✔
80

81
////////////////////////////////////////////////////////////////////////////////
82
// Take the set of all tasks and filter into a subset.
83
void Filter::subset(std::vector<Task>& output) {
1,371✔
84
  Timer timer;
1,371✔
85
  Context::getContext().cli2.prepareFilter();
1,371✔
86

87
  std::vector<std::pair<std::string, Lexer::Type>> precompiled;
1,371✔
88
  for (auto& a : Context::getContext().cli2._args)
17,599✔
89
    if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype);
16,228✔
90

91
  // Shortcut indicates that only pending.data needs to be loaded.
92
  bool shortcut = false;
1,371✔
93

94
  if (precompiled.size()) {
1,371✔
95
    Timer timer_pending;
1,210✔
96
    auto pending = Context::getContext().tdb2.pending_tasks();
1,210✔
97
    Context::getContext().time_filter_us -= timer_pending.total_us();
1,210✔
98
    _startCount = (int)pending.size();
1,210✔
99

100
    Eval eval;
1,210✔
101
    eval.addSource(domSource);
1,210✔
102

103
    // Debug output from Eval during compilation is useful.  During evaluation
104
    // it is mostly noise.
105
    eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false);
1,210✔
106
    eval.compileExpression(precompiled);
1,210✔
107

108
    output.clear();
1,209✔
109
    for (auto& task : pending) {
9,603✔
110
      // Set up context for any DOM references.
111
      auto currentTask = Context::getContext().withCurrentTask(&task);
8,394✔
112

113
      Variant var;
8,394✔
114
      eval.evaluateCompiledExpression(var);
8,394✔
115
      if (var.get_bool()) output.push_back(task);
8,394✔
116
    }
8,394✔
117

118
    shortcut = pendingOnly();
1,209✔
119
    if (!shortcut) {
1,209✔
120
      Timer timer_completed;
276✔
121
      auto completed = Context::getContext().tdb2.completed_tasks();
276✔
122
      Context::getContext().time_filter_us -= timer_completed.total_us();
276✔
123
      _startCount += (int)completed.size();
276✔
124

125
      for (auto& task : completed) {
497✔
126
        // Set up context for any DOM references.
127
        auto currentTask = Context::getContext().withCurrentTask(&task);
221✔
128

129
        Variant var;
221✔
130
        eval.evaluateCompiledExpression(var);
221✔
131
        if (var.get_bool()) output.push_back(task);
221✔
132
      }
221✔
133
    }
276✔
134

135
    eval.debug(false);
1,209✔
136
  } else {
1,211✔
137
    safety();
161✔
138

139
    Timer pending_completed;
158✔
140
    output = Context::getContext().tdb2.all_tasks();
158✔
141
    Context::getContext().time_filter_us -= pending_completed.total_us();
158✔
142
  }
143

144
  _endCount = (int)output.size();
1,367✔
145
  Context::getContext().debug(format("Filtered {1} tasks --> {2} tasks [{3}]", _startCount,
1,367✔
146
                                     _endCount, (shortcut ? "pending only" : "all tasks")));
147
  Context::getContext().time_filter_us += timer.total_us();
1,367✔
148
}
1,371✔
149

150
////////////////////////////////////////////////////////////////////////////////
NEW
151
bool Filter::hasFilter() const {
×
NEW
152
  for (const auto& a : Context::getContext().cli2._args)
×
NEW
153
    if (a.hasTag("FILTER")) return true;
×
154

155
  return false;
×
156
}
157

158
////////////////////////////////////////////////////////////////////////////////
159
// If the filter contains no 'or', 'xor' or 'not' operators, and only includes
160
// status values 'pending', 'waiting' or 'recurring', then the filter is
161
// guaranteed to only need data from pending.data.
162
bool Filter::pendingOnly() const {
1,209✔
163
  // When GC is off, there are no shortcuts.
164
  if (!Context::getContext().config.getBoolean("gc")) return false;
1,209✔
165

166
  // To skip loading completed.data, there should be:
167
  // - 'status' in filter
168
  // - no 'completed'
169
  // - no 'deleted'
170
  // - no 'xor'
171
  // - no 'or'
172
  int countStatus = 0;
1,206✔
173
  int countPending = 0;
1,206✔
174
  int countWaiting = 0;
1,206✔
175
  int countRecurring = 0;
1,206✔
176
  int countId = (int)Context::getContext().cli2._id_ranges.size();
1,206✔
177
  int countUUID = (int)Context::getContext().cli2._uuid_list.size();
1,206✔
178
  int countOr = 0;
1,206✔
179
  int countXor = 0;
1,206✔
180
  int countNot = 0;
1,206✔
181
  bool pendingTag = false;
1,206✔
182
  bool activeTag = false;
1,206✔
183

184
  for (const auto& a : Context::getContext().cli2._args) {
16,971✔
185
    if (a.hasTag("FILTER")) {
15,765✔
186
      std::string raw = a.attribute("raw");
25,636✔
187
      std::string canonical = a.attribute("canonical");
25,636✔
188

189
      if (a._lextype == Lexer::Type::op && raw == "or") ++countOr;
12,818✔
190
      if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor;
12,818✔
191
      if (a._lextype == Lexer::Type::op && raw == "not") ++countNot;
12,818✔
192
      if (a._lextype == Lexer::Type::dom && canonical == "status") ++countStatus;
12,818✔
193
      if (raw == "pending") ++countPending;
12,818✔
194
      if (raw == "waiting") ++countWaiting;
12,818✔
195
      if (raw == "recurring") ++countRecurring;
12,818✔
196
    }
12,818✔
197
  }
198

199
  for (const auto& word : Context::getContext().cli2._original_args) {
7,356✔
200
    if (word.attribute("raw") == "+PENDING") pendingTag = true;
6,150✔
201
    if (word.attribute("raw") == "+ACTIVE") activeTag = true;
6,150✔
202
  }
203

204
  if (countUUID) return false;
1,206✔
205

206
  if (countOr || countXor || countNot) return false;
1,144✔
207

208
  if (pendingTag || activeTag) return true;
1,108✔
209

210
  if (countStatus) {
1,101✔
211
    if (!countPending && !countWaiting && !countRecurring) return false;
449✔
212

213
    return true;
430✔
214
  }
215

216
  if (countId) return true;
652✔
217

218
  return false;
156✔
219
}
220

221
////////////////////////////////////////////////////////////////////////////////
222
// Disaster avoidance mechanism. If a !READONLY has no filter, then it can cause
223
// all tasks to be modified. This is usually not intended.
224
void Filter::safety() const {
161✔
225
  if (_safety) {
161✔
226
    bool readonly = true;
157✔
227
    bool filter = false;
157✔
228
    for (const auto& a : Context::getContext().cli2._args) {
557✔
229
      if (a.hasTag("CMD") && !a.hasTag("READONLY")) readonly = false;
400✔
230

231
      if (a.hasTag("FILTER")) filter = true;
400✔
232
    }
233

234
    if (!readonly && !filter) {
157✔
235
      if (!Context::getContext().config.getBoolean("allow.empty.filter"))
4✔
236
        throw std::string(
1✔
237
            "You did not specify a filter, and with the 'allow.empty.filter' value, no action is "
238
            "taken.");
2✔
239

240
      // If user is willing to be asked, this can be avoided.
241
      if (Context::getContext().config.getBoolean("confirmation") &&
8✔
242
          confirm("This command has no filter, and will modify all (including completed and "
5✔
243
                  "deleted) tasks.  Are you sure?"))
244
        return;
1✔
245

246
      // Sound the alarm.
247
      throw std::string("Command prevented from running.");
2✔
248
    }
249
  }
250
}
251

252
////////////////////////////////////////////////////////////////////////////////
253
void Filter::disableSafety() { _safety = false; }
5✔
254

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