• 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

94.66
/src/commands/CmdContext.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 <CmdConfig.h>
31
#include <CmdContext.h>
32
#include <Context.h>
33
#include <Filter.h>
34
#include <Table.h>
35
#include <format.h>
36
#include <main.h>
37
#include <shared.h>
38
#include <util.h>
39

40
#include <algorithm>
41
#include <set>
42
#include <sstream>
43

44
////////////////////////////////////////////////////////////////////////////////
45
CmdContext::CmdContext() {
4,389✔
46
  _keyword = "context";
4,389✔
47
  _usage = "task          context [<name> | <subcommand>]";
4,389✔
48
  _description = "Set and define contexts (default filters / modifications)";
4,389✔
49
  _read_only = true;
4,389✔
50
  _displays_id = false;
4,389✔
51
  _needs_gc = false;
4,389✔
52
  _uses_context = false;
4,389✔
53
  _accepts_filter = false;
4,389✔
54
  _accepts_modifications = false;
4,389✔
55
  _accepts_miscellaneous = true;
4,389✔
56
  _category = Command::Category::context;
4,389✔
57
}
4,389✔
58

59
////////////////////////////////////////////////////////////////////////////////
60
int CmdContext::execute(std::string& output) {
102✔
61
  std::stringstream out;
102✔
62

63
  // Get the non-attribute, non-fancy command line arguments.
64
  auto words = Context::getContext().cli2.getWords();
102✔
65
  if (words.size() > 0) {
102✔
66
    auto subcommand = words[0];
102✔
67

68
    if (subcommand == "define")
102✔
69
      defineContext(words, out);
55✔
70
    else if (subcommand == "delete")
47✔
71
      deleteContext(words, out);
4✔
72
    else if (subcommand == "list")
43✔
73
      listContexts(out);
3✔
74
    else if (subcommand == "none")
40✔
75
      unsetContext(out);
5✔
76
    else if (subcommand == "show")
35✔
77
      showContext(out);
8✔
78
    else if (words.size())
27✔
79
      setContext(words, out);
27✔
80
  } else {
102✔
NEW
81
    listContexts(out);
×
UNCOV
82
    out << "Use 'task context none' to unset the current context.\n";
×
83
  }
84

85
  output = out.str();
94✔
86
  return 0;
94✔
87
}
110✔
88

89
////////////////////////////////////////////////////////////////////////////////
90
// Joins all the words in the specified interval <from, to) to one string,
91
// which is then returned.
92
//
93
// If to is specified as 0 (default value), all the remaining words will be joined.
94
//
95
std::string CmdContext::joinWords(const std::vector<std::string>& words, unsigned int from,
54✔
96
                                  unsigned int to /* = 0 */) {
97
  std::string value = "";
54✔
98

99
  if (to == 0) to = words.size();
54✔
100

101
  for (unsigned int i = from; i < to; ++i) {
111✔
102
    if (i > from) value += ' ';
57✔
103

104
    value += words[i];
57✔
105
  }
106

107
  return value;
54✔
108
}
109

110
////////////////////////////////////////////////////////////////////////////////
111
// Validate the context as valid for writing and fail the write context definition
112
// A valid write context:
113
//   - does not contain any operators except AND
114
//   - does not contain tag exclusion
115
//   - does not use modifiers, except for 'equals' and 'is'
116
//
117
// Returns True if the context is a valid write context. If the context is
118
// invalid due to a wrong modifier use, the modifier string will contain the
119
// first invalid modifier.
120
//
121
bool CmdContext::validateWriteContext(const std::vector<A2>& lexedArgs, std::string& reason) {
54✔
122
  for (auto& arg : lexedArgs) {
215✔
123
    if (arg._lextype == Lexer::Type::op)
164✔
124
      if (arg.attribute("raw") == "or") {
1✔
125
        reason = "contains the 'OR' operator";
1✔
126
        return false;
3✔
127
      }
128

129
    if (arg._lextype == Lexer::Type::pair) {
163✔
130
      auto modifier = arg.attribute("modifier");
68✔
131
      if (modifier != "" && modifier != "is" && modifier != "equals") {
34✔
132
        reason = format("contains an attribute modifier '{1}'", arg.attribute("raw"));
1✔
133
        return false;
1✔
134
      }
135
    }
34✔
136

137
    if (arg._lextype == Lexer::Type::tag) {
162✔
138
      if (arg.attribute("sign") == "-") {
20✔
139
        reason = format("contains tag exclusion '{1}'", arg.attribute("raw"));
1✔
140
        return false;
1✔
141
      }
142
    }
143
  }
144

145
  return true;
51✔
146
}
147

148
////////////////////////////////////////////////////////////////////////////////
149
// Returns all user defined contexts.
150
//
151
std::vector<std::string> CmdContext::getContexts() {
32✔
152
  std::set<std::string> contexts;
32✔
153

154
  for (auto& name : Context::getContext().config)
7,978✔
155
    if (name.first.substr(0, 8) == "context.") {
7,946✔
156
      std::string suffix = name.first.substr(8);
95✔
157

158
      if (suffix.find(".") != std::string::npos)
95✔
159
        contexts.insert(suffix.substr(0, suffix.find(".")));
93✔
160
      else
161
        contexts.insert(suffix);
2✔
162
    }
95✔
163

164
  return std::vector<std::string>(contexts.begin(), contexts.end());
64✔
165
}
32✔
166

167
////////////////////////////////////////////////////////////////////////////////
168
// Defines a new user-provided context.
169
//  - The context definition is written into .taskrc as a context.<name> variable.
170
//  - Deletion of the context requires confirmation if rc.confirmation=yes.
171
//
172
// Returns: 0 if the addition of the config variable was successful, 1 otherwise
173
//
174
// Invoked with: task context define <name> <filter>
175
// Example:      task context define home project:Home
176
//
177
void CmdContext::defineContext(const std::vector<std::string>& words, std::stringstream& out) {
55✔
178
  auto config = Context::getContext().config;
55✔
179
  bool confirmation = config.getBoolean("confirmation");
55✔
180

181
  if (words.size() > 2) {
55✔
182
    auto name = "context." + words[1];
54✔
183
    auto value = joinWords(words, 2);
54✔
184

185
    // Make sure nobody creates a context with name 'list', 'none' or 'show'
186
    if (words[1] == "none" or words[1] == "list" or words[1] == "show") {
54✔
NEW
187
      throw format("The name '{1}' is reserved and not allowed to use as a context name.",
×
NEW
188
                   words[1]);
×
189
    }
190

191
    // Extract MISCELLANEOUS arguments (containing the filter definition) for later analysis
192
    std::vector<A2> lexedArgs = Context::getContext().cli2.getMiscellaneous();
54✔
193

194
    // Check if the value is a proper filter by filtering current pending.data
195
    Filter filter;
54✔
196
    std::vector<Task> filtered;
54✔
197
    auto pending = Context::getContext().tdb2.pending_tasks();
54✔
198

199
    try {
200
      // This result is not used, and is just to check validity.
201
      Context::getContext().cli2.addFilter(value);
54✔
202
      filter.subset(pending, filtered);
54✔
NEW
203
    } catch (std::string exception) {
×
NEW
204
      throw format("Filter validation failed: {1}", exception);
×
205
    }
206

207
    // Make user explicitly confirm filters that are matching no pending tasks
208
    if (filtered.size() == 0)
54✔
209
      if (confirmation &&
81✔
210
          !confirm(
28✔
211
              format("The filter '{1}' matches 0 pending tasks. Do you wish to continue?", value)))
81✔
NEW
212
        throw std::string("Context definition aborted.");
×
213

214
    std::string reason = "";
54✔
215
    bool valid_write_context = CmdContext::validateWriteContext(lexedArgs, reason);
54✔
216

217
    if (!valid_write_context) {
54✔
218
      std::stringstream warning;
3✔
219
      warning
220
          << format("The filter '{1}' is not a valid modification string, because it contains {2}.",
6✔
221
                    value, reason)
222
          << "\nAs such, value for the write context cannot be set (context will not apply on task "
223
             "add / task log).\n\n"
224
          << format(
6✔
225
                 "Please use 'task config context.{1}.write <default mods>' to set default "
226
                 "attribute values for new tasks in this context manually.\n\n",
227
                 words[1]);
6✔
228
      out << colorizeFootnote(warning.str());
3✔
229
    }
3✔
230

231
    // Set context definition config variable
232
    bool read_success = CmdConfig::setConfigVariable(name + ".read", value, confirmation);
54✔
233
    bool write_success = false;
54✔
234

235
    if (valid_write_context)
54✔
236
      write_success = CmdConfig::setConfigVariable(name + ".write", value, confirmation);
51✔
237

238
    // Remove old-school context name, if it exists, assuming the read context was defined
239
    if (read_success)
54✔
240
      if (config.has(name)) {
53✔
NEW
241
        CmdConfig::unsetConfigVariable(name, false);
×
242
      }
243

244
    if (!read_success and !write_success)
54✔
245
      throw format("Context '{1}' not defined.", words[1]);
1✔
246
    else if (!read_success)
53✔
NEW
247
      out << format("Context '{1}' defined (write only).", words[1]);
×
248
    else if (!write_success)
53✔
249
      out << format("Context '{1}' defined (read only).", words[1]);
27✔
250
    else
251
      out << format("Context '{1}' defined (read, write).", words[1]);
26✔
252

253
    out << format(" Use 'task context {1}' to activate.\n", words[1]);
53✔
254
  } else
59✔
255
    throw std::string("Both context name and its definition must be provided.");
1✔
256
}
55✔
257

