1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2006 - 2015, 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 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26 
27 #include <cmake.h>
28 #include <sstream>
29 #include <stdlib.h>
30 #include <assert.h>
31 #ifdef PRODUCT_TASKWARRIOR
32 #include <math.h>
33 #endif
34 #include <algorithm>
35 #ifdef PRODUCT_TASKWARRIOR
36 #include <Context.h>
37 #include <Nibbler.h>
38 #endif
39 #include <Date.h>
40 #include <Duration.h>
41 #include <Task.h>
42 #include <JSON.h>
43 #ifdef PRODUCT_TASKWARRIOR
44 #include <RX.h>
45 #endif
46 #include <text.h>
47 #include <util.h>
48 
49 #include <i18n.h>
50 #ifdef PRODUCT_TASKWARRIOR
51 #include <main.h>
52 
53 #define APPROACHING_INFINITY 1000   // Close enough.  This isn't rocket surgery.
54 
55 extern Context context;
56 
57 static const float epsilon = 0.000001;
58 #endif
59 
60 std::string Task::defaultProject  = "";
61 std::string Task::defaultDue      = "";
62 bool Task::searchCaseSensitive    = true;
63 bool Task::regex                  = false;
64 std::map <std::string, std::string> Task::attributes;
65 
66 std::map <std::string, float> Task::coefficients;
67 float Task::urgencyProjectCoefficient     = 0.0;
68 float Task::urgencyActiveCoefficient      = 0.0;
69 float Task::urgencyScheduledCoefficient   = 0.0;
70 float Task::urgencyWaitingCoefficient     = 0.0;
71 float Task::urgencyBlockedCoefficient     = 0.0;
72 float Task::urgencyAnnotationsCoefficient = 0.0;
73 float Task::urgencyTagsCoefficient        = 0.0;
74 float Task::urgencyNextCoefficient        = 0.0;
75 float Task::urgencyDueCoefficient         = 0.0;
76 float Task::urgencyBlockingCoefficient    = 0.0;
77 float Task::urgencyAgeCoefficient         = 0.0;
78 float Task::urgencyAgeMax                 = 0.0;
79 
80 static const std::string dummy ("");
81 
82 ////////////////////////////////////////////////////////////////////////////////
Task()83 Task::Task ()
84 : id (0)
85 , urgency_value (0.0)
86 , recalc_urgency (true)
87 , is_blocked (false)
88 , is_blocking (false)
89 , annotation_count (0)
90 {
91 }
92 
93 ////////////////////////////////////////////////////////////////////////////////
Task(const Task & other)94 Task::Task (const Task& other)
95 {
96   *this = other;
97 }
98 
99 ////////////////////////////////////////////////////////////////////////////////
operator =(const Task & other)100 Task& Task::operator= (const Task& other)
101 {
102   if (this != &other)
103   {
104     std::map <std::string, std::string>::operator= (other);
105     id = other.id;
106     urgency_value    = other.urgency_value;
107     recalc_urgency   = other.recalc_urgency;
108     is_blocked       = other.is_blocked;
109     is_blocking      = other.is_blocking;
110     annotation_count = other.annotation_count;
111   }
112 
113   return *this;
114 }
115 
116 ////////////////////////////////////////////////////////////////////////////////
117 // The uuid and id attributes must be exempt from comparison.
operator ==(const Task & other)118 bool Task::operator== (const Task& other)
119 {
120   if (size () != other.size ())
121     return false;
122 
123   Task::iterator i;
124   for (i = this->begin (); i != this->end (); ++i)
125     if (i->first != "uuid" &&
126         i->second != other.get (i->first))
127       return false;
128 
129   return true;
130 }
131 
132 ////////////////////////////////////////////////////////////////////////////////
Task(const std::string & input)133 Task::Task (const std::string& input)
134 {
135   id = 0;
136   urgency_value = 0.0;
137   recalc_urgency = true;
138   is_blocked = false;
139   is_blocking = false;
140   annotation_count = 0;
141 
142   parse (input);
143 }
144 
145 ////////////////////////////////////////////////////////////////////////////////
~Task()146 Task::~Task ()
147 {
148 }
149 
150 ////////////////////////////////////////////////////////////////////////////////
textToStatus(const std::string & input)151 Task::status Task::textToStatus (const std::string& input)
152 {
153        if (input[0] == 'p') return Task::pending;
154   else if (input[0] == 'c') return Task::completed;
155   else if (input[0] == 'd') return Task::deleted;
156   else if (input[0] == 'r') return Task::recurring;
157   else if (input[0] == 'w') return Task::waiting;
158 
159   return Task::pending;
160 }
161 
162 ////////////////////////////////////////////////////////////////////////////////
statusToText(Task::status s)163 std::string Task::statusToText (Task::status s)
164 {
165        if (s == Task::pending)   return "pending";
166   else if (s == Task::recurring) return "recurring";
167   else if (s == Task::waiting)   return "waiting";
168   else if (s == Task::completed) return "completed";
169   else if (s == Task::deleted)   return "deleted";
170 
171   return "pending";
172 }
173 
174 ////////////////////////////////////////////////////////////////////////////////
setEntry()175 void Task::setEntry ()
176 {
177   char entryTime[16];
178   sprintf (entryTime, "%u", (unsigned int) time (NULL));
179   set ("entry", entryTime);
180 
181   recalc_urgency = true;
182 }
183 
184 ////////////////////////////////////////////////////////////////////////////////
setEnd()185 void Task::setEnd ()
186 {
187   char endTime[16];
188   sprintf (endTime, "%u", (unsigned int) time (NULL));
189   set ("end", endTime);
190 
191   recalc_urgency = true;
192 }
193 
194 ////////////////////////////////////////////////////////////////////////////////
setStart()195 void Task::setStart ()
196 {
197   char startTime[16];
198   sprintf (startTime, "%u", (unsigned int) time (NULL));
199   set ("start", startTime);
200 
201   recalc_urgency = true;
202 }
203 
204 ////////////////////////////////////////////////////////////////////////////////
setModified()205 void Task::setModified ()
206 {
207   char now[16];
208   sprintf (now, "%u", (unsigned int) time (NULL));
209   set ("modified", now);
210 }
211 
212 ////////////////////////////////////////////////////////////////////////////////
has(const std::string & name) const213 bool Task::has (const std::string& name) const
214 {
215   Task::const_iterator i = this->find (name);
216   if (i != this->end ())
217     return true;
218 
219   return false;
220 }
221 
222 ////////////////////////////////////////////////////////////////////////////////
all()223 std::vector <std::string> Task::all ()
224 {
225   std::vector <std::string> all;
226   Task::iterator i;
227   for (i = this->begin (); i != this->end (); ++i)
228     all.push_back (i->first);
229 
230   return all;
231 }
232 
233 ////////////////////////////////////////////////////////////////////////////////
get(const std::string & name) const234 const std::string Task::get (const std::string& name) const
235 {
236   Task::const_iterator i = this->find (name);
237   if (i != this->end ())
238     return i->second;
239 
240   return "";
241 }
242 
243 ////////////////////////////////////////////////////////////////////////////////
get_ref(const std::string & name) const244 const std::string& Task::get_ref (const std::string& name) const
245 {
246   Task::const_iterator i = this->find (name);
247   if (i != this->end ())
248     return i->second;
249 
250   return dummy;
251 }
252 
253 ////////////////////////////////////////////////////////////////////////////////
get_int(const std::string & name) const254 int Task::get_int (const std::string& name) const
255 {
256   Task::const_iterator i = this->find (name);
257   if (i != this->end ())
258     return strtol (i->second.c_str (), NULL, 10);
259 
260   return 0;
261 }
262 
263 ////////////////////////////////////////////////////////////////////////////////
get_ulong(const std::string & name) const264 unsigned long Task::get_ulong (const std::string& name) const
265 {
266   Task::const_iterator i = this->find (name);
267   if (i != this->end ())
268     return strtoul (i->second.c_str (), NULL, 10);
269 
270   return 0;
271 }
272 
273 ////////////////////////////////////////////////////////////////////////////////
get_date(const std::string & name) const274 time_t Task::get_date (const std::string& name) const
275 {
276   Task::const_iterator i = this->find (name);
277   if (i != this->end ())
278     return (time_t) strtoul (i->second.c_str (), NULL, 10);
279 
280   return 0;
281 }
282 
283 ////////////////////////////////////////////////////////////////////////////////
set(const std::string & name,const std::string & value)284 void Task::set (const std::string& name, const std::string& value)
285 {
286   (*this)[name] = json::decode (value);
287 
288   recalc_urgency = true;
289 }
290 
291 ////////////////////////////////////////////////////////////////////////////////
set(const std::string & name,int value)292 void Task::set (const std::string& name, int value)
293 {
294   (*this)[name] = format (value);
295 
296   recalc_urgency = true;
297 }
298 
299 ////////////////////////////////////////////////////////////////////////////////
remove(const std::string & name)300 void Task::remove (const std::string& name)
301 {
302   Task::iterator it;
303   if ((it = this->find (name)) != this->end ())
304   {
305     this->erase (it);
306     recalc_urgency = true;
307   }
308 }
309 
310 ////////////////////////////////////////////////////////////////////////////////
getStatus() const311 Task::status Task::getStatus () const
312 {
313   return textToStatus (get ("status"));
314 }
315 
316 ////////////////////////////////////////////////////////////////////////////////
setStatus(Task::status status)317 void Task::setStatus (Task::status status)
318 {
319   set ("status", statusToText (status));
320 
321   recalc_urgency = true;
322 }
323 
324 #ifdef PRODUCT_TASKWARRIOR
325 ////////////////////////////////////////////////////////////////////////////////
326 // Ready means pending, not blocked and either not scheduled or scheduled before
327 // now.
is_ready() const328 bool Task::is_ready () const
329 {
330   return getStatus () == Task::pending &&
331          !is_blocked                   &&
332          (! has ("scheduled")          ||
333           Date ("now").operator> (get_date ("scheduled")));
334 }
335 
336 ////////////////////////////////////////////////////////////////////////////////
is_due() const337 bool Task::is_due () const
338 {
339   if (has ("due"))
340   {
341     Task::status status = getStatus ();
342 
343     if (status != Task::completed &&
344         status != Task::deleted)
345     {
346       if (getDueState (get ("due")) == 1)
347         return true;
348     }
349   }
350 
351   return false;
352 }
353 
354 ////////////////////////////////////////////////////////////////////////////////
is_dueyesterday() const355 bool Task::is_dueyesterday () const
356 {
357   if (has ("due"))
358   {
359     Task::status status = getStatus ();
360 
361     if (status != Task::completed &&
362         status != Task::deleted)
363     {
364       if (Date ("yesterday").sameDay (get_date ("due")))
365         return true;
366     }
367   }
368 
369   return false;
370 }
371 
372 ////////////////////////////////////////////////////////////////////////////////
is_duetoday() const373 bool Task::is_duetoday () const
374 {
375   if (has ("due"))
376   {
377     Task::status status = getStatus ();
378 
379     if (status != Task::completed &&
380         status != Task::deleted)
381     {
382       if (getDueState (get ("due")) == 2)
383         return true;
384     }
385   }
386 
387   return false;
388 }
389 
390 ////////////////////////////////////////////////////////////////////////////////
is_duetomorrow() const391 bool Task::is_duetomorrow () const
392 {
393   if (has ("due"))
394   {
395     Task::status status = getStatus ();
396 
397     if (status != Task::completed &&
398         status != Task::deleted)
399     {
400       if (Date ("tomorrow").sameDay (get_date ("due")))
401         return true;
402     }
403   }
404 
405   return false;
406 }
407 
408 ////////////////////////////////////////////////////////////////////////////////
is_dueweek() const409 bool Task::is_dueweek () const
410 {
411   if (has ("due"))
412   {
413     Task::status status = getStatus ();
414 
415     if (status != Task::completed &&
416         status != Task::deleted)
417     {
418       Date now;
419       Date due (get_date ("due"));
420       if (now.year () == due.year () &&
421           now.week () == due.week ())
422         return true;
423     }
424   }
425 
426   return false;
427 }
428 
429 ////////////////////////////////////////////////////////////////////////////////
is_duemonth() const430 bool Task::is_duemonth () const
431 {
432   if (has ("due"))
433   {
434     Task::status status = getStatus ();
435 
436     if (status != Task::completed &&
437         status != Task::deleted)
438     {
439       Date now;
440       Date due (get_date ("due"));
441       if (now.year () == due.year () &&
442           now.month () == due.month ())
443         return true;
444     }
445   }
446 
447   return false;
448 }
449 
450 ////////////////////////////////////////////////////////////////////////////////
is_dueyear() const451 bool Task::is_dueyear () const
452 {
453   if (has ("due"))
454   {
455     Task::status status = getStatus ();
456 
457     if (status != Task::completed &&
458         status != Task::deleted)
459     {
460       Date now;
461       Date due (get_date ("due"));
462       if (now.year () == due.year ())
463         return true;
464     }
465   }
466 
467   return false;
468 }
469 
470 ////////////////////////////////////////////////////////////////////////////////
is_overdue() const471 bool Task::is_overdue () const
472 {
473   if (has ("due"))
474   {
475     Task::status status = getStatus ();
476 
477     if (status != Task::completed &&
478         status != Task::deleted)
479     {
480       if (getDueState (get ("due")) == 3)
481         return true;
482     }
483   }
484 
485   return false;
486 }
487 #endif
488 
489 ////////////////////////////////////////////////////////////////////////////////
490 // Attempt an FF4 parse first, using Task::parse, and in the event of an error
491 // try a JSON parse, otherwise a legacy parse (FF3).
492 //
493 // Note that FF1 and FF2 are no longer supported.
494 //
495 // start --> [ --> Att --> ] --> end
496 //              ^       |
497 //              +-------+
498 //
parse(const std::string & input)499 void Task::parse (const std::string& input)
500 {
501   // TODO Is this simply a 'chomp'?
502   std::string copy;
503   if (input[input.length () - 1] == '\n')
504     copy = input.substr (0, input.length () - 1);
505   else
506     copy = input;
507 
508   try
509   {
510     // File format version 4, from 2009-5-16 - now, v1.7.1+
511     // This is the parse format tried first, because it is most used.
512     clear ();
513 
514     if (copy[0] == '[')
515     {
516       Nibbler n (copy);
517       std::string line;
518       if (n.skip     ('[')       &&
519           n.getUntil (']', line) &&
520           n.skip     (']')       &&
521           n.depleted ())
522       {
523         if (line.length () == 0)
524           throw std::string (STRING_RECORD_EMPTY);
525 
526         Nibbler nl (line);
527         std::string name;
528         std::string value;
529         while (!nl.depleted ())
530         {
531           if (nl.getUntil (':', name) &&
532               nl.skip (':')           &&
533               nl.getQuoted ('"', value))
534           {
535             // Experimental legacy value translation of 'recur:m' --> 'recur:mo'.
536             if (name == "recur" &&
537                 digitsOnly (value.substr (0, value.length () - 1)) &&
538                 value[value.length () - 1] == 'm')
539               value += 'o';
540 
541             // TW-1274, Standardization.
542             if (name == "modification")
543               name = "modified";
544 
545             if (name.substr (0, 11) == "annotation_")
546               ++annotation_count;
547 
548             (*this)[name] = decode (json::decode (value));
549           }
550 
551           nl.skip (' ');
552         }
553 
554         std::string remainder;
555         nl.getUntilEOS (remainder);
556         if (remainder.length ())
557           throw std::string (STRING_RECORD_JUNK_AT_EOL);
558       }
559     }
560     else if (copy[0] == '{')
561       parseJSON (copy);
562     else
563       throw std::string (STRING_RECORD_NOT_FF4);
564 
565     upgradeLegacyValues ();
566   }
567 
568   catch (const std::string&)
569   {
570     parseLegacy (copy);
571   }
572 
573   recalc_urgency = true;
574 }
575 
576 ////////////////////////////////////////////////////////////////////////////////
577 // Note that all fields undergo encode/decode.
parseJSON(const std::string & line)578 void Task::parseJSON (const std::string& line)
579 {
580   // Parse the whole thing.
581   json::value* root = json::parse (line);
582   if (root->type () == json::j_object)
583   {
584     json::object* root_obj = (json::object*)root;
585 
586     // For each object element...
587     json_object_iter i;
588     for (i  = root_obj->_data.begin ();
589          i != root_obj->_data.end ();
590          ++i)
591     {
592       // If the attribute is a recognized column.
593       std::string type = Task::attributes[i->first];
594       if (type != "")
595       {
596         // Any specified id is ignored.
597         if (i->first == "id")
598           ;
599 
600         // Urgency, if present, is ignored.
601         else if (i->first == "urgency")
602           ;
603 
604         // TW-1274 Standardization.
605         else if (i->first == "modification")
606         {
607           Date d (unquoteText (i->second->dump ()));
608           set ("modified", d.toEpochString ());
609         }
610 
611         // Dates are converted from ISO to epoch.
612         else if (type == "date")
613         {
614           std::string text = unquoteText (i->second->dump ());
615           Date d (text);
616           set (i->first, text == "" ? "" : d.toEpochString ());
617         }
618 
619         // Tags are an array of JSON strings.
620         else if (i->first == "tags" && i->second->type() == json::j_array)
621         {
622           json::array* tags = (json::array*)i->second;
623           json_array_iter t;
624           for (t  = tags->_data.begin ();
625                t != tags->_data.end ();
626                ++t)
627           {
628             json::string* tag = (json::string*)*t;
629             addTag (tag->_data);
630           }
631         }
632         // This is a temporary measure to allow Mirakel sync, and will be removed
633         // in a future release.
634         else if (i->first == "tags" && i->second->type() == json::j_string)
635         {
636           json::string* tag = (json::string*)i->second;
637           addTag (tag->_data);
638         }
639 
640         // Strings are decoded.
641         else if (type == "string")
642           set (i->first, json::decode (unquoteText (i->second->dump ())));
643 
644         // Other types are simply added.
645         else
646           set (i->first, unquoteText (i->second->dump ()));
647       }
648 
649       // UDA orphans and annotations do not have columns.
650       else
651       {
652         // Annotations are an array of JSON objects with 'entry' and
653         // 'description' values and must be converted.
654         if (i->first == "annotations" && i->second->type() == json::j_array)
655         {
656           std::map <std::string, std::string> annos;
657 
658           json::array* atts = (json::array*)i->second;
659           json_array_iter annotations;
660           for (annotations  = atts->_data.begin ();
661                annotations != atts->_data.end ();
662                ++annotations)
663           {
664             json::object* annotation = (json::object*)*annotations;
665             json::string* when = (json::string*)annotation->_data["entry"];
666             json::string* what = (json::string*)annotation->_data["description"];
667 
668             if (! when)
669               throw format (STRING_TASK_NO_ENTRY, line);
670 
671             if (! what)
672               throw format (STRING_TASK_NO_DESC, line);
673 
674             std::string name = "annotation_" + Date (when->_data).toEpochString ();
675             annos.insert (std::make_pair (name, json::decode (what->_data)));
676           }
677 
678           setAnnotations (annos);
679         }
680 
681         // UDA Orphan - must be preserved.
682         else
683         {
684 #ifdef PRODUCT_TASKWARRIOR
685           std::stringstream message;
686           message << "Task::parseJSON found orphan '"
687                   << i->first
688                   << "' with value '"
689                   << i->second
690                   << "' --> preserved\n";
691           context.debug (message.str ());
692 #endif
693           set (i->first, json::decode (unquoteText (i->second->dump ())));
694         }
695       }
696     }
697 
698     upgradeLegacyValues ();
699   }
700 }
701 
702 ////////////////////////////////////////////////////////////////////////////////
703 // Support FF2, FF3.
704 // Thankfully FF1 is no longer supported.
parseLegacy(const std::string & line)705 void Task::parseLegacy (const std::string& line)
706 {
707   switch (determineVersion (line))
708   {
709   // File format version 1, from 2006-11-27 - 2007-12-31, v0.x+ - v0.9.3
710   case 1: throw std::string (STRING_TASK_NO_FF1);
711 
712   // File format version 2, from 2008-1-1 - 2009-3-23, v0.9.3 - v1.5.0
713   case 2: throw std::string (STRING_TASK_NO_FF2);
714 
715   // File format version 3, from 2009-3-23 - 2009-05-16, v1.6.0 - v1.7.1
716   case 3:
717     {
718       if (line.length () > 49)       // ^.{36} . \[\] \[\] \[\] \n
719       {
720         set ("uuid", line.substr (0, 36));
721 
722         Task::status status = line[37] == '+' ? completed
723                             : line[37] == 'X' ? deleted
724                             : line[37] == 'r' ? recurring
725                             :                   pending;
726 
727         set ("status", statusToText (status));
728 
729         size_t openTagBracket  = line.find ("[");
730         size_t closeTagBracket = line.find ("]", openTagBracket);
731         if (openTagBracket  != std::string::npos &&
732             closeTagBracket != std::string::npos)
733         {
734           size_t openAttrBracket  = line.find ("[", closeTagBracket);
735           size_t closeAttrBracket = line.find ("]", openAttrBracket);
736           if (openAttrBracket  != std::string::npos &&
737               closeAttrBracket != std::string::npos)
738           {
739             size_t openAnnoBracket  = line.find ("[", closeAttrBracket);
740             size_t closeAnnoBracket = line.find ("]", openAnnoBracket);
741             if (openAnnoBracket  != std::string::npos &&
742                 closeAnnoBracket != std::string::npos)
743             {
744               std::string tags = line.substr (
745                 openTagBracket + 1, closeTagBracket - openTagBracket - 1);
746               std::vector <std::string> tagSet;
747               split (tagSet, tags, ' ');
748               addTags (tagSet);
749 
750               std::string attributes = line.substr (
751                 openAttrBracket + 1, closeAttrBracket - openAttrBracket - 1);
752               std::vector <std::string> pairs;
753               split (pairs, attributes, ' ');
754 
755               for (size_t i = 0; i <  pairs.size (); ++i)
756               {
757                 std::vector <std::string> pair;
758                 split (pair, pairs[i], ':');
759                 if (pair.size () == 2)
760                   set (pair[0], pair[1]);
761               }
762 
763               // Extract and split the annotations, which are of the form:
764               //   1234:"..." 5678:"..."
765               std::string annotations = line.substr (
766                 openAnnoBracket + 1, closeAnnoBracket - openAnnoBracket - 1);
767               pairs.clear ();
768 
769               std::string::size_type start = 0;
770               std::string::size_type end   = 0;
771               do
772               {
773                 end = annotations.find ('"', start);
774                 if (end != std::string::npos)
775                 {
776                   end = annotations.find ('"', end + 1);
777 
778                   if (start != std::string::npos &&
779                       end   != std::string::npos)
780                   {
781                     pairs.push_back (annotations.substr (start, end - start + 1));
782                     start = end + 2;
783                   }
784                 }
785               }
786               while (start != std::string::npos &&
787                      end   != std::string::npos);
788 
789               for (size_t i = 0; i < pairs.size (); ++i)
790               {
791                 std::string pair = pairs[i];
792                 std::string::size_type colon = pair.find (":");
793                 if (colon != std::string::npos)
794                 {
795                   std::string name = pair.substr (0, colon);
796                   std::string value = pair.substr (colon + 2, pair.length () - colon - 3);
797                   set ("annotation_" + name, value);
798                   ++annotation_count;
799                 }
800               }
801 
802               set ("description", line.substr (closeAnnoBracket + 2));
803             }
804             else
805               throw std::string (STRING_TASK_PARSE_ANNO_BRACK);
806           }
807           else
808             throw std::string (STRING_TASK_PARSE_ATT_BRACK);
809         }
810         else
811           throw std::string (STRING_TASK_PARSE_TAG_BRACK);
812       }
813       else
814         throw std::string (STRING_TASK_PARSE_TOO_SHORT);
815     }
816     break;
817 
818   default:
819     throw std::string (STRING_TASK_PARSE_UNREC_FF);
820     break;
821   }
822 
823   recalc_urgency = true;
824 }
825 
826 ////////////////////////////////////////////////////////////////////////////////
827 // The format is:
828 //
829 //   [ <name>:<value> ... ]
830 //
composeF4() const831 std::string Task::composeF4 () const
832 {
833   std::string ff4 = "[";
834 
835   bool first = true;
836   Task::const_iterator it;
837   for (it = this->begin (); it != this->end (); ++it)
838   {
839     if (it->second != "")
840     {
841       ff4 += (first ? "" : " ")
842            + it->first
843            + ":\""
844            + encode (json::encode (it->second))
845            + "\"";
846 
847       first = false;
848     }
849   }
850 
851   ff4 += "]";
852   return ff4;
853 }
854 
855 ////////////////////////////////////////////////////////////////////////////////
composeJSON(bool decorate) const856 std::string Task::composeJSON (bool decorate /*= false*/) const
857 {
858   std::stringstream out;
859   out << "{";
860 
861   // ID inclusion is optional, but not a good idea, because it remains correct
862   // only until the next gc.
863   if (decorate)
864     out << "\"id\":" << id << ",";
865 
866   // First the non-annotations.
867   int attributes_written = 0;
868   Task::const_iterator i;
869   for (i = this->begin (); i != this->end (); ++i)
870   {
871     // Annotations are not written out here.
872     if (i->first.substr (0, 11) == "annotation_")
873       continue;
874 
875     // If value is an empty string, do not ever output it
876     if (i->second == "")
877         continue;
878 
879     if (attributes_written)
880       out << ",";
881 
882     std::string type = Task::attributes[i->first];
883     if (type == "")
884       type = "string";
885 
886     // Date fields are written as ISO 8601.
887     if (type == "date")
888     {
889       Date d (i->second);
890       out << "\""
891           << (i->first == "modification" ? "modified" : i->first)
892           << "\":\""
893           // Date was deleted, do not export parsed empty string
894           << (i->second == "" ? "" : d.toISO ())
895             << "\"";
896 
897       ++attributes_written;
898     }
899 
900     else if (type == "numeric")
901     {
902         out << "\""
903             << i->first
904           << "\":"
905           << i->second;
906 
907       ++attributes_written;
908     }
909 
910     // Tags are converted to an array.
911     else if (i->first == "tags")
912     {
913       std::vector <std::string> tags;
914       split (tags, i->second, ',');
915 
916       out << "\"tags\":[";
917 
918       std::vector <std::string>::iterator i;
919       for (i = tags.begin (); i != tags.end (); ++i)
920       {
921         if (i != tags.begin ())
922           out << ",";
923 
924         out << "\"" << *i << "\"";
925       }
926 
927       out << "]";
928 
929       ++attributes_written;
930     }
931 
932     // Everything else is a quoted value.
933     else
934     {
935       out << "\""
936           << i->first
937           << "\":\""
938           << json::encode (i->second)
939           << "\"";
940 
941       ++attributes_written;
942     }
943   }
944 
945   // Now the annotations, if any.
946   if (annotation_count)
947   {
948     out << ","
949         << "\"annotations\":[";
950 
951     int annotations_written = 0;
952     for (i = this->begin (); i != this->end (); ++i)
953     {
954       if (i->first.substr (0, 11) == "annotation_")
955       {
956         if (annotations_written)
957           out << ",";
958 
959         Date d (i->first.substr (11));
960         out << "{\"entry\":\""
961             << d.toISO ()
962             << "\",\"description\":\""
963             << json::encode (i->second)
964             << "\"}";
965 
966         ++annotations_written;
967       }
968     }
969 
970     out << "]";
971   }
972 
973 #ifdef PRODUCT_TASKWARRIOR
974   // Include urgency.
975   if (decorate)
976     out << ","
977         << "\"urgency\":"
978         << urgency_c ();
979 #endif
980 
981   out << "}";
982   return out.str ();
983 }
984 
985 ////////////////////////////////////////////////////////////////////////////////
hasAnnotations() const986 bool Task::hasAnnotations () const
987 {
988   return annotation_count ? true : false;
989 }
990 
991 ////////////////////////////////////////////////////////////////////////////////
992 // The timestamp is part of the name:
993 //    annotation_1234567890:"..."
994 //
995 // Note that the time is incremented (one second) in order to find a unique
996 // timestamp.
addAnnotation(const std::string & description)997 void Task::addAnnotation (const std::string& description)
998 {
999   time_t now = time (NULL);
1000   std::string key;
1001 
1002   do
1003   {
1004     key = "annotation_" + format ((int) now);
1005     ++now;
1006   }
1007   while (has (key));
1008 
1009   (*this)[key] = json::decode (description);
1010   ++annotation_count;
1011   recalc_urgency = true;
1012 }
1013 
1014 ////////////////////////////////////////////////////////////////////////////////
removeAnnotations()1015 void Task::removeAnnotations ()
1016 {
1017   // Erase old annotations.
1018   Task::iterator i = this->begin ();
1019   while (i != this->end ())
1020   {
1021     if (i->first.substr (0, 11) == "annotation_")
1022     {
1023       --annotation_count;
1024       this->erase (i++);
1025     }
1026     else
1027       i++;
1028   }
1029 
1030   recalc_urgency = true;
1031 }
1032 
1033 ////////////////////////////////////////////////////////////////////////////////
getAnnotations(std::map<std::string,std::string> & annotations) const1034 void Task::getAnnotations (std::map <std::string, std::string>& annotations) const
1035 {
1036   annotations.clear ();
1037 
1038   Task::const_iterator ci;
1039   for (ci = this->begin (); ci != this->end (); ++ci)
1040     if (ci->first.substr (0, 11) == "annotation_")
1041       annotations.insert (*ci);
1042 }
1043 
1044 ////////////////////////////////////////////////////////////////////////////////
setAnnotations(const std::map<std::string,std::string> & annotations)1045 void Task::setAnnotations (const std::map <std::string, std::string>& annotations)
1046 {
1047   // Erase old annotations.
1048   removeAnnotations ();
1049 
1050   std::map <std::string, std::string>::const_iterator ci;
1051   for (ci = annotations.begin (); ci != annotations.end (); ++ci)
1052     this->insert (*ci);
1053 
1054   annotation_count = annotations.size ();
1055   recalc_urgency = true;
1056 }
1057 
1058 #ifdef PRODUCT_TASKWARRIOR
1059 ////////////////////////////////////////////////////////////////////////////////
addDependency(int id)1060 void Task::addDependency (int id)
1061 {
1062   // Check that id is resolvable.
1063   std::string uuid = context.tdb2.pending.uuid (id);
1064   if (uuid == "")
1065     throw format (STRING_TASK_DEPEND_MISS_CREA, id);
1066 
1067   std::string depends = get ("depends");
1068   if (depends.find (uuid) != std::string::npos)
1069     throw format (STRING_TASK_DEPEND_DUP, this->id, id);
1070 
1071   addDependency(uuid);
1072 }
1073 
1074 ////////////////////////////////////////////////////////////////////////////////
addDependency(const std::string & uuid)1075 void Task::addDependency (const std::string& uuid)
1076 {
1077   if (uuid == get ("uuid"))
1078     throw std::string (STRING_TASK_DEPEND_ITSELF);
1079 
1080   // Check that uuid is resolvable.
1081   int id = context.tdb2.pending.id (uuid);
1082   if (id == 0)
1083     throw format (STRING_TASK_DEPEND_MISS_CREA, id);
1084 
1085   // Store the dependency.
1086   std::string depends = get ("depends");
1087   if (depends != "")
1088   {
1089     // Check for extant dependency.
1090     if (depends.find (uuid) == std::string::npos)
1091       set ("depends", depends + "," + uuid);
1092     else
1093       throw format (STRING_TASK_DEPEND_DUP, this->get ("uuid"), uuid);
1094   }
1095   else
1096     set ("depends", uuid);
1097 
1098   // Prevent circular dependencies.
1099   if (dependencyIsCircular (*this))
1100     throw std::string (STRING_TASK_DEPEND_CIRCULAR);
1101 
1102   recalc_urgency = true;
1103 }
1104 
1105 ////////////////////////////////////////////////////////////////////////////////
removeDependency(const std::string & uuid)1106 void Task::removeDependency (const std::string& uuid)
1107 {
1108   std::vector <std::string> deps;
1109   split (deps, get ("depends"), ',');
1110 
1111   std::vector <std::string>::iterator i;
1112   i = std::find (deps.begin (), deps.end (), uuid);
1113   if (i != deps.end ())
1114   {
1115     deps.erase (i);
1116     std::string combined;
1117     join (combined, ",", deps);
1118     set ("depends", combined);
1119     recalc_urgency = true;
1120   }
1121   else
1122     throw format (STRING_TASK_DEPEND_MISS_DEL, uuid);
1123 }
1124 
1125 ////////////////////////////////////////////////////////////////////////////////
removeDependency(int id)1126 void Task::removeDependency (int id)
1127 {
1128   std::string depends = get ("depends");
1129   std::string uuid = context.tdb2.pending.uuid (id);
1130   if (uuid != "" && depends.find (uuid) != std::string::npos)
1131     removeDependency (uuid);
1132   else
1133     throw format (STRING_TASK_DEPEND_MISS_DEL, id);
1134 }
1135 
1136 ////////////////////////////////////////////////////////////////////////////////
getDependencies(std::vector<int> & all) const1137 void Task::getDependencies (std::vector <int>& all) const
1138 {
1139   std::vector <std::string> deps;
1140   split (deps, get ("depends"), ',');
1141 
1142   all.clear ();
1143 
1144   std::vector <std::string>::iterator i;
1145   for (i = deps.begin (); i != deps.end (); ++i)
1146     all.push_back (context.tdb2.pending.id (*i));
1147 }
1148 
1149 ////////////////////////////////////////////////////////////////////////////////
getDependencies(std::vector<std::string> & all) const1150 void Task::getDependencies (std::vector <std::string>& all) const
1151 {
1152   all.clear ();
1153   split (all, get ("depends"), ',');
1154 }
1155 #endif
1156 
1157 ////////////////////////////////////////////////////////////////////////////////
getTagCount() const1158 int Task::getTagCount () const
1159 {
1160   std::vector <std::string> tags;
1161   split (tags, get ("tags"), ',');
1162 
1163   return (int) tags.size ();
1164 }
1165 
1166 ////////////////////////////////////////////////////////////////////////////////
hasTag(const std::string & tag) const1167 bool Task::hasTag (const std::string& tag) const
1168 {
1169   // Synthetic tags - dynamically generated, but do not occupy storage space.
1170   if (tag == "BLOCKED")   return is_blocked;
1171   if (tag == "UNBLOCKED") return !is_blocked;
1172   if (tag == "BLOCKING")  return is_blocking;
1173 #ifdef PRODUCT_TASKWARRIOR
1174   if (tag == "READY")     return is_ready ();
1175   if (tag == "DUE")       return is_due ();
1176   if (tag == "DUETODAY")  return is_duetoday ();
1177   if (tag == "TODAY")     return is_duetoday ();
1178   if (tag == "YESTERDAY") return is_dueyesterday ();
1179   if (tag == "TOMORROW")  return is_duetomorrow ();
1180   if (tag == "OVERDUE")   return is_overdue ();
1181   if (tag == "WEEK")      return is_dueweek ();
1182   if (tag == "MONTH")     return is_duemonth ();
1183   if (tag == "YEAR")      return is_dueyear ();
1184 #endif
1185   if (tag == "ACTIVE")    return has ("start");
1186   if (tag == "SCHEDULED") return has ("scheduled");
1187   if (tag == "CHILD")     return has ("parent");
1188   if (tag == "UNTIL")     return has ("until");
1189   if (tag == "WAITING")   return has ("wait");
1190   if (tag == "ANNOTATED") return hasAnnotations ();
1191   if (tag == "PARENT")    return has ("mask");
1192 
1193   // Concrete tags.
1194   std::vector <std::string> tags;
1195   split (tags, get ("tags"), ',');
1196 
1197   if (std::find (tags.begin (), tags.end (), tag) != tags.end ())
1198     return true;
1199 
1200   return false;
1201 }
1202 
1203 ////////////////////////////////////////////////////////////////////////////////
addTag(const std::string & tag)1204 void Task::addTag (const std::string& tag)
1205 {
1206   std::vector <std::string> tags;
1207   split (tags, get ("tags"), ',');
1208 
1209   if (std::find (tags.begin (), tags.end (), tag) == tags.end ())
1210   {
1211     tags.push_back (tag);
1212     std::string combined;
1213     join (combined, ",", tags);
1214     set ("tags", combined);
1215 
1216     recalc_urgency = true;
1217   }
1218 }
1219 
1220 ////////////////////////////////////////////////////////////////////////////////
addTags(const std::vector<std::string> & tags)1221 void Task::addTags (const std::vector <std::string>& tags)
1222 {
1223   remove ("tags");
1224 
1225   std::vector <std::string>::const_iterator it;
1226   for (it = tags.begin (); it != tags.end (); ++it)
1227     addTag (*it);
1228 
1229   recalc_urgency = true;
1230 }
1231 
1232 ////////////////////////////////////////////////////////////////////////////////
getTags(std::vector<std::string> & tags) const1233 void Task::getTags (std::vector<std::string>& tags) const
1234 {
1235   split (tags, get ("tags"), ',');
1236 }
1237 
1238 ////////////////////////////////////////////////////////////////////////////////
removeTag(const std::string & tag)1239 void Task::removeTag (const std::string& tag)
1240 {
1241   std::vector <std::string> tags;
1242   split (tags, get ("tags"), ',');
1243 
1244   std::vector <std::string>::iterator i;
1245   i = std::find (tags.begin (), tags.end (), tag);
1246   if (i != tags.end ())
1247   {
1248     tags.erase (i);
1249     std::string combined;
1250     join (combined, ",", tags);
1251     set ("tags", combined);
1252   }
1253 
1254   recalc_urgency = true;
1255 }
1256 
1257 #ifdef PRODUCT_TASKWARRIOR
1258 ////////////////////////////////////////////////////////////////////////////////
1259 // A UDA is an attribute that has supporting config entries such as a data type:
1260 // 'uda.<name>.type'
getUDAs(std::vector<std::string> & names) const1261 void Task::getUDAs (std::vector <std::string>& names) const
1262 {
1263   Task::const_iterator it;
1264   for (it = this->begin (); it != this->end (); ++it)
1265     if (context.config.get ("uda." + it->first + ".type") != "")
1266       names.push_back (it->first);
1267 }
1268 
1269 ////////////////////////////////////////////////////////////////////////////////
1270 // A UDA Orphan is an attribute that is not represented in context.columns.
getUDAOrphans(std::vector<std::string> & names) const1271 void Task::getUDAOrphans (std::vector <std::string>& names) const
1272 {
1273   Task::const_iterator it;
1274   for (it = this->begin (); it != this->end (); ++it)
1275     if (it->first.substr (0, 11) != "annotation_")
1276       if (context.columns.find (it->first) == context.columns.end ())
1277         names.push_back (it->first);
1278 }
1279 
1280 ////////////////////////////////////////////////////////////////////////////////
substitute(const std::string & from,const std::string & to,bool global)1281 void Task::substitute (
1282   const std::string& from,
1283   const std::string& to,
1284   bool global)
1285 {
1286   // Get the data to modify.
1287   std::string description = get ("description");
1288   std::map <std::string, std::string> annotations;
1289   getAnnotations (annotations);
1290 
1291   // Count the changes, so we know whether to proceed to annotations, after
1292   // modifying description.
1293   int changes = 0;
1294   bool done = false;
1295 
1296   // Regex support is optional.
1297   if (Task::regex)
1298   {
1299     // Create the regex.
1300     RX rx (from, Task::searchCaseSensitive);
1301     std::vector <int> start;
1302     std::vector <int> end;
1303 
1304     // Perform all subs on description.
1305     if (rx.match (start, end, description))
1306     {
1307       int skew = 0;
1308       for (unsigned int i = 0; i < start.size () && !done; ++i)
1309       {
1310         description.replace (start[i + skew], end[i] - start[i], to);
1311         skew += to.length () - (end[i] - start[i]);
1312         ++changes;
1313 
1314         if (!global)
1315           done = true;
1316       }
1317     }
1318 
1319     if (!done)
1320     {
1321       // Perform all subs on annotations.
1322       std::map <std::string, std::string>::iterator it;
1323       for (it = annotations.begin (); it != annotations.end () && !done; ++it)
1324       {
1325         start.clear ();
1326         end.clear ();
1327         if (rx.match (start, end, it->second))
1328         {
1329           int skew = 0;
1330           for (unsigned int i = 0; i < start.size () && !done; ++i)
1331           {
1332             it->second.replace (start[i + skew], end[i] - start[i], to);
1333             skew += to.length () - (end[i] - start[i]);
1334             ++changes;
1335 
1336             if (!global)
1337               done = true;
1338           }
1339         }
1340       }
1341     }
1342   }
1343   else
1344   {
1345     // Perform all subs on description.
1346     int counter = 0;
1347     std::string::size_type pos = 0;
1348     int skew = 0;
1349 
1350     while ((pos = ::find (description, from, pos, Task::searchCaseSensitive)) != std::string::npos && !done)
1351     {
1352       description.replace (pos + skew, from.length (), to);
1353       skew += to.length () - from.length ();
1354 
1355       pos += to.length ();
1356       ++changes;
1357 
1358       if (!global)
1359         done = true;
1360 
1361       if (++counter > APPROACHING_INFINITY)
1362         throw format (STRING_INFINITE_LOOP, APPROACHING_INFINITY);
1363     }
1364 
1365     if (!done)
1366     {
1367       // Perform all subs on annotations.
1368       counter = 0;
1369       std::map <std::string, std::string>::iterator i;
1370       for (i = annotations.begin (); i != annotations.end () && !done; ++i)
1371       {
1372         pos = 0;
1373         skew = 0;
1374         while ((pos = ::find (i->second, from, pos, Task::searchCaseSensitive)) != std::string::npos && !done)
1375         {
1376           i->second.replace (pos + skew, from.length (), to);
1377           skew += to.length () - from.length ();
1378 
1379           pos += to.length ();
1380           ++changes;
1381 
1382           if (!global)
1383             done = true;
1384 
1385           if (++counter > APPROACHING_INFINITY)
1386             throw format (STRING_INFINITE_LOOP, APPROACHING_INFINITY);
1387         }
1388       }
1389     }
1390   }
1391 
1392   if (changes)
1393   {
1394     set ("description", description);
1395     setAnnotations (annotations);
1396     recalc_urgency = true;
1397   }
1398 }
1399 #endif
1400 
1401 ////////////////////////////////////////////////////////////////////////////////
1402 // The purpose of Task::validate is three-fold:
1403 //   1) To provide missing attributes where possible
1404 //   2) To provide suitable warnings about odd states
1405 //   3) To generate errors when the inconsistencies are not fixable
1406 //
validate(bool applyDefault)1407 void Task::validate (bool applyDefault /* = true */)
1408 {
1409   Task::status status = getStatus ();
1410 
1411   // 1) Provide missing attributes where possible
1412   // Provide a UUID if necessary.
1413   if (! has ("uuid") || get ("uuid") == "")
1414     set ("uuid", uuid ());
1415 
1416   // Recurring tasks get a special status.
1417   if (status == Task::pending &&
1418       has ("due")             &&
1419       has ("recur")           &&
1420       (! has ("parent") || get ("parent") == ""))
1421     status = Task::recurring;
1422 
1423   // Tasks with a wait: date get a special status.
1424   else if (status == Task::pending &&
1425            has ("wait"))
1426     status = Task::waiting;
1427 
1428   // By default, tasks are pending.
1429   else if (! has ("status") || get ("status") == "")
1430     status = Task::pending;
1431 
1432   // Store the derived status.
1433   setStatus (status);
1434 
1435 #ifdef PRODUCT_TASKWARRIOR
1436   // Provide an entry date unless user already specified one.
1437   if (!has ("entry"))
1438     setEntry ();
1439 
1440   // Completed tasks need an end date, so inherit the entry date.
1441   if (! has ("end") &&
1442       (getStatus () == Task::completed ||
1443        getStatus () == Task::deleted))
1444     setEnd ();
1445 
1446   // Override with default.project, if not specified.
1447   if (applyDefault && ! has ("project"))
1448   {
1449     if (Task::defaultProject != "" &&
1450         context.columns["project"]->validate (Task::defaultProject))
1451       set ("project", Task::defaultProject);
1452   }
1453 
1454   // Override with default.due, if not specified.
1455   if (applyDefault && get ("due") == "")
1456   {
1457     if (Task::defaultDue != "" &&
1458         context.columns["due"]->validate (Task::defaultDue))
1459       set ("due", Date (Task::defaultDue).toEpoch ());
1460   }
1461 
1462   // If a UDA has a default value in the configuration,
1463   // override with uda.(uda).default, if not specified.
1464   if (applyDefault)
1465   {
1466     // Gather a list of all UDAs with a .default value
1467     std::vector <std::string> udas;
1468     Config::const_iterator var;
1469     for (var = context.config.begin (); var != context.config.end (); ++var)
1470     {
1471       if (var->first.substr (0, 4) == "uda." &&
1472           var->first.find (".default") != std::string::npos)
1473       {
1474         std::string::size_type period = var->first.find ('.', 4);
1475         if (period != std::string::npos)
1476           udas.push_back (var->first.substr (4, period - 4));
1477       }
1478     }
1479 
1480     if (udas.size ())
1481     {
1482       // For each of those, setup the default value on the task now,
1483       // of course only if we don't have one on the command line already
1484       std::vector <std::string>::iterator uda;
1485       for (uda = udas.begin (); uda != udas.end (); ++uda)
1486       {
1487         std::string type    = context.config.get ("uda." + *uda + ".type");
1488         std::string defVal  = context.config.get ("uda." + *uda + ".default");
1489 
1490         // If the default is empty, or we already have a value, skip it
1491         if (defVal != "" && get (*uda) == "")
1492           set (*uda, defVal);
1493       }
1494     }
1495   }
1496 #endif
1497 
1498   // 2) To provide suitable warnings about odd states
1499 
1500   // Date relationships.
1501   validate_before ("wait",      "due");
1502   validate_before ("entry",     "start");
1503   validate_before ("entry",     "end");
1504   validate_before ("wait",      "scheduled");
1505   validate_before ("scheduled", "start");
1506   validate_before ("scheduled", "due");
1507   validate_before ("scheduled", "end");
1508 
1509   // 3) To generate errors when the inconsistencies are not fixable
1510 
1511   // There is no fixing a missing description.
1512   if (!has ("description"))
1513     throw std::string (STRING_TASK_VALID_DESC);
1514   else if (get ("description") == "")
1515     throw std::string (STRING_TASK_VALID_BLANK);
1516 
1517   // Cannot have a recur frequency with no due date - when would it recur?
1518   if (! has ("due") && has ("recur"))
1519     throw std::string (STRING_TASK_VALID_REC_DUE);
1520 
1521   // Recur durations must be valid.
1522   if (has ("recur"))
1523   {
1524     Duration d;
1525     if (! d.valid (get ("recur")))
1526       throw std::string (format (STRING_TASK_VALID_RECUR, get ("recur")));
1527   }
1528 }
1529 
1530 ////////////////////////////////////////////////////////////////////////////////
validate_before(const std::string & left,const std::string & right)1531 void Task::validate_before (const std::string& left, const std::string& right)
1532 {
1533 #ifdef PRODUCT_TASKWARRIOR
1534   if (has (left) &&
1535       has (right))
1536   {
1537     Date date_left (get_date (left));
1538     Date date_right (get_date (right));
1539 
1540     if (date_left > date_right)
1541       context.footnote (format (STRING_TASK_VALID_BEFORE, left, right));
1542   }
1543 #endif
1544 }
1545 
1546 ////////////////////////////////////////////////////////////////////////////////
1547 // Encode values prior to serialization.
1548 //   [  -> &open;
1549 //   ]  -> &close;
encode(const std::string & value) const1550 const std::string Task::encode (const std::string& value) const
1551 {
1552   std::string modified = value;
1553 
1554   str_replace (modified, "[",  "&open;");
1555   str_replace (modified, "]",  "&close;");
1556 
1557   return modified;
1558 }
1559 
1560 ////////////////////////////////////////////////////////////////////////////////
1561 // Decode values after parse.
1562 //   "  <- &dquot;
1563 //   '  <- &squot; or &quot;
1564 //   ,  <- &comma;
1565 //   [  <- &open;
1566 //   ]  <- &close;
1567 //   :  <- &colon;
decode(const std::string & value) const1568 const std::string Task::decode (const std::string& value) const
1569 {
1570   if (value.find ('&') != std::string::npos)
1571   {
1572     std::string modified = value;
1573 
1574     // Supported encodings.
1575     str_replace (modified, "&open;",  "[");
1576     str_replace (modified, "&close;", "]");
1577 
1578     // Support for deprecated encodings.  These cannot be removed or old files
1579     // will not be parsable.  Not just old files - completed.data can contain
1580     // tasks formatted/encoded using these.
1581     str_replace (modified, "&dquot;", "\"");
1582     str_replace (modified, "&quot;",  "'");
1583     str_replace (modified, "&squot;", "'");  // Deprecated 2.0
1584     str_replace (modified, "&comma;", ",");  // Deprecated 2.0
1585     str_replace (modified, "&colon;", ":");  // Deprecated 2.0
1586 
1587     return modified;
1588   }
1589 
1590   return value;
1591 }
1592 
1593 ////////////////////////////////////////////////////////////////////////////////
determineVersion(const std::string & line)1594 int Task::determineVersion (const std::string& line)
1595 {
1596   // Version 2 looks like:
1597   //
1598   //   uuid status [tags] [attributes] description\n
1599   //
1600   // Where uuid looks like:
1601   //
1602   //   27755d92-c5e9-4c21-bd8e-c3dd9e6d3cf7
1603   //
1604   // Scan for the hyphens in the uuid, the following space, and a valid status
1605   // character.
1606   if (line[8]  == '-' &&
1607       line[13] == '-' &&
1608       line[18] == '-' &&
1609       line[23] == '-' &&
1610       line[36] == ' ' &&
1611       (line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r'))
1612   {
1613     // Version 3 looks like:
1614     //
1615     //   uuid status [tags] [attributes] [annotations] description\n
1616     //
1617     // Scan for the number of [] pairs.
1618     std::string::size_type tagAtts  = line.find ("] [", 0);
1619     std::string::size_type attsAnno = line.find ("] [", tagAtts + 1);
1620     std::string::size_type annoDesc = line.find ("] ",  attsAnno + 1);
1621     if (tagAtts  != std::string::npos &&
1622         attsAnno != std::string::npos &&
1623         annoDesc != std::string::npos)
1624       return 3;
1625     else
1626       return 2;
1627   }
1628 
1629   // Version 4 looks like:
1630   //
1631   //   [name:"value" ...]
1632   //
1633   // Scan for [, ] and :".
1634   else if (line[0] == '[' &&
1635            line[line.length () - 1] == ']' &&
1636            line.find ("uuid:\"") != std::string::npos)
1637     return 4;
1638 
1639   // Version 1 looks like:
1640   //
1641   //   [tags] [attributes] description\n
1642   //   X [tags] [attributes] description\n
1643   //
1644   // Scan for the first character being either the bracket or X.
1645   else if (line.find ("X [") == 0 ||
1646            line.find ("uuid") == std::string::npos ||
1647            (line[0] == '[' &&
1648             line.substr (line.length () - 1, 1) != "]"))
1649     return 1;
1650 
1651   // Version 5?
1652   //
1653   // Fortunately, with the hindsight that will come with version 5, the
1654   // identifying characteristics of 1, 2, 3 and 4 may be modified such that if 5
1655   // has a UUID followed by a status, then there is still a way to differentiate
1656   // between 2, 3, 4 and 5.
1657   //
1658   // The danger is that a version 3 binary reads and misinterprets a version 4
1659   // file.  This is why it is a good idea to rely on an explicit version
1660   // declaration rather than chance positioning.
1661 
1662   // Zero means 'no idea'.
1663   return 0;
1664 }
1665 
1666 ////////////////////////////////////////////////////////////////////////////////
1667 // Urgency is defined as a polynomial, the value of which is calculated in this
1668 // function, according to:
1669 //
1670 //   U = A.t  + B.t  + C.t  ...
1671 //          a      b      c
1672 //
1673 //   U       = urgency
1674 //   A       = coefficient for term a
1675 //   t sub a = numeric scale from 0 -> 1, with 1 being the highest
1676 //             urgency, derived from one task attribute and mapped
1677 //             to the numeric scale
1678 //
1679 // See rfc31-urgency.txt for full details.
1680 //
urgency_c() const1681 float Task::urgency_c () const
1682 {
1683   float value = 0.0;
1684 #ifdef PRODUCT_TASKWARRIOR
1685   value += fabsf (Task::urgencyProjectCoefficient)     > epsilon ? (urgency_project ()     * Task::urgencyProjectCoefficient)     : 0.0;
1686   value += fabsf (Task::urgencyActiveCoefficient)      > epsilon ? (urgency_active ()      * Task::urgencyActiveCoefficient)      : 0.0;
1687   value += fabsf (Task::urgencyScheduledCoefficient)   > epsilon ? (urgency_scheduled ()   * Task::urgencyScheduledCoefficient)   : 0.0;
1688   value += fabsf (Task::urgencyWaitingCoefficient)     > epsilon ? (urgency_waiting ()     * Task::urgencyWaitingCoefficient)     : 0.0;
1689   value += fabsf (Task::urgencyBlockedCoefficient)     > epsilon ? (urgency_blocked ()     * Task::urgencyBlockedCoefficient)     : 0.0;
1690   value += fabsf (Task::urgencyAnnotationsCoefficient) > epsilon ? (urgency_annotations () * Task::urgencyAnnotationsCoefficient) : 0.0;
1691   value += fabsf (Task::urgencyTagsCoefficient)        > epsilon ? (urgency_tags ()        * Task::urgencyTagsCoefficient)        : 0.0;
1692   value += fabsf (Task::urgencyNextCoefficient)        > epsilon ? (urgency_next ()        * Task::urgencyNextCoefficient)        : 0.0;
1693   value += fabsf (Task::urgencyDueCoefficient)         > epsilon ? (urgency_due ()         * Task::urgencyDueCoefficient)         : 0.0;
1694   value += fabsf (Task::urgencyBlockingCoefficient)    > epsilon ? (urgency_blocking ()    * Task::urgencyBlockingCoefficient)    : 0.0;
1695   value += fabsf (Task::urgencyAgeCoefficient)         > epsilon ? (urgency_age ()         * Task::urgencyAgeCoefficient)         : 0.0;
1696 
1697   // Tag- and project-specific coefficients.
1698   std::map <std::string, float>::iterator var;
1699   for (var = Task::coefficients.begin (); var != Task::coefficients.end (); ++var)
1700   {
1701     if (fabs (var->second) > epsilon)
1702     {
1703       if (var->first.substr (0, 13) == "urgency.user.")
1704       {
1705         // urgency.user.project.<project>.coefficient
1706         std::string::size_type end = std::string::npos;
1707         if (var->first.substr (13, 8) == "project." &&
1708             (end = var->first.find (".coefficient")) != std::string::npos)
1709         {
1710           std::string project = var->first.substr (21, end - 21);
1711 
1712           if (get ("project").find (project) == 0)
1713             value += var->second;
1714         }
1715 
1716         // urgency.user.tag.<tag>.coefficient
1717         if (var->first.substr (13, 4) == "tag." &&
1718             (end = var->first.find (".coefficient")) != std::string::npos)
1719         {
1720           std::string tag = var->first.substr (17, end - 17);
1721 
1722           if (hasTag (tag))
1723             value += var->second;
1724         }
1725       }
1726       else if (var->first.substr (0, 12) == "urgency.uda.")
1727       {
1728         // urgency.uda.<name>.coefficient
1729         std::string::size_type end = var->first.find (".coefficient");
1730         if (end != std::string::npos)
1731           if (has (var->first.substr (12, end - 12)))
1732             value += var->second;
1733       }
1734     }
1735   }
1736 #endif
1737 
1738   return value;
1739 }
1740 
1741 ////////////////////////////////////////////////////////////////////////////////
urgency()1742 float Task::urgency ()
1743 {
1744   if (recalc_urgency)
1745   {
1746     urgency_value = urgency_c ();
1747 
1748     // Return the sum of all terms.
1749     recalc_urgency = false;
1750   }
1751 
1752   return urgency_value;
1753 }
1754 
1755 ////////////////////////////////////////////////////////////////////////////////
urgency_project() const1756 float Task::urgency_project () const
1757 {
1758   if (has ("project"))
1759     return 1.0;
1760 
1761   return 0.0;
1762 }
1763 
1764 ////////////////////////////////////////////////////////////////////////////////
urgency_active() const1765 float Task::urgency_active () const
1766 {
1767   if (has ("start"))
1768     return 1.0;
1769 
1770   return 0.0;
1771 }
1772 
1773 ////////////////////////////////////////////////////////////////////////////////
urgency_scheduled() const1774 float Task::urgency_scheduled () const
1775 {
1776   if (has ("scheduled") &&
1777       get_date ("scheduled") < time (NULL))
1778     return 1.0;
1779 
1780   return 0.0;
1781 }
1782 
1783 ////////////////////////////////////////////////////////////////////////////////
urgency_waiting() const1784 float Task::urgency_waiting () const
1785 {
1786   if (get_ref ("status") == "waiting")
1787     return 1.0;
1788 
1789   return 0.0;
1790 }
1791 
1792 ////////////////////////////////////////////////////////////////////////////////
1793 // A task is blocked only if the task it depends upon is pending/waiting.
urgency_blocked() const1794 float Task::urgency_blocked () const
1795 {
1796   if (is_blocked)
1797     return 1.0;
1798 
1799   return 0.0;
1800 }
1801 
1802 ////////////////////////////////////////////////////////////////////////////////
urgency_annotations() const1803 float Task::urgency_annotations () const
1804 {
1805        if (annotation_count >= 3) return 1.0;
1806   else if (annotation_count == 2) return 0.9;
1807   else if (annotation_count == 1) return 0.8;
1808 
1809   return 0.0;
1810 }
1811 
1812 ////////////////////////////////////////////////////////////////////////////////
urgency_tags() const1813 float Task::urgency_tags () const
1814 {
1815   switch (getTagCount ())
1816   {
1817   case 0:  return 0.0;
1818   case 1:  return 0.8;
1819   case 2:  return 0.9;
1820   default: return 1.0;
1821   }
1822 }
1823 
1824 ////////////////////////////////////////////////////////////////////////////////
urgency_next() const1825 float Task::urgency_next () const
1826 {
1827   if (hasTag ("next"))
1828     return 1.0;
1829 
1830   return 0.0;
1831 }
1832 
1833 ////////////////////////////////////////////////////////////////////////////////
1834 //
1835 //     Past                  Present                              Future
1836 //     Overdue               Due                                     Due
1837 //
1838 //     -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 days
1839 //
1840 // <-- 1.0                         linear                            0.2 -->
1841 //     capped                                                        capped
1842 //
1843 //
urgency_due() const1844 float Task::urgency_due () const
1845 {
1846   if (has ("due"))
1847   {
1848     Date now;
1849     Date due (get_date ("due"));
1850 
1851     // Map a range of 21 days to the value 0.2 - 1.0
1852     float days_overdue = (now - due) / 86400.0;
1853          if (days_overdue >= 7.0)   return 1.0;   // < 1 wk ago
1854     else if (days_overdue >= -14.0) return ((days_overdue + 14.0) * 0.8 / 21.0) + 0.2;
1855     else                            return 0.2;   // > 2 wks
1856   }
1857 
1858   return 0.0;
1859 }
1860 
1861 ////////////////////////////////////////////////////////////////////////////////
urgency_age() const1862 float Task::urgency_age () const
1863 {
1864   assert (has ("entry"));
1865 
1866   Date now;
1867   Date entry (get_date ("entry"));
1868   int   age  = (now - entry) / 86400;  // in days
1869 
1870   if (Task::urgencyAgeMax == 0 || age > Task::urgencyAgeMax)
1871     return 1.0;
1872 
1873   return (1.0 * age / Task::urgencyAgeMax);
1874 }
1875 
1876 ////////////////////////////////////////////////////////////////////////////////
urgency_blocking() const1877 float Task::urgency_blocking () const
1878 {
1879   if (is_blocking)
1880     return 1.0;
1881 
1882   return 0.0;
1883 }
1884 
1885 ////////////////////////////////////////////////////////////////////////////////
upgradeLegacyValues()1886 void Task::upgradeLegacyValues ()
1887 {
1888   // 2.4.0 Update recurrence values.
1889   if (has ("recur"))
1890   {
1891     std::string value = get ("recur");
1892     std::string new_value = "";
1893     std::string::size_type len = value.length ();
1894     std::string::size_type p;
1895 
1896          if (value == "-")                                                    new_value = "0s";
1897     else if ((p = value.find ("hr"))    != std::string::npos && p == len - 2) new_value = value.substr (0, p) + "h";
1898     else if ((p = value.find ("hrs"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "h";
1899     else if ((p = value.find ("mins"))  != std::string::npos && p == len - 4) new_value = value.substr (0, p) + "min";
1900     else if ((p = value.find ("mnths")) != std::string::npos && p == len - 5) new_value = value.substr (0, p) + "mo";
1901     else if ((p = value.find ("mos"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "mo";
1902     else if ((p = value.find ("mth"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "mo";
1903     else if ((p = value.find ("mths"))  != std::string::npos && p == len - 4) new_value = value.substr (0, p) + "mo";
1904     else if ((p = value.find ("qrtrs")) != std::string::npos && p == len - 5) new_value = value.substr (0, p) + "q";
1905     else if ((p = value.find ("qtr"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "q";
1906     else if ((p = value.find ("qtrs"))  != std::string::npos && p == len - 4) new_value = value.substr (0, p) + "q";
1907     else if ((p = value.find ("sec"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "s";
1908     else if ((p = value.find ("secs"))  != std::string::npos && p == len - 4) new_value = value.substr (0, p) + "s";
1909     else if ((p = value.find ("wk"))    != std::string::npos && p == len - 2) new_value = value.substr (0, p) + "w";
1910     else if ((p = value.find ("wks"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "w";
1911     else if ((p = value.find ("yr"))    != std::string::npos && p == len - 2) new_value = value.substr (0, p) + "y";
1912     else if ((p = value.find ("yrs"))   != std::string::npos && p == len - 3) new_value = value.substr (0, p) + "y";
1913 
1914     if (new_value != "" &&
1915         new_value != value)
1916     {
1917       set ("recur", new_value);
1918     }
1919   }
1920 }
1921 
1922 ////////////////////////////////////////////////////////////////////////////////
1923