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

GothenburgBitFactory / taskwarrior / 13755667881

10 Mar 2025 02:39AM UTC coverage: 85.248% (+0.1%) from 85.153%
13755667881

Pull #3811

github

web-flow
Merge 2b8e6a7ec into 5c67d2254
Pull Request #3811: Release 3.4.0

6 of 6 new or added lines in 1 file covered. (100.0%)

15 existing lines in 2 files now uncovered.

19556 of 22940 relevant lines covered (85.25%)

23392.65 hits per line

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

56.28
/src/commands/CmdNews.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 <CmdNews.h>
31
#include <Context.h>
32
#include <Datetime.h>
33
#include <Duration.h>
34
#include <Table.h>
35
#include <format.h>
36
#include <shared.h>
37
#include <util.h>
38

39
#include <cmath>
40
#include <csignal>
41
#include <iostream>
42

43
/* Adding a new version:
44
 *
45
 * - Add a new `versionX_Y_Z` method to `NewsItem`, and add news items for the new
46
 *   release.
47
 * - Call the new method in `NewsItem.all()`. Calls should be in version order.
48
 * - Test with `task news`.
49
 */
50

51
////////////////////////////////////////////////////////////////////////////////
52
CmdNews::CmdNews() {
4,558✔
53
  _keyword = "news";
4,558✔
54
  _usage = "task          news";
4,558✔
55
  _description = "Displays news about the recent releases";
4,558✔
56
  _read_only = true;
4,558✔
57
  _displays_id = false;
4,558✔
58
  _needs_gc = false;
4,558✔
59
  _needs_recur_update = false;
4,558✔
60
  _uses_context = false;
4,558✔
61
  _accepts_filter = false;
4,558✔
62
  _accepts_modifications = false;
4,558✔
63
  _accepts_miscellaneous = false;
4,558✔
64
  _category = Command::Category::misc;
4,558✔
65
}
4,558✔
66

67
////////////////////////////////////////////////////////////////////////////////
68
static void signal_handler(int s) {
×
69
  if (s == SIGINT) {
×
70
    Color footnote;
×
71
    if (Context::getContext().color()) {
×
72
      if (Context::getContext().config.has("color.footnote"))
×
73
        footnote = Color(Context::getContext().config.get("color.footnote"));
×
74
    }
75

76
    std::cout << "\n\nCome back and read about new features later!\n";
×
77

78
    std::cout << footnote.colorize(
×
79
        "\nIf you enjoy Taskwarrior, please consider supporting the project at:\n"
80
        "    https://github.com/sponsors/GothenburgBitFactory/\n");
×
81
    exit(1);
×
82
  }
83
}
×
84

85
void wait_for_enter() {
×
86
  signal(SIGINT, signal_handler);
×
87

88
  std::string dummy;
×
89
  std::cout << "\nPress enter to continue..";
×
90
  std::getline(std::cin, dummy);
×
91
  std::cout << "\33[2K\033[A\33[2K";  // Erase current line, move up, and erase again
×
92

93
  signal(SIGINT, SIG_DFL);
×
94
}
×
95

96
////////////////////////////////////////////////////////////////////////////////
97
// Holds information about single improvement / bug.
98
//
99
NewsItem::NewsItem(Version version, const std::string& title, const std::string& bg_title,
8,820✔
100
                   const std::string& background, const std::string& punchline,
101
                   const std::string& update, const std::string& reasoning,
102
                   const std::string& actions) {
8,820✔
103
  _version = version;
8,820✔
104
  _title = title;
8,820✔
105
  _bg_title = bg_title;
8,820✔
106
  _background = background;
8,820✔
107
  _punchline = punchline;
8,820✔
108
  _update = update;
8,820✔
109
  _reasoning = reasoning;
8,820✔
110
  _actions = actions;
8,820✔
111
}
8,820✔
112

