Use Taskchampion to store Taskwarrior data

This replaces the TF2 task files with a TaskChampion replica.
This commit is contained in:
Dustin J. Mitchell
2022-12-15 02:52:47 +00:00
committed by Dustin J. Mitchell
parent 4b814bc602
commit 5bb9857984
24 changed files with 537 additions and 1362 deletions

View File

@@ -560,8 +560,8 @@ int Context::initialize (int argc, const char** argv)
if (taskdata_overridden && verbose ("override")) if (taskdata_overridden && verbose ("override"))
header (format ("TASKDATA override: {1}", data_dir._data)); header (format ("TASKDATA override: {1}", data_dir._data));
tdb2.set_location (data_dir); bool create_if_missing = !config.getBoolean ("exit.on.missing.db");
createDefaultConfig (); tdb2.open_replica (data_dir, create_if_missing);
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
@@ -1254,23 +1254,6 @@ void Context::createDefaultConfig ()
if (! File::write (rc_file._data, contents.str ())) if (! File::write (rc_file._data, contents.str ()))
throw format ("Could not write to '{1}'.", rc_file._data); throw format ("Could not write to '{1}'.", rc_file._data);
} }
// Create data location, if necessary.
Directory d (data_dir);
if (! d.exists ())
{
if (config.getBoolean ("exit.on.missing.db"))
throw std::string ("Error: rc.data.location does not exist - exiting according to rc.exit.on.missing.db setting.");
d.create ();
if (config.has ("hooks.location"))
d = Directory (config.get ("hooks.location"));
else
d += "hooks";
d.create ();
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -35,71 +35,8 @@
#include <stdio.h> #include <stdio.h>
#include <FS.h> #include <FS.h>
#include <Task.h> #include <Task.h>
#include <tc/WorkingSet.h>
// TF2 Class represents a single file in the task database. #include <tc/Replica.h>
class TF2
{
public:
TF2 ();
~TF2 ();
void target (const std::string&);
const std::vector <Task>& get_tasks ();
const std::vector <std::string>& get_lines ();
bool get (int, Task&);
bool get (const std::string&, Task&);
bool has (const std::string&);
void add_task (Task&);
bool modify_task (const Task&);
bool purge_task (const Task&);
void add_line (const std::string&);
void clear_tasks ();
void clear_lines ();
void commit ();
Task load_task (const std::string&);
void load_gc (Task&);
void load_tasks (bool from_gc = false);
void load_lines ();
// ID <--> UUID mapping.
std::string uuid (int);
int id (const std::string&);
void has_ids ();
void auto_dep_scan ();
void clear ();
const std::string dump ();
void dependency_scan ();
bool _read_only;
bool _dirty;
bool _loaded_tasks;
bool _loaded_lines;
bool _has_ids;
bool _auto_dep_scan;
std::vector <Task> _tasks;
// _tasks_map was introduced mainly for speeding up "task import".
// Iterating over all _tasks for each imported task is slow, making use of
// appropriate data structures is fast.
std::unordered_map <std::string, Task> _tasks_map;
std::vector <Task> _added_tasks;
std::vector <Task> _modified_tasks;
std::unordered_set <std::string> _purged_tasks;
std::vector <std::string> _lines;
std::vector <std::string> _added_lines;
File _file;
private:
std::unordered_map <int, std::string> _I2U; // ID -> UUID map
std::unordered_map <std::string, int> _U2I; // UUID -> ID map
};
// TDB2 Class represents all the files in the task database. // TDB2 Class represents all the files in the task database.
class TDB2 class TDB2
@@ -109,14 +46,13 @@ public:
TDB2 (); TDB2 ();
void set_location (const std::string&); void open_replica (const std::string&, bool create_if_missing);
void add (Task&, bool add_to_backlog = true); void add (Task&, bool add_to_backlog = true);
void modify (Task&, bool add_to_backlog = true); void modify (Task&, bool add_to_backlog = true);
void commit (); void commit ();
void get_changes (std::vector <Task>&); void get_changes (std::vector <Task>&);
void revert (); void revert ();
void gc (); void gc ();
int next_id ();
int latest_id (); int latest_id ();
// Generalized task accessors. // Generalized task accessors.
@@ -139,28 +75,11 @@ public:
void dump (); void dump ();
private: private:
void gather_changes (); tc::Replica replica;
void update (Task&, const bool, const bool addition = false); std::optional<tc::WorkingSet> _working_set;
bool verifyUniqueUUID (const std::string&);
const tc::WorkingSet &working_set ();
void show_diff (const std::string&, const std::string&, const std::string&); void show_diff (const std::string&, const std::string&, const std::string&);
void revert_undo (std::vector <std::string>&, std::string&, std::string&, std::string&, std::string&);
void revert_pending (std::vector <std::string>&, const std::string&, const std::string&);
void revert_completed (std::vector <std::string>&, std::vector <std::string>&, const std::string&, const std::string&);
void revert_backlog (std::vector <std::string>&, const std::string&, const std::string&, const std::string&);
protected:
friend class TF2; // TF2 reaches into TDB2 internals for gc
TF2 pending;
TF2 completed;
friend class CmdSync; // CmdSync accesses the backlog directly
TF2 backlog;
TF2 undo;
private:
std::string _location;
int _id;
std::vector <Task> _changes;
}; };
#endif #endif

