Update task news to support 3.0.0 (#3342)

* Introduce Version, use it to check current version in custom reports
* Support multiple versions in 'task news'
This commit is contained in:
Dustin J. Mitchell 2024-04-15 22:04:16 -04:00 committed by GitHub
parent d243d000eb
commit 4d9bb20bdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 302 additions and 36 deletions

View file

@ -2,6 +2,8 @@
To release Taskwarrior, follow this process: To release Taskwarrior, follow this process:
- Examine the changes since the last version, and update `src/commands/CmdNews.cpp` accordingly.
There are instructions at the top of the file.
- Create a release PR - Create a release PR
- Update version in CMakeLists.txt - Update version in CMakeLists.txt
- Update Changelog - Update Changelog

View file

@ -18,6 +18,7 @@ add_library (task STATIC CLI2.cpp CLI2.h
TDB2.cpp TDB2.h TDB2.cpp TDB2.h
Task.cpp Task.h Task.cpp Task.h
Variant.cpp Variant.h Variant.cpp Variant.h
Version.cpp Version.h
ViewTask.cpp ViewTask.h ViewTask.cpp ViewTask.h
dependency.cpp dependency.cpp
feedback.cpp feedback.cpp

118
src/Version.cpp Normal file
View file

@ -0,0 +1,118 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024, Dustin Mitchell.
//
// 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 <Version.h>
#include <cmake.h>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
////////////////////////////////////////////////////////////////////////////////
Version::Version(std::string version) {
std::vector<int> parts;
std::string part;
std::istringstream input(version);
while (std::getline(input, part, '.')) {
int value;
// Try converting string to integer
if (std::stringstream(part) >> value && value >= 0) {
parts.push_back(value);
} else {
return;
}
}
if (parts.size() != 3) {
return;
}
major = parts[0];
minor = parts[1];
patch = parts[2];
}
////////////////////////////////////////////////////////////////////////////////
Version Version::Current() { return Version(PACKAGE_VERSION); }
////////////////////////////////////////////////////////////////////////////////
bool Version::is_valid() const { return major >= 0; }
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<(const Version &other) const {
return std::tie(major, minor, patch) <
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<=(const Version &other) const {
return std::tie(major, minor, patch) <=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>(const Version &other) const {
return std::tie(major, minor, patch) >
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>=(const Version &other) const {
return std::tie(major, minor, patch) >=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator==(const Version &other) const {
return std::tie(major, minor, patch) ==
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator!=(const Version &other) const {
std::cout << other;
return std::tie(major, minor, patch) !=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
Version::operator std::string() const {
std::ostringstream output;
if (is_valid()) {
output << major << '.' << minor << '.' << patch;
} else {
output << "(invalid version)";
}
return output.str();
}
////////////////////////////////////////////////////////////////////////////////
std::ostream &operator<<(std::ostream &os, const Version &version) {
os << std::string(version);
return os;
}

72
src/Version.h Normal file
View file

@ -0,0 +1,72 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024, Dustin Mitchell.
//
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_VERSION
#define INCLUDED_VERSION
#include <string>
// A utility class for handling Taskwarrior versions.
class Version {
public:
// Parse a version from a string. This must be of the format
// digits.digits.digits.
explicit Version(std::string version);
// Create an invalid version.
Version() = default;
Version(const Version &other) = default;
Version(Version &&other) = default;
Version &operator=(const Version &) = default;
Version &operator=(Version &&) = default;
// Return a version representing the release being built.
static Version Current();
bool is_valid() const;
// Compare versions.
bool operator<(const Version &) const;
bool operator<=(const Version &) const;
bool operator>(const Version &) const;
bool operator>=(const Version &) const;
bool operator==(const Version &) const;
bool operator!=(const Version &) const;
// Convert back to a string.
operator std::string() const;
friend std::ostream& operator<<(std::ostream& os, const Version& version);
private:
int major = -1;
int minor = -1;
int patch = -1;
};
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -35,6 +35,7 @@
#include <Context.h> #include <Context.h>
#include <Filter.h> #include <Filter.h>
#include <Lexer.h> #include <Lexer.h>
#include <Version.h>
#include <ViewTask.h> #include <ViewTask.h>
#include <format.h> #include <format.h>
#include <shared.h> #include <shared.h>
@ -250,24 +251,24 @@ int CmdCustom::execute (std::string& output)
} }
// Inform user about the new release highlights if not presented yet // Inform user about the new release highlights if not presented yet
if (Context::getContext ().config.get ("news.version") != "2.6.0") Version news_version(Context::getContext ().config.get ("news.version"));
Version current_version = Version::Current();
if (news_version != current_version)
{ {
std::random_device device; std::random_device device;
std::mt19937 random_generator(device()); std::mt19937 random_generator(device());
std::uniform_int_distribution<std::mt19937::result_type> twentyfive_percent(1, 4); std::uniform_int_distribution<std::mt19937::result_type> twentyfive_percent(1, 4);
std::string NEWS_NOTICE = (
"Recently upgraded to 2.6.0. "
"Please run 'task news' to read highlights about the new release."
);
// 1 in 10 chance to display the message. // 1 in 10 chance to display the message.
if (twentyfive_percent(random_generator) == 4) if (twentyfive_percent(random_generator) == 4)
{ {
std::ostringstream notice;
notice << "Recently upgraded to " << current_version << ". "
"Please run 'task news' to read highlights about the new release.";
if (Context::getContext ().verbose ("footnote")) if (Context::getContext ().verbose ("footnote"))
Context::getContext ().footnote (NEWS_NOTICE); Context::getContext ().footnote (notice.str());
else if (Context::getContext ().verbose ("header")) else if (Context::getContext ().verbose ("header"))
Context::getContext ().header (NEWS_NOTICE); Context::getContext ().header (notice.str());
} }
} }

View file

@ -38,6 +38,14 @@
#include <util.h> #include <util.h>
#include <main.h> #include <main.h>
/* Adding a new version:
*
* - Add a new `versionX_Y_Z` method to `NewsItem`, and add news items for the new
* release.
* - Call the new method in `NewsItem.all()`. Calls should be in version order.
* - Test with `task news`.
*/
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
CmdNews::CmdNews () CmdNews::CmdNews ()
{ {
@ -91,6 +99,7 @@ void wait_for_enter ()
// Holds information about single improvement / bug. // Holds information about single improvement / bug.
// //
NewsItem::NewsItem ( NewsItem::NewsItem (
Version version,
bool major, bool major,
const std::string& title, const std::string& title,
const std::string& bg_title, const std::string& bg_title,
@ -100,6 +109,7 @@ NewsItem::NewsItem (
const std::string& reasoning, const std::string& reasoning,
const std::string& actions const std::string& actions
) { ) {
_version = version;
_major = major; _major = major;
_title = title; _title = title;
_bg_title = bg_title; _bg_title = bg_title;
@ -127,7 +137,7 @@ void NewsItem::render () {
// TODO: For some reason, bold cannot be blended in 256-color terminals // TODO: For some reason, bold cannot be blended in 256-color terminals
// Apply this workaround of colorizing twice. // Apply this workaround of colorizing twice.
std::cout << bold.colorize (header.colorize (format ("{1}\n", _title))); std::cout << bold.colorize (header.colorize (format ("{1} ({2})\n", _title, _version)));
if (_background.size ()) { if (_background.size ()) {
if (_bg_title.empty ()) if (_bg_title.empty ())
_bg_title = "Background"; _bg_title = "Background";
@ -138,7 +148,7 @@ void NewsItem::render () {
wait_for_enter (); wait_for_enter ();
std::cout << " " << underline.colorize ("What changed in 2.6.0?\n"); std::cout << " " << underline.colorize (format ("What changed in {1}?\n", _version));
if (_punchline.size ()) if (_punchline.size ())
std::cout << footnote.colorize (format ("{1}\n", _punchline)); std::cout << footnote.colorize (format ("{1}\n", _punchline));
@ -160,6 +170,13 @@ void NewsItem::render () {
} }
} }
std::vector<NewsItem> NewsItem::all () {
std::vector<NewsItem> items;
version2_6_0(items);
version3_0_0(items);
return items;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Generate the highlights for the 2.6.0 version. // Generate the highlights for the 2.6.0 version.
// //
@ -174,7 +191,8 @@ void NewsItem::render () {
// - The .by attribute modifier // - The .by attribute modifier
// - Exporting a report // - Exporting a report
// - Multi-day holidays // - Multi-day holidays
void CmdNews::version2_6_0 (std::vector<NewsItem>& items) { void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
Version version("2.6.0");
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// - Writeable context (major) // - Writeable context (major)
@ -234,6 +252,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
" Read more about how to use contexts in CONTEXT section of 'man task'."; " Read more about how to use contexts in CONTEXT section of 'man task'.";
NewsItem writeable_context ( NewsItem writeable_context (
version,
true, true,
"'Writeable' context", "'Writeable' context",
"Background - what is context?", "Background - what is context?",
@ -277,6 +296,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - 64-bit datetime support (major) // - 64-bit datetime support (major)
NewsItem uint64_support ( NewsItem uint64_support (
version,
false, false,
"Support for 64-bit timestamps and numeric values", "Support for 64-bit timestamps and numeric values",
"", "",
@ -294,6 +314,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Waiting is a virtual status // - Waiting is a virtual status
NewsItem waiting_status ( NewsItem waiting_status (
version,
true, true,
"Deprecation of the status:waiting", "Deprecation of the status:waiting",
"", "",
@ -315,6 +336,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Support for environment variables in the taskrc // - Support for environment variables in the taskrc
NewsItem env_vars ( NewsItem env_vars (
version,
true, true,
"Environment variables in the taskrc", "Environment variables in the taskrc",
"", "",
@ -333,6 +355,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Reports outside of context // - Reports outside of context
NewsItem contextless_reports ( NewsItem contextless_reports (
version,
true, true,
"Context-less reports", "Context-less reports",
"", "",
@ -354,6 +377,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Exporting a particular report // - Exporting a particular report
NewsItem exportable_reports ( NewsItem exportable_reports (
version,
false, false,
"Exporting a particular report", "Exporting a particular report",
"", "",
@ -377,6 +401,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Multi-day holidays // - Multi-day holidays
NewsItem multi_holidays ( NewsItem multi_holidays (
version,
false, false,
"Multi-day holidays", "Multi-day holidays",
"", "",
@ -399,6 +424,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Unicode 12 // - Unicode 12
NewsItem unicode_12 ( NewsItem unicode_12 (
version,
false, false,
"Extended Unicode support (Unicode 12)", "Extended Unicode support (Unicode 12)",
"", "",
@ -417,6 +443,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - The .by attribute modifier // - The .by attribute modifier
NewsItem by_modifier ( NewsItem by_modifier (
version,
false, false,
"The .by attribute modifier", "The .by attribute modifier",
"", "",
@ -435,6 +462,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Context-specific configuration overrides // - Context-specific configuration overrides
NewsItem context_config ( NewsItem context_config (
version,
false, false,
"Context-specific configuration overrides", "Context-specific configuration overrides",
"", "",
@ -459,6 +487,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - XDG config home support // - XDG config home support
NewsItem xdg_support ( NewsItem xdg_support (
version,
true, true,
"Support for XDG Base Directory Specification", "Support for XDG Base Directory Specification",
"", "",
@ -487,6 +516,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
// - Update holiday data // - Update holiday data
NewsItem holidata_2022 ( NewsItem holidata_2022 (
version,
false, false,
"Updated holiday data for 2022", "Updated holiday data for 2022",
"", "",
@ -500,6 +530,28 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
items.push_back(holidata_2022); items.push_back(holidata_2022);
} }
void NewsItem::version3_0_0 (std::vector<NewsItem>& items) {
Version version("3.0.0");
NewsItem sync {
version,
/*major=*/true,
/*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);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int CmdNews::execute (std::string& output) int CmdNews::execute (std::string& output)
{ {
@ -509,11 +561,13 @@ int CmdNews::execute (std::string& output)
// Supress compiler warning about unused argument // Supress compiler warning about unused argument
output = ""; output = "";
// TODO: 2.6.0 is the only version with explicit release notes, but in the std::vector<NewsItem> items = NewsItem::all();
// future we need to only execute yet unread release notes Version news_version(Context::getContext ().config.get ("news.version"));
std::vector<NewsItem> items; Version current_version = Version::Current();
std::string version = "2.6.0";
version2_6_0 (items); // 2.6.0 is the earliest version with news support.
if (!news_version.is_valid())
news_version = Version("2.6.0");
bool full_summary = false; bool full_summary = false;
bool major_items = true; bool major_items = true;
@ -538,6 +592,12 @@ int CmdNews::execute (std::string& output)
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 ()
);
// Remove non-major items if displaying a non-full (abbreviated) summary // Remove non-major items if displaying a non-full (abbreviated) summary
int total_highlights = items.size (); int total_highlights = items.size ();
if (! full_summary) if (! full_summary)
@ -546,23 +606,25 @@ int CmdNews::execute (std::string& output)
items.end () items.end ()
); );
// Print release notes
Color bold = Color ("bold"); Color bold = Color ("bold");
std::cout << bold.colorize (format ( if (items.empty ()) {
"\n" std::cout << bold.colorize ("You are up to date!\n");
"==========================================\n" } else {
"Taskwarrior {1} {2} Release highlights\n" // Print release notes
"==========================================\n", std::cout << bold.colorize (format (
version, "\n"
(full_summary ? "All" : (major_items ? "Major" : "Minor")) "================================================\n"
)); "Taskwarrior {1} through {2} Release Highlights\n"
"================================================\n",
news_version,
current_version));
for (unsigned short i=0; i < items.size (); i++) { for (unsigned short i=0; i < items.size (); i++) {
std::cout << format ("\n({1}/{2}) ", i+1, items.size ()); std::cout << format ("\n({1}/{2}) ", i+1, items.size ());
items[i].render (); items[i].render ();
}
std::cout << "Thank you for catching up on the new features!\n";
} }
std::cout << "Thank you for catching up on the new features!\n";
wait_for_enter (); wait_for_enter ();
// Display outro // Display outro
@ -588,9 +650,9 @@ int CmdNews::execute (std::string& output)
std::cout << outro.str (); std::cout << outro.str ();
// Set a mark in the config to remember which version's release notes were displayed // Set a mark in the config to remember which version's release notes were displayed
if (config.get ("news.version") != "2.6.0") if (full_summary && news_version != current_version)
{ {
CmdConfig::setConfigVariable ("news.version", "2.6.0", false); CmdConfig::setConfigVariable ("news.version", std::string(current_version), false);
// Revert back to default signal handling after displaying the outro // Revert back to default signal handling after displaying the outro
signal (SIGINT, SIG_DFL); signal (SIGINT, SIG_DFL);
@ -627,14 +689,15 @@ int CmdNews::execute (std::string& output)
else else
wait_for_enter (); // Do not display the outro and footnote at once wait_for_enter (); // Do not display the outro and footnote at once
if (! full_summary && major_items) if (! items.empty() && ! full_summary && major_items) {
Context::getContext ().footnote (format ( Context::getContext ().footnote (format (
"Only major highlights were displayed ({1} out of {2} total).\n" "Only major highlights were displayed ({1} out of {2} total).\n"
"If you're interested in more release highlights, run 'task news {3} minor'.", "If you're interested in more release highlights, run 'task news {3} minor'.",
items.size (), items.size (),
total_highlights, total_highlights,
version current_version
)); ));
}
return 0; return 0;
} }

View file

@ -31,9 +31,11 @@
#include <Command.h> #include <Command.h>
#include <CmdConfig.h> #include <CmdConfig.h>
#include <CmdContext.h> #include <CmdContext.h>
#include <Version.h>
class NewsItem { class NewsItem {
public: public:
Version _version;
bool _major = false; bool _major = false;
std::string _title; std::string _title;
std::string _bg_title; std::string _bg_title;
@ -42,7 +44,16 @@ public:
std::string _update; std::string _update;
std::string _reasoning; std::string _reasoning;
std::string _actions; std::string _actions;
void render ();
static std::vector<NewsItem> all();
static void version2_6_0 (std::vector<NewsItem>&);
static void version3_0_0 (std::vector<NewsItem>&);
private:
NewsItem ( NewsItem (
Version,
bool, bool,
const std::string&, const std::string&,
const std::string& = "", const std::string& = "",
@ -52,7 +63,6 @@ public:
const std::string& = "", const std::string& = "",
const std::string& = "" const std::string& = ""
); );
void render ();
}; };
class CmdNews : public Command class CmdNews : public Command
@ -60,7 +70,6 @@ class CmdNews : public Command
public: public:
CmdNews (); CmdNews ();
int execute (std::string&); int execute (std::string&);
void version2_6_0 (std::vector<NewsItem>&);
}; };
#endif #endif