113
void NewsItem::render() {
×
114
  auto config = Context::getContext().config;
×
115
  Color header;
×
116
  Color footnote;
×
117
  Color bold;
×
118
  Color underline;
×
119
  if (Context::getContext().color()) {
×
120
    bold = Color("bold");
×
121
    underline = Color("underline");
×
122
    if (config.has("color.header")) header = Color(config.get("color.header"));
×
123
    if (config.has("color.footnote")) footnote = Color(config.get("color.footnote"));
×
124
  }
125

126
  // TODO: For some reason, bold cannot be blended in 256-color terminals
127
  // Apply this workaround of colorizing twice.
128
  std::cout << bold.colorize(header.colorize(format("{1} ({2})\n", _title, _version)));
×
129
  if (_background.size()) {
×
130
    if (_bg_title.empty()) _bg_title = "Background";
×
131

132
    std::cout << "\n  " << underline.colorize(_bg_title) << std::endl << _background << std::endl;
×
133
  }
134

135
  wait_for_enter();
×
136

137
  std::cout << "  " << underline.colorize(format("What changed in {1}?\n", _version));
×
138
  if (_punchline.size()) std::cout << footnote.colorize(format("{1}\n", _punchline));
×
139

140
  if (_update.size()) std::cout << format("{1}\n", _update);
×
141

142
  wait_for_enter();
×
143

144
  if (_reasoning.size()) {
×
145
    std::cout << "  " << underline.colorize("What was the motivation behind this feature?\n")
×
146
              << _reasoning << std::endl;
×
147
    wait_for_enter();
×
148
  }
149

150
  if (_actions.size()) {
×
151
    std::cout << "  " << underline.colorize("What do I have to do?\n") << _actions << std::endl;
×
152
    wait_for_enter();
×
153
  }
154
}
×
155

156
std::vector<NewsItem> NewsItem::all() {
490✔
157
  std::vector<NewsItem> items;
490✔
158
  version2_6_0(items);
490✔
159
  version3_0_0(items);
490✔
160
  version3_1_0(items);
490✔
161
  version3_2_0(items);
490✔
162
  version3_3_0(items);
490✔
163
  version3_4_0(items);
490✔
164
  return items;
490✔
165
}
×
166

