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 <CmdDenotate.h>
29 #include <iostream>
30 #include <Context.h>
31 #include <Filter.h>
32 #include <shared.h>
33 #include <format.h>
34 #include <util.h>
35 #include <main.h>
36 
37 #define STRING_CMD_DENO_NO           "Task not denotated."
38 #define STRING_CMD_DENO_1            "Denotated {1} task."
39 #define STRING_CMD_DENO_N            "Denotated {1} tasks."
40 
41 ////////////////////////////////////////////////////////////////////////////////
CmdDenotate()42 CmdDenotate::CmdDenotate ()
43 {
44   _keyword               = "denotate";
45   _usage                 = "task <filter> denotate <pattern>";
46   _description           = "Deletes an annotation";
47   _read_only             = false;
48   _displays_id           = false;
49   _needs_gc              = false;
50   _uses_context          = true;
51   _accepts_filter        = true;
52   _accepts_modifications = false;
53   _accepts_miscellaneous = true;
54   _category              = Command::Category::operation;
55 }
56 
57 ////////////////////////////////////////////////////////////////////////////////
execute(std::string &)58 int CmdDenotate::execute (std::string&)
59 {
60   auto rc = 0;
61   auto count = 0;
62   auto sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive");
63 
64   // Apply filter.
65   Filter filter;
66   std::vector <Task> filtered;
67   filter.subset (filtered);
68   if (filtered.size () == 0)
69   {
70     Context::getContext ().footnote ("No tasks specified.");
71     return 1;
72   }
73 
74   // Extract all the ORIGINAL MODIFICATION args as simple text patterns.
75   std::string pattern = "";
76   for (auto& a : Context::getContext ().cli2._args)
77   {
78     if (a.hasTag ("MISCELLANEOUS"))
79     {
80       if (pattern != "")
81         pattern += ' ';
82 
83       pattern += a.attribute ("raw");
84     }
85   }
86 
87   // Accumulated project change notifications.
88   std::map <std::string, std::string> projectChanges;
89 
90   if(filtered.size() > 1) {
91     feedback_affected("This command will alter {1} tasks.", filtered.size());
92   }
93   for (auto& task : filtered)
94   {
95     Task before (task);
96 
97     auto annotations = task.getAnnotations ();
98 
99     if (annotations.size () == 0)
100       throw std::string ("The specified task has no annotations that can be deleted.");
101 
102     std::string anno;
103     auto match = false;
104     for (auto i = annotations.begin (); i != annotations.end (); ++i)
105     {
106       if (i->second == pattern)
107       {
108         match = true;
109         anno = i->second;
110         annotations.erase (i);
111         task.setAnnotations (annotations);
112         break;
113       }
114     }
115 
116     if (! match)
117     {
118       for (auto i = annotations.begin (); i != annotations.end (); ++i)
119       {
120         auto loc = find (i->second, pattern, sensitive);
121         if (loc != std::string::npos)
122         {
123           anno = i->second;
124           annotations.erase (i);
125           task.setAnnotations (annotations);
126           break;
127         }
128       }
129     }
130 
131     if (before.data != task.data)
132     {
133       auto question = format ("Denotate task {1} '{2}'?",
134                               task.identifier (true),
135                               task.get ("description"));
136 
137       if (permission (before.diff (task) + question, filtered.size ()))
138       {
139         ++count;
140         Context::getContext ().tdb2.modify (task);
141         feedback_affected (format ("Found annotation '{1}' and deleted it.", anno));
142         if (Context::getContext ().verbose ("project"))
143           projectChanges[task.get ("project")] = onProjectChange (task, false);
144       }
145       else
146       {
147         std::cout << STRING_CMD_DENO_NO << '\n';
148         rc = 1;
149         if (_permission_quit)
150           break;
151       }
152     }
153     else
154     {
155       std::cout << format ("Did not find any matching annotation to be deleted for '{1}'.\n", pattern);
156       rc = 1;
157     }
158   }
159 
160   // Now list the project changes.
161   for (const auto& change : projectChanges)
162     if (change.first != "")
163       Context::getContext ().footnote (change.second);
164 
165   feedback_affected (count == 1 ? STRING_CMD_DENO_1 : STRING_CMD_DENO_N, count);
166   return rc;
167 }
168 
169 ////////////////////////////////////////////////////////////////////////////////
170