From 31829d61fca049abf5ac05832bab9bc8d20bb56d Mon Sep 17 00:00:00 2001 From: Ram-Z Date: Mon, 21 Apr 2025 01:51:38 +0100 Subject: [PATCH] Add uuid UDA type (#3827) Mainly so that UDAs that refer to another task can be formated as "short". --- doc/man/taskrc.5.in | 2 +- src/Task.cpp | 2 +- src/columns/ColUDA.cpp | 20 +++++++++++ src/columns/ColUDA.h | 8 +++++ src/columns/Column.cpp | 8 ++++- test/columns.test.py | 79 +++++++++++++++++++++++++++++++++++++----- 6 files changed, 107 insertions(+), 12 deletions(-) diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index 6f60a6439..9b8e4903a 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -1384,7 +1384,7 @@ if you define a UDA named 'estimate', Taskwarrior will not know that this value is weeks, hours, minutes, money, or some other resource count. .TP -.B uda..type=string|numeric|date|duration +.B uda..type=string|numeric|uuid|date|duration .RS Defines a UDA called '', of the specified type. .RE diff --git a/src/Task.cpp b/src/Task.cpp index 9779ed3bf..f6026b848 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -1999,7 +1999,7 @@ void Task::modify(modType type, bool text_required /* = false */) { // Delegate modification to the column object or their base classes. if (name == "depends" || name == "tags" || name == "recur" || column->type() == "date" || column->type() == "duration" || column->type() == "numeric" || - column->type() == "string") { + column->type() == "string" || column->type() == "uuid") { column->modify(*this, value); mods = true; } diff --git a/src/columns/ColUDA.cpp b/src/columns/ColUDA.cpp index 48686ba70..a6452c631 100644 --- a/src/columns/ColUDA.cpp +++ b/src/columns/ColUDA.cpp @@ -297,3 +297,23 @@ void ColumnUDADuration::render(std::vector& lines, Task& task, int } //////////////////////////////////////////////////////////////////////////////// +ColumnUDAUUID::ColumnUDAUUID() { + _name = ""; + _type = "uuid"; + _style = "long"; + _label = ""; + _modifiable = true; + _uda = true; + _styles = {"long", "short"}; + _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; +} + +//////////////////////////////////////////////////////////////////////////////// +bool ColumnUDAUUID::validate(const std::string& input) const { + Lexer lex(input); + std::string token; + Lexer::Type type; + return lex.isUUID(token, type, true); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColUDA.h b/src/columns/ColUDA.h index ce8aaef97..04d57a8d7 100644 --- a/src/columns/ColUDA.h +++ b/src/columns/ColUDA.h @@ -31,6 +31,7 @@ #include #include #include +#include //////////////////////////////////////////////////////////////////////////////// class ColumnUDAString : public ColumnTypeString { @@ -83,5 +84,12 @@ class ColumnUDADuration : public ColumnTypeDuration { std::vector _values; }; +//////////////////////////////////////////////////////////////////////////////// +class ColumnUDAUUID : public ColumnUUID { + public: + ColumnUDAUUID(); + bool validate(const std::string&) const; +}; + #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/Column.cpp b/src/columns/Column.cpp index f9231af1a..131b48da4 100644 --- a/src/columns/Column.cpp +++ b/src/columns/Column.cpp @@ -246,9 +246,15 @@ Column* Column::uda(const std::string& name) { c->_label = label; if (values != "") c->_values = split(values, ','); return c; + } else if (type == "uuid") { + auto c = new ColumnUDAUUID(); + c->_name = name; + c->_label = label; + return c; } else if (type != "") throw std::string( - "User defined attributes may only be of type 'string', 'date', 'duration' or 'numeric'."); + "User defined attributes may only be of type 'string', 'uuid', date', 'duration' or " + "'numeric'."); return nullptr; } diff --git a/test/columns.test.py b/test/columns.test.py index 7e839414e..7f14c571e 100755 --- a/test/columns.test.py +++ b/test/columns.test.py @@ -109,29 +109,26 @@ class TestUUIDFormats(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("report.xxx.columns", "id,uuid") + cls.t.config("report.xxx.columns", "uuid") cls.t.config("verbose", "nothing") cls.t("add zero") code, out, err = cls.t("_get 1.uuid") cls.uuid = out.strip() - def setUp(self): - """Executed before each test in the class""" - def test_uuid_long(self): """Verify formatting of 'uuid.long' column""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,uuid.long") - self.assertIn(self.uuid, out) + code, out, err = self.t("xxx rc.report.xxx.columns:uuid.long") + self.assertEqual(self.uuid, out.strip()) def test_uuid_short(self): """Verify formatting of 'uuid.short' column""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,uuid.short") - self.assertIn(self.uuid[:7], out) + code, out, err = self.t("xxx rc.report.xxx.columns:uuid.short") + self.assertEqual(self.uuid[:8], out.strip()) def test_uuid_format_unrecognized(self): """Verify uuid.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,uuid.donkey") + code, out, err = self.t.runError("xxx rc.report.xxx.columns:uuid.donkey") self.assertEqual(err, "Unrecognized column format 'uuid.donkey'\n") @@ -482,6 +479,70 @@ start active* ✓ """ +class TestUDAUUIDFormats(TestCase): + @classmethod + def setUpClass(cls): + """Executed once before any test in the class""" + cls.t = Task() + cls.t.config("verbose", "nothing") + cls.t.config("uda.uda_uuid.label", "uda_uuid") + cls.t.config("uda.uda_uuid.type", "uuid") + cls.t.config("report.xxx.columns", "uda_uuid") + + cls.t("add zero") + code, out, err = cls.t("_get 1.uuid") + cls.t("add uda_uuid:{} one".format(out.strip())) + code, out, err = cls.t("_get 2.uda_uuid") + cls.uda_uuid = out.strip() + + def test_uda_uuid_invalid_fails(self): + """Verify adding invalid uuid fails""" + code, out, err = self.t.runError("add uda_uuid:shrek three") + self.assertNotEqual(code, 0) + self.assertIn("uda_uuid", err.strip()) + self.assertIn("shrek", err.strip()) + + def test_uda_uuid_long(self): + """Verify formatting of 'uda_uuid.long' column""" + code, out, err = self.t("2 xxx rc.report.xxx.columns:uda_uuid.long") + self.assertEqual(self.uda_uuid, out.strip()) + + def test_uda_uuid_short(self): + """Verify formatting of 'uda_uuid.short' column""" + code, out, err = self.t("2 xxx rc.report.xxx.columns:uda_uuid.short") + self.assertEqual(self.uda_uuid[:8], out.strip()) + + def test_uda_uuid_format_unrecognized(self): + """Verify uda_uuid.donkey formatting fails""" + code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,uda_uuid.donkey") + self.assertEqual(err, "Unrecognized column format 'uda_uuid.donkey'\n") + + +class TestUDAUUIDReconfiguredFromString(TestCase): + @classmethod + def setUpClass(cls): + """Executed once before any test in the class""" + cls.t = Task() + cls.t.config("verbose", "nothing") + cls.t.config("uda.uda_uuid.label", "uda_uuid") + cls.t.config("report.xxx.columns", "uda_uuid") + + cls.t.config("uda.uda_uuid.type", "string") + cls.expected_str = 3 * "littlepigs" + cls.t("add uda_uuid:{} one".format(cls.expected_str)) + cls.t.config("uda.uda_uuid.type", "uuid") + + def test_uda_uuid_long(self): + """Verify formatting of 'uda_uuid.long' column""" + code, out, err = self.t("1 xxx rc.report.xxx.columns:uda_uuid.long") + self.assertEqual(self.expected_str, out.strip()) + + def test_uda_uuid_short(self): + """Verify formatting of 'uda_uuid.short' column""" + code, out, err = self.t("1 xxx rc.report.xxx.columns:uda_uuid.short") + self.assertEqual(self.expected_str[:8], out.strip()) + + class TestFeature1061(TestCase): def setUp(self): """Executed before each test in the class"""