mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
task <filter> export [<report>]
This commit is contained in:
parent
ef176478e9
commit
9ce366ddab
4 changed files with 169 additions and 4 deletions
|
@ -102,9 +102,11 @@ int CmdCustom::execute (std::string& output)
|
||||||
if (reportFilter != "")
|
if (reportFilter != "")
|
||||||
Context::getContext ().cli2.addFilter (reportFilter);
|
Context::getContext ().cli2.addFilter (reportFilter);
|
||||||
|
|
||||||
// Apply filter.
|
// Make sure reccurent tasks are generated.
|
||||||
handleUntil ();
|
handleUntil ();
|
||||||
handleRecurrence ();
|
handleRecurrence ();
|
||||||
|
|
||||||
|
// Apply filter.
|
||||||
Filter filter;
|
Filter filter;
|
||||||
std::vector <Task> filtered;
|
std::vector <Task> filtered;
|
||||||
filter.subset (filtered);
|
filter.subset (filtered);
|
||||||
|
|
|
@ -28,13 +28,16 @@
|
||||||
#include <CmdExport.h>
|
#include <CmdExport.h>
|
||||||
#include <Context.h>
|
#include <Context.h>
|
||||||
#include <Filter.h>
|
#include <Filter.h>
|
||||||
|
#include <format.h>
|
||||||
|
#include <shared.h>
|
||||||
|
#include <shared.h>
|
||||||
#include <main.h>
|
#include <main.h>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
CmdExport::CmdExport ()
|
CmdExport::CmdExport ()
|
||||||
{
|
{
|
||||||
_keyword = "export";
|
_keyword = "export";
|
||||||
_usage = "task <filter> export";
|
_usage = "task <filter> export [<report>]";
|
||||||
_description = "Exports tasks in JSON format";
|
_description = "Exports tasks in JSON format";
|
||||||
_read_only = true;
|
_read_only = true;
|
||||||
_displays_id = true;
|
_displays_id = true;
|
||||||
|
@ -42,7 +45,7 @@ CmdExport::CmdExport ()
|
||||||
_uses_context = false;
|
_uses_context = false;
|
||||||
_accepts_filter = true;
|
_accepts_filter = true;
|
||||||
_accepts_modifications = false;
|
_accepts_modifications = false;
|
||||||
_accepts_miscellaneous = false;
|
_accepts_miscellaneous = true;
|
||||||
_category = Command::Category::migration;
|
_category = Command::Category::migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +54,40 @@ int CmdExport::execute (std::string& output)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
|
auto words = Context::getContext ().cli2.getWords ();
|
||||||
|
std::string selectedReport = "";
|
||||||
|
|
||||||
|
if (words.size () == 1)
|
||||||
|
{
|
||||||
|
// Find the report matching the prompt
|
||||||
|
for (auto& command : Context::getContext ().commands)
|
||||||
|
{
|
||||||
|
if (command.second->category () == Command::Category::report &&
|
||||||
|
closeEnough(command.second->keyword (), words[0]))
|
||||||
|
{
|
||||||
|
selectedReport = command.second->keyword ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedReport.empty ()) {
|
||||||
|
Context::getContext ().error("Unable to find report that matches '" + words[0] + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto reportSort = Context::getContext ().config.get ("report." + selectedReport + ".sort");
|
||||||
|
auto reportFilter = Context::getContext ().config.get ("report." + selectedReport + ".filter");
|
||||||
|
|
||||||
|
auto sortOrder = split (reportSort, ',');
|
||||||
|
if (sortOrder.size () != 0 &&
|
||||||
|
sortOrder[0] != "none") {
|
||||||
|
validateSortColumns (sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the report filter to any existing filter.
|
||||||
|
if (reportFilter != "")
|
||||||
|
Context::getContext ().cli2.addFilter (reportFilter);
|
||||||
|
|
||||||
// Make sure reccurent tasks are generated.
|
// Make sure reccurent tasks are generated.
|
||||||
handleUntil ();
|
handleUntil ();
|
||||||
handleRecurrence ();
|
handleRecurrence ();
|
||||||
|
@ -60,6 +97,32 @@ int CmdExport::execute (std::string& output)
|
||||||
std::vector <Task> filtered;
|
std::vector <Task> filtered;
|
||||||
filter.subset (filtered);
|
filter.subset (filtered);
|
||||||
|
|
||||||
|
std::vector <int> sequence;
|
||||||
|
if (sortOrder.size () &&
|
||||||
|
sortOrder[0] == "none")
|
||||||
|
{
|
||||||
|
// Assemble a sequence vector that represents the tasks listed in
|
||||||
|
// Context::getContext ().cli2._uuid_ranges, in the order in which they appear. This
|
||||||
|
// equates to no sorting, just a specified order.
|
||||||
|
sortOrder.clear ();
|
||||||
|
for (auto& i : Context::getContext ().cli2._uuid_list)
|
||||||
|
for (unsigned int t = 0; t < filtered.size (); ++t)
|
||||||
|
if (filtered[t].get ("uuid") == i)
|
||||||
|
sequence.push_back (t);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There is a sortOrder, so sorting will take place, which means the initial
|
||||||
|
// order of sequence is ascending.
|
||||||
|
for (unsigned int i = 0; i < filtered.size (); ++i)
|
||||||
|
sequence.push_back (i);
|
||||||
|
|
||||||
|
// Sort the tasks.
|
||||||
|
if (sortOrder.size ()) {
|
||||||
|
sort_tasks (filtered, sequence, reportSort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Export == render.
|
// Export == render.
|
||||||
Timer timer;
|
Timer timer;
|
||||||
|
|
||||||
|
@ -77,8 +140,9 @@ int CmdExport::execute (std::string& output)
|
||||||
output += "[\n";
|
output += "[\n";
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
for (auto& task : filtered)
|
for (auto& t : sequence)
|
||||||
{
|
{
|
||||||
|
auto task = filtered[t];
|
||||||
if (counter)
|
if (counter)
|
||||||
{
|
{
|
||||||
if (json_array)
|
if (json_array)
|
||||||
|
@ -104,3 +168,9 @@ int CmdExport::execute (std::string& output)
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void CmdExport::validateSortColumns (std::vector <std::string>& columns)
|
||||||
|
{
|
||||||
|
for (auto& col : columns)
|
||||||
|
legacySortColumnMap (col);
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ class CmdExport : public Command
|
||||||
public:
|
public:
|
||||||
CmdExport ();
|
CmdExport ();
|
||||||
int execute (std::string&);
|
int execute (std::string&);
|
||||||
|
private:
|
||||||
|
void validateSortColumns (std::vector <std::string>&);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
91
test/tw-2575.t
Normal file
91
test/tw-2575.t
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Ensure python finds the local simpletap module
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from basetest import Task, TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestExport(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.t = Task()
|
||||||
|
|
||||||
|
self.t("add one")
|
||||||
|
self.t("add two project:strange")
|
||||||
|
self.t("add task1 +home project:A")
|
||||||
|
self.t("add task2 +work project:A")
|
||||||
|
|
||||||
|
self.t.config("report.foo.description", "DESC")
|
||||||
|
self.t.config("report.foo.labels", "ID,DESCRIPTION")
|
||||||
|
self.t.config("report.foo.columns", "id,description")
|
||||||
|
self.t.config("report.foo.sort", "urgency+")
|
||||||
|
self.t.config("report.foo.filter", "project:A")
|
||||||
|
self.t.config("urgency.user.tag.home.coefficient", "15")
|
||||||
|
|
||||||
|
def assertTaskEqual(self, t1, t2):
|
||||||
|
keys = [k for k in sorted(t1.keys()) if k not in ["entry", "modified", "uuid"]]
|
||||||
|
for k in keys:
|
||||||
|
self.assertEqual(t1[k], t2[k])
|
||||||
|
|
||||||
|
def test_exports(self):
|
||||||
|
"""Verify exports work"""
|
||||||
|
code, out, err = self.t("export")
|
||||||
|
out = json.loads(out)
|
||||||
|
|
||||||
|
self.assertEqual(len(out), 4)
|
||||||
|
|
||||||
|
self.assertTaskEqual(out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0})
|
||||||
|
self.assertTaskEqual(out[1], {"id": 2, "description": "two", "project": "strange", "status": "pending", "urgency": 1})
|
||||||
|
self.assertTaskEqual(out[2], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8})
|
||||||
|
self.assertTaskEqual(out[3], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8})
|
||||||
|
|
||||||
|
def test_exports_filter(self):
|
||||||
|
"""Verify exports with filter work"""
|
||||||
|
code, out, err = self.t("one export")
|
||||||
|
out = json.loads(out)
|
||||||
|
|
||||||
|
self.assertEqual(len(out), 1)
|
||||||
|
|
||||||
|
self.assertTaskEqual(out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0})
|
||||||
|
|
||||||
|
def test_exports_with_limits_and_filter(self):
|
||||||
|
"""Verify exports with limits and filter work"""
|
||||||
|
code, out, err = self.t("task limit:4 export")
|
||||||
|
out = json.loads(out)
|
||||||
|
|
||||||
|
self.assertEqual(len(out), 2)
|
||||||
|
|
||||||
|
self.assertTaskEqual(out[0], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8})
|
||||||
|
self.assertTaskEqual(out[1], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8})
|
||||||
|
|
||||||
|
code, out, err = self.t("task limit:1 export")
|
||||||
|
out = json.loads(out)
|
||||||
|
|
||||||
|
self.assertEqual(len(out), 1)
|
||||||
|
|
||||||
|
self.assertTaskEqual(out[0], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8})
|
||||||
|
|
||||||
|
def test_exports_report(self):
|
||||||
|
"""Verify exports with report work"""
|
||||||
|
code, out, err = self.t("export foo")
|
||||||
|
out = json.loads(out)
|
||||||
|
|
||||||
|
self.assertEqual(len(out), 2)
|
||||||
|
|
||||||
|
self.assertTaskEqual(out[0], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8})
|
||||||
|
self.assertTaskEqual(out[1], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from simpletap import TAPTestRunner
|
||||||
|
|
||||||
|
unittest.main(testRunner=TAPTestRunner())
|
||||||
|
|
||||||
|
# vim: ai sts=4 et sw=4 ft=python syntax=python
|
Loading…
Add table
Add a link
Reference in a new issue