Compare commits
77 Commits
v2.6.1
...
cce24d2f46
| Author | SHA1 | Date | |
|---|---|---|---|
| cce24d2f46 | |||
| 9d7828a714 | |||
|
|
3aab541d9c | ||
| 3682bbc1a9 | |||
|
|
ccfc96f1cb | ||
|
|
7e1c8a3b5e | ||
|
|
5fe78f5627 | ||
|
|
e3f81cf0e4 | ||
|
|
7400e6ed6b | ||
|
|
93394e7054 | ||
|
|
f030154ef6 | ||
|
|
10a2225aae | ||
|
|
de793e5902 | ||
|
|
1fc1884017 | ||
|
|
75ce386b44 | ||
|
|
e60fdafdaa | ||
|
|
d541e0da65 | ||
|
|
59a1729a05 | ||
|
|
112ac54a57 | ||
|
|
0cc2de6179 | ||
|
|
28e268bd26 | ||
|
|
7321febe4f | ||
|
|
96d6c1df9f | ||
|
|
cb058f2e4b | ||
|
|
c564bbc0b7 | ||
|
|
b066a17ebe | ||
|
|
277ce0e226 | ||
|
|
65830dd705 | ||
|
|
53127bf844 | ||
|
|
2ea6dd627e | ||
|
|
db26a28bf9 | ||
|
|
6e9ad1048d | ||
|
|
e98b61f2b5 | ||
|
|
4fa1c0bcfb | ||
|
|
e3dd91d45e | ||
|
|
e768e2c100 | ||
|
|
ac24ec1387 | ||
|
|
3a61289f6c | ||
|
|
9f149a7f35 | ||
|
|
70f83b34ef | ||
|
|
8d3953183a | ||
|
|
2812a8c77a | ||
|
|
3af5ceadc1 | ||
|
|
0d9e402d3e | ||
|
|
dede40bc4e | ||
|
|
3a00956144 | ||
|
|
01add8a34a | ||
|
|
a09712d5d2 | ||
|
|
8074e509ba | ||
|
|
af10774aec | ||
|
|
d54c7e090e | ||
|
|
3937f1efb0 | ||
|
|
3e8190831c | ||
|
|
3c2b74a36f | ||
|
|
0558b6c7aa | ||
|
|
774f6df210 | ||
|
|
1e1c0e8f04 | ||
|
|
caf0f9db3e | ||
|
|
da43078eba | ||
|
|
6fae705b43 | ||
|
|
e4f1e05c1d | ||
|
|
4559287d07 | ||
|
|
2d4776586d | ||
|
|
ed4b932530 | ||
|
|
f0f704fc99 | ||
|
|
403c44b1fa | ||
|
|
493f36ecdd | ||
|
|
5d8f8dac35 | ||
|
|
f5dce013ce | ||
|
|
531881f651 | ||
|
|
7de681aa3b | ||
|
|
04ef785eea | ||
|
|
e1cfb91d42 | ||
|
|
72930708ec | ||
|
|
58763fd49f | ||
|
|
a89c875c49 | ||
|
|
e0f598f91c |
16
.gitea/workflows/release.yml
Normal file
16
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
on:
|
||||
- push
|
||||
jobs:
|
||||
ReleaseSrc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: True
|
||||
- name: Release Archive
|
||||
uses: https://gitea.com/actions/gitea-release-action
|
||||
with:
|
||||
files: ./*
|
||||
sha256sum: True
|
||||
|
||||
@@ -9,9 +9,6 @@ jobs:
|
||||
- name: "Centos 8"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: centos8
|
||||
- name: "Fedora 31"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: fedora31
|
||||
- name: "Fedora 32"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: fedora32
|
||||
@@ -21,6 +18,9 @@ jobs:
|
||||
- name: "Fedora 34"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: fedora34
|
||||
- name: "Fedora 35"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: fedora35
|
||||
- name: "Debian Testing"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: debiantesting
|
||||
@@ -33,9 +33,12 @@ jobs:
|
||||
- name: "Ubuntu 21.04"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: ubuntu2104
|
||||
- name: "OpenSUSE 15.0"
|
||||
- name: "Ubuntu 21.10"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: opensuse1500
|
||||
dockerfile: ubuntu2110
|
||||
- name: "OpenSUSE 15"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: opensuse15
|
||||
- name: "Archlinux Base (Rolling)"
|
||||
runner: ubuntu-latest
|
||||
dockerfile: arch
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
cmake_minimum_required (VERSION 3.5)
|
||||
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
include (CheckFunctionExists)
|
||||
@@ -9,7 +9,7 @@ set (HAVE_CMAKE true)
|
||||
project (task)
|
||||
include (CXXSniffer)
|
||||
|
||||
set (PROJECT_VERSION "2.6.1")
|
||||
set (PROJECT_VERSION "2.6.2")
|
||||
|
||||
OPTION (ENABLE_WASM "Enable 'wasm' support" OFF)
|
||||
|
||||
|
||||
27
ChangeLog
27
ChangeLog
@@ -1,4 +1,25 @@
|
||||
2.6.1 () -
|
||||
------ current release ---------------------------
|
||||
2.6.2 -
|
||||
|
||||
- TW #502 Sequence of IDs doesn't work with attribute "depends"
|
||||
Thanks to Andreas Kalex and Reg for reporting.
|
||||
- TW #2648 xdg-open is not available on Mac OS-X
|
||||
Thanks to chapterjson for reporting.
|
||||
- TW #2655 The bulk removal of depends: and tags: is ignored
|
||||
Thanks to angelus2014 for reporting.
|
||||
- TW #2664 Tag exclusion should be detected as invalid write context
|
||||
Thanks to bentwitthold for reporting.
|
||||
- TW #2671 The value of soww named date is incorrect
|
||||
Thanks to Lennart Kill for reporting.
|
||||
- TW #2689 Corruption of the depends attribute upon syncing with taskd 1.1.0
|
||||
Thanks to Klaus Ethgen for reporting, Dusting J. Mitchell for
|
||||
contributing.
|
||||
- TW #2707 Assigning dependencies via ID ranges
|
||||
Thanks to jaker-dotcom for reporting.
|
||||
- TW #2748 Recurring report does not include parent tasks
|
||||
Thanks to Klaus Ethgen for reporting.
|
||||
|
||||
2.6.1 (2021-10-19) - a696b6b155f9c8af87a6a496c0d298c58e6fe369
|
||||
|
||||
- TW #2619 Fish autocompletion fails when completing tag removal
|
||||
Thanks to Alexandre Provencio for reporting, Tin Lai and Alexandre
|
||||
@@ -10,7 +31,7 @@
|
||||
Thanks to Scott Mcdermott for reporting.
|
||||
- TW #2626 Waiting report lists deleted and/or completed tasks.
|
||||
Thanks to Don Harper for reporting.
|
||||
- TW #2629 New-style context definition should take precendence over old-style
|
||||
- TW #2629 New-style context definition should take precedence over old-style
|
||||
even if the new-style is an empty string.
|
||||
Thanks to Denis Kasak for reporting.
|
||||
- TW #2632 Cannot build on Cygwin
|
||||
@@ -18,8 +39,6 @@
|
||||
- TW #2639 Unable to use ranges for some tasks with IDs above 1000.
|
||||
Thanks to Manuel Haussmann for reporting.
|
||||
|
||||
------ current release ---------------------------
|
||||
|
||||
2.6.0 (2021-10-03) - 8174287f917a3b24c19a6601ba36fca9bdaa78c9
|
||||
|
||||
- TW #1654 "Due" parsing behaviour seems inconsistent
|
||||
|
||||
17
README.md
17
README.md
@@ -4,7 +4,6 @@
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/actions)
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||

|
||||
[](https://github.com/sponsors/GothenburgBitFactory/)
|
||||
</br>
|
||||
[](https://twitter.com/taskwarrior)
|
||||
@@ -20,6 +19,14 @@ features](https://taskwarrior.org/docs/), developed as a portable open source pr
|
||||
with an active and quite vast [ecosystem of tools, hooks and
|
||||
extensions](https://taskwarrior.org/tools/).
|
||||
|
||||
### HEADS UP!
|
||||
### This fork is to intended to preserve the taskwarrior 2.6.2 codebase, and to allow for further fixes and improvements!!
|
||||
It's great to see that taskwarrior is still an active project and the active developers, working on version 3.x, have a vision that fuses taskchampion and an SQL database. It's cool, and in rust, and everything.. but they are decidedly uninterested in further work on the 2.x (data as text files) codebase.
|
||||
|
||||
It was the fact that task used a text-file to store data, that first hooked me to the project. I'm almost certainly first taskwarrior user, and technically the original taskwarrior Designer! I worked with the orginal programmer, Paul Beckingham, as he brilliantly implemented so many of my crazy ideas, like colors, urgency, reports, UDAs, attribute modifiers and SO much more!
|
||||
|
||||
Because I'm an old fart, set im my ways like that, and kind of squeamish about keeping my task data as a SQL database, this repo is somewhare I can try to apply fixes and cherry-pick from changes made after official 2.6.2 releases, and to curate some of the things that I think belong with it. That said, I'm a terrible programmer, a dubious developer, and would welcome input, ideas and contributions.
|
||||
|
||||
## Install
|
||||
[](https://archlinux.org/packages/community/x86_64/task/)
|
||||
[](https://packages.debian.org/search?keywords=task&searchon=names&suite=all§ion=all)
|
||||
@@ -82,14 +89,14 @@ There are many binary packages available, but to install from source requires:
|
||||
* cmake
|
||||
* make
|
||||
* C++ compiler, currently gcc 7.1+ or clang 5.0+ for full C++17 support
|
||||
* libuuid
|
||||
* uuid-dev (was libuuid-dev)
|
||||
* GnuTLS (optional, required for sync)
|
||||
|
||||
Download the tarball, and expand it:
|
||||
|
||||
$ curl -O https://taskwarrior.org/download/task-2.6.1.tar.gz
|
||||
$ tar xzf task-2.6.1.tar.gz
|
||||
$ cd task-2.6.1
|
||||
$ curl -O https://taskwarrior.org/download/task-2.6.2.tar.gz
|
||||
$ tar xzf task-2.6.2.tar.gz
|
||||
$ cd task-2.6.2
|
||||
|
||||
Or clone this repository:
|
||||
|
||||
|
||||
@@ -16,14 +16,6 @@ services:
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-fedora31:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: test/docker/fedora31
|
||||
network_mode: "host"
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-fedora32:
|
||||
build:
|
||||
context: .
|
||||
@@ -48,6 +40,14 @@ services:
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-fedora35:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: test/docker/fedora35
|
||||
network_mode: "host"
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-ubuntu1804:
|
||||
build:
|
||||
context: .
|
||||
@@ -72,6 +72,14 @@ services:
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-ubuntu2110:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: test/docker/ubuntu2110
|
||||
network_mode: "host"
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-debianstable:
|
||||
build:
|
||||
context: .
|
||||
@@ -96,10 +104,10 @@ services:
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
tty: true
|
||||
test-opensuse1500:
|
||||
test-opensuse15:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: test/docker/opensuse1500
|
||||
dockerfile: test/docker/opensuse15
|
||||
network_mode: "host"
|
||||
security_opt:
|
||||
- label=type:container_runtime_t
|
||||
|
||||
@@ -5,7 +5,7 @@ FROM centos:8
|
||||
|
||||
RUN dnf update -y
|
||||
RUN yum install epel-release -y
|
||||
RUN dnf install python38 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man gdb -y
|
||||
RUN dnf install python38 vim git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man gdb -y
|
||||
|
||||
RUN useradd warrior
|
||||
RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <shared.h>
|
||||
#include <format.h>
|
||||
#include <main.h>
|
||||
#include <regex>
|
||||
|
||||
#ifdef HAVE_COMMIT
|
||||
#include <commit.h>
|
||||
@@ -115,7 +116,7 @@ std::string configurationDefaults =
|
||||
"expressions=infix # Prefer infix over postfix expressions\n"
|
||||
"json.array=1 # Enclose JSON output in [ ]\n"
|
||||
"abbreviation.minimum=2 # Shortest allowed abbreviation\n"
|
||||
"news.version= # Latest version higlights read by the user\n"
|
||||
"news.version= # Latest version highlights read by the user\n"
|
||||
"\n"
|
||||
"# Dates\n"
|
||||
"dateformat=Y-M-D # Preferred input and display date format\n"
|
||||
@@ -358,9 +359,9 @@ std::string configurationDefaults =
|
||||
"report.completed.context=1\n"
|
||||
"\n"
|
||||
"report.recurring.description=Recurring Tasks\n"
|
||||
"report.recurring.labels=ID,Active,Age,D,P,Project,Tags,Recur,Sch,Due,Until,Description,Urg\n"
|
||||
"report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
|
||||
"report.recurring.filter=status:pending and (+PARENT or +CHILD)\n"
|
||||
"report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description,Urg\n"
|
||||
"report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short,project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
|
||||
"report.recurring.filter=(status:pending and +CHILD) or (status:recurring and +PARENT)\n"
|
||||
"report.recurring.sort=due+,urgency-,entry+\n"
|
||||
"report.recurring.context=1\n"
|
||||
"\n"
|
||||
@@ -474,7 +475,7 @@ int Context::initialize (int argc, const char** argv)
|
||||
// [1] Load the correct config file.
|
||||
// - Default to ~/.taskrc (ctor).
|
||||
// - If no ~/.taskrc, use $XDG_CONFIG_HOME/task/taskrc if exists, or
|
||||
// ~/.config/task/taskrc if $XDG_DATA_HOME is unset
|
||||
// ~/.config/task/taskrc if $XDG_CONFIG_HOME is unset
|
||||
// - Allow $TASKRC override.
|
||||
// - Allow command line override rc:<file>
|
||||
// - Load resultant file.
|
||||
@@ -667,9 +668,13 @@ int Context::initialize (int argc, const char** argv)
|
||||
rc = 4;
|
||||
}
|
||||
|
||||
catch (const std::regex_error& e)
|
||||
{
|
||||
std::cout << "regex_error caught: " << e.what() << '\n';
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error ("Unknown error. Please report.");
|
||||
error ("knknown error. Please report.");
|
||||
rc = 3;
|
||||
}
|
||||
|
||||
@@ -1323,6 +1328,12 @@ void Context::debugTiming (const std::string& details, const Timer& timer)
|
||||
debug (out.str ());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
CurrentTask Context::withCurrentTask (const Task *task)
|
||||
{
|
||||
return CurrentTask(*this, task);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// This capability is to answer the question of 'what did I just do to generate
|
||||
// this output?'.
|
||||
@@ -1424,6 +1435,19 @@ void Context::debug (const std::string& input)
|
||||
debugMessages.push_back (input);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
CurrentTask::CurrentTask (Context &context, const Task *task)
|
||||
: context {context}, previous {context.currentTask}
|
||||
{
|
||||
context.currentTask = task;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
CurrentTask::~CurrentTask ()
|
||||
{
|
||||
context.currentTask = previous;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// vim ts=2:sw=2
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include <Timer.h>
|
||||
#include <set>
|
||||
|
||||
class CurrentTask;
|
||||
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
@@ -73,6 +75,9 @@ public:
|
||||
void decomposeSortField (const std::string&, std::string&, bool&, bool&);
|
||||
void debugTiming (const std::string&, const Timer&);
|
||||
|
||||
CurrentTask withCurrentTask (const Task *);
|
||||
friend class CurrentTask;
|
||||
|
||||
private:
|
||||
void staticInitialization ();
|
||||
void createDefaultConfig ();
|
||||
@@ -115,6 +120,25 @@ public:
|
||||
long time_sort_us {0};
|
||||
long time_render_us {0};
|
||||
long time_hooks_us {0};
|
||||
|
||||
// the current task for DOM references, or NULL if there is no task
|
||||
const Task * currentTask {NULL};
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// CurrentTask resets Context::currentTask to previous context task on destruction; this ensures
|
||||
// that this context value is restored when exiting the scope where the context was applied.
|
||||
class CurrentTask {
|
||||
public:
|
||||
~CurrentTask();
|
||||
|
||||
private:
|
||||
CurrentTask(Context &context, const Task *previous);
|
||||
|
||||
Context &context;
|
||||
const Task *previous;
|
||||
|
||||
friend class Context;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
132
src/DOM.cpp
132
src/DOM.cpp
@@ -239,22 +239,25 @@ bool getDOM (const std::string& name, Variant& value)
|
||||
//
|
||||
// This code emphasizes speed, hence 'id' and 'urgency' being evaluated first
|
||||
// as special cases.
|
||||
bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
//
|
||||
// If task is NULL, then the contextual task will be determined from the DOM
|
||||
// string, if any exists.
|
||||
bool getDOM (const std::string& name, const Task* task, Variant& value)
|
||||
{
|
||||
// Special case, blank refs cause problems.
|
||||
if (name == "")
|
||||
return false;
|
||||
|
||||
// Quickly deal with the most common cases.
|
||||
if (task.data.size () && name == "id")
|
||||
if (task && name == "id")
|
||||
{
|
||||
value = Variant (static_cast<int> (task.id));
|
||||
value = Variant (static_cast<int> (task->id));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (task.data.size () && name == "urgency")
|
||||
if (task && name == "urgency")
|
||||
{
|
||||
value = Variant (task.urgency_c ());
|
||||
value = Variant (task->urgency_c ());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -262,54 +265,55 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
auto elements = split (name, '.');
|
||||
Task loaded_task;
|
||||
|
||||
// Use a lambda to decide whether the reference is going to be the passed
|
||||
// decide whether the reference is going to be the passed
|
||||
// "task" or whether it's going to be a newly loaded task (if id/uuid was
|
||||
// given).
|
||||
const Task& ref = [&]() -> const Task&
|
||||
const Task* ref = task;
|
||||
Lexer lexer (elements[0]);
|
||||
std::string token;
|
||||
Lexer::Type type;
|
||||
|
||||
// If this can be ID/UUID reference (the name contains '.'),
|
||||
// lex it to figure out. Otherwise don't lex, as lexing can be slow.
|
||||
if ((elements.size() > 1) and lexer.token (token, type))
|
||||
{
|
||||
Lexer lexer (elements[0]);
|
||||
std::string token;
|
||||
Lexer::Type type;
|
||||
bool reloaded = false;
|
||||
|
||||
// If this can be ID/UUID reference (the name contains '.'),
|
||||
// lex it to figure out. Otherwise don't lex, as lexing can be slow.
|
||||
if ((elements.size() > 1) and lexer.token (token, type))
|
||||
if (type == Lexer::Type::uuid &&
|
||||
token.length () == elements[0].length ())
|
||||
{
|
||||
bool reloaded = false;
|
||||
|
||||
if (type == Lexer::Type::uuid &&
|
||||
token.length () == elements[0].length ())
|
||||
if (!task || token != task->get ("uuid"))
|
||||
{
|
||||
if (token != task.get ("uuid"))
|
||||
{
|
||||
Context::getContext ().tdb2.get (token, loaded_task);
|
||||
if (Context::getContext ().tdb2.get (token, loaded_task))
|
||||
reloaded = true;
|
||||
}
|
||||
|
||||
// Eat elements[0]/UUID.
|
||||
elements.erase (elements.begin ());
|
||||
}
|
||||
else if (type == Lexer::Type::number &&
|
||||
token.find ('.') == std::string::npos)
|
||||
{
|
||||
auto id = strtol (token.c_str (), nullptr, 10);
|
||||
if (id && id != task.id)
|
||||
{
|
||||
Context::getContext ().tdb2.get (id, loaded_task);
|
||||
reloaded = true;
|
||||
}
|
||||
|
||||
// Eat elements[0]/ID.
|
||||
elements.erase (elements.begin ());
|
||||
}
|
||||
|
||||
if (reloaded)
|
||||
return loaded_task;
|
||||
// Eat elements[0]/UUID.
|
||||
elements.erase (elements.begin ());
|
||||
}
|
||||
else if (type == Lexer::Type::number &&
|
||||
token.find ('.') == std::string::npos)
|
||||
{
|
||||
auto id = strtol (token.c_str (), nullptr, 10);
|
||||
if (id && (!task || id != task->id))
|
||||
{
|
||||
if (Context::getContext ().tdb2.get (id, loaded_task))
|
||||
reloaded = true;
|
||||
}
|
||||
|
||||
// Eat elements[0]/ID.
|
||||
elements.erase (elements.begin ());
|
||||
}
|
||||
|
||||
return task;
|
||||
if (reloaded)
|
||||
ref = &loaded_task;
|
||||
}
|
||||
|
||||
} ();
|
||||
|
||||
// The remainder of this method requires a contextual task, so if we do not
|
||||
// have one, delegate to the two-argument getDOM
|
||||
if (!ref)
|
||||
return getDOM (name, value);
|
||||
|
||||
auto size = elements.size ();
|
||||
|
||||
@@ -318,31 +322,31 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
{
|
||||
// Now that 'ref' is the contextual task, and any ID/UUID is chopped off the
|
||||
// elements vector, DOM resolution is now simple.
|
||||
if (ref.data.size () && size == 1 && canonical == "id")
|
||||
if (size == 1 && canonical == "id")
|
||||
{
|
||||
value = Variant (static_cast<int> (ref.id));
|
||||
value = Variant (static_cast<int> (ref->id));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 1 && canonical == "urgency")
|
||||
if (size == 1 && canonical == "urgency")
|
||||
{
|
||||
value = Variant (ref.urgency_c ());
|
||||
value = Variant (ref->urgency_c ());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special handling of status required for virtual waiting status
|
||||
// implementation. Remove in 3.0.0.
|
||||
if (ref.data.size () && size == 1 && canonical == "status")
|
||||
if (size == 1 && canonical == "status")
|
||||
{
|
||||
value = Variant (ref.statusToText (ref.getStatus ()));
|
||||
value = Variant (ref->statusToText (ref->getStatus ()));
|
||||
return true;
|
||||
}
|
||||
|
||||
Column* column = Context::getContext ().columns[canonical];
|
||||
|
||||
if (ref.data.size () && size == 1 && column)
|
||||
if (size == 1 && column)
|
||||
{
|
||||
if (column->is_uda () && ! ref.has (canonical))
|
||||
if (column->is_uda () && ! ref->has (canonical))
|
||||
{
|
||||
value = Variant ("");
|
||||
return true;
|
||||
@@ -350,7 +354,7 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
|
||||
if (column->type () == "date")
|
||||
{
|
||||
auto numeric = ref.get_date (canonical);
|
||||
auto numeric = ref->get_date (canonical);
|
||||
if (numeric == 0)
|
||||
value = Variant ("");
|
||||
else
|
||||
@@ -358,32 +362,32 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
}
|
||||
else if (column->type () == "duration" || canonical == "recur")
|
||||
{
|
||||
auto period = ref.get (canonical);
|
||||
auto period = ref->get (canonical);
|
||||
|
||||
Duration iso;
|
||||
std::string::size_type cursor = 0;
|
||||
if (iso.parse (period, cursor))
|
||||
value = Variant (iso.toTime_t (), Variant::type_duration);
|
||||
else
|
||||
value = Variant (Duration (ref.get (canonical)).toTime_t (), Variant::type_duration);
|
||||
value = Variant (Duration (ref->get (canonical)).toTime_t (), Variant::type_duration);
|
||||
}
|
||||
else if (column->type () == "numeric")
|
||||
value = Variant (ref.get_float (canonical));
|
||||
value = Variant (ref->get_float (canonical));
|
||||
else
|
||||
value = Variant (ref.get (canonical));
|
||||
value = Variant (ref->get (canonical));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 2 && canonical == "tags")
|
||||
if (size == 2 && canonical == "tags")
|
||||
{
|
||||
value = Variant (ref.hasTag (elements[1]) ? elements[1] : "");
|
||||
value = Variant (ref->hasTag (elements[1]) ? elements[1] : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 2 && column && column->type () == "date")
|
||||
if (size == 2 && column && column->type () == "date")
|
||||
{
|
||||
Datetime date (ref.get_date (canonical));
|
||||
Datetime date (ref->get_date (canonical));
|
||||
if (elements[1] == "year") { value = Variant (static_cast<int> (date.year ())); return true; }
|
||||
else if (elements[1] == "month") { value = Variant (static_cast<int> (date.month ())); return true; }
|
||||
else if (elements[1] == "day") { value = Variant (static_cast<int> (date.day ())); return true; }
|
||||
@@ -396,15 +400,15 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
}
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 2 && elements[0] == "annotations" && elements[1] == "count")
|
||||
if (size == 2 && elements[0] == "annotations" && elements[1] == "count")
|
||||
{
|
||||
value = Variant (static_cast<int> (ref.getAnnotationCount ()));
|
||||
value = Variant (static_cast<int> (ref->getAnnotationCount ()));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 3 && elements[0] == "annotations")
|
||||
if (size == 3 && elements[0] == "annotations")
|
||||
{
|
||||
auto annos = ref.getAnnotations ();
|
||||
auto annos = ref->getAnnotations ();
|
||||
|
||||
int a = strtol (elements[1].c_str (), nullptr, 10);
|
||||
int count = 0;
|
||||
@@ -430,9 +434,9 @@ bool getDOM (const std::string& name, const Task& task, Variant& value)
|
||||
}
|
||||
}
|
||||
|
||||
if (ref.data.size () && size == 4 && elements[0] == "annotations" && elements[2] == "entry")
|
||||
if (size == 4 && elements[0] == "annotations" && elements[2] == "entry")
|
||||
{
|
||||
auto annos = ref.getAnnotations ();
|
||||
auto annos = ref->getAnnotations ();
|
||||
|
||||
int a = strtol (elements[1].c_str (), nullptr, 10);
|
||||
int count = 0;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
// 2017-04-22 Deprecated, use DOM::get.
|
||||
bool getDOM (const std::string&, Variant&);
|
||||
bool getDOM (const std::string&, const Task&, Variant&);
|
||||
bool getDOM (const std::string&, const Task*, Variant&);
|
||||
|
||||
class DOM
|
||||
{
|
||||
|
||||
30
src/Eval.cpp
30
src/Eval.cpp
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <cmake.h>
|
||||
#include <Eval.h>
|
||||
#include <DOM.h>
|
||||
#include <map>
|
||||
#include <time.h>
|
||||
#include <Context.h>
|
||||
@@ -34,8 +35,6 @@
|
||||
#include <shared.h>
|
||||
#include <format.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Supported operators, borrowed from C++, particularly the precedence.
|
||||
// Note: table is sorted by length of operator string, so searches match
|
||||
@@ -101,6 +100,19 @@ static bool namedConstants (const std::string& name, Variant& value)
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Support for evaluating DOM references (add with `e.AddSource(domSource)`)
|
||||
bool domSource (const std::string& identifier, Variant& value)
|
||||
{
|
||||
if (getDOM (identifier, Context::getContext ().currentTask, value))
|
||||
{
|
||||
value.source (identifier);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Eval::Eval ()
|
||||
{
|
||||
@@ -278,6 +290,8 @@ void Eval::evaluatePostfixStack (
|
||||
Variant left = values.back ();
|
||||
values.pop_back ();
|
||||
|
||||
auto contextTask = Context::getContext ().currentTask;
|
||||
|
||||
// Ordering these by anticipation frequency of use is a good idea.
|
||||
Variant result;
|
||||
if (token.first == "and") result = left && right;
|
||||
@@ -299,10 +313,14 @@ void Eval::evaluatePostfixStack (
|
||||
else if (token.first == "^") result = left ^ right;
|
||||
else if (token.first == "%") result = left % right;
|
||||
else if (token.first == "xor") result = left.operator_xor (right);
|
||||
else if (token.first == "~") result = left.operator_match (right, contextTask);
|
||||
else if (token.first == "!~") result = left.operator_nomatch (right, contextTask);
|
||||
else if (token.first == "_hastag_") result = left.operator_hastag (right, contextTask);
|
||||
else if (token.first == "_notag_") result = left.operator_notag (right, contextTask);
|
||||
else if (contextTask) {
|
||||
if (token.first == "~") result = left.operator_match (right, *contextTask);
|
||||
else if (token.first == "!~") result = left.operator_nomatch (right, *contextTask);
|
||||
else if (token.first == "_hastag_") result = left.operator_hastag (right, *contextTask);
|
||||
else if (token.first == "_notag_") result = left.operator_notag (right, *contextTask);
|
||||
else
|
||||
throw format ("Unsupported operator '{1}'.", token.first);
|
||||
}
|
||||
else
|
||||
throw format ("Unsupported operator '{1}'.", token.first);
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <Lexer.h>
|
||||
#include <Variant.h>
|
||||
|
||||
bool domSource (const std::string&, Variant&);
|
||||
|
||||
class Eval
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -35,23 +35,6 @@
|
||||
#include <format.h>
|
||||
#include <shared.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Const iterator that can be derefenced into a Task by domSource.
|
||||
static Task dummy;
|
||||
Task& contextTask = dummy;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool domSource (const std::string& identifier, Variant& value)
|
||||
{
|
||||
if (getDOM (identifier, contextTask, value))
|
||||
{
|
||||
value.source (identifier);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Take an input set of tasks and filter into a subset.
|
||||
void Filter::subset (const std::vector <Task>& input, std::vector <Task>& output)
|
||||
@@ -79,7 +62,7 @@ void Filter::subset (const std::vector <Task>& input, std::vector <Task>& output
|
||||
for (auto& task : input)
|
||||
{
|
||||
// Set up context for any DOM references.
|
||||
contextTask = task;
|
||||
auto currentTask = Context::getContext ().withCurrentTask(&task);
|
||||
|
||||
Variant var;
|
||||
eval.evaluateCompiledExpression (var);
|
||||
@@ -131,7 +114,7 @@ void Filter::subset (std::vector <Task>& output)
|
||||
for (auto& task : pending)
|
||||
{
|
||||
// Set up context for any DOM references.
|
||||
contextTask = task;
|
||||
auto currentTask = Context::getContext ().withCurrentTask(&task);
|
||||
|
||||
Variant var;
|
||||
eval.evaluateCompiledExpression (var);
|
||||
@@ -150,7 +133,7 @@ void Filter::subset (std::vector <Task>& output)
|
||||
for (auto& task : completed)
|
||||
{
|
||||
// Set up context for any DOM references.
|
||||
contextTask = task;
|
||||
auto currentTask = Context::getContext ().withCurrentTask(&task);
|
||||
|
||||
Variant var;
|
||||
eval.evaluateCompiledExpression (var);
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <Task.h>
|
||||
#include <Variant.h>
|
||||
|
||||
bool domSource (const std::string&, Variant&);
|
||||
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
|
||||
195
src/TDB2.cpp
195
src/TDB2.cpp
@@ -1025,203 +1025,24 @@ void TDB2::show_diff (
|
||||
Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : "");
|
||||
Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : "");
|
||||
|
||||
auto before = prior == "" ? Task() : Task(prior);
|
||||
auto after = Task(current);
|
||||
|
||||
if (Context::getContext ().config.get ("undo.style") == "side")
|
||||
{
|
||||
Table view = before.diffForUndoSide(after);
|
||||
|
||||
std::cout << '\n'
|
||||
<< format ("The last modification was made {1}", lastChange.toString ())
|
||||
<< '\n';
|
||||
|
||||
// Attributes are all there is, so figure the different attribute names
|
||||
// between before and after.
|
||||
Table view;
|
||||
view.width (Context::getContext ().getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add ("");
|
||||
view.add ("Prior Values");
|
||||
view.add ("Current Values");
|
||||
setHeaderUnderline (view);
|
||||
|
||||
Task after (current);
|
||||
|
||||
if (prior != "")
|
||||
{
|
||||
Task before (prior);
|
||||
|
||||
std::vector <std::string> beforeAtts;
|
||||
for (auto& att : before.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);
|
||||
|
||||
int row;
|
||||
for (auto& name : beforeOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name);
|
||||
view.set (row, 1, renderAttribute (name, before.get (name)), color_red);
|
||||
}
|
||||
|
||||
for (auto& att : before.data)
|
||||
{
|
||||
std::string priorValue = before.get (att.first);
|
||||
std::string currentValue = after.get (att.first);
|
||||
|
||||
if (currentValue != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, att.first);
|
||||
view.set (row, 1, renderAttribute (att.first, priorValue),
|
||||
(priorValue != currentValue ? color_red : Color ()));
|
||||
view.set (row, 2, renderAttribute (att.first, currentValue),
|
||||
(priorValue != currentValue ? color_green : Color ()));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& name : afterOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name);
|
||||
view.set (row, 2, renderAttribute (name, after.get (name)), color_green);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int row;
|
||||
for (auto& att : after.data)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, att.first);
|
||||
view.set (row, 2, renderAttribute (att.first, after.get (att.first)), color_green);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << '\n'
|
||||
<< '\n'
|
||||
<< '\n'
|
||||
<< view.render ()
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
// This style looks like this:
|
||||
// --- before 2009-07-04 00:00:25.000000000 +0200
|
||||
// +++ after 2009-07-04 00:00:45.000000000 +0200
|
||||
//
|
||||
// - name: old // att deleted
|
||||
// + name:
|
||||
//
|
||||
// - name: old // att changed
|
||||
// + name: new
|
||||
//
|
||||
// - name:
|
||||
// + name: new // att added
|
||||
//
|
||||
else if (Context::getContext ().config.get ("undo.style") == "diff")
|
||||
{
|
||||
// Create reference tasks.
|
||||
Task before;
|
||||
if (prior != "")
|
||||
before.parse (prior);
|
||||
|
||||
Task after (current);
|
||||
|
||||
// Generate table header.
|
||||
Table view;
|
||||
view.width (Context::getContext ().getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add ("");
|
||||
view.add ("");
|
||||
|
||||
int row = view.addRow ();
|
||||
view.set (row, 0, "--- previous state", color_red);
|
||||
view.set (row, 1, "Undo will restore this state", color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+++ current state ", color_green);
|
||||
view.set (row, 1, format ("Change made {1}",
|
||||
lastChange.toString (Context::getContext ().config.get ("dateformat"))),
|
||||
color_green);
|
||||
|
||||
view.addRow ();
|
||||
|
||||
// Add rows to table showing diffs.
|
||||
std::vector <std::string> all = Context::getContext ().getColumns ();
|
||||
|
||||
// Now factor in the annotation attributes.
|
||||
for (auto& it : before.data)
|
||||
if (it.first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it.first);
|
||||
|
||||
for (auto& it : after.data)
|
||||
if (it.first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it.first);
|
||||
|
||||
// Now render all the attributes.
|
||||
std::sort (all.begin (), all.end ());
|
||||
|
||||
std::string before_att;
|
||||
std::string after_att;
|
||||
std::string last_att;
|
||||
for (auto& a : all)
|
||||
{
|
||||
if (a != last_att) // Skip duplicates.
|
||||
{
|
||||
last_att = a;
|
||||
|
||||
before_att = before.get (a);
|
||||
after_att = after.get (a);
|
||||
|
||||
// Don't report different uuid.
|
||||
// Show nothing if values are the unchanged.
|
||||
if (a == "uuid" ||
|
||||
before_att == after_att)
|
||||
{
|
||||
// Show nothing - no point displaying that which did not change.
|
||||
|
||||
// row = view.addRow ();
|
||||
// view.set (row, 0, *a + ":");
|
||||
// view.set (row, 1, before_att);
|
||||
}
|
||||
|
||||
// Attribute deleted.
|
||||
else if (before_att != "" && after_att == "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
}
|
||||
|
||||
// Attribute added.
|
||||
else if (before_att == "" && after_att != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
|
||||
// Attribute changed.
|
||||
else
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Table view = before.diffForUndoPatch(after, lastChange);
|
||||
std::cout << '\n'
|
||||
<< view.render ()
|
||||
<< '\n';
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <TLSClient.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -44,6 +45,7 @@
|
||||
#include <shared.h>
|
||||
#include <format.h>
|
||||
|
||||
#define HEADER_SIZE 4
|
||||
#define MAX_BUF 16384
|
||||
|
||||
#if GNUTLS_VERSION_NUMBER < 0x030406
|
||||
@@ -469,24 +471,15 @@ void TLSClient::send (const std::string& data)
|
||||
packet[3] = l;
|
||||
|
||||
unsigned int total = 0;
|
||||
unsigned int remaining = packet.length ();
|
||||
|
||||
while (total < packet.length ())
|
||||
int status;
|
||||
do
|
||||
{
|
||||
int status;
|
||||
do
|
||||
{
|
||||
status = gnutls_record_send (_session, packet.c_str () + total, remaining); // All
|
||||
}
|
||||
while (errno == GNUTLS_E_INTERRUPTED ||
|
||||
errno == GNUTLS_E_AGAIN);
|
||||
|
||||
if (status == -1)
|
||||
break;
|
||||
|
||||
total += (unsigned int) status;
|
||||
remaining -= (unsigned int) status;
|
||||
status = gnutls_record_send (_session, packet.c_str () + total, packet.length () - total); // All
|
||||
}
|
||||
while ((status > 0 && (total += status) < packet.length ()) ||
|
||||
status == GNUTLS_E_INTERRUPTED ||
|
||||
status == GNUTLS_E_AGAIN);
|
||||
|
||||
if (_debug)
|
||||
std::cout << "c: INFO Sending 'XXXX"
|
||||
@@ -500,18 +493,22 @@ void TLSClient::recv (std::string& data)
|
||||
{
|
||||
data = ""; // No appending of data.
|
||||
int received = 0;
|
||||
int total = 0;
|
||||
|
||||
// Get the encoded length.
|
||||
unsigned char header[4] {};
|
||||
unsigned char header[HEADER_SIZE] {};
|
||||
do
|
||||
{
|
||||
received = gnutls_record_recv (_session, header, 4); // All
|
||||
received = gnutls_record_recv (_session, header + total, HEADER_SIZE - total); // All
|
||||
}
|
||||
while (received > 0 &&
|
||||
(errno == GNUTLS_E_INTERRUPTED ||
|
||||
errno == GNUTLS_E_AGAIN));
|
||||
while ((received > 0 && (total += received) < HEADER_SIZE) ||
|
||||
received == GNUTLS_E_INTERRUPTED ||
|
||||
received == GNUTLS_E_AGAIN);
|
||||
|
||||
int total = received;
|
||||
if (total < HEADER_SIZE) {
|
||||
throw std::string ("Failed to receive header: ") +
|
||||
(received < 0 ? gnutls_strerror(received) : "connection lost?");
|
||||
}
|
||||
|
||||
// Decode the length.
|
||||
unsigned long expected = (header[0]<<24) |
|
||||
@@ -521,7 +518,11 @@ void TLSClient::recv (std::string& data)
|
||||
if (_debug)
|
||||
std::cout << "c: INFO expecting " << expected << " bytes.\n";
|
||||
|
||||
// TODO This would be a good place to assert 'expected < _limit'.
|
||||
if (_limit && expected >= (unsigned long) _limit) {
|
||||
std::ostringstream err_str;
|
||||
err_str << "Expected message size " << expected << " is larger than allowed limit " << _limit;
|
||||
throw err_str.str ();
|
||||
}
|
||||
|
||||
// Arbitrary buffer size.
|
||||
char buffer[MAX_BUF];
|
||||
@@ -531,13 +532,18 @@ void TLSClient::recv (std::string& data)
|
||||
// fits in the buffer.
|
||||
do
|
||||
{
|
||||
int chunk_size = 0;
|
||||
do
|
||||
{
|
||||
received = gnutls_record_recv (_session, buffer, MAX_BUF - 1); // All
|
||||
received = gnutls_record_recv (_session, buffer + chunk_size, MAX_BUF - chunk_size); // All
|
||||
if (received > 0) {
|
||||
total += received;
|
||||
chunk_size += received;
|
||||
}
|
||||
}
|
||||
while (received > 0 &&
|
||||
(errno == GNUTLS_E_INTERRUPTED ||
|
||||
errno == GNUTLS_E_AGAIN));
|
||||
while ((received > 0 && (unsigned long) total < expected && chunk_size < MAX_BUF) ||
|
||||
received == GNUTLS_E_INTERRUPTED ||
|
||||
received == GNUTLS_E_AGAIN);
|
||||
|
||||
// Other end closed the connection.
|
||||
if (received == 0)
|
||||
@@ -548,17 +554,10 @@ void TLSClient::recv (std::string& data)
|
||||
}
|
||||
|
||||
// Something happened.
|
||||
if (received < 0 && gnutls_error_is_fatal (received) == 0) // All
|
||||
{
|
||||
if (_debug)
|
||||
std::cout << "c: WARNING " << gnutls_strerror (received) << '\n'; // All
|
||||
}
|
||||
else if (received < 0)
|
||||
if (received < 0)
|
||||
throw std::string (gnutls_strerror (received)); // All
|
||||
|
||||
buffer [received] = '\0';
|
||||
data += buffer;
|
||||
total += received;
|
||||
data.append (buffer, chunk_size);
|
||||
|
||||
// Stop at defined limit.
|
||||
if (_limit && total > _limit)
|
||||
|
||||
261
src/Task.cpp
261
src/Task.cpp
@@ -60,8 +60,6 @@
|
||||
|
||||
#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery.
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
static const float epsilon = 0.000001;
|
||||
#endif
|
||||
|
||||
@@ -114,6 +112,12 @@ bool Task::operator== (const Task& other)
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
bool Task::operator!= (const Task& other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
Task::Task (const std::string& input)
|
||||
{
|
||||
@@ -363,6 +367,14 @@ Task::dateState Task::getDateState (const std::string& name) const
|
||||
return dateNotDue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// An empty task is typically a "dummy", such as in DOM evaluation, which may or
|
||||
// may not occur in the context of a task.
|
||||
bool Task::is_empty () const
|
||||
{
|
||||
return data.size () == 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Ready means pending, not blocked and either not scheduled or scheduled before
|
||||
// now.
|
||||
@@ -765,7 +777,25 @@ void Task::parseJSON (const json::object* root_obj)
|
||||
else if (i.first == "depends" && i.second->type() == json::j_string)
|
||||
{
|
||||
auto deps = (json::string*)i.second;
|
||||
auto uuids = split (deps->_data, ',');
|
||||
|
||||
// Fix for issue#2689: taskserver sometimes encodes the depends
|
||||
// property as a string of the format `[\"uuid\",\"uuid\"]`
|
||||
// The string includes the backslash-escaped `"` characters, making
|
||||
// it invalid JSON. Since we know the characters we're looking for,
|
||||
// we'll just filter out everything else.
|
||||
std::string deps_str = deps->_data;
|
||||
if (deps_str.front () == '[' && deps_str.back () == ']') {
|
||||
std::string filtered;
|
||||
for (auto &c: deps_str) {
|
||||
if ((c >= '0' && c <= '9') ||
|
||||
(c >= 'a' && c <= 'f') ||
|
||||
c == ',' || c == '-') {
|
||||
filtered.push_back(c);
|
||||
}
|
||||
}
|
||||
deps_str = filtered;
|
||||
}
|
||||
auto uuids = split (deps_str, ',');
|
||||
|
||||
for (const auto& uuid : uuids)
|
||||
addDependency (uuid);
|
||||
@@ -1545,7 +1575,7 @@ bool Task::isAnnotationAttr(const std::string& attr)
|
||||
#ifdef PRODUCT_TASKWARRIOR
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// A UDA Orphan is an attribute that is not represented in context.columns.
|
||||
std::vector <std::string> Task::getUDAOrphanUUIDs () const
|
||||
std::vector <std::string> Task::getUDAOrphans () const
|
||||
{
|
||||
std::vector <std::string> orphans;
|
||||
for (auto& it : data)
|
||||
@@ -2257,6 +2287,10 @@ void Task::modify (modType type, bool text_required /* = false */)
|
||||
{
|
||||
std::string label = " [1;37;43mMODIFICATION[0m ";
|
||||
|
||||
// while reading the parse tree, consider DOM references in the context of
|
||||
// this task
|
||||
auto currentTask = Context::getContext ().withCurrentTask(this);
|
||||
|
||||
// Need this for later comparison.
|
||||
auto originalStatus = getStatus ();
|
||||
|
||||
@@ -2276,6 +2310,19 @@ void Task::modify (modType type, bool text_required /* = false */)
|
||||
value == "''" ||
|
||||
value == "\"\"")
|
||||
{
|
||||
// Special case: Handle bulk removal of 'tags' and 'depends" virtual
|
||||
// attributes
|
||||
if (name == "depends")
|
||||
{
|
||||
for (auto dep: getDependencyUUIDs ())
|
||||
removeDependency(dep);
|
||||
}
|
||||
else if (name == "tags")
|
||||
{
|
||||
for (auto tag: getTags ())
|
||||
removeTag(tag);
|
||||
}
|
||||
|
||||
// ::composeF4 will skip if the value is blank, but the presence of
|
||||
// the attribute will prevent ::validate from applying defaults.
|
||||
if ((has (name) && get (name) != "") ||
|
||||
@@ -2404,7 +2451,8 @@ void Task::modify (modType type, bool text_required /* = false */)
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Compare this task to another and summarize the differences for display
|
||||
// Compare this task to another and summarize the differences for display, in
|
||||
// the future tense ("Foo will be set to ..").
|
||||
std::string Task::diff (const Task& after) const
|
||||
{
|
||||
// Attributes are all there is, so figure the different attribute names
|
||||
@@ -2649,3 +2697,206 @@ std::string Task::diffForInfo (
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Similar to diff, but formatted as a side-by-side table for an Undo preview
|
||||
Table Task::diffForUndoSide (
|
||||
const Task& after) const
|
||||
{
|
||||
// Set the colors.
|
||||
Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : "");
|
||||
Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : "");
|
||||
|
||||
// Attributes are all there is, so figure the different attribute names
|
||||
// between before and after.
|
||||
Table view;
|
||||
view.width (Context::getContext ().getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add ("");
|
||||
view.add ("Prior Values");
|
||||
view.add ("Current Values");
|
||||
setHeaderUnderline (view);
|
||||
|
||||
if (!is_empty ())
|
||||
{
|
||||
const Task &before = *this;
|
||||
|
||||
std::vector <std::string> beforeAtts;
|
||||
for (auto& att : before.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);
|
||||
|
||||
int row;
|
||||
for (auto& name : beforeOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name);
|
||||
view.set (row, 1, renderAttribute (name, before.get (name)), color_red);
|
||||
}
|
||||
|
||||
for (auto& att : before.data)
|
||||
{
|
||||
std::string priorValue = before.get (att.first);
|
||||
std::string currentValue = after.get (att.first);
|
||||
|
||||
if (currentValue != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, att.first);
|
||||
view.set (row, 1, renderAttribute (att.first, priorValue),
|
||||
(priorValue != currentValue ? color_red : Color ()));
|
||||
view.set (row, 2, renderAttribute (att.first, currentValue),
|
||||
(priorValue != currentValue ? color_green : Color ()));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& name : afterOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name);
|
||||
view.set (row, 2, renderAttribute (name, after.get (name)), color_green);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int row;
|
||||
for (auto& att : after.data)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, att.first);
|
||||
view.set (row, 2, renderAttribute (att.first, after.get (att.first)), color_green);
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Similar to diff, but formatted as a diff for an Undo preview
|
||||
Table Task::diffForUndoPatch (
|
||||
const Task& after,
|
||||
const Datetime& lastChange) const
|
||||
{
|
||||
// This style looks like this:
|
||||
// --- before 2009-07-04 00:00:25.000000000 +0200
|
||||
// +++ after 2009-07-04 00:00:45.000000000 +0200
|
||||
//
|
||||
// - name: old // att deleted
|
||||
// + name:
|
||||
//
|
||||
// - name: old // att changed
|
||||
// + name: new
|
||||
//
|
||||
// - name:
|
||||
// + name: new // att added
|
||||
//
|
||||
|
||||
// Set the colors.
|
||||
Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : "");
|
||||
Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : "");
|
||||
|
||||
const Task &before = *this;
|
||||
|
||||
// Generate table header.
|
||||
Table view;
|
||||
view.width (Context::getContext ().getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add ("");
|
||||
view.add ("");
|
||||
|
||||
int row = view.addRow ();
|
||||
view.set (row, 0, "--- previous state", color_red);
|
||||
view.set (row, 1, "Undo will restore this state", color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+++ current state ", color_green);
|
||||
view.set (row, 1, format ("Change made {1}",
|
||||
lastChange.toString (Context::getContext ().config.get ("dateformat"))),
|
||||
color_green);
|
||||
|
||||
view.addRow ();
|
||||
|
||||
// Add rows to table showing diffs.
|
||||
std::vector <std::string> all = Context::getContext ().getColumns ();
|
||||
|
||||
// Now factor in the annotation attributes.
|
||||
for (auto& it : before.data)
|
||||
if (it.first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it.first);
|
||||
|
||||
for (auto& it : after.data)
|
||||
if (it.first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it.first);
|
||||
|
||||
// Now render all the attributes.
|
||||
std::sort (all.begin (), all.end ());
|
||||
|
||||
std::string before_att;
|
||||
std::string after_att;
|
||||
std::string last_att;
|
||||
for (auto& a : all)
|
||||
{
|
||||
if (a != last_att) // Skip duplicates.
|
||||
{
|
||||
last_att = a;
|
||||
|
||||
before_att = before.get (a);
|
||||
after_att = after.get (a);
|
||||
|
||||
// Don't report different uuid.
|
||||
// Show nothing if values are the unchanged.
|
||||
if (a == "uuid" ||
|
||||
before_att == after_att)
|
||||
{
|
||||
// Show nothing - no point displaying that which did not change.
|
||||
|
||||
// row = view.addRow ();
|
||||
// view.set (row, 0, *a + ":");
|
||||
// view.set (row, 1, before_att);
|
||||
}
|
||||
|
||||
// Attribute deleted.
|
||||
else if (before_att != "" && after_att == "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
}
|
||||
|
||||
// Attribute added.
|
||||
else if (before_att == "" && after_att != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
|
||||
// Attribute changed.
|
||||
else
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '-' + a + ':', color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, '+' + a + ':', color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
13
src/Task.h
13
src/Task.h
@@ -33,6 +33,8 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <JSON.h>
|
||||
#include <Table.h>
|
||||
#include <Datetime.h>
|
||||
|
||||
class Task
|
||||
{
|
||||
@@ -60,6 +62,7 @@ public:
|
||||
public:
|
||||
Task () = default;
|
||||
bool operator== (const Task&);
|
||||
bool operator!= (const Task&);
|
||||
Task (const std::string&);
|
||||
Task (const json::object*);
|
||||
|
||||
@@ -74,7 +77,6 @@ public:
|
||||
enum dateState {dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday};
|
||||
|
||||
// Public data.
|
||||
std::map <std::string, std::string> data {};
|
||||
int id {0};
|
||||
float urgency_value {0.0};
|
||||
bool recalc_urgency {true};
|
||||
@@ -100,6 +102,8 @@ public:
|
||||
void set (const std::string&, long long);
|
||||
void remove (const std::string&);
|
||||
|
||||
bool is_empty () const;
|
||||
|
||||
#ifdef PRODUCT_TASKWARRIOR
|
||||
bool is_ready () const;
|
||||
bool is_due () const;
|
||||
@@ -154,7 +158,7 @@ public:
|
||||
std::vector <Task> getBlockedTasks () const;
|
||||
std::vector <Task> getDependencyTasks () const;
|
||||
|
||||
std::vector <std::string> getUDAOrphanUUIDs () const;
|
||||
std::vector <std::string> getUDAOrphans () const;
|
||||
|
||||
void substitute (const std::string&, const std::string&, const std::string&);
|
||||
#endif
|
||||
@@ -171,6 +175,8 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
int determineVersion (const std::string&);
|
||||
@@ -187,6 +193,9 @@ private:
|
||||
void fixDependsAttribute ();
|
||||
void fixTagsAttribute ();
|
||||
|
||||
protected:
|
||||
std::map <std::string, std::string> data {};
|
||||
|
||||
public:
|
||||
float urgency_project () const;
|
||||
float urgency_active () const;
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
#include <format.h>
|
||||
#include <utf8.h>
|
||||
#include <main.h>
|
||||
#include <util.h>
|
||||
#include <stdlib.h>
|
||||
#include <regex>
|
||||
|
||||
#define STRING_COLUMN_LABEL_DEP "Depends"
|
||||
|
||||
@@ -152,20 +154,59 @@ void ColumnDepends::modify (Task& task, const std::string& value)
|
||||
// Apply or remove dendencies in turn.
|
||||
for (auto& dep : split (value, ','))
|
||||
{
|
||||
bool removal = false;
|
||||
if (dep[0] == '-')
|
||||
{
|
||||
if (dep.length () == 37)
|
||||
task.removeDependency (dep.substr (1));
|
||||
else
|
||||
task.removeDependency (strtol (dep.substr (1).c_str (), nullptr, 10));
|
||||
removal = true;
|
||||
dep = dep.substr(1);
|
||||
}
|
||||
else
|
||||
|
||||
auto hyphen = dep.find ('-');
|
||||
long lower, upper; // For ID ranges
|
||||
std::regex valid_uuid ("[a-f0-9]{8}([a-f0-9-]{4,28})?"); // TODO: Make more precise
|
||||
|
||||
// UUID
|
||||
if (dep.length () >= 8 && std::regex_match (dep, valid_uuid))
|
||||
{
|
||||
if (dep.length () == 36)
|
||||
task.addDependency (dep);
|
||||
else
|
||||
task.addDependency (strtol (dep.c_str (), nullptr, 10));
|
||||
// Full UUID, can be added directly
|
||||
if (dep.length () == 36)
|
||||
if (removal)
|
||||
task.removeDependency (dep);
|
||||
else
|
||||
task.addDependency (dep);
|
||||
|
||||
// Short UUID, need to look up full form
|
||||
else
|
||||
{
|
||||
Task loaded_task;
|
||||
if (Context::getContext ().tdb2.get (dep, loaded_task))
|
||||
if (removal)
|
||||
task.removeDependency (loaded_task.get ("uuid"));
|
||||
else
|
||||
task.addDependency (loaded_task.get ("uuid"));
|
||||
else
|
||||
throw format ("Dependency could not be set - task with UUID '{1}' does not exist.", dep);
|
||||
}
|
||||
}
|
||||
// ID range
|
||||
else if (dep.find ('-') != std::string::npos &&
|
||||
extractLongInteger (dep.substr (0, hyphen), lower) &&
|
||||
extractLongInteger (dep.substr (hyphen + 1), upper))
|
||||
{
|
||||
for (long i = lower; i <= upper; i++)
|
||||
if (removal)
|
||||
task.removeDependency (i);
|
||||
else
|
||||
task.addDependency (i);
|
||||
}
|
||||
// Simple ID
|
||||
else if (extractLongInteger (dep, lower))
|
||||
if (removal)
|
||||
task.removeDependency (lower);
|
||||
else
|
||||
task.addDependency (lower);
|
||||
else
|
||||
throw format ("Invalid dependency value: '{1}'", dep);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include <utf8.h>
|
||||
#include <util.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnProject::ColumnProject ()
|
||||
{
|
||||
@@ -121,7 +119,6 @@ void ColumnProject::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
|
||||
Variant v;
|
||||
e.evaluateInfixExpression (value, v);
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include <format.h>
|
||||
#include <utf8.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnRecur::ColumnRecur ()
|
||||
{
|
||||
@@ -108,7 +106,6 @@ void ColumnRecur::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
e.evaluateInfixExpression (value, evaluatedValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include <utf8.h>
|
||||
#include <main.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnTags::ColumnTags ()
|
||||
{
|
||||
@@ -162,7 +160,6 @@ void ColumnTags::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
|
||||
Variant v;
|
||||
e.evaluateInfixExpression (value, v);
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
#include <Filter.h>
|
||||
#include <format.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnTypeDate::ColumnTypeDate ()
|
||||
{
|
||||
@@ -213,7 +211,6 @@ void ColumnTypeDate::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
e.evaluateInfixExpression (value, evaluatedValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <Filter.h>
|
||||
#include <format.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnTypeDuration::ColumnTypeDuration ()
|
||||
{
|
||||
@@ -55,7 +53,6 @@ void ColumnTypeDuration::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
e.evaluateInfixExpression (value, evaluatedValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <Filter.h>
|
||||
#include <format.h>
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnTypeNumeric::ColumnTypeNumeric ()
|
||||
{
|
||||
@@ -55,7 +53,6 @@ void ColumnTypeNumeric::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
e.evaluateInfixExpression (value, evaluatedValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
|
||||
#define STRING_INVALID_MOD "The '{1}' attribute does not allow a value of '{2}'."
|
||||
|
||||
extern Task& contextTask;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ColumnTypeString::ColumnTypeString ()
|
||||
{
|
||||
@@ -67,7 +65,6 @@ void ColumnTypeString::modify (Task& task, const std::string& value)
|
||||
{
|
||||
Eval e;
|
||||
e.addSource (domSource);
|
||||
contextTask = task;
|
||||
|
||||
Variant v;
|
||||
e.evaluateInfixExpression (value, v);
|
||||
|
||||
@@ -51,6 +51,9 @@ int CmdAdd::execute (std::string& output)
|
||||
{
|
||||
// Apply the command line modifications to the new task.
|
||||
Task task;
|
||||
|
||||
// the task is empty, but DOM references can refer to earlier parts of the
|
||||
// command line, e.g., `task add due:20110101 wait:due`.
|
||||
task.modify (Task::modReplace, true);
|
||||
Context::getContext ().tdb2.add (task);
|
||||
|
||||
|
||||
@@ -110,33 +110,43 @@ std::string CmdContext::joinWords (const std::vector <std::string>& words, unsig
|
||||
// Validate the context as valid for writing and fail the write context definition
|
||||
// A valid write context:
|
||||
// - does not contain any operators except AND
|
||||
// - does not use modifiers
|
||||
// - does not contain tag exclusion
|
||||
// - does not use modifiers, except for 'equals' and 'is'
|
||||
//
|
||||
// Returns True if the context is a valid write context. If the context is
|
||||
// invalid due to a wrong modifier use, the modifier string will contain the
|
||||
// first invalid modifier.
|
||||
bool CmdContext::validateWriteContext (const std::vector <A2>& lexedArgs, std::string& modifier_token)
|
||||
//
|
||||
bool CmdContext::validateWriteContext (const std::vector <A2>& lexedArgs, std::string& reason)
|
||||
{
|
||||
bool contains_or = false;
|
||||
bool contains_modifier = false;
|
||||
|
||||
for (auto &arg: lexedArgs) {
|
||||
if (arg._lextype == Lexer::Type::op)
|
||||
if (arg.attribute ("raw") == "or")
|
||||
contains_or = true;
|
||||
{
|
||||
reason = "contains the 'OR' operator";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arg._lextype == Lexer::Type::pair) {
|
||||
auto modifier = arg.attribute ("modifier");
|
||||
if (modifier != "" && modifier != "is" && modifier != "equals")
|
||||
{
|
||||
contains_modifier = true;
|
||||
modifier_token = arg.attribute ("raw");
|
||||
break;
|
||||
reason = format ("contains an attribute modifier '{1}'", arg.attribute ("raw"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg._lextype == Lexer::Type::tag) {
|
||||
if (arg.attribute ("sign") == "-")
|
||||
{
|
||||
reason = format ("contains tag exclusion '{1}'", arg.attribute ("raw"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return not contains_or and not contains_modifier;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -211,14 +221,13 @@ void CmdContext::defineContext (const std::vector <std::string>& words, std::str
|
||||
! confirm (format ("The filter '{1}' matches 0 pending tasks. Do you wish to continue?", value)))
|
||||
throw std::string ("Context definition aborted.");
|
||||
|
||||
std::string modifier_token = "";
|
||||
bool valid_write_context = CmdContext::validateWriteContext (lexedArgs, modifier_token);
|
||||
std::string reason = "";
|
||||
bool valid_write_context = CmdContext::validateWriteContext (lexedArgs, reason);
|
||||
|
||||
if (! valid_write_context)
|
||||
{
|
||||
std::stringstream warning;
|
||||
warning << format ("The filter '{1}' is not a valid modification string, because it contains ", value)
|
||||
<< ( modifier_token.empty () ? "the OR operator." : format ("an attribute modifier ({1}).", modifier_token) )
|
||||
warning << format ("The filter '{1}' is not a valid modification string, because it contains {2}.", value, reason)
|
||||
<< "\nAs such, value for the write context cannot be set (context will not apply on task add / task log).\n\n"
|
||||
<< format ("Please use 'task config context.{1}.write <default mods>' to set default attribute values for new tasks in this context manually.\n\n", words[1]);
|
||||
out << colorizeFootnote (warning.str ());
|
||||
|
||||
@@ -249,7 +249,7 @@ int CmdCustom::execute (std::string& output)
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
// Inform user about the new release higlights if not presented yet
|
||||
// Inform user about the new release highlights if not presented yet
|
||||
if (Context::getContext ().config.get ("news.version") != "2.6.0")
|
||||
{
|
||||
std::random_device device;
|
||||
@@ -258,7 +258,7 @@ int CmdCustom::execute (std::string& output)
|
||||
|
||||
std::string NEWS_NOTICE = (
|
||||
"Recently upgraded to 2.6.0. "
|
||||
"Please run 'task news' to read higlights about the new release."
|
||||
"Please run 'task news' to read highlights about the new release."
|
||||
);
|
||||
|
||||
// 1 in 10 chance to display the message.
|
||||
|
||||
@@ -128,7 +128,7 @@ int CmdDenotate::execute (std::string&)
|
||||
}
|
||||
}
|
||||
|
||||
if (before.data != task.data)
|
||||
if (before.getAnnotations () != task.getAnnotations ())
|
||||
{
|
||||
auto question = format ("Denotate task {1} '{2}'?",
|
||||
task.identifier (true),
|
||||
|
||||
@@ -324,7 +324,7 @@ std::string CmdEdit::formatTask (Task task, const std::string& dateformat)
|
||||
}
|
||||
|
||||
// UDA orphans
|
||||
auto orphans = task.getUDAOrphanUUIDs ();
|
||||
auto orphans = task.getUDAOrphans ();
|
||||
if (orphans.size ())
|
||||
{
|
||||
before << "# User Defined Attribute Orphans\n";
|
||||
|
||||
@@ -64,9 +64,8 @@ int CmdGet::execute (std::string& output)
|
||||
{
|
||||
case Lexer::Type::dom:
|
||||
{
|
||||
Task t;
|
||||
Variant result;
|
||||
if (getDOM (arg.attribute ("raw"), t, result))
|
||||
if (getDOM (arg.attribute ("raw"), NULL, result))
|
||||
results.emplace_back (result);
|
||||
else
|
||||
results.emplace_back ("");
|
||||
|
||||
@@ -198,7 +198,7 @@ void CmdImport::importSingleTask (json::object* obj)
|
||||
if (hasGeneratedEnd)
|
||||
task.set ("end", before.get ("end"));
|
||||
|
||||
if (before.data != task.data)
|
||||
if (before != task)
|
||||
{
|
||||
CmdModify modHelper;
|
||||
modHelper.checkConsistency (before, task);
|
||||
|
||||
@@ -79,7 +79,7 @@ int CmdModify::execute (std::string&)
|
||||
Task before (task);
|
||||
task.modify (Task::modReplace);
|
||||
|
||||
if (before.data != task.data)
|
||||
if (before != task)
|
||||
{
|
||||
// Abort if change introduces inconsistencies.
|
||||
checkConsistency(before, task);
|
||||
|
||||
@@ -477,7 +477,7 @@ void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
|
||||
" hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n"
|
||||
" Solutions in the past required symlinks or more cumbersome configuration overrides.",
|
||||
" If you configure your data.location and hooks.location as above, ensure\n"
|
||||
" that the XFG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n"
|
||||
" that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n"
|
||||
" otherwise they're going to expand to empty string. Alternatively you can\n"
|
||||
" hardcode the desired paths on your system."
|
||||
);
|
||||
@@ -616,7 +616,11 @@ int CmdNews::execute (std::string& output)
|
||||
autoComplete (answer, options, matches, 1); // Hard-coded 1.
|
||||
|
||||
if (matches.size () == 1 && matches[0] == "yes")
|
||||
#if defined (DARWIN)
|
||||
system ("open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
#else
|
||||
system ("xdg-open 'https://github.com/sponsors/GothenburgBitFactory/'");
|
||||
#endif
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@@ -625,7 +629,7 @@ int CmdNews::execute (std::string& output)
|
||||
|
||||
if (! full_summary && major_items)
|
||||
Context::getContext ().footnote (format (
|
||||
"Only major higlights were displayed ({1} out of {2} total).\n"
|
||||
"Only major highlights were displayed ({1} out of {2} total).\n"
|
||||
"If you're interested in more release highlights, run 'task news {3} minor'.",
|
||||
items.size (),
|
||||
total_highlights,
|
||||
|
||||
@@ -134,12 +134,8 @@ int CmdUDAs::execute (std::string& output)
|
||||
std::map <std::string, int> orphans;
|
||||
for (auto& i : filtered)
|
||||
{
|
||||
for (auto& att : i.data)
|
||||
if (! Task::isAnnotationAttr (att.first) &&
|
||||
! Task::isTagAttr (att.first) &&
|
||||
! Task::isDepAttr (att.first) &&
|
||||
Context::getContext ().columns.find (att.first) == Context::getContext ().columns.end ())
|
||||
orphans[att.first]++;
|
||||
for (auto& att : i.getUDAOrphans ())
|
||||
orphans[att]++;
|
||||
}
|
||||
|
||||
if (orphans.size ())
|
||||
|
||||
Submodule src/libshared updated: 8baf2dbcad...a803061222
@@ -29,6 +29,7 @@
|
||||
#include <new>
|
||||
#include <cstring>
|
||||
#include <Context.h>
|
||||
#include <regex>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int main (int argc, const char** argv)
|
||||
@@ -64,6 +65,11 @@ int main (int argc, const char** argv)
|
||||
status = -3;
|
||||
}
|
||||
|
||||
catch (const std::regex_error& e)
|
||||
{
|
||||
std::cout << "regex_error caught: " << e.what() << '\n';
|
||||
}
|
||||
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Unknown error. Please report.\n";
|
||||
|
||||
@@ -189,10 +189,9 @@ static void colorizeKeyword (Task& task, const std::string& rule, const Color& b
|
||||
// first match.
|
||||
else
|
||||
{
|
||||
for (const auto& att : task.data)
|
||||
for (const auto& att : task.getAnnotations ())
|
||||
{
|
||||
if (! att.first.compare (0, 11, "annotation_", 11) &&
|
||||
find (att.second, rule.substr (14), sensitive) != std::string::npos)
|
||||
if (find (att.second, rule.substr (14), sensitive) != std::string::npos)
|
||||
{
|
||||
applyColor (base, c, merge);
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
#include <format.h>
|
||||
#include <shared.h>
|
||||
// If <iostream> is included, put it after <stdio.h>, because it includes
|
||||
// <stdio.h>, and therefore would ignore the _WITH_GETLINE.
|
||||
@@ -309,3 +310,10 @@ void setHeaderUnderline (Table& table)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Perform strtol on a string and check if the extracted value matches.
|
||||
//
|
||||
bool extractLongInteger (const std::string& input, long& output)
|
||||
{
|
||||
output = strtol (input.c_str (), nullptr, 10);
|
||||
return (format ("{1}", output) == input);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ const std::vector <std::string> extractParents (
|
||||
bool nontrivial (const std::string&);
|
||||
const char* optionalBlankLine ();
|
||||
void setHeaderUnderline (Table&);
|
||||
bool extractLongInteger (const std::string&, long&);
|
||||
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
1
test/.gitignore
vendored
1
test/.gitignore
vendored
@@ -34,5 +34,6 @@ variant_partial.t
|
||||
variant_subtract.t
|
||||
variant_xor.t
|
||||
view.t
|
||||
tw-2689.t
|
||||
|
||||
json_test
|
||||
|
||||
@@ -16,7 +16,7 @@ include_directories (${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/test
|
||||
${TASK_INCLUDE_DIRS})
|
||||
|
||||
set (test_SRCS col.t dom.t eval.t lexer.t t.t tdb2.t util.t variant_add.t variant_and.t variant_cast.t variant_divide.t variant_equal.t variant_exp.t variant_gt.t variant_gte.t variant_inequal.t variant_lt.t variant_lte.t variant_match.t variant_math.t variant_modulo.t variant_multiply.t variant_nomatch.t variant_not.t variant_or.t variant_partial.t variant_subtract.t variant_xor.t view.t)
|
||||
set (test_SRCS col.t dom.t eval.t lexer.t t.t tw-2689.t tdb2.t util.t variant_add.t variant_and.t variant_cast.t variant_divide.t variant_equal.t variant_exp.t variant_gt.t variant_gte.t variant_inequal.t variant_lt.t variant_lte.t variant_match.t variant_math.t variant_modulo.t variant_multiply.t variant_nomatch.t variant_not.t variant_or.t variant_partial.t variant_subtract.t variant_xor.t view.t)
|
||||
|
||||
add_custom_target (test ./run_all --verbose
|
||||
DEPENDS ${test_SRCS} task_executable
|
||||
|
||||
@@ -121,15 +121,15 @@ class ContextManagementTest(TestCase):
|
||||
self.assertEqual(self.t.taskrc_content.count(context_line), 1)
|
||||
|
||||
# Assert the config does not contain write context definition
|
||||
context_line = 'context.work.write=due.before:today\n'
|
||||
context_line = 'context.urgent.write=due.before:today\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
# Assert that the write context was not set at all
|
||||
self.assertNotIn('context.work.write=', self.t.taskrc_content)
|
||||
self.assertNotIn('context.urgent.write=', self.t.taskrc_content)
|
||||
|
||||
# Assert that legacy style was not used
|
||||
# Assert the config contains read context definition
|
||||
context_line = 'context.work=due.before:today\n'
|
||||
context_line = 'context.urgent=due.before:today\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
def test_context_define_invalid_for_write_due_to_operator(self):
|
||||
@@ -146,15 +146,40 @@ class ContextManagementTest(TestCase):
|
||||
self.assertEqual(self.t.taskrc_content.count(context_line), 1)
|
||||
|
||||
# Assert the config does not contain write context definition
|
||||
context_line = 'context.work.write=due:today or +next\n'
|
||||
context_line = 'context.urgent.write=due:today or +next\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
# Assert that the write context was not set at all
|
||||
self.assertNotIn('context.work.write=', self.t.taskrc_content)
|
||||
self.assertNotIn('context.urgent.write=', self.t.taskrc_content)
|
||||
|
||||
# Assert that legacy style was not used
|
||||
# Assert the config contains read context definition
|
||||
context_line = 'context.work=due:today or +next\n'
|
||||
context_line = 'context.urgent=due:today or +next\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
def test_context_define_invalid_for_write_due_to_tag_exclusion(self):
|
||||
"""Test definition of a context that is not a valid write context because it contains a tag exclusion."""
|
||||
self.t.config("confirmation", "off")
|
||||
code, out, err = self.t('context define nowork due:today -work')
|
||||
self.assertIn("Context 'nowork' defined", out)
|
||||
|
||||
# Assert the config contains read context definition
|
||||
context_line = 'context.nowork.read=due:today -work\n'
|
||||
self.assertIn(context_line, self.t.taskrc_content)
|
||||
|
||||
# Assert that it contains the definition only once
|
||||
self.assertEqual(self.t.taskrc_content.count(context_line), 1)
|
||||
|
||||
# Assert the config does not contain write context definition
|
||||
context_line = 'context.nowork.write=due:today -work\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
# Assert that the write context was not set at all
|
||||
self.assertNotIn('context.nowork.write=', self.t.taskrc_content)
|
||||
|
||||
# Assert that legacy style was not used
|
||||
# Assert the config contains read context definition
|
||||
context_line = 'context.nowork=due:today -work\n'
|
||||
self.assertNotIn(context_line, self.t.taskrc_content)
|
||||
|
||||
def test_context_delete(self):
|
||||
|
||||
@@ -145,6 +145,22 @@ class TestDependencies(TestCase):
|
||||
code, out, err = self.t("_get 1.tags.BLOCKED")
|
||||
self.assertEqual("\n", out)
|
||||
|
||||
def test_dependency_bulk_removal(self):
|
||||
"""2655: Check that one can bulk undepend a task"""
|
||||
self.t("add three")
|
||||
self.t("1 modify dep:2,3")
|
||||
|
||||
code, out, err = self.t("_get 1.tags.BLOCKED")
|
||||
self.assertEqual("BLOCKED\n", out)
|
||||
|
||||
self.t("1 modify depends:")
|
||||
|
||||
code, out, err = self.t("_get 1.tags.BLOCKED")
|
||||
self.assertEqual("\n", out)
|
||||
|
||||
code, out, err = self.t("_get 1.depends")
|
||||
self.assertEqual("\n", out)
|
||||
|
||||
def test_chain_repair(self):
|
||||
"""Check that a broken chain is repaired"""
|
||||
self.t("add three")
|
||||
@@ -169,7 +185,6 @@ class TestDependencies(TestCase):
|
||||
self.assertNotIn("Would you like the dependency chain fixed?", out)
|
||||
self.assertIn("Deleted 1 task", out)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_id_range_dep(self):
|
||||
"""Check that an ID range can be used for deps"""
|
||||
self.t("add three")
|
||||
@@ -178,7 +193,7 @@ class TestDependencies(TestCase):
|
||||
self.t("3 modify dep:1-2")
|
||||
code, out, err = self.t("_get 1.tags.BLOCKING")
|
||||
self.assertEqual("BLOCKING\n", out)
|
||||
code, out, err = self.t("_get 2.tag.BLOCKING")
|
||||
code, out, err = self.t("_get 2.tags.BLOCKING")
|
||||
self.assertEqual("BLOCKING\n", out)
|
||||
|
||||
def test_id_uuid_dep(self):
|
||||
@@ -196,6 +211,22 @@ class TestDependencies(TestCase):
|
||||
code, out, err = self.t("3 modify dep:-1,-%s" % uuid)
|
||||
self.assertIn("Modifying task 3 'three'.", out)
|
||||
|
||||
def test_id_uuid_short_dep(self):
|
||||
"""Check that short UUIDs are usable for deps"""
|
||||
|
||||
# Get 2.uuid
|
||||
code, out, err = self.t("_get 2.uuid")
|
||||
short_uuid = out.strip().split("-")[0]
|
||||
|
||||
# Add a mix of IDs and UUID
|
||||
code, out, err = self.t("add three dep:%s" % short_uuid)
|
||||
self.assertIn("Created task 3.", out)
|
||||
|
||||
# Remove a mix of IЅs and UUID
|
||||
code, out, err = self.t("3 modify dep:-%s" % short_uuid)
|
||||
self.assertIn("Modifying task 3 'three'.", out)
|
||||
|
||||
|
||||
class TestBug697(TestCase):
|
||||
def setUp(self):
|
||||
"""Executed before each test in the class"""
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
FROM centos:8
|
||||
|
||||
# Fix missing repo metadata
|
||||
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Linux-*
|
||||
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-*
|
||||
|
||||
RUN dnf update -y
|
||||
RUN dnf install python3 git gcc gcc-c++ make gnutls-devel libuuid-devel glibc-langpack-en -y
|
||||
RUN dnf install epel-release -y
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:31
|
||||
FROM fedora:35
|
||||
|
||||
RUN dnf update -y
|
||||
RUN dnf install python3 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime glibc-langpack-en -y
|
||||
27
test/docker/ubuntu2110
Normal file
27
test/docker/ubuntu2110
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM ubuntu:21.10
|
||||
|
||||
RUN apt-get update
|
||||
RUN DEBIAN_FRONTEND="noninteractive" apt-get install -y build-essential cmake git uuid-dev libgnutls28-dev faketime locales python3
|
||||
|
||||
# Setup language environment
|
||||
RUN locale-gen en_US.UTF-8
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US.UTF-8
|
||||
|
||||
# Setup taskwarrior
|
||||
ADD . /root/code/
|
||||
WORKDIR /root/code/
|
||||
RUN git clean -dfx
|
||||
RUN git submodule init
|
||||
RUN git submodule update
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=debug .
|
||||
RUN make -j8
|
||||
RUN make install
|
||||
RUN task --version
|
||||
|
||||
# Setup tests
|
||||
WORKDIR /root/code/test/
|
||||
RUN make -j8
|
||||
|
||||
CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"]
|
||||
@@ -500,17 +500,9 @@ class TestBug1900(TestCase):
|
||||
|
||||
def test_project_eval(self):
|
||||
"""1900: Project name can contain dashes"""
|
||||
self.t("add foo project:due-b")
|
||||
self.t("add foo project:doo-bee")
|
||||
code, out, err = self.t("_get 1.project")
|
||||
self.assertEqual("due-b\n", out)
|
||||
|
||||
self.t("add foo project:scheduled-home")
|
||||
code, out, err = self.t("_get 2.project")
|
||||
self.assertEqual("scheduled-home\n", out)
|
||||
|
||||
self.t("add foo project:entry-work")
|
||||
code, out, err = self.t("_get 3.project")
|
||||
self.assertEqual("entry-work\n", out)
|
||||
self.assertEqual("doo-bee\n", out)
|
||||
|
||||
|
||||
class TestBug1904(TestCase):
|
||||
|
||||
@@ -126,7 +126,8 @@ class TestRecurrenceWeekdays(TestCase):
|
||||
# The due dates should be Friday and Monday, three days apart,
|
||||
# having skipped the weekend.
|
||||
# Note: On daylight savings in the fall, this '3' becomes '2.9583'.
|
||||
self.assertTrue(int(monday.strip()) - int(friday.strip()) >= 2)
|
||||
# Note: when monday is next year, friday+2 > 365
|
||||
self.assertTrue(int(monday.strip()) >= (int(friday.strip()) + 2) % 365)
|
||||
|
||||
|
||||
class TestRecurrenceUntil(TestCase):
|
||||
|
||||
@@ -187,7 +187,7 @@ TODO Task::decode
|
||||
test.is (task.get ("three"), "four", "three=four");
|
||||
|
||||
// Task::set
|
||||
task.data.clear ();
|
||||
task = Task();
|
||||
task.set ("name", "value");
|
||||
test.is (task.composeF4 (), "[name:\"value\"]", "Task::set");
|
||||
|
||||
@@ -211,7 +211,7 @@ TODO Task::decode
|
||||
test.is (task.composeF4 (), "[name:\"value\"]", "Task::remove");
|
||||
|
||||
// Task::all
|
||||
test.is (task.data.size (), (size_t)1, "Task::all size");
|
||||
test.is (task.all ().size (), (size_t)1, "Task::all size");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
18
test/tag.t
18
test/tag.t
@@ -36,13 +36,10 @@ from basetest import Task, TestCase
|
||||
|
||||
|
||||
class TestTags(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Executed once before any test in the class"""
|
||||
cls.t = Task()
|
||||
|
||||
def setUp(self):
|
||||
"""Executed before each test in the class"""
|
||||
self.t = Task()
|
||||
|
||||
def split_tags(self, tags):
|
||||
return sorted(tags.strip().split(','))
|
||||
@@ -81,6 +78,19 @@ class TestTags(TestCase):
|
||||
code, out, err = self.t("1 modify -missing")
|
||||
self.assertIn("Modified 0 tasks", out)
|
||||
|
||||
def test_tag_bulk_removal(self):
|
||||
"""2655: Test bulk removal of tags"""
|
||||
self.t("add +one This +two is a test +three")
|
||||
code, out, err = self.t("_get 1.tags")
|
||||
self.assertEqual(
|
||||
sorted(["one", "two", "three"]),
|
||||
self.split_tags(out))
|
||||
|
||||
# Remove all tags in bulk
|
||||
self.t("1 modify tags:")
|
||||
code, out, err = self.t("_get 1.tags")
|
||||
self.assertEqual("\n", out)
|
||||
|
||||
|
||||
class TestVirtualTags(TestCase):
|
||||
@classmethod
|
||||
|
||||
89
test/tw-2689.t.cpp
Normal file
89
test/tw-2689.t.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <iostream>
|
||||
#include <cmake.h>
|
||||
#include <stdlib.h>
|
||||
#include <main.h>
|
||||
#include <test.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int main (int, char**)
|
||||
{
|
||||
UnitTest test (12);
|
||||
|
||||
// Ensure environment has no influence.
|
||||
unsetenv ("TASKDATA");
|
||||
unsetenv ("TASKRC");
|
||||
|
||||
// Inform Task about the attributes in the JSON below
|
||||
Task::attributes["depends"] = "string";
|
||||
Task::attributes["uuid"] = "string";
|
||||
|
||||
// depends in [..] string from a taskserver (issue#2689)
|
||||
auto sample = "{"
|
||||
"\"depends\":\"[\\\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\\\",\\\"e08e35fa-e42b-4de0-acc4-518fca8f6365\\\"]\","
|
||||
"\"uuid\":\"00000000-0000-0000-0000-000000000000\""
|
||||
"}";
|
||||
auto json = Task (sample);
|
||||
auto value = json.get ("uuid");
|
||||
test.is (value, "00000000-0000-0000-0000-000000000000", "json [..] uuid");
|
||||
value = json.get ("depends");
|
||||
test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json [..] depends");
|
||||
test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json [..] dep attr");
|
||||
test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json [..] dep attr");
|
||||
|
||||
// depends in comma-delimited string from a taskserver (deprecated format)
|
||||
sample = "{"
|
||||
"\"depends\":\"92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365\","
|
||||
"\"uuid\":\"00000000-0000-0000-0000-000000000000\""
|
||||
"}";
|
||||
json = Task (sample);
|
||||
value = json.get ("uuid");
|
||||
test.is (value, "00000000-0000-0000-0000-000000000000", "json comma-separated uuid");
|
||||
value = json.get ("depends");
|
||||
test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json comma-separated depends");
|
||||
test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json comma-separated dep attr");
|
||||
test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json comma-separated dep attr");
|
||||
|
||||
// depends in a JSON array from a taskserver
|
||||
sample = "{"
|
||||
"\"depends\":[\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\",\"e08e35fa-e42b-4de0-acc4-518fca8f6365\"],"
|
||||
"\"uuid\":\"00000000-0000-0000-0000-000000000000\""
|
||||
"}";
|
||||
json = Task (sample);
|
||||
value = json.get ("uuid");
|
||||
test.is (value, "00000000-0000-0000-0000-000000000000", "json array uuid");
|
||||
value = json.get ("depends");
|
||||
test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json array depends");
|
||||
test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json array dep attr");
|
||||
test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json array dep attr");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -58,7 +58,7 @@ int main (int, char**)
|
||||
Task rightAgain (right);
|
||||
|
||||
std::string output = left.diff (right);
|
||||
t.ok (left.data != right.data, "Detected changes");
|
||||
t.ok (!(left == right), "Detected changes");
|
||||
t.ok (output.find ("Zero will be changed from '0' to '00'") != std::string::npos, "Detected change zero:0 -> zero:00");
|
||||
t.ok (output.find ("One will be deleted") != std::string::npos, "Detected deletion one:1 ->");
|
||||
t.ok (output.find ("Two") == std::string::npos, "Detected no change two:2 -> two:2");
|
||||
|
||||
@@ -77,11 +77,6 @@ class TestVersion(TestCase):
|
||||
self.assertIn("MIT license", out)
|
||||
self.assertIn("https://taskwarrior.org", out)
|
||||
|
||||
def slurp_git(self):
|
||||
git_cmd = ("git", "rev-parse", "--short", "--verify", "HEAD")
|
||||
_, hash, _ = run_cmd_wait(git_cmd)
|
||||
return hash.rstrip("\n")
|
||||
|
||||
def test_under_version(self):
|
||||
"""_version and diagnostics output expected version and syntax"""
|
||||
code, out, err = self.t("_version")
|
||||
@@ -94,8 +89,7 @@ class TestVersion(TestCase):
|
||||
if os.path.exists("../.git"):
|
||||
if 2 >= len(version) > 0:
|
||||
git = version[1]
|
||||
git_expected = "({0})".format(self.slurp_git())
|
||||
self.assertEqual(git_expected, git)
|
||||
self.assertRegex(git, r'\([a-f0-9]*\)'))
|
||||
else:
|
||||
raise ValueError("Unexpected output from _version '{0}'".format(
|
||||
out))
|
||||
|
||||
Reference in New Issue
Block a user