167
////////////////////////////////////////////////////////////////////////////////
168
// Generate the highlights for the 2.6.0 version.
169
//
170
// - XDG directory mode (high)
171
// - Support for Unicode 11 characters (high)
172
// - 64 bit values, UDAs, Datetime values until year 9999 (high)
173
// - Config context variables
174
// - Reports outside of context
175
// - Environment variables in taskrc (high)
176
// - Waiting is a virtual concept (high)
177
// - Improved parser and task display mechanism
178
// - The .by attribute modifier
179
// - Exporting a report
180
// - Multi-day holidays
181
void NewsItem::version2_6_0(std::vector<NewsItem>& items) {
490✔
182
  Version version("2.6.0");
490✔
183
  /////////////////////////////////////////////////////////////////////////////
184
  // - Writeable context
185

186
  // Detect whether user uses any contexts
187
  auto config = Context::getContext().config;
490✔
188
  std::stringstream advice;
490✔
189

190
  auto defined = CmdContext::getContexts();
490✔
191
  if (defined.size()) {
490✔
192
    // Detect the old-style contexts
193
    std::vector<std::string> old_style;
18✔
194
    std::copy_if(defined.begin(), defined.end(), std::back_inserter(old_style),
18✔
195
                 [&](auto& name) { return config.has("context." + name); });
54✔
196

197
    if (old_style.size()) {
18✔
198
      advice << format("  You have {1} defined contexts, out of which {2} are old-style:\n",
×
199
                       defined.size(),
200
                       std::count_if(defined.begin(), defined.end(),
201
                                     [&](auto& name) { return config.has("context." + name); }));
×
202

203
      for (auto context : defined) {
×
204
        std::string old_definition = config.get("context." + context);
×
205
        if (old_definition != "") advice << format("  * {1}: {2}\n", context, old_definition);
×
206
      }
×
207

208
      advice << "\n"
209
                "  These need to be migrated to new-style, which uses context.<name>.read and\n"
210
                "  context.<name>.write config variables. Please run the following commands:\n";
×
211

212
      for (auto context : defined) {
×
213
        std::string old_definition = config.get("context." + context);
×
214
        if (old_definition != "")
×
215
          advice << format("  $ task context define {1} '{2}'\n", context, old_definition);
×
216
      }
×
217

218
      advice
219
          << "\n"
220
             "  Please check these filters are also valid modifications. If a context filter is "
221
             "not\n"
222
             "  a valid modification, you can set the context.<name>.write configuration variable "
223
             "to\n"
224
             "  specify the write context explicitly. Read more in CONTEXT section of man taskrc.";
×
225
    } else
226
      advice << "  You don't have any old-style contexts defined, so you're good to go as is!";
18✔
227
  } else
18✔
228
    advice << "  You don't have any contexts defined, so you're good to go as is!\n"
229
              "  Read more about how to use contexts in CONTEXT section of 'man task'.";
472✔
230

231
  NewsItem writeable_context(
232
      version, "'Writeable' context", "Background - what is context?",
233
      "  The 'context' is a feature (introduced in 2.5.0) that allows users to apply a\n"
234
      "  predefined filter to all task reports.\n"
235
      "  \n"
236
      "    $ task context define work \"project:Work or +urgent\"\n"
237
      "    $ task context work\n"
238
      "    Context 'work' set. Use 'task context none' to remove.\n"
239
      "  \n"
240
      "  Now if we proceed to add two tasks:\n"
241
      "    $ task add Talk to Jeff pro:Work\n"
242
      "    $ task add Call mom pro:Personal\n"
243
      "  \n"
244
      "    $ task\n"
245
      "    ID Age   Project Description  Urg\n"
246
      "     1 16s   Work    Talk to Jeff    1\n"
247
      "  \n"
248
      "  The task \"Call mom\" will not be listed, because it does not match\n"
249
      "  the active context (its project is 'Personal' and not 'Work').",
250
      "  The currently active context definition is now applied as default modifications\n"
251
      "  when creating new tasks using 'task add' and 'task log'.",
252
      "  \n"
253
      "  Consider following example, using context 'work' defined as 'project:Work' above:\n"
254
      "  \n"
255
      "    $ task context work\n"
256
      "    $ task add Talk to Jeff\n"
257
      "    $ task\n"
258
      "    ID Age  Project Description  Urg \n"
259
      "     1 1s   Work    Talk to Jeff    1\n"
260
      "            ^^^^^^^\n"
261
      "  \n"
262
      "  Note that project attribute was set to 'Work' automatically.",
263
      "  This was a popular feature request. Now, if you have a context active,\n"
264
      "  newly added tasks no longer \"fall outside\" of the context by default.",
265
      advice.str());
6,370✔
266
  items.push_back(writeable_context);
490✔
267

268
  /////////////////////////////////////////////////////////////////////////////
269
  // - 64-bit datetime support
270

271
  NewsItem uint64_support(
272
      version, "Support for 64-bit timestamps and numeric values", "", "",
273
      "  Taskwarrior now supports 64-bit timestamps, making it possible to set due dates\n"
274
      "  and other date attributes beyond 19 January 2038 (limit of 32-bit timestamps).\n",
275
      "  The current limit is 31 December 9999 for display reasons (last 4-digit year).",
276
      "  With each year passing by faster than the last, setting tasks for 2040s\n"
277
      "  is not as unfeasible as it once was.",
278
      "  Don't forget that 50-year anniversary and 'task add' a long-term task today!");
6,370✔
279
  items.push_back(uint64_support);
490✔
280

281
  /////////////////////////////////////////////////////////////////////////////
282
  // - Waiting is a virtual status
283

284
  NewsItem waiting_status(
285
      version, "Deprecation of the status:waiting", "",
286
      "  If a task has a 'wait' attribute set to a date in the future, it is modified\n"
287
      "  to have a 'waiting' status. Once that date is no longer in the future, the status\n"
288
      "  is modified to back to 'pending'.",
289
      "  The 'waiting' value of status is deprecated, instead users should use +WAITING\n"
290
      "  virtual tag, or explicitly query for wait.after:now (the two are equivalent).",
291
      "  \n"
292
      "  The status:waiting query still works in 2.6.0, but support will be dropped in 3.0.",
293
      "",
294
      "  In your custom report definitions, the following expressions should be replaced:\n"
295
      "  * 'status:pending or status:waiting' should be replaced by 'status:pending'\n"
296
      "  * 'status:pending' should be replaced by 'status:pending -WAITING'");
6,370✔
297
  items.push_back(waiting_status);
490✔
298

299
  /////////////////////////////////////////////////////////////////////////////
300
  // - Support for environment variables in the taskrc
301

302
  NewsItem env_vars(
303
      version, "Environment variables in the taskrc", "", "",
304
      "  Taskwarrior now supports expanding environment variables in the taskrc file,\n"
305
      "  allowing users to customize the behaviour of 'task' based on the current env.\n",
306
      "  The environment variables can either be used in paths, or as separate values:\n"
307
      "    data.location=$XDG_DATA_HOME/task/\n"
308
      "    default.project=$PROJECT",
309
      "", "");
6,370✔
310
  items.push_back(env_vars);
490✔
311

312
  /////////////////////////////////////////////////////////////////////////////
313
  // - Reports outside of context
314

315
  NewsItem contextless_reports(
316
      version, "Context-less reports", "",
317
      "  By default, every report is affected by currently active context.",
318
      "  You can now make a selected report ignore currently active context by setting\n"
319
      "  'report.<name>.context' configuration variable to 0.",
320
      "",
321
      "  This is useful for users who utilize a single place (such as project:Inbox)\n"
322
      "  to collect their new tasks that are then triaged on a regular basis\n"
323
      "  (such as in GTD methodology).\n"
324
      "  \n"
325
      "  In such a case, defining a report that filters for project:Inbox and making it\n"
326
      "  fully accessible from any context is a major usability improvement.",
327
      "");
6,370✔
328
  items.push_back(contextless_reports);
490✔
329

330
  /////////////////////////////////////////////////////////////////////////////
331
  // - Exporting a particular report
332

333
  NewsItem exportable_reports(
334
      version, "Exporting a particular report", "", "",
335
      "  You can now export the tasks listed by a particular report as JSON by simply\n"
336
      "  calling 'task export <report>'.\n",
337
      "  The export mirrors the filter and the sort order of the report.",
338
      "  This feature can be used to quickly process the data displayed in a particular\n"
339
      "  report using other CLI tools. For example, the following oneliner\n"
340
      "  \n"
341
      "      $ task export next | jq '.[].urgency' | datamash mean 1\n"
342
      "      3.3455535142857\n"
343
      "  \n"
344
      "  combines jq and GNU datamash to compute average urgency of the tasks displayed\n"
345
      "  in the 'next' report.",
346
      "");
6,370✔
347
  items.push_back(exportable_reports);
490✔
348

349
  /////////////////////////////////////////////////////////////////////////////
350
  // - Multi-day holidays
351

352
  NewsItem multi_holidays(
353
      version, "Multi-day holidays", "",
354
      "  Holidays are currently used in 'task calendar' to visualize the workload during\n"
355
      "  the upcoming weeks/months. Up to date country-specific holiday data files can be\n"
356
      "  obtained from our website, holidata.net.",
357
      "  Instead of single-day holiday entries only, Taskwarrior now supports holidays\n"
358
      "  that span a range of days (i.e. vacation).\n",
359
      "  Use a holiday.<name>.start and holiday.<name>.end to configure a multi-day holiday:\n"
360
      "  \n"
361
      "      holiday.sysadmin.name=System Administrator Appreciation Week\n"
362
      "      holiday.sysadmin.start=20100730\n"
363
      "      holiday.sysadmin.end=20100805",
364
      "", "");
6,370✔
365
  items.push_back(multi_holidays);
490✔
366

367
  /////////////////////////////////////////////////////////////////////////////
368
  // - Unicode 12
369

370
  NewsItem unicode_12(
371
      version, "Extended Unicode support (Unicode 12)", "", "",
372
      "  The support for Unicode character set was improved to support Unicode 12.\n"
373
      "  This means better support for various language-specific characters - and emojis!",
374
      "",
375
      "  Extended unicode support for language-specific characters helps non-English speakers.\n"
376
      "  While most users don't enter emojis as task metadata, automated task creation tools,\n"
377
      "  such as bugwarrior, might create tasks with exotic Unicode data.",
378
      "  You can try it out - 'task add Prepare for an 👽 invasion!'");
6,370✔
379
  items.push_back(unicode_12);
490✔
380

381
  /////////////////////////////////////////////////////////////////////////////
382
  // - The .by attribute modifier
383

384
  NewsItem by_modifier(
385
      version, "The .by attribute modifier", "", "",
386
      "  A new attribute modifier '.by' was introduced, equivalent to the operator '<='.\n",
387
      "  This modifier can be used to list all tasks due by the end of the months,\n"
388
      "  including the last day of the month, using: 'due.by:eom' query",
389
      "  There was no convenient way to express '<=' relation using attribute modifiers.\n"
390
      "  As a workaround, instead of 'due.by:eom' one could use 'due.before:eom+1d',\n"
391
      "  but that requires a certain amount of mental overhead.",
392
      "");
6,370✔
393
  items.push_back(by_modifier);
490✔
394

395
  /////////////////////////////////////////////////////////////////////////////
396
  // - Context-specific configuration overrides
397

398
  NewsItem context_config(
399
      version, "Context-specific configuration overrides", "", "",
400
      "  Any context can now define context-specific configuration overrides\n"
401
      "  via context.<name>.rc.<setting>=<value>.\n",
402
      "  This allows the user to customize the behaviour of Taskwarrior in a given context,\n"
403
      "  for example, to change the default command in the 'work' context to 'overdue':\n"
404
      "\n"
405
      "      $ task config context.work.rc.default.command overdue\n"
406
      "\n"
407
      "  Another example would be to ensure that while context 'work' is active, tasks get\n"
408
      "  stored in a ~/.worktasks directory:\n"
409
      "\n"
410
      "      $ task config context.work.rc.data.location=~/.worktasks",
411
      "", "");
6,370✔
412
  items.push_back(context_config);
490✔
413

414
  /////////////////////////////////////////////////////////////////////////////
415
  // - XDG config home support
416

417
  NewsItem xdg_support(
418
      version, "Support for XDG Base Directory Specification", "",
419
      "  The XDG Base Directory specification provides standard locations to store\n"
420
      "  application data, configuration, state, and cached data in order to keep $HOME\n"
421
      "  clutter-free. The locations are usually set to ~/.local/share, ~/.config,\n"
422
      "  ~/.local/state and ~/.cache respectively.",
423
      "  If taskrc is not found at '~/.taskrc', Taskwarrior will attempt to find it\n"
424
      "  at '$XDG_CONFIG_HOME/task/taskrc' (defaults to '~/.config/task/taskrc').",
425
      "",
426
      "  This allows users to fully follow XDG Base Directory Spec by moving their taskrc:\n"
427
      "      $ mkdir $XDG_CONFIG_HOME/task\n"
428
      "      $ mv ~/.taskrc $XDG_CONFIG_HOME/task/taskrc\n\n"
429
      "  and further setting:\n"
430
      "      data.location=$XDG_DATA_HOME/task/\n"
431
      "      hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n"
432
      "  Solutions in the past required symlinks or more cumbersome configuration overrides.",
433
      "  If you configure your data.location and hooks.location as above, ensure\n"
434
      "  that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n"
435
      "  otherwise they're going to expand to empty string. Alternatively you can\n"
436
      "  hardcode the desired paths on your system.");
6,370✔
437
  items.push_back(xdg_support);
490✔
438

439
  /////////////////////////////////////////////////////////////////////////////
440
  // - Update holiday data
441

442
  NewsItem holidata_2022(
443
      version, "Updated holiday data for 2022", "", "",
444
      "  Holiday data has been refreshed for 2022 and five more holiday locales\n"
445
      "  have been added: fr-CA, hu-HU, pt-BR, sk-SK and sv-FI.",
446
      "",
447
      "  Refreshing the holiday data is part of every release. The addition of the new\n"
448
      "  locales allows us to better support users in those particular countries.");
6,370✔
449
  items.push_back(holidata_2022);
490✔
450
}
490✔
451

452
void NewsItem::version3_0_0(std::vector<NewsItem>& items) {
490✔
453
  Version version("3.0.0");
490✔
454
  NewsItem sync{
455
      version,
456
      /*title=*/"New data model and sync backend",
457
      /*bg_title=*/"",
458
      /*background=*/"",
459
      /*punchline=*/
460
      "The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n"
461
      "supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n"
462
      "although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n"
463
      "For details. As part of this change, the on-disk storage format has also changed.\n",
464
      /*update=*/
465
      "This is a breaking upgrade: you must export your task database from 2.x and re-import\n"
466
      "it into 3.x. Hooks run during task import, so if you have any hooks defined,\n"
467
      "temporarily disable them for this operation.\n\n"
468
      "See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior "
469
      "3.0.",
470
  };
6,860✔
471
  items.push_back(sync);
490✔
472
}
490✔
473

474
void NewsItem::version3_1_0(std::vector<NewsItem>& items) {
490✔
475
  Version version("3.1.0");
490✔
476
  NewsItem purge{
477
      version,
478
      /*title=*/"Purging Tasks, Manually or Automatically",
479
      /*bg_title=*/"",
480
      /*background=*/"",
481
      /*punchline=*/
482
      "Support for `task purge` has been restored, and new support added for automatically\n"
483
      "expiring old tasks.\n\n",
484
      /*update=*/
485
      "The `task purge` command removes tasks entirely, in contrast to `task delete` which merely\n"
486
      "sets the task status to 'Deleted'. This functionality existed in versions 2.x but was\n"
487
      "temporarily removed in 3.0.\n\n"
488
      "The new `purge.on-sync` configuration parameter controls automatic purging of old tasks.\n"
489
      "An old task is one with status 'Deleted' that has not been modified in 180 days. This\n"
490
      "functionality is optional and not enabled by default."};
6,860✔
491
  items.push_back(purge);
490✔
492
  NewsItem news{
493
      version,
494
      /*title=*/"Improved 'task news'",
495
      /*bg_title=*/"",
496
      /*background=*/"",
497
      /*punchline=*/
498
      "The news you are reading now is improved.\n\n",
499
      /*update=*/
500
      "The `task news` command now always shows all new information, not just 'major' news,\n"
501
      "and will only show that news once. New installs will assume all news has been read.\n"
502
      "Finally, news can be completely hidden by removing 'news' from the 'verbose' config."};
6,860✔
503
  items.push_back(news);
490✔
504
}
490✔
505

