mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-08-27 19:17:19 +02:00
Use Taskchampion to store Taskwarrior data
This replaces the TF2 task files with a TaskChampion replica.
This commit is contained in:
parent
4b814bc602
commit
5bb9857984
24 changed files with 537 additions and 1362 deletions
|
@ -560,8 +560,8 @@ int Context::initialize (int argc, const char** argv)
|
|||
if (taskdata_overridden && verbose ("override"))
|
||||
header (format ("TASKDATA override: {1}", data_dir._data));
|
||||
|
||||
tdb2.set_location (data_dir);
|
||||
createDefaultConfig ();
|
||||
bool create_if_missing = !config.getBoolean ("exit.on.missing.db");
|
||||
tdb2.open_replica (data_dir, create_if_missing);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@ -1254,23 +1254,6 @@ void Context::createDefaultConfig ()
|
|||
if (! File::write (rc_file._data, contents.str ()))
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
1292
src/TDB2.cpp
1292
src/TDB2.cpp
File diff suppressed because it is too large
Load diff
95
src/TDB2.h
95
src/TDB2.h
|
@ -35,71 +35,8 @@
|
|||
#include <stdio.h>
|
||||
#include <FS.h>
|
||||
#include <Task.h>
|
||||
|
||||
// TF2 Class represents a single file in the task database.
|
||||
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
|
||||
};
|
||||
#include <tc/WorkingSet.h>
|
||||
#include <tc/Replica.h>
|
||||
|
||||
// TDB2 Class represents all the files in the task database.
|
||||
class TDB2
|
||||
|
@ -109,14 +46,13 @@ public:
|
|||
|
||||
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 modify (Task&, bool add_to_backlog = true);
|
||||
void commit ();
|
||||
void get_changes (std::vector <Task>&);
|
||||
void revert ();
|
||||
void gc ();
|
||||
int next_id ();
|
||||
int latest_id ();
|
||||
|
||||
// Generalized task accessors.
|
||||
|
@ -139,28 +75,11 @@ public:
|
|||
void dump ();
|
||||
|
||||
private:
|
||||
void gather_changes ();
|
||||
void update (Task&, const bool, const bool addition = false);
|
||||
bool verifyUniqueUUID (const std::string&);
|
||||
tc::Replica replica;
|
||||
std::optional<tc::WorkingSet> _working_set;
|
||||
|
||||
const tc::WorkingSet &working_set ();
|
||||
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
|
||||
|
|
64
src/Task.cpp
64
src/Task.cpp
|
@ -144,6 +144,19 @@ Task::Task (const json::object* 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)
|
||||
{
|
||||
|
@ -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.
|
||||
void Task::parseLegacy (const std::string& line)
|
||||
|
@ -1711,6 +1747,9 @@ void Task::substitute (
|
|||
// 1) To provide missing attributes where possible
|
||||
// 2) To provide suitable warnings about odd states
|
||||
// 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 */)
|
||||
{
|
||||
|
@ -1938,6 +1977,31 @@ const std::string Task::decode (const std::string& value) const
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <JSON.h>
|
||||
#include <Table.h>
|
||||
#include <Datetime.h>
|
||||
#include <tc/Task.h>
|
||||
|
||||
class Task
|
||||
{
|
||||
|
@ -65,6 +66,7 @@ public:
|
|||
bool operator!= (const Task&);
|
||||
Task (const std::string&);
|
||||
Task (const json::object*);
|
||||
Task (tc::Task);
|
||||
|
||||
void parse (const std::string&);
|
||||
std::string composeF4 () const;
|
||||
|
@ -87,6 +89,8 @@ public:
|
|||
// Series of helper functions.
|
||||
static status textToStatus (const std::string&);
|
||||
static std::string statusToText (status);
|
||||
static tc::Status status2tc (const Task::status);
|
||||
static Task::status tc2status (const tc::Status);
|
||||
|
||||
void setAsNow (const std::string&);
|
||||
bool has (const std::string&) const;
|
||||
|
@ -178,10 +182,12 @@ public:
|
|||
Table diffForUndoSide (const Task& after) const;
|
||||
Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const;
|
||||
|
||||
|
||||
private:
|
||||
int determineVersion (const std::string&);
|
||||
void parseJSON (const std::string&);
|
||||
void parseJSON (const json::object*);
|
||||
void parseTC (const tc::Task&);
|
||||
void parseLegacy (const std::string&);
|
||||
void validate_before (const std::string&, const std::string&);
|
||||
const std::string encode (const std::string&) const;
|
||||
|
|
|
@ -112,15 +112,17 @@ int CmdExport::execute (std::string& output)
|
|||
}
|
||||
else
|
||||
{
|
||||
// There is a sortOrder, so sorting will take place, which means the initial
|
||||
// order of sequence is ascending.
|
||||
// sort_tasks requires the order array initially be identity
|
||||
for (unsigned int i = 0; i < filtered.size (); ++i)
|
||||
sequence.push_back (i);
|
||||
|
||||
// Sort the tasks.
|
||||
if (sortOrder.size ()) {
|
||||
sort_tasks (filtered, sequence, reportSort);
|
||||
// if no sort order, sort by id
|
||||
if (!sortOrder.size ()) {
|
||||
reportSort = "id";
|
||||
}
|
||||
|
||||
// Sort the tasks.
|
||||
sort_tasks (filtered, sequence, reportSort);
|
||||
}
|
||||
|
||||
// Export == render.
|
||||
|
|
|
@ -90,7 +90,6 @@ void handleRecurrence ()
|
|||
|
||||
Task rec (t); // Clone the parent.
|
||||
rec.setStatus (Task::pending); // Change the status.
|
||||
rec.id = Context::getContext ().tdb2.next_id (); // New ID.
|
||||
rec.set ("uuid", uuid ()); // New UUID.
|
||||
rec.set ("parent", t.get ("uuid")); // Remember mom.
|
||||
rec.setAsNow ("entry"); // New entry date.
|
||||
|
|
|
@ -33,6 +33,25 @@
|
|||
|
||||
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 ()
|
||||
{
|
||||
|
@ -113,6 +132,45 @@ tc::Task tc::Replica::new_task (tc::Status status, const std::string &descriptio
|
|||
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 ()
|
||||
{
|
||||
|
@ -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) {
|
||||
throw replica_error ();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
tc::ReplicaGuard tc::Replica::mutate_task (tc::Task &task) {
|
||||
return ReplicaGuard(*this, task);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::string tc::Replica::replica_error () {
|
||||
return replica_error (tc_replica_error (&*inner));
|
||||
|
|
|
@ -45,6 +45,28 @@ namespace tc {
|
|||
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.
|
||||
//
|
||||
// Except as noted, method names match the suffix to `tc_replica_..`.
|
||||
|
@ -67,12 +89,20 @@ namespace tc {
|
|||
tc::WorkingSet working_set ();
|
||||
std::optional<tc::Task> get_task (const std::string &uuid);
|
||||
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: 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);
|
||||
void rebuild_working_set ();
|
||||
private:
|
||||
void rebuild_working_set (bool force);
|
||||
|
||||
ReplicaGuard mutate_task(tc::Task &);
|
||||
void immut_task(tc::Task &);
|
||||
|
||||
protected:
|
||||
friend class ReplicaGuard;
|
||||
unique_tcreplica_ptr inner;
|
||||
|
||||
// construct an error message from tc_replica_error, or from the given
|
||||
|
@ -82,5 +112,6 @@ namespace tc {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -60,6 +60,18 @@ tc::Task& tc::Task::operator= (Task &&other) noexcept
|
|||
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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
return tc_task_is_waiting (&*inner);
|
||||
|
@ -123,6 +144,40 @@ bool tc::Task::is_blocking () const
|
|||
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 {
|
||||
TCString error = tc_task_error (&*inner);
|
||||
|
|
|
@ -31,10 +31,12 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include "tc/ffi.h"
|
||||
|
||||
namespace tc {
|
||||
class Replica;
|
||||
class ReplicaGuard;
|
||||
|
||||
enum Status {
|
||||
Pending = tc::ffi::TC_STATUS_PENDING,
|
||||
|
@ -57,10 +59,16 @@ namespace tc {
|
|||
class Task
|
||||
{
|
||||
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;
|
||||
explicit Task (tc::ffi::TCTask *);
|
||||
|
||||
// RplicaGuard handles mut/immut
|
||||
friend class tc::ReplicaGuard;
|
||||
void to_mut(tc::ffi::TCReplica *);
|
||||
void to_immut();
|
||||
|
||||
public:
|
||||
// This object "owns" inner, so copy is not allowed.
|
||||
Task (const Task &) = delete;
|
||||
|
@ -70,12 +78,11 @@ namespace tc {
|
|||
Task (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;
|
||||
Status get_status () const;
|
||||
std::map <std::string, std::string> get_taskmap() 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_wait(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 TCUdaList tc_task_get_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);
|
||||
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_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_stop(struct TCTask *task);
|
||||
// TODO: TCResult tc_task_done(struct TCTask *task);
|
||||
|
|
1
test/.gitignore
vendored
1
test/.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
*.o
|
||||
*.pyc
|
||||
*.data
|
||||
*.sqlite3
|
||||
*.log
|
||||
*.runlog
|
||||
col.t
|
||||
|
|
|
@ -13,6 +13,7 @@ include_directories (${CMAKE_SOURCE_DIR}
|
|||
${CMAKE_SOURCE_DIR}/src/commands
|
||||
${CMAKE_SOURCE_DIR}/src/columns
|
||||
${CMAKE_SOURCE_DIR}/src/libshared/src
|
||||
${CMAKE_SOURCE_DIR}/src/taskchampion/lib
|
||||
${CMAKE_SOURCE_DIR}/test
|
||||
${CMAKE_SOURCE_DIR}/taskchampion/lib
|
||||
${TASK_INCLUDE_DIRS})
|
||||
|
|
|
@ -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
|
|
@ -68,7 +68,7 @@ class TestFeature559(TestCase):
|
|||
code, out, err = self.t.runError("rc.data.location=locationdoesnotexist list")
|
||||
self.assertNotIn("footask", 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__":
|
||||
|
|
|
@ -128,7 +128,8 @@ class TestIDRangeParsing(TestCase):
|
|||
with tempfile.NamedTemporaryFile(mode='w') as f:
|
||||
f.write('\n'.join([f'{{"description": "test task {i+1}"}}' for i in range(n)]))
|
||||
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):
|
||||
"""Test that parsing single digit ID range works"""
|
||||
|
|
|
@ -377,6 +377,7 @@ class TestUpgradeToRecurring(TestCase):
|
|||
|
||||
def test_upgrade(self):
|
||||
"""Upgrade task to recurring"""
|
||||
# note that this functionality is implemented in Task::validate
|
||||
self.t("add foo")
|
||||
self.t("1 modify due:tomorrow recur:weekly")
|
||||
code, out, err = self.t("_get 1.status")
|
||||
|
|
|
@ -77,7 +77,7 @@ int main (int, char**)
|
|||
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");
|
||||
|
||||
rep.rebuild_working_set ();
|
||||
rep.rebuild_working_set (true);
|
||||
t.pass ("rebuild_working_set");
|
||||
|
||||
auto tasks = rep.all_tasks ();
|
||||
|
|
|
@ -60,7 +60,7 @@ int main (int, char**)
|
|||
context.config.set ("gc", 1);
|
||||
context.config.set ("debug", 1);
|
||||
|
||||
context.tdb2.set_location (".");
|
||||
context.tdb2.open_replica (".", true);
|
||||
|
||||
// Try reading an empty database.
|
||||
std::vector <Task> pending = context.tdb2.pending_tasks ();
|
||||
|
@ -104,7 +104,7 @@ int main (int, char**)
|
|||
|
||||
// Reset for reuse.
|
||||
cleardb ();
|
||||
context.tdb2.set_location (".");
|
||||
context.tdb2.open_replica (".", true);
|
||||
|
||||
// TODO commit
|
||||
// TODO complete a task
|
||||
|
|
|
@ -14,5 +14,5 @@ task log two depends:1
|
|||
|
||||
task /two/ export > JSON
|
||||
|
||||
rm pending.data completed.data
|
||||
rm taskchampion.sqlite3
|
||||
task import JSON
|
||||
|
|
|
@ -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" ]]
|
58
test/tw-46.t
58
test/tw-46.t
|
@ -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
|
|
@ -102,7 +102,8 @@ class TestBug634(TestCase):
|
|||
|
||||
# If a prompt happens, the test will timeout on input (exitcode != 0)
|
||||
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__":
|
||||
|
|
21
test/uuid.t
21
test/uuid.t
|
@ -179,15 +179,28 @@ class TestUUIDuplicates(TestCase):
|
|||
"""Executed before each test in the class"""
|
||||
self.t = Task()
|
||||
|
||||
def test_uuid_duplicates(self):
|
||||
"""Verify that duplicating tasks, and recurring tasks do no create duplicates UUIDs"""
|
||||
def test_uuid_duplicates_dupe(self):
|
||||
"""Verify that duplicating tasks does not create duplicate UUIDs"""
|
||||
self.t("add simple")
|
||||
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
|
||||
|
||||
uuids = list()
|
||||
for id in range(1,7):
|
||||
for id in range(1,5):
|
||||
code, out, err = self.t("_get %d.uuid" % id)
|
||||
uuids.append(out.strip())
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue