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