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 <iostream>
29 #include <vector>
30 #include <stdlib.h>
31 #include <shared.h>
32 #include <format.h>
33 #include <util.h>
34 #include <Command.h>
35 #include <main.h>
36
37 #include <CmdAdd.h>
38 #include <CmdAliases.h>
39 #include <CmdAnnotate.h>
40 #include <CmdAppend.h>
41 #include <CmdAttributes.h>
42 #include <CmdBurndown.h>
43 #include <CmdCalc.h>
44 #include <CmdCalendar.h>
45 #include <CmdColor.h>
46 #include <CmdColumns.h>
47 #include <CmdCommands.h>
48 #include <CmdConfig.h>
49 #include <CmdContext.h>
50 #include <CmdCount.h>
51 #include <CmdCustom.h>
52 #include <CmdDelete.h>
53 #include <CmdDenotate.h>
54 #include <CmdDiagnostics.h>
55 #include <CmdDone.h>
56 #include <CmdDuplicate.h>
57 #include <CmdEdit.h>
58 #ifdef HAVE_EXECUTE
59 #include <CmdExec.h>
60 #endif
61 #include <CmdExport.h>
62 #include <CmdGet.h>
63 #include <CmdHelp.h>
64 #include <CmdHistory.h>
65 #include <CmdIDs.h>
66 #include <CmdImport.h>
67 #include <CmdInfo.h>
68 #include <CmdLog.h>
69 #include <CmdLogo.h>
70 #include <CmdModify.h>
71 #include <CmdNews.h>
72 #include <CmdPrepend.h>
73 #include <CmdProjects.h>
74 #include <CmdPurge.h>
75 #include <CmdReports.h>
76 #include <CmdShow.h>
77 #include <CmdStart.h>
78 #include <CmdStats.h>
79 #include <CmdStop.h>
80 #include <CmdSummary.h>
81 #include <CmdSync.h>
82 #include <CmdTags.h>
83 #include <CmdTimesheet.h>
84 #include <CmdUDAs.h>
85 #include <CmdUndo.h>
86 #include <CmdUnique.h>
87 #include <CmdUrgency.h>
88 #include <CmdVersion.h>
89
90 #include <Context.h>
91 #include <ColProject.h>
92 #include <ColDue.h>
93
94 ////////////////////////////////////////////////////////////////////////////////
factory(std::map<std::string,Command * > & all)95 void Command::factory (std::map <std::string, Command*>& all)
96 {
97 Command* c;
98
99 c = new CmdAdd (); all[c->keyword ()] = c;
100 c = new CmdAnnotate (); all[c->keyword ()] = c;
101 c = new CmdAppend (); all[c->keyword ()] = c;
102 c = new CmdBurndownDaily (); all[c->keyword ()] = c;
103 c = new CmdBurndownMonthly (); all[c->keyword ()] = c;
104 c = new CmdBurndownWeekly (); all[c->keyword ()] = c;
105 c = new CmdCalc (); all[c->keyword ()] = c;
106 c = new CmdCalendar (); all[c->keyword ()] = c;
107 c = new CmdColor (); all[c->keyword ()] = c;
108 c = new CmdColumns (); all[c->keyword ()] = c;
109 c = new CmdCommands (); all[c->keyword ()] = c;
110 c = new CmdCompletionAliases (); all[c->keyword ()] = c;
111 c = new CmdCompletionColumns (); all[c->keyword ()] = c;
112 c = new CmdCompletionCommands (); all[c->keyword ()] = c;
113 c = new CmdCompletionConfig (); all[c->keyword ()] = c;
114 c = new CmdCompletionContext (); all[c->keyword ()] = c;
115 c = new CmdCompletionIds (); all[c->keyword ()] = c;
116 c = new CmdCompletionUDAs (); all[c->keyword ()] = c;
117 c = new CmdCompletionUuids (); all[c->keyword ()] = c;
118 c = new CmdCompletionProjects (); all[c->keyword ()] = c;
119 c = new CmdCompletionTags (); all[c->keyword ()] = c;
120 c = new CmdCompletionVersion (); all[c->keyword ()] = c;
121 c = new CmdConfig (); all[c->keyword ()] = c;
122 c = new CmdContext (); all[c->keyword ()] = c;
123 c = new CmdCount (); all[c->keyword ()] = c;
124 c = new CmdDelete (); all[c->keyword ()] = c;
125 c = new CmdDenotate (); all[c->keyword ()] = c;
126 c = new CmdDiagnostics (); all[c->keyword ()] = c;
127 c = new CmdDone (); all[c->keyword ()] = c;
128 c = new CmdDuplicate (); all[c->keyword ()] = c;
129 c = new CmdEdit (); all[c->keyword ()] = c;
130 #ifdef HAVE_EXECUTE
131 c = new CmdExec (); all[c->keyword ()] = c;
132 #endif
133 c = new CmdExport (); all[c->keyword ()] = c;
134 c = new CmdGet (); all[c->keyword ()] = c;
135 c = new CmdGHistoryDaily (); all[c->keyword ()] = c;
136 c = new CmdGHistoryWeekly (); all[c->keyword ()] = c;
137 c = new CmdGHistoryMonthly (); all[c->keyword ()] = c;
138 c = new CmdGHistoryAnnual (); all[c->keyword ()] = c;
139 c = new CmdHelp (); all[c->keyword ()] = c;
140 c = new CmdHistoryDaily (); all[c->keyword ()] = c;
141 c = new CmdHistoryWeekly (); all[c->keyword ()] = c;
142 c = new CmdHistoryMonthly (); all[c->keyword ()] = c;
143 c = new CmdHistoryAnnual (); all[c->keyword ()] = c;
144 c = new CmdIDs (); all[c->keyword ()] = c;
145 c = new CmdImport (); all[c->keyword ()] = c;
146 c = new CmdInfo (); all[c->keyword ()] = c;
147 c = new CmdLog (); all[c->keyword ()] = c;
148 c = new CmdLogo (); all[c->keyword ()] = c;
149 c = new CmdModify (); all[c->keyword ()] = c;
150 c = new CmdNews (); all[c->keyword ()] = c;
151 c = new CmdPrepend (); all[c->keyword ()] = c;
152 c = new CmdProjects (); all[c->keyword ()] = c;
153 c = new CmdPurge (); all[c->keyword ()] = c;
154 c = new CmdReports (); all[c->keyword ()] = c;
155 c = new CmdShow (); all[c->keyword ()] = c;
156 c = new CmdShowRaw (); all[c->keyword ()] = c;
157 c = new CmdStart (); all[c->keyword ()] = c;
158 c = new CmdStats (); all[c->keyword ()] = c;
159 c = new CmdStop (); all[c->keyword ()] = c;
160 c = new CmdSummary (); all[c->keyword ()] = c;
161 c = new CmdSync (); all[c->keyword ()] = c;
162 c = new CmdTags (); all[c->keyword ()] = c;
163 c = new CmdTimesheet (); all[c->keyword ()] = c;
164 c = new CmdUDAs (); all[c->keyword ()] = c;
165 c = new CmdUndo (); all[c->keyword ()] = c;
166 c = new CmdUnique (); all[c->keyword ()] = c;
167 c = new CmdUrgency (); all[c->keyword ()] = c;
168 c = new CmdUUIDs (); all[c->keyword ()] = c;
169 c = new CmdVersion (); all[c->keyword ()] = c;
170 c = new CmdZshAttributes (); all[c->keyword ()] = c;
171 c = new CmdZshCommands (); all[c->keyword ()] = c;
172 c = new CmdZshCompletionIds (); all[c->keyword ()] = c;
173 c = new CmdZshCompletionUuids (); all[c->keyword ()] = c;
174
175 // Instantiate a command object for each custom report.
176 std::vector <std::string> reports;
177 for (auto &i : Context::getContext ().config)
178 {
179 if (i.first.substr (0, 7) == "report.")
180 {
181 std::string report = i.first.substr (7);
182 auto columns = report.find (".columns");
183 if (columns != std::string::npos)
184 reports.push_back (report.substr (0, columns));
185 }
186 }
187
188 for (auto &report : reports)
189 {
190 // Make sure a custom report does not clash with a built-in command.
191 if (all.find (report) != all.end ())
192 throw format ("Custom report '{1}' conflicts with built-in task command.", report);
193
194 c = new CmdCustom (
195 report,
196 "task <filter> " + report,
197 Context::getContext ().config.get ("report." + report + ".description"));
198
199 all[c->keyword ()] = c;
200 }
201 }
202
203 ////////////////////////////////////////////////////////////////////////////////
204 const std::map <Command::Category, std::string> Command::categoryNames =
205 {
206 // These strings are intentionally not l10n'd: they are used as identifiers.
207 {Command::Category::unassigned, "unassigned"} // should never happen
208 ,{Command::Category::metadata, "metadata"}
209 ,{Command::Category::report, "report"}
210 ,{Command::Category::operation, "operation"}
211 ,{Command::Category::context, "context"}
212 ,{Command::Category::graphs, "graphs" }
213 ,{Command::Category::config, "config" }
214 ,{Command::Category::migration, "migration"}
215 ,{Command::Category::misc, "misc" }
216 ,{Command::Category::internal, "internal"}
217 ,{Command::Category::UNDOCUMENTED, "undocumented"}
218 };
219
220 ////////////////////////////////////////////////////////////////////////////////
Command()221 Command::Command ()
222 : _keyword ("")
223 , _usage ("")
224 , _description ("")
225 , _read_only (true)
226 , _displays_id (true)
227 , _needs_confirm (false)
228 , _needs_gc (true)
229 , _uses_context (false)
230 , _accepts_filter (false)
231 , _accepts_modifications (false)
232 , _accepts_miscellaneous (false)
233 , _category(Category::unassigned)
234 , _permission_quit (false)
235 , _permission_all (false)
236 , _first_iteration (true)
237 {
238 }
239
240 ////////////////////////////////////////////////////////////////////////////////
keyword() const241 std::string Command::keyword () const
242 {
243 return _keyword;
244 }
245
246 ////////////////////////////////////////////////////////////////////////////////
usage() const247 std::string Command::usage () const
248 {
249 return _usage;
250 }
251
252 ////////////////////////////////////////////////////////////////////////////////
description() const253 std::string Command::description () const
254 {
255 return _description;
256 }
257
258 ////////////////////////////////////////////////////////////////////////////////
read_only() const259 bool Command::read_only () const
260 {
261 return _read_only;
262 }
263
264 ////////////////////////////////////////////////////////////////////////////////
displays_id() const265 bool Command::displays_id () const
266 {
267 return _displays_id;
268 }
269
270 ////////////////////////////////////////////////////////////////////////////////
needs_gc() const271 bool Command::needs_gc () const
272 {
273 return _needs_gc;
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////
uses_context() const277 bool Command::uses_context () const
278 {
279 return _uses_context;
280 }
281
282 ////////////////////////////////////////////////////////////////////////////////
accepts_filter() const283 bool Command::accepts_filter () const
284 {
285 return _accepts_filter;
286 }
287
288 ////////////////////////////////////////////////////////////////////////////////
accepts_modifications() const289 bool Command::accepts_modifications () const
290 {
291 return _accepts_modifications;
292 }
293
294 ////////////////////////////////////////////////////////////////////////////////
accepts_miscellaneous() const295 bool Command::accepts_miscellaneous () const
296 {
297 return _accepts_miscellaneous;
298 }
299
300 ////////////////////////////////////////////////////////////////////////////////
category() const301 Command::Category Command::category () const
302 {
303 return _category;
304 }
305
306 ////////////////////////////////////////////////////////////////////////////////
307 // Returns true or false indicating whether to proceed with a write command, on
308 // a per-task basis, after (potentially) asking for permission.
309 //
310 // Factors:
311 // filtered.size ()
312 // rc.bulk
313 // rc.confirmation
314 // this->_read_only
permission(const std::string & question,unsigned int quantity)315 bool Command::permission (
316 const std::string& question,
317 unsigned int quantity)
318 {
319 // Read-only commands do not need to seek permission. Write commands are
320 // granted permission automatically if the 'all' selection was made in an
321 // earlier call. Or if the 'all' option has already been made.
322 if (_read_only ||
323 _permission_all)
324 return true;
325
326 // If the 'quit' selection has already been made.
327 if (_permission_quit)
328 return false;
329
330 // What remains are write commands that have not yet selected 'all' or 'quit'.
331 // Describe the task.
332 bool confirmation = Context::getContext ().config.getBoolean ("confirmation");
333 unsigned int bulk = Context::getContext ().config.getInteger ("bulk");
334
335 // Quantity 1 modifications have optional confirmation, and only (y/n).
336 if (quantity == 1)
337 {
338 if (!_needs_confirm ||
339 !confirmation)
340 return true;
341
342 bool answer = confirm (question);
343 return answer;
344 }
345
346 // 1 < Quantity < bulk modifications have optional confirmation, in the (y/n/a/q)
347 // style. Bulk = 0 denotes infinite bulk.
348 if ((bulk == 0 || quantity < bulk) && (!_needs_confirm || !confirmation))
349 return true;
350
351 if (Context::getContext ().verbose ("blank") && !_first_iteration)
352 std::cout << '\n';
353 int answer = confirm4 (question);
354 _first_iteration = false;
355 switch (answer)
356 {
357 case 1: return true; // yes
358 case 2: _permission_all = true; return true; // all
359 case 3: _permission_quit = true; return false; // quit
360 }
361
362 return false; // This line keeps the compiler happy.
363 }
364
365 ////////////////////////////////////////////////////////////////////////////////
366