mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-09-05 04:57:21 +02:00
add initial bulk run from pre-commit over all files
This commit is contained in:
parent
665aeeef61
commit
93356b39c3
418 changed files with 21354 additions and 23858 deletions
403
src/recur.cpp
403
src/recur.cpp
|
@ -27,109 +27,100 @@
|
|||
#include <cmake.h>
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/types.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <pwd.h>
|
||||
#include <time.h>
|
||||
#include <Context.h>
|
||||
#include <Lexer.h>
|
||||
#include <Datetime.h>
|
||||
#include <Duration.h>
|
||||
#include <Lexer.h>
|
||||
#include <format.h>
|
||||
#include <unicode.h>
|
||||
#include <util.h>
|
||||
#include <inttypes.h>
|
||||
#include <main.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unicode.h>
|
||||
#include <unistd.h>
|
||||
#include <util.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Scans all tasks, and for any recurring tasks, determines whether any new
|
||||
// child tasks need to be generated to fill gaps.
|
||||
void handleRecurrence ()
|
||||
{
|
||||
void handleRecurrence() {
|
||||
// Recurrence can be disabled.
|
||||
// Note: This is currently a workaround for TD-44, TW-1520.
|
||||
if (! Context::getContext ().config.getBoolean ("recurrence"))
|
||||
return;
|
||||
if (!Context::getContext().config.getBoolean("recurrence")) return;
|
||||
|
||||
auto tasks = Context::getContext ().tdb2.pending_tasks ();
|
||||
auto tasks = Context::getContext().tdb2.pending_tasks();
|
||||
Datetime now;
|
||||
|
||||
// Look at all tasks and find any recurring ones.
|
||||
for (auto& t : tasks)
|
||||
{
|
||||
if (t.getStatus () == Task::recurring)
|
||||
{
|
||||
for (auto& t : tasks) {
|
||||
if (t.getStatus() == Task::recurring) {
|
||||
// Generate a list of due dates for this recurring task, regardless of
|
||||
// the mask.
|
||||
std::vector <Datetime> due;
|
||||
if (! generateDueDates (t, due))
|
||||
{
|
||||
std::vector<Datetime> due;
|
||||
if (!generateDueDates(t, due)) {
|
||||
// Determine the end date.
|
||||
t.setStatus (Task::deleted);
|
||||
Context::getContext ().tdb2.modify (t);
|
||||
Context::getContext ().footnote (onExpiration (t));
|
||||
t.setStatus(Task::deleted);
|
||||
Context::getContext().tdb2.modify(t);
|
||||
Context::getContext().footnote(onExpiration(t));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the mask from the parent task.
|
||||
auto mask = t.get ("mask");
|
||||
auto mask = t.get("mask");
|
||||
|
||||
// Iterate over the due dates, and check each against the mask.
|
||||
auto changed = false;
|
||||
unsigned int i = 0;
|
||||
for (auto& d : due)
|
||||
{
|
||||
if (mask.length () <= i)
|
||||
{
|
||||
for (auto& d : due) {
|
||||
if (mask.length() <= i) {
|
||||
changed = true;
|
||||
|
||||
Task rec (t); // Clone the parent.
|
||||
rec.setStatus (Task::pending); // Change the status.
|
||||
rec.set ("uuid", uuid ()); // New UUID.
|
||||
rec.set ("parent", t.get ("uuid")); // Remember mom.
|
||||
rec.setAsNow ("entry"); // New entry date.
|
||||
rec.set ("due", format (d.toEpoch ()));
|
||||
Task rec(t); // Clone the parent.
|
||||
rec.setStatus(Task::pending); // Change the status.
|
||||
rec.set("uuid", uuid()); // New UUID.
|
||||
rec.set("parent", t.get("uuid")); // Remember mom.
|
||||
rec.setAsNow("entry"); // New entry date.
|
||||
rec.set("due", format(d.toEpoch()));
|
||||
|
||||
if (t.has ("wait"))
|
||||
{
|
||||
Datetime old_wait (t.get_date ("wait"));
|
||||
Datetime old_due (t.get_date ("due"));
|
||||
Datetime due (d);
|
||||
rec.set ("wait", format ((due + (old_wait - old_due)).toEpoch ()));
|
||||
rec.setStatus (Task::waiting);
|
||||
if (t.has("wait")) {
|
||||
Datetime old_wait(t.get_date("wait"));
|
||||
Datetime old_due(t.get_date("due"));
|
||||
Datetime due(d);
|
||||
rec.set("wait", format((due + (old_wait - old_due)).toEpoch()));
|
||||
rec.setStatus(Task::waiting);
|
||||
mask += 'W';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
mask += '-';
|
||||
rec.setStatus (Task::pending);
|
||||
rec.setStatus(Task::pending);
|
||||
}
|
||||
|
||||
rec.set ("imask", i);
|
||||
rec.remove ("mask"); // Remove the mask of the parent.
|
||||
rec.set("imask", i);
|
||||
rec.remove("mask"); // Remove the mask of the parent.
|
||||
|
||||
// Add the new task to the DB.
|
||||
Context::getContext ().tdb2.add (rec);
|
||||
Context::getContext().tdb2.add(rec);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
// Only modify the parent if necessary.
|
||||
if (changed)
|
||||
{
|
||||
t.set ("mask", mask);
|
||||
Context::getContext ().tdb2.modify (t);
|
||||
if (changed) {
|
||||
t.set("mask", mask);
|
||||
Context::getContext().tdb2.modify(t);
|
||||
|
||||
if (Context::getContext ().verbose ("recur"))
|
||||
Context::getContext ().footnote (format ("Creating recurring task instance '{1}'", t.get ("description")));
|
||||
if (Context::getContext().verbose("recur"))
|
||||
Context::getContext().footnote(
|
||||
format("Creating recurring task instance '{1}'", t.get("description")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,292 +131,242 @@ void handleRecurrence ()
|
|||
// period (recur). Then generate a set of corresponding dates.
|
||||
//
|
||||
// Returns false if the parent recurring task is depleted.
|
||||
bool generateDueDates (Task& parent, std::vector <Datetime>& allDue)
|
||||
{
|
||||
bool generateDueDates(Task& parent, std::vector<Datetime>& allDue) {
|
||||
// Determine due date, recur period and until date.
|
||||
Datetime due (parent.get_date ("due"));
|
||||
if (due._date == 0)
|
||||
return false;
|
||||
Datetime due(parent.get_date("due"));
|
||||
if (due._date == 0) return false;
|
||||
|
||||
std::string recur = parent.get ("recur");
|
||||
std::string recur = parent.get("recur");
|
||||
|
||||
bool specificEnd = false;
|
||||
Datetime until;
|
||||
if (parent.get ("until") != "")
|
||||
{
|
||||
until = Datetime (parent.get ("until"));
|
||||
if (parent.get("until") != "") {
|
||||
until = Datetime(parent.get("until"));
|
||||
specificEnd = true;
|
||||
}
|
||||
|
||||
auto recurrence_limit = Context::getContext ().config.getInteger ("recurrence.limit");
|
||||
auto recurrence_limit = Context::getContext().config.getInteger("recurrence.limit");
|
||||
int recurrence_counter = 0;
|
||||
Datetime now;
|
||||
for (Datetime i = due; ; i = getNextRecurrence (i, recur))
|
||||
{
|
||||
allDue.push_back (i);
|
||||
for (Datetime i = due;; i = getNextRecurrence(i, recur)) {
|
||||
allDue.push_back(i);
|
||||
|
||||
if (specificEnd && i > until)
|
||||
{
|
||||
if (specificEnd && i > until) {
|
||||
// If i > until, it means there are no more tasks to generate, and if the
|
||||
// parent mask contains all + or X, then there never will be another task
|
||||
// to generate, and this parent task may be safely reaped.
|
||||
auto mask = parent.get ("mask");
|
||||
if (mask.length () == allDue.size () &&
|
||||
mask.find ('-') == std::string::npos)
|
||||
return false;
|
||||
auto mask = parent.get("mask");
|
||||
if (mask.length() == allDue.size() && mask.find('-') == std::string::npos) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i > now)
|
||||
++recurrence_counter;
|
||||
if (i > now) ++recurrence_counter;
|
||||
|
||||
if (recurrence_counter >= recurrence_limit)
|
||||
return true;
|
||||
if (recurrence_counter >= recurrence_limit) return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Datetime getNextRecurrence (Datetime& current, std::string& period)
|
||||
{
|
||||
auto m = current.month ();
|
||||
auto d = current.day ();
|
||||
auto y = current.year ();
|
||||
auto ho = current.hour ();
|
||||
auto mi = current.minute ();
|
||||
auto se = current.second ();
|
||||
Datetime getNextRecurrence(Datetime& current, std::string& period) {
|
||||
auto m = current.month();
|
||||
auto d = current.day();
|
||||
auto y = current.year();
|
||||
auto ho = current.hour();
|
||||
auto mi = current.minute();
|
||||
auto se = current.second();
|
||||
|
||||
// Some periods are difficult, because they can be vague.
|
||||
if (period == "monthly" ||
|
||||
period == "P1M")
|
||||
{
|
||||
if (++m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
if (period == "monthly" || period == "P1M") {
|
||||
if (++m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period == "weekdays")
|
||||
{
|
||||
auto dow = current.dayOfWeek ();
|
||||
else if (period == "weekdays") {
|
||||
auto dow = current.dayOfWeek();
|
||||
int days;
|
||||
|
||||
if (dow == 5) days = 3;
|
||||
else if (dow == 6) days = 2;
|
||||
else days = 1;
|
||||
if (dow == 5)
|
||||
days = 3;
|
||||
else if (dow == 6)
|
||||
days = 2;
|
||||
else
|
||||
days = 1;
|
||||
|
||||
return current + (days * 86400);
|
||||
}
|
||||
|
||||
else if (unicodeLatinDigit (period[0]) &&
|
||||
period[period.length () - 1] == 'm')
|
||||
{
|
||||
int increment = strtol (period.substr (0, period.length () - 1).c_str (), nullptr, 10);
|
||||
else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'm') {
|
||||
int increment = strtol(period.substr(0, period.length() - 1).c_str(), nullptr, 10);
|
||||
|
||||
if (increment <= 0)
|
||||
throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment);
|
||||
throw format("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period,
|
||||
increment);
|
||||
|
||||
m += increment;
|
||||
while (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
while (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period[0] == 'P' &&
|
||||
Lexer::isAllDigits (period.substr (1, period.length () - 2)) &&
|
||||
period[period.length () - 1] == 'M')
|
||||
{
|
||||
int increment = strtol (period.substr (1, period.length () - 2).c_str (), nullptr, 10);
|
||||
else if (period[0] == 'P' && Lexer::isAllDigits(period.substr(1, period.length() - 2)) &&
|
||||
period[period.length() - 1] == 'M') {
|
||||
int increment = strtol(period.substr(1, period.length() - 2).c_str(), nullptr, 10);
|
||||
|
||||
if (increment <= 0)
|
||||
throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment);
|
||||
throw format("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period,
|
||||
increment);
|
||||
|
||||
m += increment;
|
||||
while (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
while (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d);
|
||||
return Datetime(y, m, d);
|
||||
}
|
||||
|
||||
else if (period == "quarterly" ||
|
||||
period == "P3M")
|
||||
{
|
||||
else if (period == "quarterly" || period == "P3M") {
|
||||
m += 3;
|
||||
if (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
if (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (unicodeLatinDigit (period[0]) && period[period.length () - 1] == 'q')
|
||||
{
|
||||
int increment = strtol (period.substr (0, period.length () - 1).c_str (), nullptr, 10);
|
||||
else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'q') {
|
||||
int increment = strtol(period.substr(0, period.length() - 1).c_str(), nullptr, 10);
|
||||
|
||||
if (increment <= 0)
|
||||
throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment);
|
||||
throw format("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period,
|
||||
increment);
|
||||
|
||||
m += 3 * increment;
|
||||
while (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
while (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period == "semiannual" ||
|
||||
period == "P6M")
|
||||
{
|
||||
else if (period == "semiannual" || period == "P6M") {
|
||||
m += 6;
|
||||
if (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
if (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period == "bimonthly" ||
|
||||
period == "P2M")
|
||||
{
|
||||
else if (period == "bimonthly" || period == "P2M") {
|
||||
m += 2;
|
||||
if (m > 12)
|
||||
{
|
||||
m -= 12;
|
||||
++y;
|
||||
if (m > 12) {
|
||||
m -= 12;
|
||||
++y;
|
||||
}
|
||||
|
||||
while (! Datetime::valid (y, m, d))
|
||||
--d;
|
||||
while (!Datetime::valid(y, m, d)) --d;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period == "biannual" ||
|
||||
period == "biyearly" ||
|
||||
period == "P2Y")
|
||||
{
|
||||
else if (period == "biannual" || period == "biyearly" || period == "P2Y") {
|
||||
y += 2;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
else if (period == "annual" ||
|
||||
period == "yearly" ||
|
||||
period == "P1Y")
|
||||
{
|
||||
else if (period == "annual" || period == "yearly" || period == "P1Y") {
|
||||
y += 1;
|
||||
|
||||
// If the due data just happens to be 2/29 in a leap year, then simply
|
||||
// incrementing y is going to create an invalid date.
|
||||
if (m == 2 && d == 29)
|
||||
d = 28;
|
||||
if (m == 2 && d == 29) d = 28;
|
||||
|
||||
return Datetime (y, m, d, ho, mi, se);
|
||||
return Datetime(y, m, d, ho, mi, se);
|
||||
}
|
||||
|
||||
// Add the period to current, and we're done.
|
||||
std::string::size_type idx = 0;
|
||||
Duration p;
|
||||
if (! p.parse (period, idx))
|
||||
throw std::string (format ("The recurrence value '{1}' is not valid.", period));
|
||||
if (!p.parse(period, idx))
|
||||
throw std::string(format("The recurrence value '{1}' is not valid.", period));
|
||||
|
||||
return current + p.toTime_t ();
|
||||
return current + p.toTime_t();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// When the status of a recurring child task changes, the parent task must
|
||||
// update it's mask.
|
||||
void updateRecurrenceMask (Task& task)
|
||||
{
|
||||
auto uuid = task.get ("parent");
|
||||
void updateRecurrenceMask(Task& task) {
|
||||
auto uuid = task.get("parent");
|
||||
Task parent;
|
||||
|
||||
if (uuid != "" &&
|
||||
Context::getContext ().tdb2.get (uuid, parent))
|
||||
{
|
||||
unsigned int index = strtol (task.get ("imask").c_str (), nullptr, 10);
|
||||
auto mask = parent.get ("mask");
|
||||
if (mask.length () > index)
|
||||
{
|
||||
mask[index] = (task.getStatus () == Task::pending) ? '-'
|
||||
: (task.getStatus () == Task::completed) ? '+'
|
||||
: (task.getStatus () == Task::deleted) ? 'X'
|
||||
: (task.getStatus () == Task::waiting) ? 'W'
|
||||
: '?';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uuid != "" && Context::getContext().tdb2.get(uuid, parent)) {
|
||||
unsigned int index = strtol(task.get("imask").c_str(), nullptr, 10);
|
||||
auto mask = parent.get("mask");
|
||||
if (mask.length() > index) {
|
||||
mask[index] = (task.getStatus() == Task::pending) ? '-'
|
||||
: (task.getStatus() == Task::completed) ? '+'
|
||||
: (task.getStatus() == Task::deleted) ? 'X'
|
||||
: (task.getStatus() == Task::waiting) ? 'W'
|
||||
: '?';
|
||||
} else {
|
||||
std::string mask;
|
||||
for (unsigned int i = 0; i < index; ++i)
|
||||
mask += "?";
|
||||
for (unsigned int i = 0; i < index; ++i) mask += "?";
|
||||
|
||||
mask += (task.getStatus () == Task::pending) ? '-'
|
||||
: (task.getStatus () == Task::completed) ? '+'
|
||||
: (task.getStatus () == Task::deleted) ? 'X'
|
||||
: (task.getStatus () == Task::waiting) ? 'W'
|
||||
: '?';
|
||||
mask += (task.getStatus() == Task::pending) ? '-'
|
||||
: (task.getStatus() == Task::completed) ? '+'
|
||||
: (task.getStatus() == Task::deleted) ? 'X'
|
||||
: (task.getStatus() == Task::waiting) ? 'W'
|
||||
: '?';
|
||||
}
|
||||
|
||||
parent.set ("mask", mask);
|
||||
Context::getContext ().tdb2.modify (parent);
|
||||
parent.set("mask", mask);
|
||||
Context::getContext().tdb2.modify(parent);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Delete expired tasks.
|
||||
void handleUntil ()
|
||||
{
|
||||
void handleUntil() {
|
||||
Datetime now;
|
||||
auto tasks = Context::getContext ().tdb2.pending_tasks ();
|
||||
for (auto& t : tasks)
|
||||
{
|
||||
auto tasks = Context::getContext().tdb2.pending_tasks();
|
||||
for (auto& t : tasks) {
|
||||
// TODO What about expiring template tasks?
|
||||
if (t.getStatus () == Task::pending &&
|
||||
t.has ("until"))
|
||||
{
|
||||
auto until = Datetime (t.get_date ("until"));
|
||||
if (until < now)
|
||||
{
|
||||
Context::getContext ().debug (format ("handleUntil: recurrence expired until {1} < now {2}", until.toISOLocalExtended (), now.toISOLocalExtended ()));
|
||||
t.setStatus (Task::deleted);
|
||||
Context::getContext ().tdb2.modify(t);
|
||||
Context::getContext ().footnote (onExpiration (t));
|
||||
if (t.getStatus() == Task::pending && t.has("until")) {
|
||||
auto until = Datetime(t.get_date("until"));
|
||||
if (until < now) {
|
||||
Context::getContext().debug(format("handleUntil: recurrence expired until {1} < now {2}",
|
||||
until.toISOLocalExtended(), now.toISOLocalExtended()));
|
||||
t.setStatus(Task::deleted);
|
||||
Context::getContext().tdb2.modify(t);
|
||||
Context::getContext().footnote(onExpiration(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue