1 ////////////////////////////////////////////////////////////////////////////////
3 // Copyright 2006 - 2017, Paul Beckingham, Federico Hernandez.
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:
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
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
23 // http://www.opensource.org/licenses/mit-license.php
25 ////////////////////////////////////////////////////////////////////////////////
36 #include <readline/readline.h>
37 #include <readline/history.h>
41 #include <sys/ioctl.h>
44 #include <sys/termios.h>
52 std::string getResponse (const std::string&);
54 ////////////////////////////////////////////////////////////////////////////////
55 static unsigned int getWidth ()
57 // Determine window size.
58 // int width = config.getInteger ("defaultwidth");
59 static auto width = 0;
63 unsigned short buff[4];
64 if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1)
71 ////////////////////////////////////////////////////////////////////////////////
72 static void editTask (const std::string& uuid)
74 std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " edit";
75 system (command.c_str ());
77 command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify reviewed:now";
78 system (command.c_str ());
79 std::cout << "Modified.\n\n\n\n";
82 ////////////////////////////////////////////////////////////////////////////////
83 static void modifyTask (const std::string& uuid)
85 Color text ("color15 on gray6");
86 std::string modifications;
89 modifications = getResponse (text.colorize (" Enter modification args [example: +tag -tag /teh/the/ project:X] ") + " ");
91 while (modifications == "");
93 std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify " + modifications;
94 system (command.c_str ());
96 std::cout << "Modified.\n\n\n\n";
99 ////////////////////////////////////////////////////////////////////////////////
100 static void reviewTask (const std::string& uuid)
102 std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " modify reviewed:now";
103 system (command.c_str ());
104 std::cout << "Marked as reviewed.\n\n\n\n";
107 ////////////////////////////////////////////////////////////////////////////////
108 static void completeTask (const std::string& uuid)
110 std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " done";
111 system (command.c_str ());
112 std::cout << "Completed.\n\n\n\n";
115 ////////////////////////////////////////////////////////////////////////////////
116 static void deleteTask (const std::string& uuid)
118 std::string command = "task rc.confirmation:no rc.verbose:nothing " + uuid + " delete";
119 system (command.c_str ());
120 std::cout << "Deleted.\n\n\n\n";
123 ////////////////////////////////////////////////////////////////////////////////
124 static const std::string reviewNothing ()
126 return "\nThere are no tasks needing review.\n\n";
129 ////////////////////////////////////////////////////////////////////////////////
130 static const std::string reviewStart (
133 std::string welcome = "The review process is important for keeping your list "
134 "accurate, so you are working on the right tasks.\n"
136 "For each task you are shown, look at the metadata. "
137 "Determine whether the task needs to be changed (enter "
138 "'e' to edit), or whether it is accurate ('enter' or "
139 "'r' to mark as reviewed). You may skip a task ('s') "
140 "but a skipped task is not considered reviewed.\n"
142 "You may stop at any time, and resume later right "
143 "where you left off. See 'man tasksh' for more details.";
145 std::vector <std::string> lines;
146 wrapText (lines, welcome, width, false);
147 welcome = join ("\n", lines);
149 return "\n" + welcome + "\n\n";
152 ////////////////////////////////////////////////////////////////////////////////
153 static const std::string banner (
154 unsigned int current,
157 const std::string& message)
159 std::stringstream progress;
166 Color progressColor ("color15 on color9");
167 Color descColor ("color15 on gray6");
169 std::string composed;
170 if (progress.str ().length () + message.length () + 1 < width)
171 composed = progressColor.colorize (progress.str ()) +
172 descColor.colorize (" " + message +
173 std::string (width - progress.str ().length () - message.length () - 1, ' '));
175 composed = progressColor.colorize (progress.str ()) +
176 descColor.colorize (" " + message.substr (0, message.length () - 3) + "...");
178 return composed + "\n";
181 ////////////////////////////////////////////////////////////////////////////////
182 static const std::string menu ()
184 return Color ("color15 on gray6").colorize (" (Enter) Mark as reviewed, (s)kip, (e)dit, (m)odify, (c)omplete, (d)elete, (q)uit ") + " ";
187 ////////////////////////////////////////////////////////////////////////////////
188 static void reviewLoop (const std::vector <std::string>& uuids, unsigned int limit, bool autoClear)
190 auto width = getWidth ();
191 unsigned int reviewed = 0;
193 // If a limit was specified ('review 10'), then it should override the data
194 // set size, if it is smaller.
195 unsigned int total = uuids.size ();
197 total = std::min (total, limit);
201 std::cout << reviewNothing ();
205 std::cout << reviewStart (width);
207 unsigned int current = 0;
208 while (current < total &&
209 (limit == 0 || reviewed < limit))
211 // Run 'info' report for task.
212 auto uuid = uuids[current];
214 // Display banner for this task.
216 std::string description;
218 {"_get", uuid + ".description"},
222 std::string response;
227 std::cout << banner (current + 1, total, width, Lexer::trimRight (description, "\n"));
229 // Use 'system' to run the command and show the output.
230 std::string command = "task " + uuid + " information";
231 system (command.c_str ());
233 // Display prompt, get input.
234 response = getResponse (menu ());
236 if (response == "e") { editTask (uuid); }
237 else if (response == "m") { modifyTask (uuid); repeat = true; }
238 else if (response == "s") { std::cout << "Skipped\n\n"; ++current; }
239 else if (response == "c") { completeTask (uuid); ++current; ++reviewed; }
240 else if (response == "d") { deleteTask (uuid); ++current; ++reviewed; }
241 else if (response == "") { reviewTask (uuid); ++current; ++reviewed; }
242 else if (response == "r") { reviewTask (uuid); ++current; ++reviewed; }
243 else if (response == "q") { break; }
247 std::cout << format ("Command '{1}' is not recognized.", response) << "\n";
250 // Note that just hitting <Enter> yields an empty command, which does
251 // nothing but advance to the next task.
254 std::cout << "\033[2J\033[0;0H";
263 << format ("End of review. {1} out of {2} tasks reviewed.", reviewed, total)
267 ////////////////////////////////////////////////////////////////////////////////
268 int cmdReview (const std::vector <std::string>& args, bool autoClear)
270 // Is there a specified limit?
271 unsigned int limit = 0;
272 if (args.size () == 2)
273 limit = strtol (args[1].c_str (), NULL, 10);
275 // Configure 'reviewed' UDA, but only if necessary.
278 auto status = execute ("task", {"_get", "rc.uda.reviewed.type"}, input, output);
279 if (status || output != "date\n")
281 if (confirm ("Tasksh needs to define a 'reviewed' UDA of type 'date' for all tasks. Ok to proceed?"))
283 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "uda.reviewed.type", "date"}, input, output);
284 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "uda.reviewed.label", "Reviewed"}, input, output);
288 // Configure '_reviewed' report, but only if necessary.
289 status = execute ("task", {"_get", "rc.report._reviewed.columns"}, input, output);
290 if (status || output != "uuid\n")
292 if (confirm ("Tasksh needs to define a '_reviewed' report to identify tasks needing review. Ok to proceed?"))
294 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.description",
295 "Tasksh review report. Adjust the filter to your needs." }, input, output);
296 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.columns", "uuid" }, input, output);
297 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.sort", "reviewed+,modified+"}, input, output);
298 execute ("task", {"rc.confirmation:no", "rc.verbose:nothing", "config", "report._reviewed.filter",
299 "( reviewed.none: or reviewed.before:now-6days ) and ( +PENDING or +WAITING )" }, input, output);
303 // Obtain a list of UUIDs to review.
304 status = execute ("task",
308 "rc._forcecolor=off",
309 "rc.verbose=nothing",
314 // Review the set of UUIDs.
315 auto uuids = split (Lexer::trimRight (output, "\n"), '\n');
316 reviewLoop (uuids, limit, autoClear);
320 ////////////////////////////////////////////////////////////////////////////////