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 <Context.h>
29 #include <iostream>
30 #include <fstream>
31 #include <sstream>
32 #include <iomanip>
33 #include <algorithm>
34 #include <assert.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <FS.h>
39 #include <Eval.h>
40 #include <Variant.h>
41 #include <Datetime.h>
42 #include <Duration.h>
43 #include <shared.h>
44 #include <format.h>
45 #include <main.h>
46
47 #ifdef HAVE_COMMIT
48 #include <commit.h>
49 #endif
50
51 #include <stdio.h>
52 #include <sys/ioctl.h>
53
54 #ifdef SOLARIS
55 #include <sys/termios.h>
56 #endif
57
58 ////////////////////////////////////////////////////////////////////////////////
59 // This string is parsed and used as default values for configuration.
60 std::string configurationDefaults =
61 "# Taskwarrior program configuration file.\n"
62 "# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',\n"
63 "# 'man task-sync' or 'man taskrc'\n"
64 "\n"
65 "# Here is an example of entries that use the default, override and blank values\n"
66 "# variable=foo -- By specifying a value, this overrides the default\n"
67 "# variable= -- By specifying no value, this means no default\n"
68 "# #variable=foo -- By commenting out the line, or deleting it, this uses the default\n"
69 "\n"
70 "# You can also refence environment variables:\n"
71 "# variable=$HOME/task\n"
72 "# variable=$VALUE\n"
73 "\n"
74 "# Use the command 'task show' to see all defaults and overrides\n"
75 "\n"
76 "# Files\n"
77 "data.location=~/.task\n"
78 "locking=1 # Use file-level locking\n"
79 "gc=1 # Garbage-collect data files - DO NOT CHANGE unless you are sure\n"
80 "exit.on.missing.db=0 # Whether to exit if ~/.task is not found\n"
81 "hooks=1 # Master control switch for hooks\n"
82 "\n"
83 "# Terminal\n"
84 "detection=1 # Detects terminal width\n"
85 "defaultwidth=80 # Without detection, assumed width\n"
86 "defaultheight=24 # Without detection, assumed height\n"
87 "avoidlastcolumn=0 # Fixes Cygwin width problem\n"
88 "hyphenate=1 # Hyphenates lines wrapped on non-word-breaks\n"
89 "#editor=vi # Preferred text editor\n"
90 "reserved.lines=1 # Assume a 1-line prompt\n"
91 "\n"
92 "# Miscellaneous\n"
93 "# verbose= # Comma-separated list. May contain any subset of:\n"
94 "# affected,blank,context,default,edit,filter,footnote,header,label,new-id,new-uuid,override,project,recur,special,sync\n"
95 "verbose=affected,blank,context,edit,header,footnote,label,new-id,project,special,sync,override,recur\n"
96 "confirmation=1 # Confirmation on delete, big changes\n"
97 "recurrence=1 # Enable recurrence\n"
98 "recurrence.confirmation=prompt # Confirmation for propagating changes among recurring tasks (yes/no/prompt)\n"
99 "allow.empty.filter=1 # An empty filter gets a warning and requires confirmation\n"
100 "indent.annotation=2 # Indent spaces for annotations\n"
101 "indent.report=0 # Indent spaces for whole report\n"
102 "row.padding=0 # Left and right padding for each row of report\n"
103 "column.padding=1 # Spaces between each column in a report\n"
104 "bulk=3 # 3 or more tasks considered a bulk change and is confirmed\n"
105 "nag=You have more urgent tasks. # Nag message to keep you honest\n" // TODO
106 "search.case.sensitive=1 # Setting to no allows case insensitive searches\n"
107 "active.indicator=* # What to show as an active task indicator\n"
108 "tag.indicator=+ # What to show as a tag indicator\n"
109 "dependency.indicator=D # What to show as a dependency indicator\n"
110 "recurrence.indicator=R # What to show as a task recurrence indicator\n"
111 "recurrence.limit=1 # Number of future recurring pending tasks\n"
112 "undo.style=side # Undo style - can be 'side', or 'diff'\n"
113 "regex=1 # Assume all search/filter strings are regexes\n"
114 "xterm.title=0 # Sets xterm title for some commands\n"
115 "expressions=infix # Prefer infix over postfix expressions\n"
116 "json.array=1 # Enclose JSON output in [ ]\n"
117 "abbreviation.minimum=2 # Shortest allowed abbreviation\n"
118 "news.version= # Latest version higlights read by the user\n"
119 "\n"
120 "# Dates\n"
121 "dateformat=Y-M-D # Preferred input and display date format\n"
122 "dateformat.holiday=YMD # Preferred input date format for holidays\n"
123 "dateformat.edit=Y-M-D H:N:S # Preferred display date format when editing\n"
124 "dateformat.info=Y-M-D H:N:S # Preferred display date format for information\n"
125 "dateformat.report= # Preferred display date format for reports\n"
126 "dateformat.annotation= # Preferred display date format for annotations\n"
127 "date.iso=1 # Enable ISO date support\n"
128 "weekstart=sunday # Sunday or Monday only\n"
129 "displayweeknumber=1 # Show week numbers on calendar\n"
130 "due=7 # Task is considered due in 7 days\n"
131 "\n"
132 "# Calendar controls\n"
133 "calendar.legend=1 # Display the legend on calendar\n"
134 "calendar.details=sparse # Calendar shows information for tasks w/due dates: full, sparse or none\n"
135 "calendar.details.report=list # Report to use when showing task information in cal\n"
136 "calendar.offset=0 # Apply an offset value to control the first month of the calendar\n"
137 "calendar.offset.value=-1 # The number of months the first month of the calendar is moved\n"
138 "calendar.holidays=none # Show public holidays on calendar:full, sparse or none\n"
139 "#calendar.monthsperline=3 # Number of calendar months on a line\n"
140 "\n"
141 "# Journal controls\n"
142 "journal.time=0 # Record start/stop commands as annotation\n"
143 "journal.time.start.annotation=Started task # Annotation description for the start journal entry\n"
144 "journal.time.stop.annotation=Stopped task # Annotation description for the stop journal entry\n"
145 "journal.info=1 # Display task journal with info command\n"
146 "\n"
147 "# Dependency controls\n"
148 "dependency.reminder=1 # Nags on dependency chain violations\n"
149 "dependency.confirmation=1 # Should dependency chain repair be confirmed?\n"
150 "\n"
151 "# Urgency Coefficients\n"
152 "urgency.user.tag.next.coefficient=15.0 # Urgency coefficient for 'next' special tag\n"
153 "urgency.due.coefficient=12.0 # Urgency coefficient for due dates\n"
154 "urgency.blocking.coefficient=8.0 # Urgency coefficient for blocking tasks\n"
155 "urgency.active.coefficient=4.0 # Urgency coefficient for active tasks\n"
156 "urgency.scheduled.coefficient=5.0 # Urgency coefficient for scheduled tasks\n"
157 "urgency.age.coefficient=2.0 # Urgency coefficient for age\n"
158 "urgency.annotations.coefficient=1.0 # Urgency coefficient for annotations\n"
159 "urgency.tags.coefficient=1.0 # Urgency coefficient for tags\n"
160 "urgency.project.coefficient=1.0 # Urgency coefficient for projects\n"
161 "urgency.blocked.coefficient=-5.0 # Urgency coefficient for blocked tasks\n"
162 "urgency.waiting.coefficient=-3.0 # Urgency coefficient for waiting status\n"
163 "urgency.inherit=0 # Recursively inherit highest urgency value from blocked tasks\n"
164 "urgency.age.max=365 # Maximum age in days\n"
165 "\n"
166 "#urgency.user.project.foo.coefficient=5.0 # Urgency coefficients for 'foo' project\n"
167 "#urgency.user.tag.foo.coefficient=5.0 # Urgency coefficients for 'foo' tag\n"
168 "#urgency.uda.foo.coefficient=5.0 # Urgency coefficients for UDA 'foo'\n"
169 "\n"
170 "# Color controls.\n"
171 "color=1 # Enable color\n"
172 "\n"
173 "# Here is the rule precedence order, highest to lowest.\n"
174 "# Note that these are just the color rule names, without the leading 'color.'\n"
175 "# and any trailing '.value'.\n"
176 "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda.\n"
177 "\n"
178 "# General decoration\n"
179 "rule.color.merge=1\n"
180 "color.label=\n"
181 "color.label.sort=\n"
182 "color.alternate=on gray2\n"
183 "color.header=color3\n"
184 "color.footnote=color3\n"
185 "color.warning=bold red\n"
186 "color.error=white on red\n"
187 "color.debug=color4\n"
188 "\n"
189 "# Task state\n"
190 "color.completed=\n"
191 "color.deleted=\n"
192 "color.active=rgb555 on rgb410\n"
193 "color.recurring=rgb013\n"
194 "color.scheduled=on rgb001\n"
195 "color.until=\n"
196 "color.blocked=white on color8\n"
197 "color.blocking=black on color15\n"
198 "\n"
199 "# Project\n"
200 "color.project.none=\n"
201 "\n"
202 "# Priority UDA\n"
203 "color.uda.priority.H=color255\n"
204 "color.uda.priority.L=color245\n"
205 "color.uda.priority.M=color250\n"
206 "\n"
207 "# Tags\n"
208 "color.tag.next=rgb440\n"
209 "color.tag.none=\n"
210 "color.tagged=rgb031\n"
211 "\n"
212 "# Due\n"
213 "color.due.today=rgb400\n"
214 "color.due=color1\n"
215 "color.overdue=color9\n"
216 "\n"
217 "# Report: burndown\n"
218 "color.burndown.done=on rgb010\n"
219 "color.burndown.pending=on color9\n"
220 "color.burndown.started=on color11\n"
221 "\n"
222 "# Report: history\n"
223 "color.history.add=color0 on rgb500\n"
224 "color.history.delete=color0 on rgb550\n"
225 "color.history.done=color0 on rgb050\n"
226 "\n"
227 "# Report: summary\n"
228 "color.summary.background=white on color0\n"
229 "color.summary.bar=black on rgb141\n"
230 "\n"
231 "# Command: calendar\n"
232 "color.calendar.due.today=color15 on color1\n"
233 "color.calendar.due=color0 on color1\n"
234 "color.calendar.holiday=color0 on color11\n"
235 "color.calendar.scheduled=rgb013 on color15\n"
236 "color.calendar.overdue=color0 on color9\n"
237 "color.calendar.today=color15 on rgb013\n"
238 "color.calendar.weekend=on color235\n"
239 "color.calendar.weeknumber=rgb013\n"
240 "\n"
241 "# Command: sync\n"
242 "color.sync.added=rgb010\n"
243 "color.sync.changed=color11\n"
244 "color.sync.rejected=color9\n"
245 "\n"
246 "# Command: undo\n"
247 "color.undo.after=color2\n"
248 "color.undo.before=color1\n"
249 "\n"
250 "# UDA priority\n"
251 "uda.priority.type=string # UDA priority is a string type\n"
252 "uda.priority.label=Priority # UDA priority has a display label'\n"
253 "uda.priority.values=H,M,L, # UDA priority values are 'H', 'M', 'L' or ''\n"
254 " # UDA priority sorting is 'H' > 'M' > 'L' > '' (highest to lowest)\n"
255 "#uda.priority.default=M # UDA priority default value of 'M'\n"
256 "urgency.uda.priority.H.coefficient=6.0 # UDA priority coefficient for value 'H'\n"
257 "urgency.uda.priority.M.coefficient=3.9 # UDA priority coefficient for value 'M'\n"
258 "urgency.uda.priority.L.coefficient=1.8 # UDA priority coefficient for value 'L'\n"
259 "\n"
260 "#default.project=foo # Default project for 'add' command\n"
261 "#default.due=eom # Default due date for 'add' command\n"
262 "#default.scheduled=eom # Default scheduled date for 'add' command\n"
263 "default.command=next # When no arguments are specified\n"
264 "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and end.after:now-4wks )\n"
265 "\n"
266 "_forcecolor=0 # Forces color to be on, even for non TTY output\n"
267 "complete.all.tags=0 # Include old tag names in '_ags' command\n"
268 "list.all.projects=0 # Include old project names in 'projects' command\n"
269 "summary.all.projects=0 # Include old project names in 'summary' command\n"
270 "list.all.tags=0 # Include old tag names in 'tags' command\n"
271 "print.empty.columns=0 # Print columns which have no data for any task\n"
272 "debug=0 # Display diagnostics\n"
273 "debug.tls=0 # Sync diagnostics\n"
274 "sugar=1 # Syntactic sugar\n"
275 "obfuscate=0 # Obfuscate data for error reporting\n"
276 "fontunderline=1 # Uses underlines rather than -------\n"
277 "\n"
278 "# WARNING: Please read the documentation (man task-sync) before setting up\n"
279 "# Taskwarrior for Taskserver synchronization.\n"
280 "#taskd.ca=<certificate file>\n"
281 "#taskd.certificate=<certificate file>\n"
282 "#taskd.credentials=<organization>/<name>/<password>\n"
283 "#taskd.server=<server>:<port>\n"
284 "taskd.trust=strict\n"
285 "#taskd.trust=ignore hostname\n"
286 "#taskd.trust=allow all\n"
287 "taskd.ciphers=NORMAL\n"
288 "\n"
289 "# Aliases - alternate names for commands\n"
290 "alias.rm=delete # Alias for the delete command\n"
291 "alias.history=history.monthly # Prefer monthly over annual history reports\n"
292 "alias.ghistory=ghistory.monthly # Prefer monthly graphical over annual history reports\n"
293 "alias.burndown=burndown.weekly # Prefer the weekly burndown chart\n"
294 "\n"
295 "# Reports\n"
296 "\n"
297 "report.long.description=All details of tasks\n"
298 "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until,Description\n"
299 "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur,wait.remaining,scheduled,due,until,description\n"
300 "report.long.filter=status:pending -WAITING\n"
301 "report.long.sort=modified-\n"
302 "report.long.context=1\n"
303 "\n"
304 "report.list.description=Most details of tasks\n"
305 "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n"
306 "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur.indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n"
307 "report.list.filter=status:pending -WAITING\n"
308 "report.list.sort=start-,due+,project+,urgency-\n"
309 "report.list.context=1\n"
310 "\n"
311 "report.ls.description=Few details of tasks\n"
312 "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n"
313 "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n"
314 "report.ls.filter=status:pending -WAITING\n"
315 "report.ls.sort=start-,description+\n"
316 "report.ls.context=1\n"
317 "\n"
318 "report.minimal.description=Minimal details of tasks\n"
319 "report.minimal.labels=ID,Project,Tags,Description\n"
320 "report.minimal.columns=id,project,tags.count,description.count\n"
321 "report.minimal.filter=status:pending\n"
322 "report.minimal.sort=project+/,description+\n"
323 "report.minimal.context=1\n"
324 "\n"
325 "report.newest.description=Newest tasks\n"
326 "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
327 "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
328 "report.newest.filter=status:pending\n"
329 "report.newest.sort=entry-\n"
330 "report.newest.context=1\n"
331 "\n"
332 "report.oldest.description=Oldest tasks\n"
333 "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
334 "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
335 "report.oldest.filter=status:pending\n"
336 "report.oldest.sort=entry+\n"
337 "report.oldest.context=1\n"
338 "\n"
339 "report.overdue.description=Overdue tasks\n"
340 "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n"
341 "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator,scheduled.countdown,due,until,description,urgency\n"
342 "report.overdue.filter=status:pending and +OVERDUE\n"
343 "report.overdue.sort=urgency-,due+\n"
344 "report.overdue.context=1\n"
345 "\n"
346 "report.active.description=Active tasks\n"
347 "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until,Description\n"
348 "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags,recur,wait,scheduled.remaining,due,until,description\n"
349 "report.active.filter=status:pending and +ACTIVE\n"
350 "report.active.sort=project+,start+\n"
351 "report.active.context=1\n"
352 "\n"
353 "report.completed.description=Completed tasks\n"
354 "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n"
355 "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags,recur.indicator,due,description\n"
356 "report.completed.filter=status:completed\n"
357 "report.completed.sort=end+\n"
358 "report.completed.context=1\n"
359 "\n"
360 "report.recurring.description=Recurring Tasks\n"
361 "report.recurring.labels=ID,Active,Age,D,P,Project,Tags,Recur,Sch,Due,Until,Description,Urg\n"
362 "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
363 "report.recurring.filter=status:pending and (+PARENT or +CHILD)\n"
364 "report.recurring.sort=due+,urgency-,entry+\n"
365 "report.recurring.context=1\n"
366 "\n"
367 "report.waiting.description=Waiting (hidden) tasks\n"
368 "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n"
369 "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags,recur.indicator,wait,wait.remaining,scheduled,due,until,description\n"
370 "report.waiting.filter=+WAITING\n"
371 "report.waiting.sort=due+,wait+,entry+\n"
372 "report.waiting.context=1\n"
373 "\n"
374 "report.all.description=All tasks\n"
375 "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
376 "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends.indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description\n"
377 "report.all.sort=entry-\n"
378 "report.all.context=1\n"
379 "\n"
380 "report.next.description=Most urgent tasks\n"
381 "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n"
382 "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description,urgency\n"
383 "report.next.filter=status:pending -WAITING limit:page\n"
384 "report.next.sort=urgency-\n"
385 "report.next.context=1\n"
386 "\n"
387 "report.ready.description=Most urgent actionable tasks\n"
388 "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n"
389 "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur.indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n"
390 "report.ready.filter=+READY\n"
391 "report.ready.sort=start-,urgency-\n"
392 "report.ready.context=1\n"
393 "\n"
394 "report.blocked.description=Blocked tasks\n"
395 "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
396 "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
397 "report.blocked.sort=due+,priority-,start-,project+\n"
398 "report.blocked.filter=status:pending -WAITING +BLOCKED\n"
399 "report.blocked.context=1\n"
400 "\n"
401 "report.unblocked.description=Unblocked tasks\n"
402 "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
403 "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
404 "report.unblocked.sort=due+,priority-,start-,project+\n"
405 "report.unblocked.filter=status:pending -WAITING -BLOCKED\n"
406 "report.unblocked.context=1\n"
407 "\n"
408 "report.blocking.description=Blocking tasks\n"
409 "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n"
410 "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled.remaining,due.relative,until.remaining,description.count,urgency\n"
411 "report.blocking.sort=urgency-,due+,entry+\n"
412 "report.blocking.filter=status:pending -WAITING +BLOCKING\n"
413 "report.blocking.context=1\n"
414 "\n"
415 "report.timesheet.filter=(+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks)\n"
416 "report.timesheet.context=0\n"
417 "\n";
418
419 // Supported modifiers, synonyms on the same line.
420 static const char* modifierNames[] =
421 {
422 "before", "under", "below",
423 "after", "over", "above",
424 "by",
425 "none",
426 "any",
427 "is", "equals",
428 "isnt", "not",
429 "has", "contains",
430 "hasnt",
431 "startswith", "left",
432 "endswith", "right",
433 "word",
434 "noword"
435 };
436
437 Context* Context::context;
438
439 ////////////////////////////////////////////////////////////////////////////////
getContext()440 Context& Context::getContext ()
441 {
442 return *Context::context;
443 }
444
445 ////////////////////////////////////////////////////////////////////////////////
setContext(Context * context)446 void Context::setContext (Context* context)
447 {
448 Context::context = context;
449 }
450
451 ////////////////////////////////////////////////////////////////////////////////
~Context()452 Context::~Context ()
453 {
454 for (auto& com : commands)
455 delete com.second;
456
457 for (auto& col : columns)
458 delete col.second;
459 }
460
461 ////////////////////////////////////////////////////////////////////////////////
initialize(int argc,const char ** argv)462 int Context::initialize (int argc, const char** argv)
463 {
464 timer_total.start ();
465 int rc = 0;
466 home_dir = getenv ("HOME");
467
468 std::vector <std::string> searchPaths { TASK_RCDIR };
469
470 try
471 {
472 ////////////////////////////////////////////////////////////////////////////
473 //
474 // [1] Load the correct config file.
475 // - Default to ~/.taskrc (ctor).
476 // - If no ~/.taskrc, use $XDG_CONFIG_HOME/task/taskrc if exists, or
477 // ~/.config/task/taskrc if $XDG_DATA_HOME is unset
478 // - Allow $TASKRC override.
479 // - Allow command line override rc:<file>
480 // - Load resultant file.
481 // - Apply command line overrides to the config.
482 //
483 ////////////////////////////////////////////////////////////////////////////
484
485 bool taskrc_overridden = false;
486
487 // XDG_CONFIG_HOME doesn't count as an override (no warning header)
488 if (! rc_file.exists ())
489 {
490 // Use XDG_CONFIG_HOME if defined, otherwise default to ~/.config
491 std::string xdg_config_home;
492 const char* env_xdg_config_home = getenv ("XDG_CONFIG_HOME");
493
494 if (env_xdg_config_home)
495 xdg_config_home = format ("{1}", env_xdg_config_home);
496 else
497 xdg_config_home = format ("{1}/.config", home_dir);
498
499 // Ensure the path does not end with '/'
500 if (xdg_config_home.back () == '/')
501 xdg_config_home.pop_back();
502
503 // https://github.com/GothenburgBitFactory/libshared/issues/32
504 std::string rcfile_path = format ("{1}/task/taskrc", xdg_config_home);
505
506 File maybe_rc_file = File (rcfile_path);
507 if ( maybe_rc_file.exists ())
508 rc_file = maybe_rc_file;
509 }
510
511 char *override = getenv ("TASKRC");
512 if (override)
513 {
514 rc_file = File (override);
515 taskrc_overridden = true;
516 }
517
518 taskrc_overridden =
519 CLI2::getOverride (argc, argv, rc_file) || taskrc_overridden;
520
521 // Artificial scope for timing purposes.
522 {
523 Timer timer;
524 config.parse (configurationDefaults, 1, searchPaths);
525 config.load (rc_file._data, 1, searchPaths);
526 debugTiming (format ("Config::load ({1})", rc_file._data), timer);
527 }
528
529 CLI2::applyOverrides (argc, argv);
530
531 if (taskrc_overridden && verbose ("override"))
532 header (format ("TASKRC override: {1}", rc_file._data));
533
534 ////////////////////////////////////////////////////////////////////////////
535 //
536 // [2] Locate the data directory.
537 // - Default to ~/.task (ctor).
538 // - Allow $TASKDATA override.
539 // - Allow command line override rc.data.location:<dir>
540 // - Inform TDB2 where to find data.
541 // - Create the rc_file and data_dir, if necessary.
542 //
543 ////////////////////////////////////////////////////////////////////////////
544
545 bool taskdata_overridden = false;
546
547 override = getenv ("TASKDATA");
548 if (override)
549 {
550 data_dir = Directory (override);
551 config.set ("data.location", data_dir._data);
552 taskdata_overridden = true;
553 }
554
555 taskdata_overridden =
556 CLI2::getDataLocation (argc, argv, data_dir) || taskdata_overridden;
557
558 if (taskdata_overridden && verbose ("override"))
559 header (format ("TASKDATA override: {1}", data_dir._data));
560
561 tdb2.set_location (data_dir);
562 createDefaultConfig ();
563
564 ////////////////////////////////////////////////////////////////////////////
565 //
566 // [3] Instantiate Command objects and capture command entities.
567 //
568 ////////////////////////////////////////////////////////////////////////////
569
570 Command::factory (commands);
571 for (auto& cmd : commands)
572 cli2.entity ("cmd", cmd.first);
573
574 ////////////////////////////////////////////////////////////////////////////
575 //
576 // [4] Instantiate Column objects and capture column entities.
577 //
578 ////////////////////////////////////////////////////////////////////////////
579
580 Column::factory (columns);
581 for (auto& col : columns)
582 cli2.entity ("attribute", col.first);
583
584 cli2.entity ("pseudo", "limit");
585
586 ////////////////////////////////////////////////////////////////////////////
587 //
588 // [5] Capture modifier and operator entities.
589 //
590 ////////////////////////////////////////////////////////////////////////////
591
592 for (auto& modifierName : modifierNames)
593 cli2.entity ("modifier", modifierName);
594
595 for (auto& op : Eval::getOperators ())
596 cli2.entity ("operator", op);
597
598 for (auto& op : Eval::getBinaryOperators ())
599 cli2.entity ("binary_operator", op);
600
601 ////////////////////////////////////////////////////////////////////////////
602 //
603 // [6] Complete the Context initialization.
604 //
605 ////////////////////////////////////////////////////////////////////////////
606
607 initializeColorRules ();
608 staticInitialization ();
609 propagateDebug ();
610 loadAliases ();
611
612 ////////////////////////////////////////////////////////////////////////////
613 //
614 // [7] Parse the command line.
615 //
616 ////////////////////////////////////////////////////////////////////////////
617
618 for (int i = 0; i < argc; i++)
619 cli2.add (argv[i]);
620
621 cli2.analyze ();
622
623 // Extract a recomposed command line.
624 auto foundDefault = false;
625 auto foundAssumed = false;
626 std::string combined;
627 for (auto& a : cli2._args)
628 {
629 if (combined.length ())
630 combined += ' ';
631
632 combined += a.attribute ("raw");
633
634 if (a.hasTag ("DEFAULT"))
635 foundDefault = true;
636
637 if (a.hasTag ("ASSUMED"))
638 foundAssumed = true;
639 }
640
641 if (verbose ("default")) {
642 if (foundDefault)
643 header ("[" + combined + "]");
644
645 if (foundAssumed)
646 header ("No command specified - assuming 'information'.");
647 }
648
649 ////////////////////////////////////////////////////////////////////////////
650 //
651 // [8] Initialize hooks.
652 //
653 ////////////////////////////////////////////////////////////////////////////
654
655 hooks.initialize ();
656 }
657
658 catch (const std::string& message)
659 {
660 error (message);
661 rc = 2;
662 }
663
664 catch (int)
665 {
666 // Hooks can terminate processing by throwing integers.
667 rc = 4;
668 }
669
670 catch (...)
671 {
672 error ("Unknown error. Please report.");
673 rc = 3;
674 }
675
676 // On initialization failure...
677 if (rc)
678 {
679 // Dump all debug messages, controlled by rc.debug.
680 if (config.getBoolean ("debug"))
681 {
682 for (auto& d : debugMessages)
683 if (color ())
684 std::cerr << colorizeDebug (d) << '\n';
685 else
686 std::cerr << d << '\n';
687 }
688
689 // Dump all headers, controlled by 'header' verbosity token.
690 if (verbose ("header"))
691 {
692 for (auto& h : headers)
693 if (color ())
694 std::cerr << colorizeHeader (h) << '\n';
695 else
696 std::cerr << h << '\n';
697 }
698
699 // Dump all footnotes, controlled by 'footnote' verbosity token.
700 if (verbose ("footnote"))
701 {
702 for (auto& f : footnotes)
703 if (color ())
704 std::cerr << colorizeFootnote (f) << '\n';
705 else
706 std::cerr << f << '\n';
707 }
708
709 // Dump all errors, non-maskable.
710 // Colorized as footnotes.
711 for (auto& e : errors)
712 if (color ())
713 std::cerr << colorizeFootnote (e) << '\n';
714 else
715 std::cerr << e << '\n';
716 }
717
718 time_init_us += timer_total.total_us ();
719 return rc;
720 }
721
722 ////////////////////////////////////////////////////////////////////////////////
run()723 int Context::run ()
724 {
725 int rc;
726 std::string output;
727
728 try
729 {
730 hooks.onLaunch ();
731 rc = dispatch (output);
732 tdb2.commit (); // Harmless if called when nothing changed.
733 hooks.onExit (); // No chance to update data.
734
735 timer_total.stop ();
736 time_total_us += timer_total.total_us ();
737
738 std::stringstream s;
739 s << "Perf "
740 << PACKAGE_STRING
741 << ' '
742 #ifdef HAVE_COMMIT
743 << COMMIT
744 #else
745 << '-'
746 #endif
747 << ' '
748 << Datetime ().toISO ()
749
750 << " init:" << time_init_us
751 << " load:" << time_load_us
752 << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us)
753 << " filter:" << time_filter_us
754 << " commit:" << time_commit_us
755 << " sort:" << time_sort_us
756 << " render:" << time_render_us
757 << " hooks:" << time_hooks_us
758 << " other:" << time_total_us -
759 time_init_us -
760 time_gc_us -
761 time_filter_us -
762 time_commit_us -
763 time_sort_us -
764 time_render_us -
765 time_hooks_us
766 << " total:" << time_total_us
767 << '\n';
768 debug (s.str ());
769 }
770
771 catch (const std::string& message)
772 {
773 error (message);
774 rc = 2;
775 }
776
777 catch (int)
778 {
779 // Hooks can terminate processing by throwing integers.
780 rc = 4;
781 }
782
783 catch (...)
784 {
785 error ("Unknown error. Please report.");
786 rc = 3;
787 }
788
789 // Dump all debug messages, controlled by rc.debug.
790 if (config.getBoolean ("debug"))
791 {
792 for (auto& d : debugMessages)
793 if (color ())
794 std::cerr << colorizeDebug (d) << '\n';
795 else
796 std::cerr << d << '\n';
797 }
798
799 // Dump all headers, controlled by 'header' verbosity token.
800 if (verbose ("header"))
801 {
802 for (auto& h : headers)
803 if (color ())
804 std::cerr << colorizeHeader (h) << '\n';
805 else
806 std::cerr << h << '\n';
807 }
808
809 // Dump the report output.
810 std::cout << output;
811
812 // Dump all footnotes, controlled by 'footnote' verbosity token.
813 if (verbose ("footnote"))
814 {
815 for (auto& f : footnotes)
816 if (color ())
817 std::cerr << colorizeFootnote (f) << '\n';
818 else
819 std::cerr << f << '\n';
820 }
821
822 // Dump all errors, non-maskable.
823 // Colorized as footnotes.
824 for (auto& e : errors)
825 if (color ())
826 std::cerr << colorizeError (e) << '\n';
827 else
828 std::cerr << e << '\n';
829
830 return rc;
831 }
832
833 ////////////////////////////////////////////////////////////////////////////////
834 // Dispatch to the command found by the CLI parser.
dispatch(std::string & out)835 int Context::dispatch (std::string &out)
836 {
837 // Autocomplete args against keywords.
838 std::string command = cli2.getCommand ();
839 if (command != "")
840 {
841 updateXtermTitle ();
842 updateVerbosity ();
843
844 Command* c = commands[command];
845 assert (c);
846
847 // The command know whether they need a GC.
848 if (c->needs_gc () &&
849 ! tdb2.read_only ())
850 {
851 run_gc = config.getBoolean ("gc");
852 tdb2.gc ();
853 }
854 else
855 {
856 run_gc = false;
857 }
858
859 /*
860 // Only read-only commands can be run when TDB2 is read-only.
861 // TODO Implement TDB2::read_only
862 if (tdb2.read_only () && !c->read_only ())
863 throw std::string ("");
864 */
865
866 // This is something that is only needed for write commands with no other
867 // filter processing.
868 if (c->accepts_modifications () &&
869 ! c->accepts_filter ())
870 {
871 cli2.prepareFilter ();
872 }
873
874 // With rc.debug.parser == 2, there are more tree dumps than you might want,
875 // but we need the rc.debug.parser == 1 case covered also, with the final
876 // tree.
877 if (config.getBoolean ("debug") &&
878 config.getInteger ("debug.parser") == 1)
879 debug (cli2.dump ("Parse Tree (before command-specifіc processing)"));
880
881 return c->execute (out);
882 }
883
884 assert (commands["help"]);
885 return commands["help"]->execute (out);
886 }
887
888 ////////////////////////////////////////////////////////////////////////////////
getWidth()889 int Context::getWidth ()
890 {
891 // Determine window size.
892 auto width = config.getInteger ("defaultwidth");
893
894 // A zero width value means 'infinity', which is approximated here by 2^16.
895 if (width == 0)
896 return 65536;
897
898 if (config.getBoolean ("detection"))
899 {
900 if (terminal_width == 0 &&
901 terminal_height == 0)
902 {
903 unsigned short buff[4];
904 if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1)
905 {
906 terminal_height = buff[0];
907 terminal_width = buff[1];
908 }
909 }
910
911 width = terminal_width;
912
913 // Ncurses does this, and perhaps we need to as well, to avoid a problem on
914 // Cygwin where the display goes right up to the terminal width, and causes
915 // an odd color wrapping problem.
916 if (config.getBoolean ("avoidlastcolumn"))
917 --width;
918 }
919
920 return width;
921 }
922
923 ////////////////////////////////////////////////////////////////////////////////
getHeight()924 int Context::getHeight ()
925 {
926 // Determine window size.
927 auto height = config.getInteger ("defaultheight");
928
929 // A zero height value means 'infinity', which is approximated here by 2^16.
930 if (height == 0)
931 return 65536;
932
933 if (config.getBoolean ("detection"))
934 {
935 if (terminal_width == 0 &&
936 terminal_height == 0)
937 {
938 unsigned short buff[4];
939 if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1)
940 {
941 terminal_height = buff[0];
942 terminal_width = buff[1];
943 }
944 }
945
946 height = terminal_height;
947 }
948
949 return height;
950 }
951
952 ////////////////////////////////////////////////////////////////////////////////
getTaskContext(const std::string & kind,std::string name,bool fallback)953 std::string Context::getTaskContext (const std::string& kind, std::string name, bool fallback /* = true */)
954 {
955 // Consider currently selected context, if none specified
956 if (name.empty ())
957 name = config.get ("context");
958
959 // Detect if any context is set, and bail out if not
960 if (! name.empty ())
961 debug (format ("Applying context '{1}'", name));
962 else
963 {
964 debug ("No context set");
965 return "";
966 }
967
968 // Figure out the context string for this kind (read/write)
969 std::string contextString = "";
970
971 if (! config.has ("context." + name + "." + kind) && kind == "read")
972 {
973 debug ("Specific " + kind + " context for '" + name + "' not defined. ");
974 if (fallback)
975 {
976 debug ("Trying to interpret old-style context definition as read context.");
977 contextString = config.get ("context." + name);
978 }
979 }
980 else
981 contextString = config.get ("context." + name + "." + kind);
982
983 debug (format ("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString));
984 return contextString;
985 }
986
987 ////////////////////////////////////////////////////////////////////////////////
color()988 bool Context::color ()
989 {
990 if (determine_color_use)
991 {
992 // What the config says.
993 use_color = config.getBoolean ("color");
994
995 // Only tty's support color.
996 if (! isatty (STDOUT_FILENO))
997 {
998 // No ioctl.
999 config.set ("detection", "off");
1000 config.set ("color", "off");
1001
1002 // Files don't get color.
1003 use_color = false;
1004 }
1005
1006 // Override.
1007 if (config.getBoolean ("_forcecolor"))
1008 {
1009 config.set ("color", "on");
1010 use_color = true;
1011 }
1012
1013 // No need to go through this again.
1014 determine_color_use = false;
1015 }
1016
1017 // Cached result.
1018 return use_color;
1019 }
1020
1021 ////////////////////////////////////////////////////////////////////////////////
1022 // Support verbosity levels:
1023 //
1024 // rc.verbose=1 Show all feedback.
1025 // rc.verbose=0 Show regular feedback.
1026 // rc.verbose=nothing Show the absolute minimum.
1027 // rc.verbose=one,two Show verbosity for 'one' and 'two' only.
1028 //
1029 // TODO This mechanism is clunky, and should slowly evolve into something more
1030 // logical and consistent. This should probably mean that 'nothing' should
1031 // take the place of '0'.
verbose(const std::string & token)1032 bool Context::verbose (const std::string& token)
1033 {
1034 if (verbosity.empty ())
1035 {
1036 verbosity_legacy = config.getBoolean ("verbose");
1037 for (auto& token : split (config.get ("verbose"), ','))
1038 verbosity.insert (token);
1039
1040 // Regular feedback means almost everything.
1041 // This odd test is to see if a Boolean-false value is a real one, which
1042 // means it is not 1/true/T/yes/on, but also should not be one of the
1043 // valid tokens either.
1044 if (! verbosity_legacy && ! verbosity.empty ())
1045 {
1046 std::string v = *(verbosity.begin ());
1047 if (v != "nothing" &&
1048 v != "affected" && // This list must be complete.
1049 v != "blank" && //
1050 v != "context" && //
1051 v != "default" && //
1052 v != "edit" && //
1053 v != "filter" && //
1054 v != "footnote" && //
1055 v != "header" && //
1056 v != "label" && //
1057 v != "new-id" && //
1058 v != "new-uuid" && //
1059 v != "override" && //
1060 v != "project" && //
1061 v != "recur" && //
1062 v != "special" && //
1063 v != "sync")
1064 {
1065 // This list emulates rc.verbose=off in version 1.9.4.
1066 verbosity = {"blank", "label", "new-id", "edit"};
1067 }
1068 }
1069
1070 // Some flags imply "footnote" verbosity being active. Make it so.
1071 if (! verbosity.count ("footnote"))
1072 {
1073 // TODO: Some of these may not use footnotes yet. They should.
1074 for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"})
1075 {
1076 if (verbosity.count (flag))
1077 {
1078 verbosity.insert ("footnote");
1079 break;
1080 }
1081 }
1082 }
1083
1084 // Some flags imply "header" verbosity being active. Make it so.
1085 if (! verbosity.count ("header"))
1086 {
1087 for (auto flag : {"default"})
1088 {
1089 if (verbosity.count (flag))
1090 {
1091 verbosity.insert ("header");
1092 break;
1093 }
1094 }
1095 }
1096 }
1097
1098 // rc.verbose=true|y|yes|1|on overrides all.
1099 if (verbosity_legacy)
1100 return true;
1101
1102 // rc.verbose=nothing overrides all.
1103 if (verbosity.size () == 1 &&
1104 *(verbosity.begin ()) == "nothing")
1105 return false;
1106
1107 // Specific token match.
1108 if (verbosity.count (token))
1109 return true;
1110
1111 return false;
1112 }
1113
1114 ////////////////////////////////////////////////////////////////////////////////
getColumns() const1115 const std::vector <std::string> Context::getColumns () const
1116 {
1117 std::vector <std::string> output;
1118 for (auto& col : columns)
1119 output.push_back (col.first);
1120
1121 return output;
1122 }
1123
1124 ////////////////////////////////////////////////////////////////////////////////
1125 // A value of zero mean unlimited.
1126 // A value of 'page' means however many screen lines there are.
1127 // A value of a positive integer is a row/task limit.
getLimits(int & rows,int & lines)1128 void Context::getLimits (int& rows, int& lines)
1129 {
1130 rows = 0;
1131 lines = 0;
1132
1133 // This is an integer specified as a filter (limit:10).
1134 auto limit = config.get ("limit");
1135 if (limit != "")
1136 {
1137 if (limit == "page")
1138 {
1139 rows = 0;
1140 lines = getHeight ();
1141 }
1142 else
1143 {
1144 rows = (int) strtol (limit.c_str (), nullptr, 10);
1145 lines = 0;
1146 }
1147 }
1148 }
1149
1150 ////////////////////////////////////////////////////////////////////////////////
1151 // The 'Task' object, among others, is shared between projects. To make this
1152 // easier, it has been decoupled from Context.
staticInitialization()1153 void Context::staticInitialization ()
1154 {
1155 CLI2::minimumMatchLength = config.getInteger ("abbreviation.minimum");
1156 Lexer::minimumMatchLength = config.getInteger ("abbreviation.minimum");
1157
1158 Task::defaultProject = config.get ("default.project");
1159 Task::defaultDue = config.get ("default.due");
1160 Task::defaultScheduled = config.get ("default.scheduled");
1161
1162 Task::searchCaseSensitive = Variant::searchCaseSensitive = config.getBoolean ("search.case.sensitive");
1163 Task::regex = Variant::searchUsingRegex = config.getBoolean ("regex");
1164 Lexer::dateFormat = Variant::dateFormat = config.get ("dateformat");
1165
1166 Datetime::isoEnabled = config.getBoolean ("date.iso");
1167 Datetime::standaloneDateEnabled = false;
1168 Datetime::standaloneTimeEnabled = false;
1169 Duration::standaloneSecondsEnabled = false;
1170
1171 TDB2::debug_mode = config.getBoolean ("debug");
1172
1173 for (auto& rc : config)
1174 {
1175 if (rc.first.substr (0, 4) == "uda." &&
1176 rc.first.substr (rc.first.length () - 7, 7) == ".values")
1177 {
1178 std::string name = rc.first.substr (4, rc.first.length () - 7 - 4);
1179 auto values = split (rc.second, ',');
1180
1181 for (auto r = values.rbegin(); r != values.rend (); ++r)
1182 Task::customOrder[name].push_back (*r);
1183 }
1184 }
1185
1186 for (auto& col : columns)
1187 {
1188 Task::attributes[col.first] = col.second->type ();
1189 Lexer::attributes[col.first] = col.second->type ();
1190 }
1191
1192 Task::urgencyProjectCoefficient = config.getReal ("urgency.project.coefficient");
1193 Task::urgencyActiveCoefficient = config.getReal ("urgency.active.coefficient");
1194 Task::urgencyScheduledCoefficient = config.getReal ("urgency.scheduled.coefficient");
1195 Task::urgencyWaitingCoefficient = config.getReal ("urgency.waiting.coefficient");
1196 Task::urgencyBlockedCoefficient = config.getReal ("urgency.blocked.coefficient");
1197 Task::urgencyAnnotationsCoefficient = config.getReal ("urgency.annotations.coefficient");
1198 Task::urgencyTagsCoefficient = config.getReal ("urgency.tags.coefficient");
1199 Task::urgencyDueCoefficient = config.getReal ("urgency.due.coefficient");
1200 Task::urgencyBlockingCoefficient = config.getReal ("urgency.blocking.coefficient");
1201 Task::urgencyAgeCoefficient = config.getReal ("urgency.age.coefficient");
1202 Task::urgencyAgeMax = config.getReal ("urgency.age.max");
1203
1204 // Tag- and project-specific coefficients.
1205 for (auto& var : config.all ())
1206 if (var.substr (0, 13) == "urgency.user." ||
1207 var.substr (0, 12) == "urgency.uda.")
1208 Task::coefficients[var] = config.getReal (var);
1209 }
1210
1211 ////////////////////////////////////////////////////////////////////////////////
createDefaultConfig()1212 void Context::createDefaultConfig ()
1213 {
1214 // Do we need to create a default rc?
1215 if (rc_file._data != "" && ! rc_file.exists ())
1216 {
1217 if (config.getBoolean ("confirmation") &&
1218 ! confirm ( format ("A configuration file could not be found in {1}\n\nWould you like a sample {2} created, so Taskwarrior can proceed?", home_dir, rc_file._data)))
1219 throw std::string ("Cannot proceed without rc file.");
1220
1221 // Override data.location in the defaults.
1222 auto loc = configurationDefaults.find ("data.location=~/.task");
1223 // loc+0^ +14^ +21^
1224
1225 Datetime now;
1226 std::stringstream contents;
1227 contents << "# [Created by "
1228 << PACKAGE_STRING
1229 << ' '
1230 << now.toString ("m/d/Y H:N:S")
1231 << "]\n"
1232 << configurationDefaults.substr (0, loc + 14)
1233 << data_dir._original
1234 << "\n\n# To use the default location of the XDG directories,\n"
1235 << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and uncomment below\n"
1236 << "\n#data.location=~/.local/share/task\n"
1237 << "#hooks.location=~/.config/task/hooks\n"
1238 << "\n# Color theme (uncomment one to use)\n"
1239 << "#include light-16.theme\n"
1240 << "#include light-256.theme\n"
1241 << "#include dark-16.theme\n"
1242 << "#include dark-256.theme\n"
1243 << "#include dark-red-256.theme\n"
1244 << "#include dark-green-256.theme\n"
1245 << "#include dark-blue-256.theme\n"
1246 << "#include dark-violets-256.theme\n"
1247 << "#include dark-yellow-green.theme\n"
1248 << "#include dark-gray-256.theme\n"
1249 << "#include dark-gray-blue-256.theme\n"
1250 << "#include solarized-dark-256.theme\n"
1251 << "#include solarized-light-256.theme\n"
1252 << "#include no-color.theme\n"
1253 << '\n';
1254
1255 // Write out the new file.
1256 if (! File::write (rc_file._data, contents.str ()))
1257 throw format ("Could not write to '{1}'.", rc_file._data);
1258 }
1259
1260 // Create data location, if necessary.
1261 Directory d (data_dir);
1262 if (! d.exists ())
1263 {
1264 if (config.getBoolean ("exit.on.missing.db"))
1265 throw std::string ("Error: rc.data.location does not exist - exiting according to rc.exit.on.missing.db setting.");
1266
1267 d.create ();
1268
1269 if (config.has ("hooks.location"))
1270 d = Directory (config.get ("hooks.location"));
1271 else
1272 d += "hooks";
1273
1274 d.create ();
1275 }
1276 }
1277
1278 ////////////////////////////////////////////////////////////////////////////////
decomposeSortField(const std::string & field,std::string & key,bool & ascending,bool & breakIndicator)1279 void Context::decomposeSortField (
1280 const std::string& field,
1281 std::string& key,
1282 bool& ascending,
1283 bool& breakIndicator)
1284 {
1285 int length = field.length ();
1286
1287 int decoration = 1;
1288 breakIndicator = false;
1289 if (field[length - decoration] == '/')
1290 {
1291 breakIndicator = true;
1292 ++decoration;
1293 }
1294
1295 if (field[length - decoration] == '+')
1296 {
1297 ascending = true;
1298 key = field.substr (0, length - decoration);
1299 }
1300 else if (field[length - decoration] == '-')
1301 {
1302 ascending = false;
1303 key = field.substr (0, length - decoration);
1304 }
1305 else
1306 {
1307 ascending = true;
1308 key = field;
1309 }
1310 }
1311
1312 ////////////////////////////////////////////////////////////////////////////////
debugTiming(const std::string & details,const Timer & timer)1313 void Context::debugTiming (const std::string& details, const Timer& timer)
1314 {
1315 std::stringstream out;
1316 out << "Timer "
1317 << details
1318 << ' '
1319 << std::setprecision (6)
1320 << std::fixed
1321 << timer.total_us () / 1.0e6
1322 << " sec";
1323 debug (out.str ());
1324 }
1325
1326 ////////////////////////////////////////////////////////////////////////////////
1327 // This capability is to answer the question of 'what did I just do to generate
1328 // this output?'.
updateXtermTitle()1329 void Context::updateXtermTitle ()
1330 {
1331 if (config.getBoolean ("xterm.title") && isatty (STDOUT_FILENO))
1332 {
1333 auto command = cli2.getCommand ();
1334 std::string title;
1335
1336 for (auto a = cli2._args.begin (); a != cli2._args.end (); ++a)
1337 {
1338 if (a != cli2._args.begin ())
1339 title += ' ';
1340
1341 title += a->attribute ("raw");
1342 }
1343
1344 std::cout << "]0;task " << command << ' ' << title << "";
1345 }
1346 }
1347
1348 ////////////////////////////////////////////////////////////////////////////////
1349 // This function allows a clean output if the command is a helper subcommand.
updateVerbosity()1350 void Context::updateVerbosity ()
1351 {
1352 auto command = cli2.getCommand ();
1353 if (command != "" &&
1354 command[0] == '_')
1355 {
1356 verbosity = {"nothing"};
1357 }
1358 }
1359
1360 ////////////////////////////////////////////////////////////////////////////////
loadAliases()1361 void Context::loadAliases ()
1362 {
1363 for (auto& i : config)
1364 if (i.first.substr (0, 6) == "alias.")
1365 cli2.alias (i.first.substr (6), i.second);
1366 }
1367
1368 ////////////////////////////////////////////////////////////////////////////////
1369 // Using the general rc.debug setting automaticalls sets debug.tls, debug.hooks
1370 // and debug.parser, unless they already have values, which by default they do
1371 // not.
propagateDebug()1372 void Context::propagateDebug ()
1373 {
1374 if (config.getBoolean ("debug"))
1375 {
1376 if (! config.has ("debug.tls"))
1377 config.set ("debug.tls", 2);
1378
1379 if (! config.has ("debug.hooks"))
1380 config.set ("debug.hooks", 1);
1381
1382 if (! config.has ("debug.parser"))
1383 config.set ("debug.parser", 1);
1384 }
1385 else
1386 {
1387 if ((config.has ("debug.hooks") && config.getInteger ("debug.hooks")) ||
1388 (config.has ("debug.parser") && config.getInteger ("debug.parser")) )
1389 config.set ("debug", true);
1390 }
1391 }
1392
1393 ////////////////////////////////////////////////////////////////////////////////
1394 // No duplicates.
header(const std::string & input)1395 void Context::header (const std::string& input)
1396 {
1397 if (input.length () &&
1398 std::find (headers.begin (), headers.end (), input) == headers.end ())
1399 headers.push_back (input);
1400 }
1401
1402 ////////////////////////////////////////////////////////////////////////////////
1403 // No duplicates.
footnote(const std::string & input)1404 void Context::footnote (const std::string& input)
1405 {
1406 if (input.length () &&
1407 std::find (footnotes.begin (), footnotes.end (), input) == footnotes.end ())
1408 footnotes.push_back (input);
1409 }
1410
1411 ////////////////////////////////////////////////////////////////////////////////
1412 // No duplicates.
error(const std::string & input)1413 void Context::error (const std::string& input)
1414 {
1415 if (input.length () &&
1416 std::find (errors.begin (), errors.end (), input) == errors.end ())
1417 errors.push_back (input);
1418 }
1419
1420 ////////////////////////////////////////////////////////////////////////////////
debug(const std::string & input)1421 void Context::debug (const std::string& input)
1422 {
1423 if (input.length ())
1424 debugMessages.push_back (input);
1425 }
1426
1427 ////////////////////////////////////////////////////////////////////////////////
1428
1429 // vim ts=2:sw=2
1430