Skip to content

Commit

Permalink
Restore support for task info journal (#3671)
Browse files Browse the repository at this point in the history
This support was removed before Taskwarrior-3.x, and is now restored,
including the original tests removed in
ddd3672
  • Loading branch information
djmitche authored Nov 6, 2024
1 parent 7da23ae commit c9967c2
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 101 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_library (task STATIC CLI2.cpp CLI2.h
Filter.cpp Filter.h
Hooks.cpp Hooks.h
Lexer.cpp Lexer.h
Operation.cpp Operation.h
TDB2.cpp TDB2.h
Task.cpp Task.h
Variant.cpp Variant.h
Expand Down
73 changes: 73 additions & 0 deletions src/Operation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2024, 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
//
////////////////////////////////////////////////////////////////////////////////

#include <cmake.h>
// cmake.h include header must come first

#include <Operation.h>
#include <taskchampion-cpp/lib.h>

#include <vector>

////////////////////////////////////////////////////////////////////////////////
Operation::Operation(const tc::Operation& op) : op(&op) {}

////////////////////////////////////////////////////////////////////////////////
std::vector<Operation> Operation::operations(const rust::Vec<tc::Operation>& operations) {
return {operations.begin(), operations.end()};
}

////////////////////////////////////////////////////////////////////////////////
Operation& Operation::operator=(const Operation& other) {
op = other.op;
return *this;
}

////////////////////////////////////////////////////////////////////////////////
bool Operation::operator<(Operation& other) const {
if (is_create()) {
return !other.is_create();
} else if (is_update()) {
if (other.is_create()) {
return false;
} else if (other.is_update()) {
return get_timestamp() < other.get_timestamp();
} else {
return true;
}
} else if (is_delete()) {
if (other.is_create() || other.is_update() || other.is_delete()) {
return false;
} else {
return true;
}
} else if (is_undo_point()) {
return !other.is_undo_point();
}
return false; // not reachable
}

////////////////////////////////////////////////////////////////////////////////
88 changes: 88 additions & 0 deletions src/Operation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2024, 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
//
////////////////////////////////////////////////////////////////////////////////

#ifndef INCLUDED_OPERATIOn
#define INCLUDED_OPERATIOn

#include <taskchampion-cpp/lib.h>

#include <optional>
#include <vector>

// Representation of a TaskChampion operation.
//
// This class wraps `tc::Operation&` and thus cannot outlive that underlying
// type.
class Operation {
public:
explicit Operation(const tc::Operation &);

Operation(const Operation &other) = default;
Operation &operator=(const Operation &other);

// Create a vector of Operations given the result of `Replica::get_undo_operations` or
// `Replica::get_task_operations`. The resulting vector must not outlive the input `rust::Vec`.
static std::vector<Operation> operations(const rust::Vec<tc::Operation> &);

// Methods from the underlying `tc::Operation`.
bool is_create() const { return op->is_create(); }
bool is_update() const { return op->is_update(); }
bool is_delete() const { return op->is_delete(); }
bool is_undo_point() const { return op->is_undo_point(); }
std::string get_uuid() const { return std::string(op->get_uuid().to_string()); }
::rust::Vec<::tc::PropValuePair> get_old_task() const { return op->get_old_task(); };
std::string get_property() const {
std::string value;
op->get_property(value);
return value;
}
std::optional<std::string> get_value() const {
std::optional<std::string> value{std::string()};
if (!op->get_value(value.value())) {
value = std::nullopt;
}
return value;
}
std::optional<std::string> get_old_value() const {
std::optional<std::string> old_value{std::string()};
if (!op->get_old_value(old_value.value())) {
old_value = std::nullopt;
}
return old_value;
}
time_t get_timestamp() const { return static_cast<time_t>(op->get_timestamp()); }

// Define a partial order on Operations:
// - Create < Update < Delete < UndoPoint
// - Given two updates, sort by timestamp
bool operator<(Operation &other) const;

private:
const tc::Operation *op;
};

#endif
////////////////////////////////////////////////////////////////////////////////
3 changes: 1 addition & 2 deletions src/TDB2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,7 @@ void TDB2::revert() {

////////////////////////////////////////////////////////////////////////////////
bool TDB2::confirm_revert(rust::Vec<tc::Operation>& undo_ops) {
// TODO Use show_diff rather than this basic listing of operations, though
// this might be a worthy undo.style itself.
// TODO: convert to Operation and use that type for display, similar to CmdInfo.

// Count non-undo operations
int ops_count = 0;
Expand Down
94 changes: 4 additions & 90 deletions src/Task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1243,14 +1243,14 @@ void Task::fixTagsAttribute() {
bool Task::isTagAttr(const std::string& attr) { return attr.compare(0, 4, "tag_") == 0; }

////////////////////////////////////////////////////////////////////////////////
const std::string Task::tag2Attr(const std::string& tag) const {
std::string Task::tag2Attr(const std::string& tag) {
std::stringstream tag_attr;
tag_attr << "tag_" << tag;
return tag_attr.str();
}

////////////////////////////////////////////////////////////////////////////////
const std::string Task::attr2Tag(const std::string& attr) const {
std::string Task::attr2Tag(const std::string& attr) {
assert(isTagAttr(attr));
return attr.substr(4);
}
Expand All @@ -1271,14 +1271,14 @@ void Task::fixDependsAttribute() {
bool Task::isDepAttr(const std::string& attr) { return attr.compare(0, 4, "dep_") == 0; }

////////////////////////////////////////////////////////////////////////////////
const std::string Task::dep2Attr(const std::string& tag) const {
std::string Task::dep2Attr(const std::string& tag) {
std::stringstream tag_attr;
tag_attr << "dep_" << tag;
return tag_attr.str();
}

////////////////////////////////////////////////////////////////////////////////
const std::string Task::attr2Dep(const std::string& attr) const {
std::string Task::attr2Dep(const std::string& attr) {
assert(isDepAttr(attr));
return attr.substr(4);
}
Expand Down Expand Up @@ -2151,92 +2151,6 @@ std::string Task::diff(const Task& after) const {
return out.str();
}

////////////////////////////////////////////////////////////////////////////////
// Similar to diff, but formatted for inclusion in the output of the info command
std::string Task::diffForInfo(const Task& after, const std::string& dateformat,
long& last_timestamp, const long current_timestamp) const {
// Attributes are all there is, so figure the different attribute names
// between before and after.
std::vector<std::string> beforeAtts;
for (auto& att : data) beforeAtts.push_back(att.first);

std::vector<std::string> afterAtts;
for (auto& att : after.data) afterAtts.push_back(att.first);

std::vector<std::string> beforeOnly;
std::vector<std::string> afterOnly;
listDiff(beforeAtts, afterAtts, beforeOnly, afterOnly);

// Now start generating a description of the differences.
std::stringstream out;
for (auto& name : beforeOnly) {
if (isAnnotationAttr(name)) {
out << format("Annotation '{1}' deleted.\n", get(name));
} else if (isTagAttr(name)) {
out << format("Tag '{1}' deleted.\n", attr2Tag(name));
} else if (isDepAttr(name)) {
out << format("Dependency on '{1}' deleted.\n", attr2Dep(name));
} else if (name == "depends" || name == "tags") {
// do nothing for legacy attributes
} else if (name == "start") {
Datetime started(get("start"));
Datetime stopped;

if (after.has("end"))
// Task was marked as finished, use end time
stopped = Datetime(after.get("end"));
else
// Start attribute was removed, use modification time
stopped = Datetime(current_timestamp);

out << format("{1} deleted (duration: {2}).", Lexer::ucFirst(name),
Duration(stopped - started).format())
<< "\n";
} else {
out << format("{1} deleted.\n", Lexer::ucFirst(name));
}
}

for (auto& name : afterOnly) {
if (isAnnotationAttr(name)) {
out << format("Annotation of '{1}' added.\n", after.get(name));
} else if (isTagAttr(name)) {
out << format("Tag '{1}' added.\n", attr2Tag(name));
} else if (isDepAttr(name)) {
out << format("Dependency on '{1}' added.\n", attr2Dep(name));
} else if (name == "depends" || name == "tags") {
// do nothing for legacy attributes
} else {
if (name == "start") last_timestamp = current_timestamp;

out << format("{1} set to '{2}'.", Lexer::ucFirst(name),
renderAttribute(name, after.get(name), dateformat))
<< "\n";
}
}

for (auto& name : beforeAtts)
if (name != "uuid" && name != "modified" && get(name) != after.get(name) && get(name) != "" &&
after.get(name) != "") {
if (name == "depends" || name == "tags") {
// do nothing for legacy attributes
} else if (isTagAttr(name) || isDepAttr(name)) {
// ignore new attributes
} else if (isAnnotationAttr(name)) {
out << format("Annotation changed to '{1}'.\n", after.get(name));
} else
out << format("{1} changed from '{2}' to '{3}'.", Lexer::ucFirst(name),
renderAttribute(name, get(name), dateformat),
renderAttribute(name, after.get(name), dateformat))
<< "\n";
}

// Shouldn't just say nothing.
if (out.str().length() == 0) out << "No changes made.\n";

return out.str();
}

////////////////////////////////////////////////////////////////////////////////
// Similar to diff, but formatted as a side-by-side table for an Undo preview
Table Task::diffForUndoSide(const Task& after) const {
Expand Down
11 changes: 5 additions & 6 deletions src/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ class Task {
void substitute(const std::string&, const std::string&, const std::string&);
#endif

static std::string tag2Attr(const std::string&);
static std::string attr2Tag(const std::string&);
static std::string dep2Attr(const std::string&);
static std::string attr2Dep(const std::string&);

void validate_add();
void validate(bool applyDefault = true);

Expand All @@ -176,8 +181,6 @@ class Task {
#endif

std::string diff(const Task& after) const;
std::string diffForInfo(const Task& after, const std::string& dateformat, long& last_timestamp,
const long current_timestamp) const;
Table diffForUndoSide(const Task& after) const;
Table diffForUndoPatch(const Task& after, const Datetime& lastChange) const;

Expand All @@ -190,10 +193,6 @@ class Task {
void validate_before(const std::string&, const std::string&);
const std::string encode(const std::string&) const;
const std::string decode(const std::string&) const;
const std::string tag2Attr(const std::string&) const;
const std::string attr2Tag(const std::string&) const;
const std::string dep2Attr(const std::string&) const;
const std::string attr2Dep(const std::string&) const;
void fixDependsAttribute();
void fixTagsAttribute();

Expand Down
Loading

0 comments on commit c9967c2

Please sign in to comment.