add initial bulk run from pre-commit over all files
This commit is contained in:
@@ -28,17 +28,18 @@
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <CmdNews.h>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
#include <Table.h>
|
||||
#include <Context.h>
|
||||
#include <Datetime.h>
|
||||
#include <Duration.h>
|
||||
#include <shared.h>
|
||||
#include <Table.h>
|
||||
#include <format.h>
|
||||
#include <util.h>
|
||||
#include <main.h>
|
||||
#include <shared.h>
|
||||
#include <util.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
|
||||
/* Adding a new version:
|
||||
*
|
||||
@@ -49,67 +50,56 @@
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
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;
|
||||
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 = false;
|
||||
_category = Command::Category::misc;
|
||||
_category = Command::Category::misc;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
static void signal_handler (int s)
|
||||
{
|
||||
if (s == SIGINT)
|
||||
{
|
||||
static void signal_handler(int s) {
|
||||
if (s == SIGINT) {
|
||||
Color footnote;
|
||||
if (Context::getContext ().color ()) {
|
||||
if (Context::getContext ().config.has ("color.footnote"))
|
||||
footnote = Color (Context::getContext ().config.get ("color.footnote"));
|
||||
if (Context::getContext().color()) {
|
||||
if (Context::getContext().config.has("color.footnote"))
|
||||
footnote = Color(Context::getContext().config.get("color.footnote"));
|
||||
}
|
||||
|
||||
std::cout << "\n\nCome back and read about new features later!\n";
|
||||
|
||||
std::cout << footnote.colorize (
|
||||
"\nIf you enjoy Taskwarrior, please consider supporting the project at:\n"
|
||||
" https://github.com/sponsors/GothenburgBitFactory/\n"
|
||||
);
|
||||
exit (1);
|
||||
std::cout << footnote.colorize(
|
||||
"\nIf you enjoy Taskwarrior, please consider supporting the project at:\n"
|
||||
" https://github.com/sponsors/GothenburgBitFactory/\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void wait_for_enter ()
|
||||
{
|
||||
signal (SIGINT, signal_handler);
|
||||
void wait_for_enter() {
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
std::string dummy;
|
||||
std::cout << "\nPress enter to continue..";
|
||||
std::getline (std::cin, dummy);
|
||||
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);
|
||||
signal(SIGINT, SIG_DFL);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Holds information about single improvement / bug.
|
||||
//
|
||||
NewsItem::NewsItem (
|
||||
Version version,
|
||||
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
|
||||
) {
|
||||
NewsItem::NewsItem(Version version, 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) {
|
||||
_version = version;
|
||||
_title = title;
|
||||
_bg_title = bg_title;
|
||||
@@ -120,57 +110,50 @@ NewsItem::NewsItem (
|
||||
_actions = actions;
|
||||
}
|
||||
|
||||
void NewsItem::render () {
|
||||
auto config = Context::getContext ().config;
|
||||
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"));
|
||||
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} ({2})\n", _title, _version)));
|
||||
if (_background.size ()) {
|
||||
if (_bg_title.empty ())
|
||||
_bg_title = "Background";
|
||||
std::cout << bold.colorize(header.colorize(format("{1} ({2})\n", _title, _version)));
|
||||
if (_background.size()) {
|
||||
if (_bg_title.empty()) _bg_title = "Background";
|
||||
|
||||
std::cout << "\n " << underline.colorize (_bg_title) << std::endl
|
||||
<< _background << std::endl;
|
||||
std::cout << "\n " << underline.colorize(_bg_title) << std::endl << _background << std::endl;
|
||||
}
|
||||
|
||||
wait_for_enter ();
|
||||
wait_for_enter();
|
||||
|
||||
std::cout << " " << underline.colorize (format ("What changed in {1}?\n", _version));
|
||||
if (_punchline.size ())
|
||||
std::cout << footnote.colorize (format ("{1}\n", _punchline));
|
||||
std::cout << " " << underline.colorize(format("What changed in {1}?\n", _version));
|
||||
if (_punchline.size()) std::cout << footnote.colorize(format("{1}\n", _punchline));
|
||||
|
||||
if (_update.size ())
|
||||
std::cout << format ("{1}\n", _update);
|
||||
if (_update.size()) std::cout << format("{1}\n", _update);
|
||||
|
||||
wait_for_enter ();
|
||||
wait_for_enter();
|
||||
|
||||
if (_reasoning.size ()) {
|
||||
std::cout << " " << underline.colorize ("What was the motivation behind this feature?\n")
|
||||
if (_reasoning.size()) {
|
||||
std::cout << " " << underline.colorize("What was the motivation behind this feature?\n")
|
||||
<< _reasoning << std::endl;
|
||||
wait_for_enter ();
|
||||
wait_for_enter();
|
||||
}
|
||||
|
||||
if (_actions.size ()) {
|
||||
std::cout << " " << underline.colorize ("What do I have to do?\n")
|
||||
<< _actions << 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();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<NewsItem> NewsItem::all () {
|
||||
std::vector<NewsItem> NewsItem::all() {
|
||||
std::vector<NewsItem> items;
|
||||
version2_6_0(items);
|
||||
version3_0_0(items);
|
||||
@@ -192,484 +175,420 @@ std::vector<NewsItem> NewsItem::all () {
|
||||
// - The .by attribute modifier
|
||||
// - Exporting a report
|
||||
// - Multi-day holidays
|
||||
void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
|
||||
void NewsItem::version2_6_0(std::vector<NewsItem>& items) {
|
||||
Version version("2.6.0");
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Writeable context
|
||||
|
||||
// Detect whether user uses any contexts
|
||||
auto config = Context::getContext ().config;
|
||||
auto config = Context::getContext().config;
|
||||
std::stringstream advice;
|
||||
|
||||
auto defined = CmdContext::getContexts ();
|
||||
if (defined.size ())
|
||||
{
|
||||
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);}
|
||||
);
|
||||
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);}
|
||||
));
|
||||
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);
|
||||
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);
|
||||
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 << 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
|
||||
<< "\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
|
||||
} 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 (
|
||||
version,
|
||||
"'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 context '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 ()
|
||||
);
|
||||
NewsItem writeable_context(
|
||||
version, "'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 context '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
|
||||
|
||||
NewsItem uint64_support (
|
||||
version,
|
||||
"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!"
|
||||
);
|
||||
NewsItem uint64_support(
|
||||
version, "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);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Waiting is a virtual status
|
||||
|
||||
NewsItem waiting_status (
|
||||
version,
|
||||
"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'"
|
||||
);
|
||||
NewsItem waiting_status(
|
||||
version, "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 (
|
||||
version,
|
||||
"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",
|
||||
"",
|
||||
""
|
||||
);
|
||||
NewsItem env_vars(
|
||||
version, "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);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Reports outside of context
|
||||
|
||||
NewsItem contextless_reports (
|
||||
version,
|
||||
"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.",
|
||||
""
|
||||
);
|
||||
NewsItem contextless_reports(
|
||||
version, "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);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Exporting a particular report
|
||||
|
||||
NewsItem exportable_reports (
|
||||
version,
|
||||
"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.",
|
||||
""
|
||||
);
|
||||
NewsItem exportable_reports(
|
||||
version, "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 (
|
||||
version,
|
||||
"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 holiday.<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",
|
||||
"",
|
||||
""
|
||||
);
|
||||
NewsItem multi_holidays(
|
||||
version, "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 holiday.<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);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Unicode 12
|
||||
|
||||
NewsItem unicode_12 (
|
||||
version,
|
||||
"Extended Unicode support (Unicode 12)",
|
||||
"",
|
||||
"",
|
||||
" The support for Unicode character set was improved to support Unicode 12.\n"
|
||||
" This means better support for various language-specific characters - and emojis!",
|
||||
"",
|
||||
" Extended unicode support for language-specific characters helps non-English speakers.\n"
|
||||
" While most users don't enter emojis as task metadata, automated task creation tools,\n"
|
||||
" such as bugwarrior, might create tasks with exotic Unicode data.",
|
||||
" You can try it out - 'task add Prepare for an 👽 invasion!'"
|
||||
);
|
||||
NewsItem unicode_12(
|
||||
version, "Extended Unicode support (Unicode 12)", "", "",
|
||||
" The support for Unicode character set was improved to support Unicode 12.\n"
|
||||
" This means better support for various language-specific characters - and emojis!",
|
||||
"",
|
||||
" Extended unicode support for language-specific characters helps non-English speakers.\n"
|
||||
" While most users don't enter emojis as task metadata, automated task creation tools,\n"
|
||||
" such as bugwarrior, might create tasks with exotic Unicode data.",
|
||||
" You can try it out - 'task add Prepare for an 👽 invasion!'");
|
||||
items.push_back(unicode_12);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - The .by attribute modifier
|
||||
|
||||
NewsItem by_modifier (
|
||||
version,
|
||||
"The .by attribute modifier",
|
||||
"",
|
||||
"",
|
||||
" A new attribute modifier '.by' was introduced, equivalent to the operator '<='.\n",
|
||||
" This modifier can be used to list all tasks due by the end of the months,\n"
|
||||
" including the last day of the month, using: 'due.by:eom' query",
|
||||
" There was no convenient way to express '<=' relation using attribute modifiers.\n"
|
||||
" As a workaround, instead of 'due.by:eom' one could use 'due.before:eom+1d',\n"
|
||||
" but that requires a certain amount of mental overhead.",
|
||||
""
|
||||
);
|
||||
NewsItem by_modifier(
|
||||
version, "The .by attribute modifier", "", "",
|
||||
" A new attribute modifier '.by' was introduced, equivalent to the operator '<='.\n",
|
||||
" This modifier can be used to list all tasks due by the end of the months,\n"
|
||||
" including the last day of the month, using: 'due.by:eom' query",
|
||||
" There was no convenient way to express '<=' relation using attribute modifiers.\n"
|
||||
" As a workaround, instead of 'due.by:eom' one could use 'due.before:eom+1d',\n"
|
||||
" but that requires a certain amount of mental overhead.",
|
||||
"");
|
||||
items.push_back(by_modifier);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Context-specific configuration overrides
|
||||
|
||||
NewsItem context_config (
|
||||
version,
|
||||
"Context-specific configuration overrides",
|
||||
"",
|
||||
"",
|
||||
" Any context can now define context-specific configuration overrides\n"
|
||||
" via context.<name>.rc.<setting>=<value>.\n",
|
||||
" This allows the user to customize the behaviour of Taskwarrior in a given context,\n"
|
||||
" for example, to change the default command in the 'work' context to 'overdue':\n"
|
||||
"\n"
|
||||
" $ task config context.work.rc.default.command overdue\n"
|
||||
"\n"
|
||||
" Another example would be to ensure that while context 'work' is active, tasks get\n"
|
||||
" stored in a ~/.worktasks directory:\n"
|
||||
"\n"
|
||||
" $ task config context.work.rc.data.location=~/.worktasks",
|
||||
"",
|
||||
""
|
||||
);
|
||||
NewsItem context_config(
|
||||
version, "Context-specific configuration overrides", "", "",
|
||||
" Any context can now define context-specific configuration overrides\n"
|
||||
" via context.<name>.rc.<setting>=<value>.\n",
|
||||
" This allows the user to customize the behaviour of Taskwarrior in a given context,\n"
|
||||
" for example, to change the default command in the 'work' context to 'overdue':\n"
|
||||
"\n"
|
||||
" $ task config context.work.rc.default.command overdue\n"
|
||||
"\n"
|
||||
" Another example would be to ensure that while context 'work' is active, tasks get\n"
|
||||
" stored in a ~/.worktasks directory:\n"
|
||||
"\n"
|
||||
" $ task config context.work.rc.data.location=~/.worktasks",
|
||||
"", "");
|
||||
items.push_back(context_config);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - XDG config home support
|
||||
|
||||
NewsItem xdg_support (
|
||||
version,
|
||||
"Support for XDG Base Directory Specification",
|
||||
"",
|
||||
" The XDG Base Directory specification provides standard locations to store\n"
|
||||
" application data, configuration, state, and cached data in order to keep $HOME\n"
|
||||
" clutter-free. The locations are usually set to ~/.local/share, ~/.config,\n"
|
||||
" ~/.local/state and ~/.cache respectively.",
|
||||
" If taskrc is not found at '~/.taskrc', Taskwarrior will attempt to find it\n"
|
||||
" at '$XDG_CONFIG_HOME/task/taskrc' (defaults to '~/.config/task/taskrc').",
|
||||
"",
|
||||
" This allows users to fully follow XDG Base Directory Spec by moving their taskrc:\n"
|
||||
" $ mkdir $XDG_CONFIG_HOME/task\n"
|
||||
" $ mv ~/.taskrc $XDG_CONFIG_HOME/task/taskrc\n\n"
|
||||
" and further setting:\n"
|
||||
" data.location=$XDG_DATA_HOME/task/\n"
|
||||
" hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n"
|
||||
" Solutions in the past required symlinks or more cumbersome configuration overrides.",
|
||||
" If you configure your data.location and hooks.location as above, ensure\n"
|
||||
" that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n"
|
||||
" otherwise they're going to expand to empty string. Alternatively you can\n"
|
||||
" hardcode the desired paths on your system."
|
||||
);
|
||||
NewsItem xdg_support(
|
||||
version, "Support for XDG Base Directory Specification", "",
|
||||
" The XDG Base Directory specification provides standard locations to store\n"
|
||||
" application data, configuration, state, and cached data in order to keep $HOME\n"
|
||||
" clutter-free. The locations are usually set to ~/.local/share, ~/.config,\n"
|
||||
" ~/.local/state and ~/.cache respectively.",
|
||||
" If taskrc is not found at '~/.taskrc', Taskwarrior will attempt to find it\n"
|
||||
" at '$XDG_CONFIG_HOME/task/taskrc' (defaults to '~/.config/task/taskrc').",
|
||||
"",
|
||||
" This allows users to fully follow XDG Base Directory Spec by moving their taskrc:\n"
|
||||
" $ mkdir $XDG_CONFIG_HOME/task\n"
|
||||
" $ mv ~/.taskrc $XDG_CONFIG_HOME/task/taskrc\n\n"
|
||||
" and further setting:\n"
|
||||
" data.location=$XDG_DATA_HOME/task/\n"
|
||||
" hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n"
|
||||
" Solutions in the past required symlinks or more cumbersome configuration overrides.",
|
||||
" If you configure your data.location and hooks.location as above, ensure\n"
|
||||
" that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n"
|
||||
" otherwise they're going to expand to empty string. Alternatively you can\n"
|
||||
" hardcode the desired paths on your system.");
|
||||
items.push_back(xdg_support);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// - Update holiday data
|
||||
|
||||
NewsItem holidata_2022 (
|
||||
version,
|
||||
"Updated holiday data for 2022",
|
||||
"",
|
||||
"",
|
||||
" Holiday data has been refreshed for 2022 and five more holiday locales\n"
|
||||
" have been added: fr-CA, hu-HU, pt-BR, sk-SK and sv-FI.",
|
||||
"",
|
||||
" Refreshing the holiday data is part of every release. The addition of the new\n"
|
||||
" locales allows us to better support users in those particular countries."
|
||||
);
|
||||
NewsItem holidata_2022(
|
||||
version, "Updated holiday data for 2022", "", "",
|
||||
" Holiday data has been refreshed for 2022 and five more holiday locales\n"
|
||||
" have been added: fr-CA, hu-HU, pt-BR, sk-SK and sv-FI.",
|
||||
"",
|
||||
" Refreshing the holiday data is part of every release. The addition of the new\n"
|
||||
" locales allows us to better support users in those particular countries.");
|
||||
items.push_back(holidata_2022);
|
||||
}
|
||||
|
||||
void NewsItem::version3_0_0 (std::vector<NewsItem>& items) {
|
||||
void NewsItem::version3_0_0(std::vector<NewsItem>& items) {
|
||||
Version version("3.0.0");
|
||||
NewsItem sync {
|
||||
version,
|
||||
/*title=*/"New data model and sync backend",
|
||||
/*bg_title=*/"",
|
||||
/*background=*/"",
|
||||
/*punchline=*/
|
||||
"The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n"
|
||||
"supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n"
|
||||
"although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n"
|
||||
"For details. As part of this change, the on-disk storage format has also changed.\n",
|
||||
/*update=*/
|
||||
"This is a breaking upgrade: you must export your task database from 2.x and re-import\n"
|
||||
"it into 3.x. Hooks run during task import, so if you have any hooks defined,\n"
|
||||
"temporarily disable them for this operation.\n\n"
|
||||
"See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior 3.0.",
|
||||
NewsItem sync{
|
||||
version,
|
||||
/*title=*/"New data model and sync backend",
|
||||
/*bg_title=*/"",
|
||||
/*background=*/"",
|
||||
/*punchline=*/
|
||||
"The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n"
|
||||
"supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n"
|
||||
"although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n"
|
||||
"For details. As part of this change, the on-disk storage format has also changed.\n",
|
||||
/*update=*/
|
||||
"This is a breaking upgrade: you must export your task database from 2.x and re-import\n"
|
||||
"it into 3.x. Hooks run during task import, so if you have any hooks defined,\n"
|
||||
"temporarily disable them for this operation.\n\n"
|
||||
"See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior "
|
||||
"3.0.",
|
||||
};
|
||||
items.push_back(sync);
|
||||
}
|
||||
|
||||
void NewsItem::version3_1_0 (std::vector<NewsItem>& items) {
|
||||
void NewsItem::version3_1_0(std::vector<NewsItem>& items) {
|
||||
Version version("3.1.0");
|
||||
NewsItem sync {
|
||||
version,
|
||||
/*title=*/"Purging Tasks, Manually or Automatically",
|
||||
/*bg_title=*/"",
|
||||
/*background=*/"",
|
||||
/*punchline=*/
|
||||
"Support for `task purge` has been restored, and new support added for automatically expiring\n"
|
||||
"old tasks.\n\n"
|
||||
/*update=*/
|
||||
"The `task purge` command removes tasks entirely, in contrast to `task delete` which merely sets\n"
|
||||
"the task status to 'Deleted'. This functionality existed in versions 2.x but was temporarily\n"
|
||||
"removed in 3.0.\n\n"
|
||||
"The new `purge.on-sync` configuration parameter controls automatic purging of old tasks.\n"
|
||||
"An old task is one with status 'Deleted' that has not been modified in 180 days. This\n"
|
||||
"functionality is optional and not enabled by default."
|
||||
};
|
||||
NewsItem sync{
|
||||
version,
|
||||
/*title=*/"Purging Tasks, Manually or Automatically",
|
||||
/*bg_title=*/"",
|
||||
/*background=*/"",
|
||||
/*punchline=*/
|
||||
"Support for `task purge` has been restored, and new support added for automatically "
|
||||
"expiring\n"
|
||||
"old tasks.\n\n"
|
||||
/*update=*/
|
||||
"The `task purge` command removes tasks entirely, in contrast to `task delete` which merely "
|
||||
"sets\n"
|
||||
"the task status to 'Deleted'. This functionality existed in versions 2.x but was "
|
||||
"temporarily\n"
|
||||
"removed in 3.0.\n\n"
|
||||
"The new `purge.on-sync` configuration parameter controls automatic purging of old tasks.\n"
|
||||
"An old task is one with status 'Deleted' that has not been modified in 180 days. This\n"
|
||||
"functionality is optional and not enabled by default."};
|
||||
items.push_back(sync);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int CmdNews::execute (std::string& output)
|
||||
{
|
||||
auto words = Context::getContext ().cli2.getWords ();
|
||||
auto config = Context::getContext ().config;
|
||||
int CmdNews::execute(std::string& output) {
|
||||
auto words = Context::getContext().cli2.getWords();
|
||||
auto config = Context::getContext().config;
|
||||
|
||||
// Supress compiler warning about unused argument
|
||||
output = "";
|
||||
|
||||
std::vector<NewsItem> items = NewsItem::all();
|
||||
Version news_version(Context::getContext ().config.get ("news.version"));
|
||||
Version news_version(Context::getContext().config.get("news.version"));
|
||||
Version current_version = Version::Current();
|
||||
|
||||
// 2.6.0 is the earliest version with news support.
|
||||
if (!news_version.is_valid())
|
||||
news_version = Version("2.6.0");
|
||||
if (!news_version.is_valid()) news_version = Version("2.6.0");
|
||||
|
||||
signal (SIGINT, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
// Remove items that have already been shown
|
||||
items.erase (
|
||||
std::remove_if (items.begin (), items.end (), [&](const NewsItem& n){return n._version <= news_version;}),
|
||||
items.end ()
|
||||
);
|
||||
items.erase(std::remove_if(items.begin(), items.end(),
|
||||
[&](const NewsItem& n) { return n._version <= news_version; }),
|
||||
items.end());
|
||||
|
||||
|
||||
Color bold = Color ("bold");
|
||||
if (items.empty ()) {
|
||||
std::cout << bold.colorize ("You are up to date!\n");
|
||||
Color bold = Color("bold");
|
||||
if (items.empty()) {
|
||||
std::cout << bold.colorize("You are up to date!\n");
|
||||
} else {
|
||||
// Print release notes
|
||||
std::cout << bold.colorize (format (
|
||||
"\n"
|
||||
"================================================\n"
|
||||
"Taskwarrior {1} through {2} Release Highlights\n"
|
||||
"================================================\n",
|
||||
news_version,
|
||||
current_version));
|
||||
std::cout << bold.colorize(
|
||||
format("\n"
|
||||
"================================================\n"
|
||||
"Taskwarrior {1} through {2} Release Highlights\n"
|
||||
"================================================\n",
|
||||
news_version, current_version));
|
||||
|
||||
for (unsigned short i=0; i < items.size (); i++) {
|
||||
std::cout << format ("\n({1}/{2}) ", i+1, items.size ());
|
||||
items[i].render ();
|
||||
for (unsigned short i = 0; i < items.size(); i++) {
|
||||
std::cout << format("\n({1}/{2}) ", i + 1, items.size());
|
||||
items[i].render();
|
||||
}
|
||||
std::cout << "Thank you for catching up on the new features!\n";
|
||||
}
|
||||
wait_for_enter ();
|
||||
wait_for_enter();
|
||||
|
||||
// Display outro
|
||||
Datetime now;
|
||||
Datetime beginning (2006, 11, 29);
|
||||
Duration development_time = Duration (now - beginning);
|
||||
Datetime beginning(2006, 11, 29);
|
||||
Duration development_time = Duration(now - beginning);
|
||||
|
||||
Color underline = Color ("underline");
|
||||
Color underline = Color("underline");
|
||||
|
||||
std::stringstream outro;
|
||||
outro << underline.colorize (bold.colorize ("Taskwarrior crowdfunding\n"));
|
||||
outro << format (
|
||||
"Taskwarrior has been in development for {1} years but its survival\n"
|
||||
"depends on your support!\n\n"
|
||||
"Please consider joining our {2} fundraiser to help us fund maintenance\n"
|
||||
"and development of new features:\n\n",
|
||||
std::lround (static_cast<float>(development_time.days ()) / 365.25),
|
||||
now.year ()
|
||||
);
|
||||
outro << underline.colorize(bold.colorize("Taskwarrior crowdfunding\n"));
|
||||
outro << format(
|
||||
"Taskwarrior has been in development for {1} years but its survival\n"
|
||||
"depends on your support!\n\n"
|
||||
"Please consider joining our {2} fundraiser to help us fund maintenance\n"
|
||||
"and development of new features:\n\n",
|
||||
std::lround(static_cast<float>(development_time.days()) / 365.25), now.year());
|
||||
outro << bold.colorize(" https://github.com/sponsors/GothenburgBitFactory/\n\n");
|
||||
outro << "Perks are available for our sponsors.\n";
|
||||
|
||||
std::cout << outro.str ();
|
||||
std::cout << outro.str();
|
||||
|
||||
// Set a mark in the config to remember which version's release notes were displayed
|
||||
if (news_version != current_version)
|
||||
{
|
||||
if (news_version != current_version) {
|
||||
std::cout << "UPDATING\n";
|
||||
CmdConfig::setConfigVariable ("news.version", std::string(current_version), false);
|
||||
CmdConfig::setConfigVariable("news.version", std::string(current_version), false);
|
||||
|
||||
// Revert back to default signal handling after displaying the outro
|
||||
signal (SIGINT, SIG_DFL);
|
||||
signal(SIGINT, SIG_DFL);
|
||||
|
||||
std::string question = format (
|
||||
"\nWould you like to open Taskwarrior {1} fundraising campaign to read more?",
|
||||
now.year ()
|
||||
);
|
||||
std::string question = format(
|
||||
"\nWould you like to open Taskwarrior {1} fundraising campaign to read more?", now.year());
|
||||
|
||||
std::vector <std::string> options {"yes", "no"};
|
||||
std::vector <std::string> matches;
|
||||
std::vector<std::string> options{"yes", "no"};
|
||||
std::vector<std::string> matches;
|
||||
|
||||
std::cout << question << " (YES/no) ";
|
||||
|
||||
std::string answer;
|
||||
std::getline (std::cin, answer);
|
||||
std::getline(std::cin, answer);
|
||||
|
||||
if (std::cin.eof () || trim (answer).empty ())
|
||||
if (std::cin.eof() || trim(answer).empty())
|
||||
answer = "yes";
|
||||
else
|
||||
lowerCase (trim (answer));
|
||||
lowerCase(trim(answer));
|
||||
|
||||
autoComplete (answer, options, matches, 1); // Hard-coded 1.
|
||||
autoComplete(answer, options, matches, 1); // Hard-coded 1.
|
||||
|
||||
if (matches.size () == 1 && matches[0] == "yes")
|
||||
#if defined (DARWIN)
|
||||
system ("open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
if (matches.size() == 1 && matches[0] == "yes")
|
||||
#if defined(DARWIN)
|
||||
system("open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
#else
|
||||
system ("xdg-open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
system("xdg-open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
#endif
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
else
|
||||
wait_for_enter (); // Do not display the outro and footnote at once
|
||||
} else
|
||||
wait_for_enter(); // Do not display the outro and footnote at once
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user