506
void NewsItem::version3_2_0(std::vector<NewsItem>& items) {
490✔
507
  Version version("3.2.0");
490✔
508
  NewsItem info{
509
      version,
510
      /*title=*/"`task info` Journal Restored",
511
      /*bg_title=*/"",
512
      /*background=*/"",
513
      /*punchline=*/"",
514
      /*update=*/
515
      "Support for the \"journal\" output in `task info` has been restored. The command now\n"
516
      "displays a list of changes made to the task, with timestamps.\n\n"};
6,860✔
517
  items.push_back(info);
490✔
518
}
490✔
519

520
void NewsItem::version3_3_0(std::vector<NewsItem>& items) {
490✔
521
  Version version("3.3.0");
490✔
522
  NewsItem info{
523
      version,
524
      /*title=*/"AWS S3 Sync",
525
      /*bg_title=*/"",
526
      /*background=*/"",
527
      /*punchline=*/"Use an AWS S3 bucket to sync Taskwarrior",
528
      /*update=*/
529
      "Taskwarrior now supports AWS as a backend for sync, in addition to existing support\n"
530
      "for GCP and taskchampion-sync-server. See `man task-sync` for details.\n\n"};
6,860✔
531
  items.push_back(info);
490✔
532
}
490✔
533

534
void NewsItem::version3_4_0(std::vector<NewsItem>& items) {
490✔
535
  Version version("3.4.0");
490✔
536
  NewsItem info{
537
      version,
538
      /*title=*/"Read-Only Access",
539
      /*bg_title=*/"",
540
      /*background=*/"",
541
      /*punchline=*/"Some Taskwarrior commands operate faster in read-only mode",
542
      /*update=*/
543
      "Some commands do not need to write to the DB, so can open it in read-only\n"
544
      "mode and thus more quickly. This does not include reports (task lists),\n"
545
      "unless the `gc` config is false. Use `rc.gc=0` in command-lines to allow\n"
546
      "read-only access.\n\n"};
6,860✔
547
  items.push_back(info);
490✔
548
}
490✔
549

