mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
451 lines
16 KiB
C++
451 lines
16 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
//
|
|
// https://www.opensource.org/licenses/mit-license.php
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <cmake.h>
|
|
#include <CmdNews.h>
|
|
#include <iostream>
|
|
#include <csignal>
|
|
#include <Table.h>
|
|
#include <Context.h>
|
|
#include <shared.h>
|
|
#include <format.h>
|
|
#include <util.h>
|
|
#include <main.h>
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
CmdNews::CmdNews ()
|
|
{
|
|
_keyword = "news";
|
|
_usage = "task news";
|
|
_description = "Displays news about the recent releases";
|
|
_read_only = true;
|
|
_displays_id = false;
|
|
_needs_gc = false;
|
|
_uses_context = false;
|
|
_accepts_filter = false;
|
|
_accepts_modifications = false;
|
|
_accepts_miscellaneous = true;
|
|
_category = Command::Category::misc;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static void signal_handler (int s)
|
|
{
|
|
if (s == SIGINT)
|
|
{
|
|
std::cout << "\n\nCome back and read about new features later!\n";
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
void wait_for_enter ()
|
|
{
|
|
signal (SIGINT, signal_handler);
|
|
|
|
std::string dummy;
|
|
std::cout << "\nPress enter to continue..";
|
|
std::getline (std::cin, dummy);
|
|
std::cout << "\33[2K\033[A\33[2K"; // Erase current line, move up, and erase again
|
|
|
|
signal (SIGINT, SIG_DFL);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Holds information about single improvement / bug.
|
|
//
|
|
NewsItem::NewsItem (
|
|
bool major,
|
|
const std::string& title,
|
|
const std::string& bg_title,
|
|
const std::string& background,
|
|
const std::string& punchline,
|
|
const std::string& update,
|
|
const std::string& reasoning,
|
|
const std::string& actions
|
|
) {
|
|
_major = major;
|
|
_title = title;
|
|
_bg_title = bg_title;
|
|
_background = background;
|
|
_punchline = punchline;
|
|
_update = update;
|
|
_reasoning = reasoning;
|
|
_actions = actions;
|
|
}
|
|
|
|
void NewsItem::render () {
|
|
auto config = Context::getContext ().config;
|
|
Color header;
|
|
Color footnote;
|
|
Color bold;
|
|
Color underline;
|
|
if (Context::getContext ().color ()) {
|
|
bold = Color ("bold");
|
|
underline = Color ("underline");
|
|
if (config.has ("color.header"))
|
|
header = Color (config.get ("color.header"));
|
|
if (config.has ("color.footnote"))
|
|
footnote = Color (config.get ("color.footnote"));
|
|
}
|
|
|
|
// TODO: For some reason, bold cannot be blended in 256-color terminals
|
|
// Apply this workaround of colorizing twice.
|
|
std::cout << bold.colorize (header.colorize (format ("{1}\n", _title)));
|
|
if (_background.size ()) {
|
|
if (_bg_title.empty ())
|
|
_bg_title = "Background";
|
|
|
|
std::cout << "\n " << underline.colorize (_bg_title) << std::endl
|
|
<< _background << std::endl;
|
|
}
|
|
|
|
wait_for_enter ();
|
|
|
|
std::cout << " " << underline.colorize ("What changed in 2.6.0?\n");
|
|
if (_punchline.size ())
|
|
std::cout << footnote.colorize (format ("{1}\n", _punchline));
|
|
|
|
if (_update.size ())
|
|
std::cout << format ("{1}\n", _update);
|
|
|
|
wait_for_enter ();
|
|
|
|
if (_reasoning.size ()) {
|
|
std::cout << " " << underline.colorize ("What is the motivation for this feature?\n")
|
|
<< _reasoning << std::endl;
|
|
wait_for_enter ();
|
|
}
|
|
|
|
if (_actions.size ()) {
|
|
std::cout << " " << underline.colorize ("What do I have to do?\n")
|
|
<< _actions << std::endl;
|
|
wait_for_enter ();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Generate the highlights for the 2.6.0 version.
|
|
//
|
|
// - XDG directory mode (high)
|
|
// - Support for Unicode 11 characters (high)
|
|
// - 64 bit values, UDAs, Datetime values until year 9999 (high)
|
|
// - Config context variables
|
|
// - Reports outside of context
|
|
// - Environment variables in taskrc (high)
|
|
// - Waiting is a virtual concept (high)
|
|
// - Improved parser and task display mechanism
|
|
// - The .by attribute modifier
|
|
// - Exporting a report
|
|
// - Multi-day holidays
|
|
void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Writeable context (major)
|
|
|
|
// Detect whether user uses any contexts
|
|
auto config = Context::getContext ().config;
|
|
std::stringstream advice;
|
|
|
|
auto defined = CmdContext::getContexts ();
|
|
if (defined.size ())
|
|
{
|
|
// Detect the old-style contexts
|
|
std::vector<std::string> old_style;
|
|
std::copy_if (
|
|
defined.begin(),
|
|
defined.end(),
|
|
std::back_inserter(old_style),
|
|
[&](auto& name){return config.has ("context." + name);}
|
|
);
|
|
|
|
if (old_style.size ())
|
|
{
|
|
advice << format (
|
|
" You have {1} defined contexts, out of which {2} are old-style:\n",
|
|
defined.size (),
|
|
std::count_if (
|
|
defined.begin (),
|
|
defined.end (),
|
|
[&](auto& name){return config.has ("context." + name);}
|
|
));
|
|
|
|
for (auto context: defined) {
|
|
std::string old_definition = config.get ("context." + context);
|
|
if (old_definition != "")
|
|
advice << format (" * {1}: {2}\n", context, old_definition);
|
|
}
|
|
|
|
advice << "\n"
|
|
" These need to be migrated to new-style, which uses context.<name>.read and\n"
|
|
" context.<name>.write config variables. Please run the following commands:\n";
|
|
|
|
for (auto context: defined) {
|
|
std::string old_definition = config.get ("context." + context);
|
|
if (old_definition != "")
|
|
advice << format (" $ task context define {1} '{2}'\n", context, old_definition);
|
|
}
|
|
|
|
advice << "\n"
|
|
" Please check these filters are also valid modifications. If a context filter is not\n"
|
|
" a valid modification, you can set the context.<name>.write configuration variable to\n"
|
|
" specify the write context explicitly. Read more in CONTEXT section of man taskrc.";
|
|
}
|
|
else
|
|
advice << " You don't have any old-style contexts defined, so you're good to go as is!";
|
|
}
|
|
else
|
|
advice << " You don't have any contexts defined, so you're good to go as is!\n"
|
|
" Read more about how to use contexts in CONTEXT section of 'man task'.";
|
|
|
|
NewsItem writeable_context (
|
|
true,
|
|
"'Writeable' context",
|
|
"Background - what is context?",
|
|
" The 'context' is a feature (introduced in 2.5.0) that allows users to apply a\n"
|
|
" predefined filter to all task reports.\n"
|
|
" \n"
|
|
" $ task context define work \"project:Work or +urgent\"\n"
|
|
" $ task context work\n"
|
|
" Context 'work' set. Use 'task context none' to remove.\n"
|
|
" \n"
|
|
" Now if we proceed to add two tasks:\n"
|
|
" $ task add Talk to Jeff pro:Work\n"
|
|
" $ task add Call mom pro:Personal\n"
|
|
" \n"
|
|
" $ task\n"
|
|
" ID Age Project Description Urg\n"
|
|
" 1 16s Work Talk to Jeff 1\n"
|
|
" \n"
|
|
" The task \"Call mom\" will not be listed, because it does not match\n"
|
|
" the active context (its project is 'Personal' and not 'Work').",
|
|
" The currently active context definition is now applied as default modifications\n"
|
|
" when creating new tasks using 'task add' and 'task log'.",
|
|
" \n"
|
|
" Consider following example, using contex 'work' defined as 'project:Work' above:\n"
|
|
" \n"
|
|
" $ task context work\n"
|
|
" $ task add Talk to Jeff\n"
|
|
" $ task\n"
|
|
" ID Age Project Description Urg \n"
|
|
" 1 1s Work Talk to Jeff 1\n"
|
|
" ^^^^^^^\n"
|
|
" \n"
|
|
" Note that project attribute was set to 'Work' automatically",
|
|
" This was a popular feature request. Now, if you have a context active,\n"
|
|
" newly added tasks no longer \"fall outside\" of the context by default.",
|
|
advice.str ()
|
|
);
|
|
items.push_back(writeable_context);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - 64-bit datetime support (major)
|
|
|
|
NewsItem uint64_support (
|
|
false,
|
|
"Support for 64-bit timestamps and numeric values",
|
|
"",
|
|
"",
|
|
" Taskwarrior now supports 64-bit timestamps, making it possible to set due dates\n"
|
|
" and other date attributes beyond 19 January 2038 (limit of 32-bit timestamps).\n",
|
|
" The current limit is 31 December 9999 for display reasons (last 4-digit year).",
|
|
" With each year passing by faster than the last, setting tasks for 2040s\n"
|
|
" is not as unfeasible as it once was.",
|
|
" Don't forget that 50-year anniversary and 'task add' a long-term task today!"
|
|
);
|
|
items.push_back(uint64_support);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Reports outside of context
|
|
|
|
NewsItem contextless_reports (
|
|
false,
|
|
"Context-less reports",
|
|
"",
|
|
" By default, every report is affected by currently active context.",
|
|
" You can now make a selected report ignore currently active context by setting\n"
|
|
" 'report.<name>.context' configuration variable to 0.",
|
|
"",
|
|
" This is useful for users who utilize a single place (such as project:Inbox)\n"
|
|
" to collect their new tasks that are then triaged on a regular basis\n"
|
|
" (such as in GTD methodology).\n"
|
|
" \n"
|
|
" In such a case, defining a report that filters for project:Inbox and making it\n"
|
|
" fully accessible from any context is a major usability improvement.",
|
|
""
|
|
);
|
|
items.push_back(contextless_reports);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Waiting is a virtual status
|
|
|
|
NewsItem waiting_status (
|
|
false,
|
|
"Deprecation of the status:waiting",
|
|
"",
|
|
" If a task has a 'wait' attribute set to a date in the future, it is modified.\n"
|
|
" to have a 'waiting' status. Once that date is no longer in the future, the status\n"
|
|
" is modified to back to 'pending'.",
|
|
" The 'waiting' value of status is deprecated, instead users should use +WAITING\n"
|
|
" virtual tag, or explicitly query for wait.after:now (the two are equivalent).",
|
|
" \n"
|
|
" The status:waiting query still works in 2.6.0, but support will be dropped in 3.0.",
|
|
"",
|
|
" In your custom report definitions, the following expressions should be replaced:\n"
|
|
" * 'status:pending or status:waiting' should be replaced by 'status:pending'\n"
|
|
" * 'status:pending' should be replaced by 'status:pending -WAITING'"
|
|
);
|
|
items.push_back(waiting_status);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Support for environment variables in the taskrc
|
|
|
|
NewsItem env_vars (
|
|
false,
|
|
"Environment variables in the taskrc",
|
|
"",
|
|
"",
|
|
" Taskwarrior now supports expanding environment variables in the taskrc file,\n"
|
|
" allowing users to customize the behaviour of 'task' based on the current env.\n",
|
|
" The environment variables can either be used in paths, or as separate values:\n"
|
|
" data.location=$XDG_DATA_HOME/task/\n"
|
|
" default.project=$PROJECT",
|
|
"",
|
|
""
|
|
);
|
|
items.push_back(env_vars);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Exporting a particular report
|
|
|
|
NewsItem exportable_reports (
|
|
false,
|
|
"Exporting a particular report",
|
|
"",
|
|
"",
|
|
" You can now export the tasks listed by a particular report as JSON by simply\n"
|
|
" calling 'task export <report>'.\n",
|
|
" The export mirrors the filter and the sort order of the report.",
|
|
" This feature can be used to quickly process the data displayed in a particular\n"
|
|
" report using other CLI tools. For example, the following oneliner\n"
|
|
" \n"
|
|
" $ task export next | jq '.[].urgency' | datamash mean 1\n"
|
|
" 3.3455535142857\n"
|
|
" \n"
|
|
" combines jq and GNU datamash to compute average urgency of the tasks displayed\n"
|
|
" in the 'next' report.",
|
|
""
|
|
);
|
|
items.push_back(exportable_reports);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// - Multi-day holidays
|
|
|
|
NewsItem multi_holidays (
|
|
false,
|
|
"Multi-day holidays",
|
|
"",
|
|
" Holidays are currently used in 'task calendar' to visualize the workload during\n"
|
|
" the upcoming weeks/months. Up to date country-specific holiday data files can be\n"
|
|
" obtained from our website, holidata.net",
|
|
" Instead of single-day holiday entries only, Taskwarrior now supports holidays\n"
|
|
" that span a range of days (i.e. vacation).\n",
|
|
" Use a holday.<name>.start and holiday.<name>.end to configure a multi-day holiday:\n"
|
|
" \n"
|
|
" holiday.sysadmin.name=System Administrator Appreciation Week\n"
|
|
" holiday.sysadmin.start=20100730\n"
|
|
" holiday.sysadmin.end=20100805",
|
|
"",
|
|
""
|
|
);
|
|
items.push_back(multi_holidays);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int CmdNews::execute (std::string& output)
|
|
{
|
|
auto words = Context::getContext ().cli2.getWords ();
|
|
auto config = Context::getContext ().config;
|
|
|
|
// TODO: 2.6.0 is the only version with explicit release notes, but in the
|
|
// future we need to only execute yet unread release notes
|
|
std::vector<NewsItem> items;
|
|
version2_6_0 (items);
|
|
|
|
// Determine whether to use full or short summary
|
|
std::vector <std::string> options {"full", "short"};
|
|
std::vector <std::string> matches;
|
|
|
|
signal (SIGINT, signal_handler);
|
|
|
|
do
|
|
{
|
|
std::cout << format (
|
|
"Do you want to see full summary ({1} feature(s)) or a short one ({2} feature(s))? (full/short) ",
|
|
items.size (),
|
|
std::count_if (items.begin (), items.end (), [](const NewsItem& n){return n._major;})
|
|
);
|
|
|
|
std::string answer;
|
|
std::getline (std::cin, answer);
|
|
answer = std::cin.eof () ? "full" : lowerCase (trim (answer));
|
|
|
|
autoComplete (answer, options, matches, 1); // Hard-coded 1.
|
|
}
|
|
while (! std::cin.eof () && matches.size () != 1);
|
|
|
|
signal (SIGINT, SIG_DFL);
|
|
bool full_summary = matches.size () == 1 && matches[0] == "full" ? true : false;
|
|
|
|
// Remove non-major items if displaying a non-full (abbreviated) summary
|
|
if (! full_summary)
|
|
items.erase (
|
|
std::remove_if (items.begin (), items.end (), [](const NewsItem& n){return n._major == false;}),
|
|
items.end ()
|
|
);
|
|
|
|
// Print release notes
|
|
Color bold = Color ("bold");
|
|
std::cout << bold.colorize (
|
|
"\n"
|
|
"===============================\n"
|
|
"Taskwarrior 2.6.0 Release Notes\n"
|
|
"===============================\n"
|
|
);
|
|
|
|
for (unsigned short i=0; i < items.size (); i++) {
|
|
std::cout << format ("\n({1}/{2}) ", i+1, items.size ());
|
|
items[i].render ();
|
|
}
|
|
|
|
// Set a mark in the config to remember which version's release notes were displayed
|
|
if (config.get ("news.version") == "2.6.0")
|
|
output = "Repetition is the mother of all learning!\n";
|
|
else {
|
|
CmdConfig::setConfigVariable ("news.version", "2.6.0", false);
|
|
output = "Thank you for catching up on the new features!\n";
|
|
}
|
|
|
|
return 0;
|
|
}
|