View File

@@ -144,6 +144,19 @@ Task::Task (const json::object* obj)
parseJSON (obj); parseJSON (obj);
} }
////////////////////////////////////////////////////////////////////////////////
Task::Task (tc::Task obj)
{
id = 0;
urgency_value = 0.0;
recalc_urgency = true;
is_blocked = false;
is_blocking = false;
annotation_count = 0;
parseTC (obj);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
Task::status Task::textToStatus (const std::string& input) Task::status Task::textToStatus (const std::string& input)
{ {
@@ -895,6 +908,29 @@ void Task::parseJSON (const json::object* root_obj)
} }
} }
////////////////////////////////////////////////////////////////////////////////
// Note that all fields undergo encode/decode.
void Task::parseTC (const tc::Task& task)
{
data = task.get_taskmap ();
// count annotations
annotation_count = 0;
for (auto i : data)
{
if (isAnnotationAttr (i.first))
{
++annotation_count;
}
}
data["uuid"] = task.get_uuid ();
id = Context::getContext ().tdb2.id (data["uuid"]);
is_blocking = task.is_blocking();
is_blocked = task.is_blocked();
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// No legacy formats are currently supported as of 2.4.0. // No legacy formats are currently supported as of 2.4.0.
void Task::parseLegacy (const std::string& line) void Task::parseLegacy (const std::string& line)
@@ -1711,6 +1747,9 @@ void Task::substitute (
// 1) To provide missing attributes where possible // 1) To provide missing attributes where possible
// 2) To provide suitable warnings about odd states // 2) To provide suitable warnings about odd states
// 3) To generate errors when the inconsistencies are not fixable // 3) To generate errors when the inconsistencies are not fixable
// 4) To update status depending on other attributes
//
// Critically, note that despite the name this is not a read-only function.
// //
void Task::validate (bool applyDefault /* = true */) void Task::validate (bool applyDefault /* = true */)
{ {
@@ -1938,6 +1977,31 @@ const std::string Task::decode (const std::string& value) const
return str_replace (modified, "&close;", "]"); return str_replace (modified, "&close;", "]");
} }
////////////////////////////////////////////////////////////////////////////////
tc::Status Task::status2tc (const Task::status status)
{
switch (status) {
case Task::pending: return tc::Status::Pending;
case Task::completed: return tc::Status::Completed;
case Task::deleted: return tc::Status::Deleted;
case Task::waiting: return tc::Status::Pending; // waiting is no longer a status
case Task::recurring: return tc::Status::Recurring;
default: return tc::Status::Unknown;
}
}
////////////////////////////////////////////////////////////////////////////////
Task::status Task::tc2status (const tc::Status status)
{
switch (status) {
case tc::Status::Pending: return Task::pending;
case tc::Status::Completed: return Task::completed;
case tc::Status::Deleted: return Task::deleted;
case tc::Status::Recurring: return Task::recurring;
default: return Task::pending;
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
int Task::determineVersion (const std::string& line) int Task::determineVersion (const std::string& line)
{ {

View File

@@ -35,6 +35,7 @@
#include <JSON.h> #include <JSON.h>
#include <Table.h> #include <Table.h>
#include <Datetime.h> #include <Datetime.h>
#include <tc/Task.h>
class Task class Task
{ {
@@ -65,6 +66,7 @@ public:
bool operator!= (const Task&); bool operator!= (const Task&);
Task (const std::string&); Task (const std::string&);
Task (const json::object*); Task (const json::object*);
Task (tc::Task);
void parse (const std::string&); void parse (const std::string&);
std::string composeF4 () const; std::string composeF4 () const;
@@ -87,6 +89,8 @@ public:
// Series of helper functions. // Series of helper functions.
static status textToStatus (const std::string&); static status textToStatus (const std::string&);
static std::string statusToText (status); static std::string statusToText (status);
static tc::Status status2tc (const Task::status);
static Task::status tc2status (const tc::Status);
void setAsNow (const std::string&); void setAsNow (const std::string&);
bool has (const std::string&) const; bool has (const std::string&) const;
@@ -178,10 +182,12 @@ public:
Table diffForUndoSide (const Task& after) const; Table diffForUndoSide (const Task& after) const;
Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const; Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const;
private: private:
int determineVersion (const std::string&); int determineVersion (const std::string&);
void parseJSON (const std::string&); void parseJSON (const std::string&);
void parseJSON (const json::object*); void parseJSON (const json::object*);
void parseTC (const tc::Task&);
void parseLegacy (const std::string&); void parseLegacy (const std::string&);
void validate_before (const std::string&, const std::string&); void validate_before (const std::string&, const std::string&);
const std::string encode (const std::string&) const; const std::string encode (const std::string&) const;

View File

@@ -112,15 +112,17 @@ int CmdExport::execute (std::string& output)
} }
else else
{ {
// There is a sortOrder, so sorting will take place, which means the initial // sort_tasks requires the order array initially be identity
// order of sequence is ascending.
for (unsigned int i = 0; i < filtered.size (); ++i) for (unsigned int i = 0; i < filtered.size (); ++i)
sequence.push_back (i); sequence.push_back (i);
// Sort the tasks. // if no sort order, sort by id
if (sortOrder.size ()) { if (!sortOrder.size ()) {
sort_tasks (filtered, sequence, reportSort); reportSort = "id";
} }
// Sort the tasks.
sort_tasks (filtered, sequence, reportSort);
} }
// Export == render. // Export == render.

View File

@@ -90,7 +90,6 @@ void handleRecurrence ()
Task rec (t); // Clone the parent. Task rec (t); // Clone the parent.
rec.setStatus (Task::pending); // Change the status. rec.setStatus (Task::pending); // Change the status.
rec.id = Context::getContext ().tdb2.next_id (); // New ID.
rec.set ("uuid", uuid ()); // New UUID. rec.set ("uuid", uuid ()); // New UUID.
rec.set ("parent", t.get ("uuid")); // Remember mom. rec.set ("parent", t.get ("uuid")); // Remember mom.
rec.setAsNow ("entry"); // New entry date. rec.setAsNow ("entry"); // New entry date.

View File

@@ -33,6 +33,25 @@
using namespace tc::ffi; using namespace tc::ffi;
////////////////////////////////////////////////////////////////////////////////
tc::ReplicaGuard::ReplicaGuard (Replica &replica, Task &task) :
replica(replica),
task(task)
{
// "steal" the reference from the Replica and store it locally, so that any
// attempt to use the Replica will fail
tcreplica = replica.inner.release();
task.to_mut(tcreplica);
}
////////////////////////////////////////////////////////////////////////////////
tc::ReplicaGuard::~ReplicaGuard ()
{
task.to_immut();
// return the reference to the Replica.
replica.inner.reset(tcreplica);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
tc::Replica::Replica () tc::Replica::Replica ()
{ {
@@ -113,6 +132,45 @@ tc::Task tc::Replica::new_task (tc::Status status, const std::string &descriptio
return Task (tctask); return Task (tctask);
} }
////////////////////////////////////////////////////////////////////////////////
tc::Task tc::Replica::import_task_with_uuid (const std::string &uuid)
{
TCTask *tctask = tc_replica_import_task_with_uuid (&*inner, uuid2tc (uuid));
if (!tctask) {
throw replica_error ();
}
return Task (tctask);
}
////////////////////////////////////////////////////////////////////////////////
void tc::Replica::undo (int32_t *undone_out)
{
auto res = tc_replica_undo (&*inner, undone_out);
if (res != TC_RESULT_OK) {
throw replica_error ();
}
}
////////////////////////////////////////////////////////////////////////////////
int64_t tc::Replica::num_local_operations ()
{
auto num = tc_replica_num_local_operations (&*inner);
if (num < 0) {
throw replica_error ();
}
return num;
}
////////////////////////////////////////////////////////////////////////////////
int64_t tc::Replica::num_undo_points ()
{
auto num = tc_replica_num_undo_points (&*inner);
if (num < 0) {
throw replica_error ();
}
return num;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::vector<tc::Task> tc::Replica::all_tasks () std::vector<tc::Task> tc::Replica::all_tasks ()
{ {
@@ -134,14 +192,19 @@ std::vector<tc::Task> tc::Replica::all_tasks ()
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void tc::Replica::rebuild_working_set () void tc::Replica::rebuild_working_set (bool force)
{ {
auto res = tc_replica_rebuild_working_set (&*inner, true); auto res = tc_replica_rebuild_working_set (&*inner, force);
if (res != TC_RESULT_OK) { if (res != TC_RESULT_OK) {
throw replica_error (); throw replica_error ();
} }
} }
////////////////////////////////////////////////////////////////////////////////
tc::ReplicaGuard tc::Replica::mutate_task (tc::Task &task) {
return ReplicaGuard(*this, task);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string tc::Replica::replica_error () { std::string tc::Replica::replica_error () {
return replica_error (tc_replica_error (&*inner)); return replica_error (tc_replica_error (&*inner));

View File

@@ -45,6 +45,28 @@ namespace tc {
tc::ffi::TCReplica, tc::ffi::TCReplica,
std::function<void(tc::ffi::TCReplica*)>>; std::function<void(tc::ffi::TCReplica*)>>;
// ReplicaGuard uses RAII to ensure that a Replica is not accessed while it
// is mutably borrowed (specifically, to make a task mutable).
class ReplicaGuard {
protected:
friend class Replica;
explicit ReplicaGuard (Replica &, Task &);
public:
~ReplicaGuard();
// No moving or copying allowed
ReplicaGuard (const ReplicaGuard &) = delete;
ReplicaGuard &operator=(const ReplicaGuard &) = delete;
ReplicaGuard (ReplicaGuard &&) = delete;
ReplicaGuard &operator=(Replica &&) = delete;
private:
Replica &replica;
tc::ffi::TCReplica *tcreplica;
Task &task;
};
// Replica wraps the TCReplica type, managing its memory, errors, and so on. // Replica wraps the TCReplica type, managing its memory, errors, and so on.
// //
// Except as noted, method names match the suffix to `tc_replica_..`. // Except as noted, method names match the suffix to `tc_replica_..`.
@@ -67,12 +89,20 @@ namespace tc {
tc::WorkingSet working_set (); tc::WorkingSet working_set ();
std::optional<tc::Task> get_task (const std::string &uuid); std::optional<tc::Task> get_task (const std::string &uuid);
tc::Task new_task (Status status, const std::string &description); tc::Task new_task (Status status, const std::string &description);
tc::Task import_task_with_uuid (const std::string &uuid);
// TODO: struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); // TODO: struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid);
// TODO: TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); // TODO: TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots);
// TODO: TCResult tc_replica_undo(struct TCReplica *rep, int32_t *undone_out); void undo (int32_t *undone_out);
int64_t num_local_operations ();
int64_t num_undo_points ();
// TODO: TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); // TODO: TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force);
void rebuild_working_set (); void rebuild_working_set (bool force);
private:
ReplicaGuard mutate_task(tc::Task &);
void immut_task(tc::Task &);
protected:
friend class ReplicaGuard;
unique_tcreplica_ptr inner; unique_tcreplica_ptr inner;
// construct an error message from tc_replica_error, or from the given // construct an error message from tc_replica_error, or from the given
@@ -82,5 +112,6 @@ namespace tc {
}; };
} }
#endif #endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -60,6 +60,18 @@ tc::Task& tc::Task::operator= (Task &&other) noexcept
return *this; return *this;
} }
////////////////////////////////////////////////////////////////////////////////
void tc::Task::to_mut (TCReplica *replica)
{
tc_task_to_mut(&*inner, replica);
}
////////////////////////////////////////////////////////////////////////////////
void tc::Task::to_immut ()
{
tc_task_to_immut(&*inner);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string tc::Task::get_uuid () const std::string tc::Task::get_uuid () const
{ {
@@ -100,6 +112,15 @@ std::string tc::Task::get_description () const
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::optional<std::string> tc::Task::get_value (std::string property) const
{
auto maybe_desc = tc_task_get_value (&*inner, string2tc(property));
if (maybe_desc.ptr == NULL) {
return std::nullopt;
}
return std::make_optional(tc2string(maybe_desc));
}
bool tc::Task::is_waiting () const bool tc::Task::is_waiting () const
{ {
return tc_task_is_waiting (&*inner); return tc_task_is_waiting (&*inner);
@@ -123,6 +144,40 @@ bool tc::Task::is_blocking () const
return tc_task_is_blocking (&*inner); return tc_task_is_blocking (&*inner);
} }
////////////////////////////////////////////////////////////////////////////////
void tc::Task::set_status (tc::Status status)
{
TCResult res = tc_task_set_status (&*inner, (TCStatus)status);
if (res != TC_RESULT_OK) {
throw task_error ();
}
}
////////////////////////////////////////////////////////////////////////////////
void tc::Task::set_value (std::string property, std::optional<std::string> value)
{
TCResult res;
if (value.has_value()) {
res = tc_task_set_value (&*inner, string2tc(property), string2tc(value.value()));
} else {
TCString nullstr;
nullstr.ptr = NULL;
res = tc_task_set_value (&*inner, string2tc(property), nullstr);
}
if (res != TC_RESULT_OK) {
throw task_error ();
}
}
////////////////////////////////////////////////////////////////////////////////
void tc::Task::set_modified (time_t modified)
{
TCResult res = tc_task_set_modified (&*inner, modified);
if (res != TC_RESULT_OK) {
throw task_error ();
}
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string tc::Task::task_error () const { std::string tc::Task::task_error () const {
TCString error = tc_task_error (&*inner); TCString error = tc_task_error (&*inner);

View File

@@ -31,10 +31,12 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <map> #include <map>
#include <optional>
#include "tc/ffi.h" #include "tc/ffi.h"
namespace tc { namespace tc {
class Replica; class Replica;
class ReplicaGuard;
enum Status { enum Status {
Pending = tc::ffi::TC_STATUS_PENDING, Pending = tc::ffi::TC_STATUS_PENDING,
@@ -57,10 +59,16 @@ namespace tc {
class Task class Task
{ {
protected: protected:
// Tasks may only be created by tc::Replica // Tasks may only be created and made mutable/immutable
// by tc::Replica
friend class tc::Replica; friend class tc::Replica;
explicit Task (tc::ffi::TCTask *); explicit Task (tc::ffi::TCTask *);
// RplicaGuard handles mut/immut
friend class tc::ReplicaGuard;
void to_mut(tc::ffi::TCReplica *);
void to_immut();
public: public:
// This object "owns" inner, so copy is not allowed. // This object "owns" inner, so copy is not allowed.
Task (const Task &) = delete; Task (const Task &) = delete;
@@ -70,12 +78,11 @@ namespace tc {
Task (Task &&) noexcept; Task (Task &&) noexcept;
Task &operator=(Task &&) noexcept; Task &operator=(Task &&) noexcept;
// TODO: void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica);
// TODO: void tc_task_to_immut(struct TCTask *task);
std::string get_uuid () const; std::string get_uuid () const;
Status get_status () const; Status get_status () const;
std::map <std::string, std::string> get_taskmap() const; std::map <std::string, std::string> get_taskmap() const;
std::string get_description() const; std::string get_description() const;
std::optional<std::string> get_value(std::string property) const;
// TODO: time_t tc_task_get_entry(struct TCTask *task); // TODO: time_t tc_task_get_entry(struct TCTask *task);
// TODO: time_t tc_task_get_wait(struct TCTask *task); // TODO: time_t tc_task_get_wait(struct TCTask *task);
// TODO: time_t tc_task_get_modified(struct TCTask *task); // TODO: time_t tc_task_get_modified(struct TCTask *task);
@@ -90,11 +97,12 @@ namespace tc {
// TODO: struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); // TODO: struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key);
// TODO: struct TCUdaList tc_task_get_udas(struct TCTask *task); // TODO: struct TCUdaList tc_task_get_udas(struct TCTask *task);
// TODO: struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); // TODO: struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task);
// TODO: TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); void set_status(Status status);
// TODO: TCResult tc_task_set_description(struct TCTask *task, struct TCString description); // TODO: TCResult tc_task_set_description(struct TCTask *task, struct TCString description);
void set_value(std::string property, std::optional<std::string> value);
// TODO: TCResult tc_task_set_entry(struct TCTask *task, time_t entry); // TODO: TCResult tc_task_set_entry(struct TCTask *task, time_t entry);
// TODO: TCResult tc_task_set_wait(struct TCTask *task, time_t wait); // TODO: TCResult tc_task_set_wait(struct TCTask *task, time_t wait);
// TODO: TCResult tc_task_set_modified(struct TCTask *task, time_t modified); void set_modified(time_t modified);
// TODO: TCResult tc_task_start(struct TCTask *task); // TODO: TCResult tc_task_start(struct TCTask *task);
// TODO: TCResult tc_task_stop(struct TCTask *task); // TODO: TCResult tc_task_stop(struct TCTask *task);
// TODO: TCResult tc_task_done(struct TCTask *task); // TODO: TCResult tc_task_done(struct TCTask *task);

1
test/.gitignore vendored
View File

@@ -1,6 +1,7 @@
*.o *.o
*.pyc *.pyc
*.data *.data
*.sqlite3
*.log *.log
*.runlog *.runlog
col.t col.t

View File

@@ -13,6 +13,7 @@ include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src ${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/src/taskchampion/lib
${CMAKE_SOURCE_DIR}/test ${CMAKE_SOURCE_DIR}/test
${CMAKE_SOURCE_DIR}/taskchampion/lib ${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS}) ${TASK_INCLUDE_DIRS})

View File

@@ -1,88 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
###############################################################################
#
# 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
#
###############################################################################
import sys
import os
import unittest
# Ensure python finds the local simpletap module
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from basetest import Task, TestCase
class Test1510(TestCase):
def setUp(self):
self.t = Task()
def assertNoEmptyValueInBacklog(self, attribute_name):
backlog_path = os.path.join(self.t.datadir, 'backlog.data')
with open(backlog_path) as backlog:
empty_value = '"%s":""' % attribute_name
self.assertFalse(any(empty_value in line
for line in backlog.readlines()))
def test_no_empty_value_for_deleted_due_in_backlog(self):
"""
1510: Make sure deleted due attribute does not get into
backlog.data with empty string value
"""
self.t('add test due:2015-05-05')
self.t('1 mod due:')
self.assertNoEmptyValueInBacklog('due')
def test_no_empty_value_for_empty_priority_in_backlog(self):
"""
1510: Make sure empty priority attribute does not get into
backlog.data with empty string value
"""
self.t('add test pri:""')
self.t("add test2 pri:''")
self.t("add test3 pri:")
self.t("add test4 pri:H")
self.assertNoEmptyValueInBacklog('priority')
def test_no_empty_value_for_empty_project_in_backlog(self):
"""
1510: Make sure empty project attribute does not get into
backlog.data with empty string value
"""
self.t('add test project:""')
self.t("add test2 project:''")
self.t("add test3 project:")
self.t("add test4 project:random")
self.assertNoEmptyValueInBacklog('project')
if __name__ == "__main__":
from simpletap import TAPTestRunner
unittest.main(testRunner=TAPTestRunner())
# vim: ai sts=4 et sw=4 ft=python

View File

@@ -68,7 +68,7 @@ class TestFeature559(TestCase):
code, out, err = self.t.runError("rc.data.location=locationdoesnotexist list") code, out, err = self.t.runError("rc.data.location=locationdoesnotexist list")
self.assertNotIn("footask", out) self.assertNotIn("footask", out)
self.assertNotIn("Error", out) self.assertNotIn("Error", out)
self.assertRegex(err, re.compile("Error:.+does not exist", re.DOTALL)) self.assertRegex(err, re.compile("Could not.+unable to open database file", re.DOTALL))
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -128,7 +128,8 @@ class TestIDRangeParsing(TestCase):
with tempfile.NamedTemporaryFile(mode='w') as f: with tempfile.NamedTemporaryFile(mode='w') as f:
f.write('\n'.join([f'{{"description": "test task {i+1}"}}' for i in range(n)])) f.write('\n'.join([f'{{"description": "test task {i+1}"}}' for i in range(n)]))
f.flush() f.flush()
code, out, err = self.t(f'import {f.name}') # use a long timeout here, because import is quite slow
code, out, err = self.t(f'import {f.name}', timeout=100)
def test_single_digit_range(self): def test_single_digit_range(self):
"""Test that parsing single digit ID range works""" """Test that parsing single digit ID range works"""

View File

@@ -377,6 +377,7 @@ class TestUpgradeToRecurring(TestCase):
def test_upgrade(self): def test_upgrade(self):
"""Upgrade task to recurring""" """Upgrade task to recurring"""
# note that this functionality is implemented in Task::validate
self.t("add foo") self.t("add foo")
self.t("1 modify due:tomorrow recur:weekly") self.t("1 modify due:tomorrow recur:weekly")
code, out, err = self.t("_get 1.status") code, out, err = self.t("_get 1.status")

View File

@@ -77,7 +77,7 @@ int main (int, char**)
t.ok(maybe_task2.has_value(), "task lookup by uuid finds task"); t.ok(maybe_task2.has_value(), "task lookup by uuid finds task");
t.is ((*maybe_task2).get_description (), std::string ("a test"), "task description round-trip"); t.is ((*maybe_task2).get_description (), std::string ("a test"), "task description round-trip");
rep.rebuild_working_set (); rep.rebuild_working_set (true);
t.pass ("rebuild_working_set"); t.pass ("rebuild_working_set");
auto tasks = rep.all_tasks (); auto tasks = rep.all_tasks ();

View File

@@ -60,7 +60,7 @@ int main (int, char**)
context.config.set ("gc", 1); context.config.set ("gc", 1);
context.config.set ("debug", 1); context.config.set ("debug", 1);
context.tdb2.set_location ("."); context.tdb2.open_replica (".", true);
// Try reading an empty database. // Try reading an empty database.
std::vector <Task> pending = context.tdb2.pending_tasks (); std::vector <Task> pending = context.tdb2.pending_tasks ();
@@ -104,7 +104,7 @@ int main (int, char**)
// Reset for reuse. // Reset for reuse.
cleardb (); cleardb ();
context.tdb2.set_location ("."); context.tdb2.open_replica (".", true);
// TODO commit // TODO commit
// TODO complete a task // TODO complete a task

View File

@@ -14,5 +14,5 @@ task log two depends:1
task /two/ export > JSON task /two/ export > JSON
rm pending.data completed.data rm taskchampion.sqlite3
task import JSON task import JSON

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
# This tests the migration path from 2.5.3 or earlier to 2.6.0 with respect to
# the upgrade of the status field from waiting to pending
. bash_tap_tw.sh
# Setup
task add Actionable task wait:yesterday
task add Non-actionable task wait:tomorrow+1h
# Simulate this was created in 2.5.3 or earlier (status is equal to waiting,
# not pending). Using more cumbersome sed syntax for Mac OS-X compatibility.
sed -i".bak" 's/pending/waiting/g' $TASKDATA/pending.data
rm -f $TASKDATA/pending.data.bak
# Trigger upgrade
task all
# Report file content
echo pending.data
cat $TASKDATA/pending.data
echo completed.data
cat $TASKDATA/completed.data
# Assertion: Exactly one task is considered waiting
[[ `task +WAITING count` == "1" ]]
[[ `task status:waiting count` == "1" ]]
# Assertion: Exactly one task is considered pending
[[ `task +PENDING count` == "1" ]]
[[ `task status:pending count` == "1" ]]
# Assertion: Task 1 is pending
[[ `task _get 1.status` == "pending" ]]
# Assertion: Task 2 is waiting
[[ `task _get 2.status` == "waiting" ]]
# Assertion: No lines in data files with "waiting" status
[[ -z `cat $TASKDATA/pending.data | grep waiting` ]]
[[ -z `cat $TASKDATA/completed.data | grep waiting` ]]
# Assertion: No tasks were moved into completed.data
cat $TASKDATA/pending.data | wc -l | tr -d ' '
[[ `cat $TASKDATA/pending.data | wc -l | tr -d ' '` == "2" ]]
[[ `cat $TASKDATA/completed.data | wc -l | tr -d ' '` == "0" ]]

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
###############################################################################
#
# 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
#
###############################################################################
import sys
import os
import unittest
# Ensure python finds the local simpletap module
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from basetest import Task, TestCase
class TestBug46(TestCase):
def setUp(self):
self.t = Task()
self.t("add one")
self.t("add two")
self.t("add three")
def test_bug46(self):
"""Circular dependency detection broken by deletion of completed.data"""
self.t("1 mod dep:2")
self.t("2 delete", input="y\n")
self.t("rc.gc=on list")
os.remove(os.path.join(self.t.datadir, "completed.data"))
self.t("1 mod dep:2")
if __name__ == "__main__":
from simpletap import TAPTestRunner
unittest.main(testRunner=TAPTestRunner())
# vim: ai sts=4 et sw=4 ft=python

View File

@@ -102,7 +102,8 @@ class TestBug634(TestCase):
# If a prompt happens, the test will timeout on input (exitcode != 0) # If a prompt happens, the test will timeout on input (exitcode != 0)
code, out, err = self.t("rc.confirmation=off undo") code, out, err = self.t("rc.confirmation=off undo")
self.assertIn("Task removed", out) code, out, err = self.t("_get 1.description")
self.assertEqual(out.strip(), '') # task is gone
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -179,15 +179,28 @@ class TestUUIDuplicates(TestCase):
"""Executed before each test in the class""" """Executed before each test in the class"""
self.t = Task() self.t = Task()
def test_uuid_duplicates(self): def test_uuid_duplicates_dupe(self):
"""Verify that duplicating tasks, and recurring tasks do no create duplicates UUIDs""" """Verify that duplicating tasks does not create duplicate UUIDs"""
self.t("add simple") self.t("add simple")
self.t("1 duplicate") self.t("1 duplicate")
self.t("add periodic recur:daily due:yesterday")
uuids = list()
for id in range(1,3):
code, out, err = self.t("_get %d.uuid" % id)
uuids.append(out.strip())
self.assertEqual(len(uuids), len(set(uuids)))
code, out, err = self.t("diag")
self.assertIn("No duplicates found", out)
def test_uuid_duplicates_recurrence(self):
"""Verify that recurring tasks do not create duplicate UUIDs"""
print(self.t("add periodic recur:daily due:yesterday"))
self.t("list") # GC/handleRecurrence self.t("list") # GC/handleRecurrence
uuids = list() uuids = list()
for id in range(1,7): for id in range(1,5):
code, out, err = self.t("_get %d.uuid" % id) code, out, err = self.t("_get %d.uuid" % id)
uuids.append(out.strip()) uuids.append(out.strip())