1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // https://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <CmdExport.h>
29 #include <Context.h>
30 #include <Filter.h>
31 #include <format.h>
32 #include <shared.h>
33 #include <shared.h>
34 #include <main.h>
35
36 ////////////////////////////////////////////////////////////////////////////////
CmdExport()37 CmdExport::CmdExport ()
38 {
39 _keyword = "export";
40 _usage = "task <filter> export [<report>]";
41 _description = "Exports tasks in JSON format";
42 _read_only = true;
43 _displays_id = true;
44 _needs_gc = true;
45 _uses_context = false;
46 _accepts_filter = true;
47 _accepts_modifications = false;
48 _accepts_miscellaneous = true;
49 _category = Command::Category::migration;
50 }
51
52 ////////////////////////////////////////////////////////////////////////////////
execute(std::string & output)53 int CmdExport::execute (std::string& output)
54 {
55 int rc = 0;
56
57 auto words = Context::getContext ().cli2.getWords ();
58 std::string selectedReport = "";
59
60 if (words.size () == 1)
61 {
62 // Find the report matching the prompt
63 for (auto& command : Context::getContext ().commands)
64 {
65 if (command.second->category () == Command::Category::report &&
66 closeEnough(command.second->keyword (), words[0]))
67 {
68 selectedReport = command.second->keyword ();
69 break;
70 }
71 }
72
73 if (selectedReport.empty ()) {
74 throw format("Unable to find report that matches '{1}'.", words[0]);
75 }
76 }
77
78 auto reportSort = Context::getContext ().config.get ("report." + selectedReport + ".sort");
79 auto reportFilter = Context::getContext ().config.get ("report." + selectedReport + ".filter");
80
81 auto sortOrder = split (reportSort, ',');
82 if (sortOrder.size () != 0 &&
83 sortOrder[0] != "none") {
84 validateSortColumns (sortOrder);
85 }
86
87 // Add the report filter to any existing filter.
88 if (reportFilter != "")
89 Context::getContext ().cli2.addFilter (reportFilter);
90
91 // Make sure reccurent tasks are generated.
92 handleUntil ();
93 handleRecurrence ();
94
95 // Apply filter.
96 Filter filter;
97 std::vector <Task> filtered;
98 filter.subset (filtered);
99
100 std::vector <int> sequence;
101 if (sortOrder.size () &&
102 sortOrder[0] == "none")
103 {
104 // Assemble a sequence vector that represents the tasks listed in
105 // Context::getContext ().cli2._uuid_ranges, in the order in which they appear. This
106 // equates to no sorting, just a specified order.
107 sortOrder.clear ();
108 for (auto& i : Context::getContext ().cli2._uuid_list)
109 for (unsigned int t = 0; t < filtered.size (); ++t)
110 if (filtered[t].get ("uuid") == i)
111 sequence.push_back (t);
112 }
113 else
114 {
115 // There is a sortOrder, so sorting will take place, which means the initial
116 // order of sequence is ascending.
117 for (unsigned int i = 0; i < filtered.size (); ++i)
118 sequence.push_back (i);
119
120 // Sort the tasks.
121 if (sortOrder.size ()) {
122 sort_tasks (filtered, sequence, reportSort);
123 }
124 }
125
126 // Export == render.
127 Timer timer;
128
129 // Obey 'limit:N'.
130 int rows = 0;
131 int lines = 0;
132 Context::getContext ().getLimits (rows, lines);
133 int limit = (rows > lines ? rows : lines);
134
135 // Is output contained within a JSON array?
136 bool json_array = Context::getContext ().config.getBoolean ("json.array");
137
138 // Compose output.
139 if (json_array)
140 output += "[\n";
141
142 int counter = 0;
143 for (auto& t : sequence)
144 {
145 auto task = filtered[t];
146 if (counter)
147 {
148 if (json_array)
149 output += ',';
150 output += '\n';
151 }
152
153 output += task.composeJSON (true);
154
155 ++counter;
156 if (limit && counter >= limit)
157 break;
158 }
159
160 if (filtered.size ())
161 output += '\n';
162
163 if (json_array)
164 output += "]\n";
165
166 Context::getContext ().time_render_us += timer.total_us ();
167 return rc;
168 }
169
170 ////////////////////////////////////////////////////////////////////////////////
171
validateSortColumns(std::vector<std::string> & columns)172 void CmdExport::validateSortColumns (std::vector <std::string>& columns)
173 {
174 for (auto& col : columns)
175 legacySortColumnMap (col);
176 }
177