550
////////////////////////////////////////////////////////////////////////////////
551
int CmdNews::execute(std::string& output) {
×
552
  auto words = Context::getContext().cli2.getWords();
×
UNCOV
553
  auto config = Context::getContext().config;
×
554

555
  // Supress compiler warning about unused argument
UNCOV
556
  output = "";
×
557

558
  std::vector<NewsItem> items = NewsItem::all();
×
559
  Version news_version(Context::getContext().config.get("news.version"));
×
UNCOV
560
  Version current_version = Version::Current();
×
561

562
  // 2.6.0 is the earliest version with news support.
UNCOV
563
  if (!news_version.is_valid()) news_version = Version("2.6.0");
×
564

UNCOV
565
  signal(SIGINT, signal_handler);
×
566

567
  // Remove items that have already been shown
568
  items.erase(std::remove_if(items.begin(), items.end(),
×
569
                             [&](const NewsItem& n) { return n._version <= news_version; }),
×
UNCOV
570
              items.end());
×
571

572
  Color bold = Color("bold");
×
573
  if (items.empty()) {
×
UNCOV
574
    std::cout << bold.colorize("You are up to date!\n");
×
575
  } else {
576
    // Print release notes
577
    std::cout << bold.colorize(
×
UNCOV
578
        format("\n"
×
579
               "================================================\n"
580
               "Taskwarrior {1} through {2} Release Highlights\n"
581
               "================================================\n",
UNCOV
582
               news_version, current_version));
×
583

584
    for (unsigned short i = 0; i < items.size(); i++) {
×
585
      std::cout << format("\n({1}/{2}) ", i + 1, items.size());
×
UNCOV
586
      items[i].render();
×
587
    }
UNCOV
588
    std::cout << "Thank you for catching up on the new features!\n";
×
589
  }
UNCOV
590
  wait_for_enter();
×
591

592
  return 0;
×
UNCOV
593
}
×
594

595
bool CmdNews::should_nag() {
657✔
596
  if (!Context::getContext().verbose("news")) {
1,971✔
597
    return false;
146✔
598
  }
599

600
  Version news_version(Context::getContext().config.get("news.version"));
1,022✔
601
  if (!news_version.is_valid()) news_version = Version("2.6.0");
511✔
602

603
  Version current_version = Version::Current();
511✔
604

605
  if (news_version >= current_version) {
511✔
606
    return false;
21✔
607
  }
608

609
  // Check if there are actually any interesting news items to show.
610
  std::vector<NewsItem> items = NewsItem::all();
490✔
611
  for (auto& item : items) {
6,370✔
612
    if (item._version > news_version) {
6,370✔
613
      return true;
490✔
614
    }
615
  }
616

UNCOV
617
  return false;
×
618
}
490✔
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