258
////////////////////////////////////////////////////////////////////////////////
259
// Deletes the specified context.
260
//  - If the deleted context is currently active, unset it.
261
//  - Deletion of the context requires confirmation if rc.confirmation=yes.
262
//
263
// Returns: 0 if the removal of the config variable was successful, 1 otherwise
264
//
265
// Invoked with: task context delete <name>
266
// Example:      task context delete home
267
//
268
void CmdContext::deleteContext(const std::vector<std::string>& words, std::stringstream& out) {
4✔
269
  if (words.size() > 1) {
4✔
270
    // Delete the specified context
271
    auto name = "context." + words[1];
3✔
272

273
    auto confirmation = Context::getContext().config.getBoolean("confirmation");
3✔
274
    if (confirmation && !confirm(format("Do you want to delete context '{1}'?", words[1])))
3✔
NEW
275
      throw format("Context '{1}' not deleted.", words[1]);
×
276

277
    // Delete legacy format and .read / .write flavours
278
    auto rc = CmdConfig::unsetConfigVariable(name, false);
3✔
279
    rc += CmdConfig::unsetConfigVariable(name + ".read", false);
3✔
280
    rc += CmdConfig::unsetConfigVariable(name + ".write", false);
3✔
281

282
    // If the currently set context was deleted, unset it
283
    if (Context::getContext().config.get("context") == words[1])
3✔
284
      CmdConfig::unsetConfigVariable("context", false);
1✔
285

286
    // Output feedback, rc should be even because only 0 (found and removed)
287
    // and 2 (not found) are aceptable return values from unsetConfigVariable
288
    if (rc % 2 != 0)
3✔
NEW
289
      throw format("Context '{1}' not deleted.", words[1]);
×
290
    else if (rc == 6)
3✔
291
      throw format("Context '{1}' not found.", words[1]);
1✔
292

293
    out << format("Context '{1}' deleted.\n", words[1]);
2✔
294
  } else
3✔
295
    throw std::string("Context name needs to be specified.");
1✔
296
}
2✔
297

298
////////////////////////////////////////////////////////////////////////////////
299
// Render a list of context names and their definitions.
300
//
301
// Returns: 0 the resulting list is non-empty, 1 otherwise
302
//
303
// Invoked with: task context list
304
// Example:      task context list
305
//
306
void CmdContext::listContexts(std::stringstream& out) {
3✔
307
  auto contexts = getContexts();
3✔
308
  if (contexts.size()) {
3✔
309
    std::sort(contexts.begin(), contexts.end());
2✔
310

311
    Table table;
2✔
312
    table.width(Context::getContext().getWidth());
2✔
313
    table.add("Name");
2✔
314
    table.add("Type");
2✔
315
    table.add("Definition");
2✔
316
    table.add("Active");
2✔
317
    setHeaderUnderline(table);
2✔
318

319
    std::string activeContext = Context::getContext().config.get("context");
4✔
320

321
    for (auto& userContext : contexts) {
5✔
322
      std::string active = "no";
3✔
323
      if (userContext == activeContext) active = "yes";
3✔
324

325
      int row = table.addRow();
3✔
326
      table.set(row, 0, userContext);
3✔
327
      table.set(row, 1, "read");
3✔
328
      table.set(row, 2, Context::getContext().getTaskContext("read", userContext));
3✔
329
      table.set(row, 3, active);
3✔
330

331
      row = table.addRow();
3✔
332
      table.set(row, 0, "");
3✔
333
      table.set(row, 1, "write");
3✔
334
      table.set(row, 2, Context::getContext().getTaskContext("write", userContext));
3✔
335
      table.set(row, 3, active);
3✔
336
    }
3✔
337

338
    out << optionalBlankLine() << table.render() << optionalBlankLine();
2✔
339
  } else
2✔
340
    throw std::string("No contexts defined.");
1✔
341
}
3✔
342

343
////////////////////////////////////////////////////////////////////////////////
344
// Sets the specified context as currently active.
345
//   - If some other context was active, the value of currently active context
346
//     is replaced, not added.
347
//   - Setting of the context does not require confirmation.
348
//
349
// Returns: 0 if the setting of the context was successful, 1 otherwise
350
//
351
// Invoked with: task context <name>
352
// Example:      task context home
353
//
354
void CmdContext::setContext(const std::vector<std::string>& words, std::stringstream& out) {
27✔
355
  auto value = words[0];
27✔
356
  auto contexts = getContexts();
27✔
357

358
  // Check that the specified context is defined
359
  if (std::find(contexts.begin(), contexts.end(), value) == contexts.end())
27✔
360
    throw format("Context '{1}' not found.", value);
2✔
361

362
  // Set the active context.
363
  // Should always succeed, as we do not require confirmation.
364
  bool success = CmdConfig::setConfigVariable("context", value, false);
25✔
365

366
  if (!success) throw format("Context '{1}' not applied.", value);
25✔
367

368
  out << format("Context '{1}' set. Use 'task context none' to remove.\n", value);
25✔
369
}
29✔
370

371
////////////////////////////////////////////////////////////////////////////////
372
// Shows the currently active context.
373
//
374
// Returns: Always returns 0.
375
//
376
// Invoked with: task context show
377
// Example:      task context show
378
//
379
void CmdContext::showContext(std::stringstream& out) {
8✔
380
  auto currentContext = Context::getContext().config.get("context");
16✔
381

382
  if (currentContext == "")
8✔
383
    out << "No context is currently applied.\n";
7✔
384
  else {
385
    out << format(
2✔
386
        "Context '{1}' with \n\n* read filter: '{2}'\n* write filter: '{3}'\n\nis currently "
387
        "applied.\n",
388
        currentContext, Context::getContext().getTaskContext("read", ""),
2✔
389
        Context::getContext().getTaskContext("write", ""));
3✔
390
  }
391
}
8✔
392

393
////////////////////////////////////////////////////////////////////////////////
394
// Unsets the currently active context.
395
//   - Unsetting of the context does not require confirmation.
396
//
397
// Returns: 0 if the unsetting of the context was successful, 1 otherwise (also
398
//          returned if no context is currently active)
399
//
400
// Invoked with: task context none
401
// Example:      task context none
402
//
403
void CmdContext::unsetContext(std::stringstream& out) {
5✔
404
  if (CmdConfig::unsetConfigVariable("context", false)) throw std::string("Context not unset.");
5✔
405

406
  out << "Context unset.\n";
4✔
407
}
4✔
408

409
////////////////////////////////////////////////////////////////////////////////
410
CmdCompletionContext::CmdCompletionContext() {
4,389✔
411
  _keyword = "_context";
4,389✔
412
  _usage = "task          _context";
4,389✔
413
  _description = "Lists all supported contexts, for completion purposes";
4,389✔
414
  _read_only = true;
4,389✔
415
  _displays_id = false;
4,389✔
416
  _needs_gc = false;
4,389✔
417
  _uses_context = false;
4,389✔
418
  _accepts_filter = false;
4,389✔
419
  _accepts_modifications = false;
4,389✔
420
  _accepts_miscellaneous = false;
4,389✔
421
  _category = Command::Category::internal;
4,389✔
422
}
4,389✔
423

424
////////////////////////////////////////////////////////////////////////////////
425
int CmdCompletionContext::execute(std::string& output) {
2✔
426
  for (auto& context : CmdContext::getContexts()) output += context + '\n';
6✔
427

428
  return 0;
2✔
429
}
430

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