• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 12358478612

16 Dec 2024 05:59PM UTC coverage: 84.898% (-0.6%) from 85.522%
12358478612

push

github

web-flow
[pre-commit.ci] pre-commit autoupdate (#3725)

updates:
- [github.com/pre-commit/mirrors-clang-format: v19.1.4 → v19.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.4...v19.1.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

19276 of 22705 relevant lines covered (84.9%)

23265.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/src/Context.cpp
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
// cmake.h include header must come first
29

30
#include <Context.h>
31
#include <Datetime.h>
32
#include <Duration.h>
33
#include <Eval.h>
34
#include <FS.h>
35
#include <Variant.h>
36
#include <Version.h>
37
#include <assert.h>
38
#include <format.h>
39
#include <main.h>
40
#include <rust/cxx.h>
41
#include <shared.h>
42
#include <stdlib.h>
43
#include <string.h>
44
#include <taskchampion-cpp/lib.h>
45
#include <unistd.h>
46

47
#include <algorithm>
48
#include <fstream>
49
#include <iomanip>
50
#include <iostream>
51
#include <regex>
52
#include <sstream>
53

54
#ifdef HAVE_COMMIT
55
#include <commit.h>
56
#endif
57

58
#include <stdio.h>
59
#include <sys/ioctl.h>
60

61
#ifdef SOLARIS
62
#include <sys/termios.h>
63
#endif
64

65
////////////////////////////////////////////////////////////////////////////////
66
// This string is parsed and used as default values for configuration.
67
// Note: New configuration options should be added to the vim syntax file in
68
// scripts/vim/syntax/taskrc.vim
69
std::string configurationDefaults =
70
    "# Taskwarrior program configuration file.\n"
71
    "# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',\n"
72
    "# 'man task-sync' or 'man taskrc'\n"
73
    "\n"
74
    "# Here is an example of entries that use the default, override and blank values\n"
75
    "#   variable=foo   -- By specifying a value, this overrides the default\n"
76
    "#   variable=      -- By specifying no value, this means no default\n"
77
    "#   #variable=foo  -- By commenting out the line, or deleting it, this uses the default\n"
78
    "\n"
79
    "# You can also refence environment variables:\n"
80
    "#   variable=$HOME/task\n"
81
    "#   variable=$VALUE\n"
82
    "\n"
83
    "# Use the command 'task show' to see all defaults and overrides\n"
84
    "\n"
85
    "# Files\n"
86
    "data.location=~/.task\n"
87
    "gc=1                                           # Garbage-collect data files - DO NOT CHANGE "
88
    "unless you are sure\n"
89
    "exit.on.missing.db=0                           # Whether to exit if ~/.task is not found\n"
90
    "hooks=1                                        # Master control switch for hooks\n"
91
    "\n"
92
    "# Terminal\n"
93
    "detection=1                                    # Detects terminal width\n"
94
    "defaultwidth=80                                # Without detection, assumed width\n"
95
    "defaultheight=24                               # Without detection, assumed height\n"
96
    "avoidlastcolumn=0                              # Fixes Cygwin width problem\n"
97
    "hyphenate=1                                    # Hyphenates lines wrapped on non-word-breaks\n"
98
    "#editor=vi                                     # Preferred text editor\n"
99
    "reserved.lines=1                               # Assume a 1-line prompt\n"
100
    "\n"
101
    "# Miscellaneous\n"
102
    "# verbose=                                     # Comma-separated list.  May contain any "
103
    "subset of:\n"
104
    "# "
105
    "affected,blank,context,default,edit,filter,footnote,header,label,new-id,new-uuid,news,"
106
    "override,project,recur,special,sync\n"
107
    "verbose=affected,blank,context,edit,header,footnote,label,new-id,news,project,special,sync,"
108
    "override,recur\n"
109
    "confirmation=1                                 # Confirmation on delete, big changes\n"
110
    "recurrence=1                                   # Enable recurrence\n"
111
    "recurrence.confirmation=prompt                 # Confirmation for propagating changes among "
112
    "recurring tasks (yes/no/prompt)\n"
113
    "allow.empty.filter=1                           # An empty filter gets a warning and requires "
114
    "confirmation\n"
115
    "indent.annotation=2                            # Indent spaces for annotations\n"
116
    "indent.report=0                                # Indent spaces for whole report\n"
117
    "row.padding=0                                  # Left and right padding for each row of "
118
    "report\n"
119
    "column.padding=1                               # Spaces between each column in a report\n"
120
    "bulk=3                                         # 3 or more tasks considered a bulk change and "
121
    "is confirmed\n"
122
    "nag=You have more urgent tasks.                # Nag message to keep you honest\n"  // TODO
123
    "search.case.sensitive=1                        # Setting to no allows case insensitive "
124
    "searches\n"
125
    "active.indicator=*                             # What to show as an active task indicator\n"
126
    "tag.indicator=+                                # What to show as a tag indicator\n"
127
    "dependency.indicator=D                         # What to show as a dependency indicator\n"
128
    "recurrence.indicator=R                         # What to show as a task recurrence indicator\n"
129
    "recurrence.limit=1                             # Number of future recurring pending tasks\n"
130
    "regex=1                                        # Assume all search/filter strings are "
131
    "regexes\n"
132
    "xterm.title=0                                  # Sets xterm title for some commands\n"
133
    "expressions=infix                              # Prefer infix over postfix expressions\n"
134
    "json.array=1                                   # Enclose JSON output in [ ]\n"
135
    "abbreviation.minimum=2                         # Shortest allowed abbreviation\n"
136
    "news.version=                                  # Latest version highlights read by the user\n"
137
    "purge.on-sync=0                                # Purge old tasks on sync\n"
138
    "\n"
139
    "# Dates\n"
140
    "dateformat=Y-M-D                               # Preferred input and display date format\n"
141
    "dateformat.holiday=YMD                         # Preferred input date format for holidays\n"
142
    "dateformat.edit=Y-M-D H:N:S                    # Preferred display date format when editing\n"
143
    "dateformat.info=Y-M-D H:N:S                    # Preferred display date format for "
144
    "information\n"
145
    "dateformat.report=                             # Preferred display date format for reports\n"
146
    "dateformat.annotation=                         # Preferred display date format for "
147
    "annotations\n"
148
    "date.iso=1                                     # Enable ISO date support\n"
149
    "weekstart=sunday                               # Sunday or Monday only\n"
150
    "displayweeknumber=1                            # Show week numbers on calendar\n"
151
    "due=7                                          # Task is considered due in 7 days\n"
152
    "\n"
153
    "# Calendar controls\n"
154
    "calendar.legend=1                              # Display the legend on calendar\n"
155
    "calendar.details=sparse                        # Calendar shows information for tasks w/due "
156
    "dates: full, sparse or none\n"
157
    "calendar.details.report=list                   # Report to use when showing task information "
158
    "in cal\n"
159
    "calendar.offset=0                              # Apply an offset value to control the first "
160
    "month of the calendar\n"
161
    "calendar.offset.value=-1                       # The number of months the first month of the "
162
    "calendar is moved\n"
163
    "calendar.holidays=none                         # Show public holidays on calendar:full, "
164
    "sparse or none\n"
165
    "#calendar.monthsperline=3                      # Number of calendar months on a line\n"
166
    "\n"
167
    "# Journal controls\n"
168
    "journal.time=0                                 # Record start/stop commands as annotation\n"
169
    "journal.time.start.annotation=Started task     # Annotation description for the start journal "
170
    "entry\n"
171
    "journal.time.stop.annotation=Stopped task      # Annotation description for the stop  journal "
172
    "entry\n"
173
    "journal.info=1                                 # Display task journal with info command\n"
174
    "\n"
175
    "# Dependency controls\n"
176
    "dependency.reminder=1                          # Nags on dependency chain violations\n"
177
    "dependency.confirmation=1                      # Should dependency chain repair be "
178
    "confirmed?\n"
179
    "\n"
180
    "# Urgency Coefficients\n"
181
    "urgency.user.tag.next.coefficient=15.0         # Urgency coefficient for 'next' special tag\n"
182
    "urgency.due.coefficient=12.0                   # Urgency coefficient for due dates\n"
183
    "urgency.blocking.coefficient=8.0               # Urgency coefficient for blocking tasks\n"
184
    "urgency.active.coefficient=4.0                 # Urgency coefficient for active tasks\n"
185
    "urgency.scheduled.coefficient=5.0              # Urgency coefficient for scheduled tasks\n"
186
    "urgency.age.coefficient=2.0                    # Urgency coefficient for age\n"
187
    "urgency.annotations.coefficient=1.0            # Urgency coefficient for annotations\n"
188
    "urgency.tags.coefficient=1.0                   # Urgency coefficient for tags\n"
189
    "urgency.project.coefficient=1.0                # Urgency coefficient for projects\n"
190
    "urgency.blocked.coefficient=-5.0               # Urgency coefficient for blocked tasks\n"
191
    "urgency.waiting.coefficient=-3.0               # Urgency coefficient for waiting status\n"
192
    "urgency.inherit=0                              # Recursively inherit highest urgency value "
193
    "from blocked tasks\n"
194
    "urgency.age.max=365                            # Maximum age in days\n"
195
    "\n"
196
    "#urgency.user.project.foo.coefficient=5.0      # Urgency coefficients for 'foo' project\n"
197
    "#urgency.user.tag.foo.coefficient=5.0          # Urgency coefficients for 'foo' tag\n"
198
    "#urgency.uda.foo.coefficient=5.0               # Urgency coefficients for UDA 'foo'\n"
199
    "\n"
200
    "# Color controls.\n"
201
    "color=1                                        # Enable color\n"
202
    "\n"
203
    "# Here is the rule precedence order, highest to lowest.\n"
204
    "# Note that these are just the color rule names, without the leading 'color.'\n"
205
    "#      and any trailing '.value'.\n"
206
    "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due."
207
    "today,due,blocked,blocking,recurring,tagged,uda.\n"
208
    "\n"
209
    "# General decoration\n"
210
    "rule.color.merge=1\n"
211
    "color.label=\n"
212
    "color.label.sort=\n"
213
    "color.alternate=on gray2\n"
214
    "color.header=color3\n"
215
    "color.footnote=color3\n"
216
    "color.warning=bold red\n"
217
    "color.error=white on red\n"
218
    "color.debug=color4\n"
219
    "\n"
220
    "# Task state\n"
221
    "color.completed=\n"
222
    "color.deleted=\n"
223
    "color.active=rgb555 on rgb410\n"
224
    "color.recurring=rgb013\n"
225
    "color.scheduled=on rgb001\n"
226
    "color.until=\n"
227
    "color.blocked=white on color8\n"
228
    "color.blocking=black on color15\n"
229
    "\n"
230
    "# Project\n"
231
    "color.project.none=\n"
232
    "\n"
233
    "# Priority UDA\n"
234
    "color.uda.priority.H=color255\n"
235
    "color.uda.priority.L=color245\n"
236
    "color.uda.priority.M=color250\n"
237
    "\n"
238
    "# Tags\n"
239
    "color.tag.next=rgb440\n"
240
    "color.tag.none=\n"
241
    "color.tagged=rgb031\n"
242
    "\n"
243
    "# Due\n"
244
    "color.due.today=rgb400\n"
245
    "color.due=color1\n"
246
    "color.overdue=color9\n"
247
    "\n"
248
    "# Report: burndown\n"
249
    "color.burndown.done=on rgb010\n"
250
    "color.burndown.pending=on color9\n"
251
    "color.burndown.started=on color11\n"
252
    "\n"
253
    "# Report: history\n"
254
    "color.history.add=color0 on rgb500\n"
255
    "color.history.delete=color0 on rgb550\n"
256
    "color.history.done=color0 on rgb050\n"
257
    "\n"
258
    "# Report: summary\n"
259
    "color.summary.background=white on color0\n"
260
    "color.summary.bar=black on rgb141\n"
261
    "\n"
262
    "# Command: calendar\n"
263
    "color.calendar.due.today=color15 on color1\n"
264
    "color.calendar.due=color0 on color1\n"
265
    "color.calendar.holiday=color0 on color11\n"
266
    "color.calendar.scheduled=rgb013 on color15\n"
267
    "color.calendar.overdue=color0 on color9\n"
268
    "color.calendar.today=color15 on rgb013\n"
269
    "color.calendar.weekend=on color235\n"
270
    "color.calendar.weeknumber=rgb013\n"
271
    "\n"
272
    "# Command: sync\n"
273
    "color.sync.added=rgb010\n"
274
    "color.sync.changed=color11\n"
275
    "color.sync.rejected=color9\n"
276
    "\n"
277
    "# Command: undo\n"
278
    "color.undo.after=color2\n"
279
    "color.undo.before=color1\n"
280
    "\n"
281
    "# UDA priority\n"
282
    "uda.priority.type=string                       # UDA priority is a string type\n"
283
    "uda.priority.label=Priority                    # UDA priority has a display label'\n"
284
    "uda.priority.values=H,M,L,                     # UDA priority values are 'H', 'M', 'L' or ''\n"
285
    "                                               # UDA priority sorting is 'H' > 'M' > 'L' > '' "
286
    "(highest to lowest)\n"
287
    "#uda.priority.default=M                        # UDA priority default value of 'M'\n"
288
    "urgency.uda.priority.H.coefficient=6.0         # UDA priority coefficient for value 'H'\n"
289
    "urgency.uda.priority.M.coefficient=3.9         # UDA priority coefficient for value 'M'\n"
290
    "urgency.uda.priority.L.coefficient=1.8         # UDA priority coefficient for value 'L'\n"
291
    "\n"
292
    "#default.project=foo                           # Default project for 'add' command\n"
293
    "#default.due=eom                               # Default due date for 'add' command\n"
294
    "#default.scheduled=eom                         # Default scheduled date for 'add' command\n"
295
    "default.command=next                           # When no arguments are specified\n"
296
    "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and "
297
    "end.after:now-4wks )\n"
298
    "\n"
299
    "_forcecolor=0                                  # Forces color to be on, even for non TTY "
300
    "output\n"
301
    "complete.all.tags=0                            # Include old tag names in '_ags' command\n"
302
    "list.all.projects=0                            # Include old project names in 'projects' "
303
    "command\n"
304
    "summary.all.projects=0                         # Include old project names in 'summary' "
305
    "command\n"
306
    "list.all.tags=0                                # Include old tag names in 'tags' command\n"
307
    "print.empty.columns=0                          # Print columns which have no data for any "
308
    "task\n"
309
    "debug=0                                        # Display diagnostics\n"
310
    "sugar=1                                        # Syntactic sugar\n"
311
    "obfuscate=0                                    # Obfuscate data for error reporting\n"
312
    "fontunderline=1                                # Uses underlines rather than -------\n"
313
    "\n"
314
    "# WARNING: Please read the documentation (man task-sync) before setting up\n"
315
    "#          Taskwarrior for Taskserver synchronization.\n"
316
    "\n"
317
    "#sync.encryption_secret                        # Encryption secret for sync to a server\n"
318
    "#sync.server.client_id                         # Client ID for sync to a server\n"
319
    "#sync.server.url                               # URL of the sync server\n"
320
    "#sync.local.server_dir                         # Directory for local sync\n"
321
    "#sync.gcp.credential_path                      # Path to JSON file containing credentials to "
322
    "authenticate GCP Sync\n"
323
    "#sync.gcp.bucket                               # Bucket for sync to GCP\n"
324
    "\n"
325
    "# Aliases - alternate names for commands\n"
326
    "alias.rm=delete                                # Alias for the delete command\n"
327
    "alias.history=history.monthly                  # Prefer monthly over annual history reports\n"
328
    "alias.ghistory=ghistory.monthly                # Prefer monthly graphical over annual history "
329
    "reports\n"
330
    "alias.burndown=burndown.weekly                 # Prefer the weekly burndown chart\n"
331
    "\n"
332
    "# Reports\n"
333
    "\n"
334
    "report.long.description=All details of tasks\n"
335
    "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until,"
336
    "Description\n"
337
    "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur,"
338
    "wait.remaining,scheduled,due,until,description\n"
339
    "report.long.filter=status:pending -WAITING\n"
340
    "report.long.sort=modified-\n"
341
    "report.long.context=1\n"
342
    "\n"
343
    "report.list.description=Most details of tasks\n"
344
    "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n"
345
    "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
346
    "indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n"
347
    "report.list.filter=status:pending -WAITING\n"
348
    "report.list.sort=start-,due+,project+,urgency-\n"
349
    "report.list.context=1\n"
350
    "\n"
351
    "report.ls.description=Few details of tasks\n"
352
    "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n"
353
    "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait."
354
    "remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n"
355
    "report.ls.filter=status:pending -WAITING\n"
356
    "report.ls.sort=start-,description+\n"
357
    "report.ls.context=1\n"
358
    "\n"
359
    "report.minimal.description=Minimal details of tasks\n"
360
    "report.minimal.labels=ID,Project,Tags,Description\n"
361
    "report.minimal.columns=id,project,tags.count,description.count\n"
362
    "report.minimal.filter=status:pending -WAITING\n"
363
    "report.minimal.sort=project+/,description+\n"
364
    "report.minimal.context=1\n"
365
    "\n"
366
    "report.newest.description=Newest tasks\n"
367
    "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
368
    "Description\n"
369
    "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
370
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
371
    "report.newest.filter=status:pending -WAITING\n"
372
    "report.newest.sort=entry-\n"
373
    "report.newest.context=1\n"
374
    "\n"
375
    "report.oldest.description=Oldest tasks\n"
376
    "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
377
    "Description\n"
378
    "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
379
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
380
    "report.oldest.filter=status:pending -WAITING\n"
381
    "report.oldest.sort=entry+\n"
382
    "report.oldest.context=1\n"
383
    "\n"
384
    "report.overdue.description=Overdue tasks\n"
385
    "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n"
386
    "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator,"
387
    "scheduled.countdown,due,until,description,urgency\n"
388
    "report.overdue.filter=status:pending -WAITING +OVERDUE\n"
389
    "report.overdue.sort=urgency-,due+\n"
390
    "report.overdue.context=1\n"
391
    "\n"
392
    "report.active.description=Active tasks\n"
393
    "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until,"
394
    "Description\n"
395
    "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags,"
396
    "recur,wait,scheduled.remaining,due,until,description\n"
397
    "report.active.filter=status:pending -WAITING +ACTIVE\n"
398
    "report.active.sort=project+,start+\n"
399
    "report.active.context=1\n"
400
    "\n"
401
    "report.completed.description=Completed tasks\n"
402
    "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n"
403
    "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags,"
404
    "recur.indicator,due,description\n"
405
    "report.completed.filter=status:completed -WAITING \n"
406
    "report.completed.sort=end+\n"
407
    "report.completed.context=1\n"
408
    "\n"
409
    "report.recurring.description=Recurring Tasks\n"
410
    "report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description,"
411
    "Urg\n"
412
    "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short,"
413
    "project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
414
    "report.recurring.filter=(status:pending -WAITING +CHILD) or (status:recurring -WAITING "
415
    "+PARENT)\n"
416
    "report.recurring.sort=due+,urgency-,entry+\n"
417
    "report.recurring.context=1\n"
418
    "\n"
419
    "report.waiting.description=Waiting (hidden) tasks\n"
420
    "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n"
421
    "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags,"
422
    "recur.indicator,wait,wait.remaining,scheduled,due,until,description\n"
423
    "report.waiting.filter=+WAITING\n"
424
    "report.waiting.sort=due+,wait+,entry+\n"
425
    "report.waiting.context=1\n"
426
    "\n"
427
    "report.all.description=All tasks\n"
428
    "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
429
    "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends."
430
    "indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled."
431
    "remaining,due,until.remaining,description\n"
432
    "report.all.sort=entry-\n"
433
    "report.all.context=1\n"
434
    "\n"
435
    "report.next.description=Most urgent tasks\n"
436
    "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n"
437
    "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled."
438
    "countdown,due.relative,until.remaining,description,urgency\n"
439
    "report.next.filter=status:pending -WAITING limit:page\n"
440
    "report.next.sort=urgency-\n"
441
    "report.next.context=1\n"
442
    "\n"
443
    "report.ready.description=Most urgent actionable tasks\n"
444
    "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n"
445
    "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
446
    "indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n"
447
    "report.ready.filter=+READY\n"
448
    "report.ready.sort=start-,urgency-\n"
449
    "report.ready.context=1\n"
450
    "\n"
451
    "report.blocked.description=Blocked tasks\n"
452
    "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
453
    "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
454
    "report.blocked.sort=due+,priority-,start-,project+\n"
455
    "report.blocked.filter=status:pending -WAITING +BLOCKED\n"
456
    "report.blocked.context=1\n"
457
    "\n"
458
    "report.unblocked.description=Unblocked tasks\n"
459
    "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
460
    "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
461
    "report.unblocked.sort=due+,priority-,start-,project+\n"
462
    "report.unblocked.filter=status:pending -WAITING -BLOCKED\n"
463
    "report.unblocked.context=1\n"
464
    "\n"
465
    "report.blocking.description=Blocking tasks\n"
466
    "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n"
467
    "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled."
468
    "remaining,due.relative,until.remaining,description.count,urgency\n"
469
    "report.blocking.sort=urgency-,due+,entry+\n"
470
    "report.blocking.filter=status:pending -WAITING +BLOCKING\n"
471
    "report.blocking.context=1\n"
472
    "\n"
473
    "report.timesheet.filter=(+PENDING -WAITING start.after:now-4wks) or (+COMPLETED -WAITING "
474
    "end.after:now-4wks)\n"
475
    "report.timesheet.context=0\n"
476
    "\n";
477

478
// Supported modifiers, synonyms on the same line.
479
static const char* modifierNames[] = {
480
    "before",     "under", "below",    "after", "over", "above", "by",       "none",
481
    "any",        "is",    "equals",   "isnt",  "not",  "has",   "contains", "hasnt",
482
    "startswith", "left",  "endswith", "right", "word", "noword"};
483

484
Context* Context::context;
485

486
////////////////////////////////////////////////////////////////////////////////
487
Context& Context::getContext() {
1,218,327✔
488
  assert(Context::context);
1,218,327✔
489
  return *Context::context;
1,218,327✔
490
}
491

492
////////////////////////////////////////////////////////////////////////////////
493
void Context::setContext(Context* context) { Context::context = context; }
4,502✔
494

495
////////////////////////////////////////////////////////////////////////////////
496
Context::~Context() {
4,533✔
497
  for (auto& com : commands) delete com.second;
409,508✔
498

499
  for (auto& col : columns) delete col.second;
113,115✔
500
}
4,533✔
501

502
////////////////////////////////////////////////////////////////////////////////
503
int Context::initialize(int argc, const char** argv) {
4,496✔
504
  timer_total.start();
4,496✔
505
  int rc = 0;
4,496✔
506
  home_dir = getenv("HOME");
4,496✔
507

508
  std::vector<std::string> searchPaths{TASK_RCDIR};
13,488✔
509

510
  try {
511
    ////////////////////////////////////////////////////////////////////////////
512
    //
513
    // [1] Load the correct config file.
514
    //     - Default to ~/.taskrc (ctor).
515
    //     - If no ~/.taskrc, use $XDG_CONFIG_HOME/task/taskrc if exists, or
516
    //       ~/.config/task/taskrc if $XDG_CONFIG_HOME is unset
517
    //     - Allow $TASKRC override.
518
    //     - Allow command line override rc:<file>
519
    //     - Load resultant file.
520
    //     - Apply command line overrides to the config.
521
    //
522
    ////////////////////////////////////////////////////////////////////////////
523

524
    bool taskrc_overridden = false;
4,496✔
525

526
    // XDG_CONFIG_HOME doesn't count as an override (no warning header)
527
    if (!rc_file.exists()) {
4,496✔
528
      // Use XDG_CONFIG_HOME if defined, otherwise default to ~/.config
529
      std::string xdg_config_home;
4,496✔
530
      const char* env_xdg_config_home = getenv("XDG_CONFIG_HOME");
4,496✔
531

532
      if (env_xdg_config_home)
4,496✔
533
        xdg_config_home = format("{1}", env_xdg_config_home);
8,992✔
534
      else
535
        xdg_config_home = format("{1}/.config", home_dir);
×
536

537
      // Ensure the path does not end with '/'
538
      if (xdg_config_home.back() == '/') xdg_config_home.pop_back();
4,496✔
539

540
      // https://github.com/GothenburgBitFactory/libshared/issues/32
541
      std::string rcfile_path = format("{1}/task/taskrc", xdg_config_home);
13,488✔
542

543
      File maybe_rc_file = File(rcfile_path);
4,496✔
544
      if (maybe_rc_file.exists()) rc_file = maybe_rc_file;
4,496✔
545
    }
4,496✔
546

547
    char* override = getenv("TASKRC");
4,496✔
548
    if (override) {
4,496✔
549
      rc_file = File(override);
4,385✔
550
      taskrc_overridden = true;
4,385✔
551
    }
552

553
    taskrc_overridden = CLI2::getOverride(argc, argv, rc_file) || taskrc_overridden;
4,496✔
554

555
    // Artificial scope for timing purposes.
556
    {
557
      Timer timer;
4,496✔
558
      config.parse(configurationDefaults, 1, searchPaths);
4,496✔
559
      config.load(rc_file._data, 1, searchPaths);
4,496✔
560
      debugTiming(format("Config::load ({1})", rc_file._data), timer);
13,488✔
561
    }
562

563
    CLI2::applyOverrides(argc, argv);
4,496✔
564

565
    if (taskrc_overridden && verbose("override"))
13,488✔
566
      header(format("TASKRC override: {1}", rc_file._data));
12,360✔
567

568
    ////////////////////////////////////////////////////////////////////////////
569
    //
570
    // [2] Locate the data directory.
571
    //     - Default to ~/.task (ctor).
572
    //     - Allow $TASKDATA override.
573
    //     - Allow command line override rc.data.location:<dir>
574
    //     - Inform TDB2 where to find data.
575
    //     - Create the rc_file and data_dir, if necessary.
576
    //
577
    ////////////////////////////////////////////////////////////////////////////
578

579
    bool taskdata_overridden = false;
4,496✔
580

581
    override = getenv("TASKDATA");
4,496✔
582
    if (override) {
4,496✔
583
      data_dir = Directory(override);
4,493✔
584
      config.set("data.location", data_dir._data);
8,986✔
585
      taskdata_overridden = true;
4,493✔
586
    }
587

588
    taskdata_overridden = CLI2::getDataLocation(argc, argv, data_dir) || taskdata_overridden;
4,496✔
589

590
    if (taskdata_overridden && verbose("override"))
13,484✔
591
      header(format("TASKDATA override: {1}", data_dir._data));
12,354✔
592

593
    createDefaultConfig();
4,496✔
594

595
    bool create_if_missing = !config.getBoolean("exit.on.missing.db");
8,992✔
596
    tdb2.open_replica(data_dir, create_if_missing);
4,497✔
597

598
    ////////////////////////////////////////////////////////////////////////////
599
    //
600
    // [3] Instantiate Command objects and capture command entities.
601
    //
602
    ////////////////////////////////////////////////////////////////////////////
603

604
    Command::factory(commands);
4,495✔
605
    for (auto& cmd : commands) cli2.entity("cmd", cmd.first);
814,445✔
606

607
    ////////////////////////////////////////////////////////////////////////////
608
    //
609
    // [4] Instantiate Column objects and capture column entities.
610
    //
611
    ////////////////////////////////////////////////////////////////////////////
612

613
    Column::factory(columns);
4,495✔
614
    for (auto& col : columns) cli2.entity("attribute", col.first);
221,659✔
615

616
    cli2.entity("pseudo", "limit");
17,980✔
617

618
    ////////////////////////////////////////////////////////////////////////////
619
    //
620
    // [5] Capture modifier and operator entities.
621
    //
622
    ////////////////////////////////////////////////////////////////////////////
623

624
    for (auto& modifierName : modifierNames) cli2.entity("modifier", modifierName);
400,055✔
625

626
    for (auto& op : Eval::getOperators()) cli2.entity("operator", op);
238,235✔
627

628
    for (auto& op : Eval::getBinaryOperators()) cli2.entity("binary_operator", op);
193,285✔
629

630
    ////////////////////////////////////////////////////////////////////////////
631
    //
632
    // [6] Complete the Context initialization.
633
    //
634
    ////////////////////////////////////////////////////////////////////////////
635

636
    initializeColorRules();
4,495✔
637
    staticInitialization();
4,495✔
638
    propagateDebug();
4,495✔
639
    loadAliases();
4,495✔
640

641
    ////////////////////////////////////////////////////////////////////////////
642
    //
643
    // [7] Parse the command line.
644
    //
645
    ////////////////////////////////////////////////////////////////////////////
646

647
    for (int i = 0; i < argc; i++) cli2.add(argv[i]);
39,147✔
648

649
    cli2.analyze();
4,495✔
650

651
    // Extract a recomposed command line.
652
    auto foundDefault = false;
4,493✔
653
    auto foundAssumed = false;
4,493✔
654
    std::string combined;
4,493✔
655
    for (auto& a : cli2._args) {
24,037✔
656
      if (combined.length()) combined += ' ';
19,544✔
657

658
      combined += a.attribute("raw");
39,088✔
659

660
      if (a.hasTag("DEFAULT")) foundDefault = true;
39,088✔
661

662
      if (a.hasTag("ASSUMED")) foundAssumed = true;
39,088✔
663
    }
664

665
    if (verbose("default")) {
8,986✔
666
      if (foundDefault) header("[" + combined + "]");
8✔
667

668
      if (foundAssumed) header("No command specified - assuming 'information'.");
10✔
669
    }
670

671
    ////////////////////////////////////////////////////////////////////////////
672
    //
673
    // [8] Initialize hooks.
674
    //
675
    ////////////////////////////////////////////////////////////////////////////
676

677
    hooks.initialize();
4,493✔
678
  }
4,493✔
679

680
  catch (const std::string& message) {
3✔
681
    error(message);
2✔
682
    rc = 2;
2✔
683
  }
2✔
684

685
  catch (rust::Error& err) {
1✔
686
    error(err.what());
1✔
687
    rc = 2;
1✔
688
  }
1✔
689

690
  catch (int) {
×
691
    // Hooks can terminate processing by throwing integers.
692
    rc = 4;
×
693
  }
×
694

695
  catch (const std::regex_error& e) {
×
696
    std::cout << "regex_error caught: " << e.what() << '\n';
×
697
  } catch (...) {
×
698
    error("Unknown error. Please report.");
×
699
    rc = 3;
×
700
  }
×
701

702
  // On initialization failure...
703
  if (rc) {
4,496✔
704
    // Dump all debug messages, controlled by rc.debug.
705
    if (config.getBoolean("debug")) {
9✔
706
      for (auto& d : debugMessages)
×
707
        if (color())
×
708
          std::cerr << colorizeDebug(d) << '\n';
×
709
        else
710
          std::cerr << d << '\n';
×
711
    }
712

713
    // Dump all headers, controlled by 'header' verbosity token.
714
    if (verbose("header")) {
6✔
715
      for (auto& h : headers)
9✔
716
        if (color())
6✔
717
          std::cerr << colorizeHeader(h) << '\n';
×
718
        else
719
          std::cerr << h << '\n';
6✔
720
    }
721

722
    // Dump all footnotes, controlled by 'footnote' verbosity token.
723
    if (verbose("footnote")) {
6✔
724
      for (auto& f : footnotes)
4✔
725
        if (color())
1✔
726
          std::cerr << colorizeFootnote(f) << '\n';
×
727
        else
728
          std::cerr << f << '\n';
1✔
729
    }
730

731
    // Dump all errors, non-maskable.
732
    // Colorized as footnotes.
733
    for (auto& e : errors)
6✔
734
      if (color())
3✔
735
        std::cerr << colorizeFootnote(e) << '\n';
×
736
      else
737
        std::cerr << e << '\n';
3✔
738
  }
739

740
  time_init_us += timer_total.total_us();
4,496✔
741
  return rc;
4,496✔
742
}
17,984✔
743

744
////////////////////////////////////////////////////////////////////////////////
745
int Context::run() {
4,493✔
746
  int rc;
747
  std::string output;
4,493✔
748

749
  try {
750
    hooks.onLaunch();
4,493✔
751
    rc = dispatch(output);
4,490✔
752
    hooks.onExit();  // No chance to update data.
4,014✔
753

754
    timer_total.stop();
4,012✔
755
    time_total_us += timer_total.total_us();
4,012✔
756

757
    std::stringstream s;
4,012✔
758
    s << "Perf " << PACKAGE_STRING << ' '
759
#ifdef HAVE_COMMIT
760
      << COMMIT
761
#else
762
      << '-'
763
#endif
764
      << ' ' << Datetime().toISO()
8,024✔
765

766
      << " init:" << time_init_us << " load:" << time_load_us
8,024✔
767
      << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us)
4,012✔
768
      << " filter:" << time_filter_us << " commit:" << time_commit_us << " sort:" << time_sort_us
4,012✔
769
      << " render:" << time_render_us << " hooks:" << time_hooks_us << " other:"
4,012✔
770
      << time_total_us - time_init_us - time_gc_us - time_filter_us - time_commit_us -
4,012✔
771
             time_sort_us - time_render_us - time_hooks_us
4,012✔
772
      << " total:" << time_total_us << '\n';
4,012✔
773
    debug(s.str());
4,012✔
774
  }
4,012✔
775

776
  catch (const std::string& message) {
481✔
777
    error(message);
463✔
778
    rc = 2;
463✔
779
  }
463✔
780

781
  catch (rust::Error& err) {
×
782
    error(err.what());
×
783
    rc = 2;
×
784
  }
×
785

786
  catch (int) {
18✔
787
    // Hooks can terminate processing by throwing integers.
788
    rc = 4;
18✔
789
  }
18✔
790

791
  catch (...) {
×
792
    error("Unknown error. Please report.");
×
793
    rc = 3;
×
794
  }
×
795

796
  // Dump all debug messages, controlled by rc.debug.
797
  if (config.getBoolean("debug")) {
13,479✔
798
    for (auto& d : debugMessages)
176✔
799
      if (color())
166✔
800
        std::cerr << colorizeDebug(d) << '\n';
8✔
801
      else
802
        std::cerr << d << '\n';
158✔
803
  }
804

805
  // Dump all headers, controlled by 'header' verbosity token.
806
  if (verbose("header")) {
8,986✔
807
    for (auto& h : headers)
11,382✔
808
      if (color())
7,582✔
809
        std::cerr << colorizeHeader(h) << '\n';
364✔
810
      else
811
        std::cerr << h << '\n';
7,218✔
812
  }
813

814
  // Dump the report output.
815
  std::cout << output;
4,493✔
816

817
  // Dump all footnotes, controlled by 'footnote' verbosity token.
818
  if (verbose("footnote")) {
8,986✔
819
    for (auto& f : footnotes)
5,124✔
820
      if (color())
1,258✔
821
        std::cerr << colorizeFootnote(f) << '\n';
69✔
822
      else
823
        std::cerr << f << '\n';
1,189✔
824
  }
825

826
  // Dump all errors, non-maskable.
827
  // Colorized as footnotes.
828
  for (auto& e : errors)
4,976✔
829
    if (color())
483✔
830
      std::cerr << colorizeError(e) << '\n';
1✔
831
    else
832
      std::cerr << e << '\n';
482✔
833

834
  return rc;
4,493✔
835
}
4,493✔
836

837
////////////////////////////////////////////////////////////////////////////////
838
// Dispatch to the command found by the CLI parser.
839
int Context::dispatch(std::string& out) {
4,490✔
840
  // Autocomplete args against keywords.
841
  std::string command = cli2.getCommand();
4,490✔
842
  if (command != "") {
4,490✔
843
    updateXtermTitle();
4,490✔
844
    updateVerbosity();
4,490✔
845

846
    Command* c = commands[command];
4,490✔
847
    assert(c);
4,490✔
848

849
    // The command know whether they need a GC.
850
    if (c->needs_gc()) {
4,490✔
851
      tdb2.gc();
913✔
852
    }
853

854
    // This is something that is only needed for write commands with no other
855
    // filter processing.
856
    if (c->accepts_modifications() && !c->accepts_filter()) {
4,490✔
857
      cli2.prepareFilter();
1,667✔
858
    }
859

860
    // With rc.debug.parser == 2, there are more tree dumps than you might want,
861
    // but we need the rc.debug.parser == 1 case covered also, with the final
862
    // tree.
863
    if (config.getBoolean("debug") && config.getInteger("debug.parser") == 1)
13,490✔
864
      debug(cli2.dump("Parse Tree (before command-specifÑ–c processing)"));
12✔
865

866
    return c->execute(out);
4,490✔
867
  }
868

869
  assert(commands["help"]);
×
870
  return commands["help"]->execute(out);
×
871
}
4,490✔
872

873
////////////////////////////////////////////////////////////////////////////////
874
int Context::getWidth() {
1,146✔
875
  // Determine window size.
876
  auto width = config.getInteger("defaultwidth");
2,292✔
877

878
  // A zero width value means 'infinity', which is approximated here by 2^16.
879
  if (width == 0) return 65536;
1,146✔
880

881
  if (config.getBoolean("detection")) {
3,399✔
882
    if (terminal_width == 0 && terminal_height == 0) {
1✔
883
      unsigned short buff[4];
884
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
1✔
885
        terminal_height = buff[0];
×
886
        terminal_width = buff[1];
×
887
      }
888
    }
889

890
    width = terminal_width;
1✔
891

892
    // Ncurses does this, and perhaps we need to as well, to avoid a problem on
893
    // Cygwin where the display goes right up to the terminal width, and causes
894
    // an odd color wrapping problem.
895
    if (config.getBoolean("avoidlastcolumn")) --width;
3✔
896
  }
897

898
  return width;
1,133✔
899
}
900

901
////////////////////////////////////////////////////////////////////////////////
902
int Context::getHeight() {
72✔
903
  // Determine window size.
904
  auto height = config.getInteger("defaultheight");
144✔
905

906
  // A zero height value means 'infinity', which is approximated here by 2^16.
907
  if (height == 0) return 65536;
72✔
908

909
  if (config.getBoolean("detection")) {
216✔
910
    if (terminal_width == 0 && terminal_height == 0) {
×
911
      unsigned short buff[4];
912
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
×
913
        terminal_height = buff[0];
×
914
        terminal_width = buff[1];
×
915
      }
916
    }
917

918
    height = terminal_height;
×
919
  }
920

921
  return height;
72✔
922
}
923

924
////////////////////////////////////////////////////////////////////////////////
925
std::string Context::getTaskContext(const std::string& kind, std::string name,
3,136✔
926
                                    bool fallback /* = true */) {
927
  // Consider currently selected context, if none specified
928
  if (name.empty()) name = config.get("context");
9,396✔
929

930
  // Detect if any context is set, and bail out if not
931
  if (!name.empty())
3,136✔
932
    debug(format("Applying context '{1}'", name));
108✔
933
  else {
934
    debug("No context set");
6,200✔
935
    return "";
6,200✔
936
  }
937

938
  // Figure out the context string for this kind (read/write)
939
  std::string contextString = "";
36✔
940

941
  if (!config.has("context." + name + "." + kind) && kind == "read") {
36✔
942
    debug("Specific " + kind + " context for '" + name + "' not defined. ");
1✔
943
    if (fallback) {
1✔
944
      debug("Trying to interpret old-style context definition as read context.");
1✔
945
      contextString = config.get("context." + name);
1✔
946
    }
947
  } else
948
    contextString = config.get("context." + name + "." + kind);
35✔
949

950
  debug(format("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString));
111✔
951
  return contextString;
36✔
952
}
36✔
953

954
////////////////////////////////////////////////////////////////////////////////
955
bool Context::color() {
27,356✔
956
  if (determine_color_use) {
27,356✔
957
    // What the config says.
958
    use_color = config.getBoolean("color");
8,994✔
959

960
    // Only tty's support color.
961
    if (!isatty(STDOUT_FILENO)) {
4,497✔
962
      // No ioctl.
963
      config.set("detection", "off");
17,988✔
964
      config.set("color", "off");
17,988✔
965

966
      // Files don't get color.
967
      use_color = false;
4,497✔
968
    }
969

970
    // Override.
971
    if (config.getBoolean("_forcecolor")) {
13,491✔
972
      config.set("color", "on");
732✔
973
      use_color = true;
183✔
974
    }
975

976
    // No need to go through this again.
977
    determine_color_use = false;
4,497✔
978
  }
979

980
  // Cached result.
981
  return use_color;
27,356✔
982
}
983

984
////////////////////////////////////////////////////////////////////////////////
985
// Support verbosity levels:
986
//
987
//   rc.verbose=1          Show all feedback.
988
//   rc.verbose=0          Show regular feedback.
989
//   rc.verbose=nothing    Show the absolute minimum.
990
//   rc.verbose=one,two    Show verbosity for 'one' and 'two' only.
991
//
992
// TODO This mechanism is clunky, and should slowly evolve into something more
993
//      logical and consistent.  This should probably mean that 'nothing' should
994
//      take the place of '0'.
995
bool Context::verbose(const std::string& token) {
45,252✔
996
  if (verbosity.empty()) {
45,252✔
997
    verbosity_legacy = config.getBoolean("verbose");
8,994✔
998
    for (auto& token : split(config.get("verbose"), ',')) verbosity.insert(token);
71,451✔
999

1000
    // Regular feedback means almost everything.
1001
    // This odd test is to see if a Boolean-false value is a real one, which
1002
    // means it is not 1/true/T/yes/on, but also should not be one of the
1003
    // valid tokens either.
1004
    if (!verbosity_legacy && !verbosity.empty()) {
4,497✔
1005
      std::string v = *(verbosity.begin());
4,490✔
1006
      if (v != "nothing" && v != "affected" &&  // This list must be complete.
8,684✔
1007
          v != "blank" &&                       //
37✔
1008
          v != "context" &&                     //
36✔
1009
          v != "default" &&                     //
36✔
1010
          v != "edit" &&                        //
35✔
1011
          v != "filter" &&                      //
35✔
1012
          v != "footnote" &&                    //
35✔
1013
          v != "header" &&                      //
33✔
1014
          v != "label" &&                       //
32✔
1015
          v != "new-id" &&                      //
31✔
1016
          v != "new-uuid" &&                    //
29✔
1017
          v != "news" &&                        //
26✔
1018
          v != "override" &&                    //
26✔
1019
          v != "project" &&                     //
25✔
1020
          v != "recur" &&                       //
24✔
1021
          v != "special" &&                     //
8,707✔
1022
          v != "sync") {
23✔
1023
        // This list emulates rc.verbose=off in version 1.9.4.
1024
        verbosity = {"blank", "label", "new-id", "edit"};
115✔
1025
      }
1026
    }
4,490✔
1027

1028
    // Some flags imply "footnote" verbosity being active.  Make it so.
1029
    if (!verbosity.count("footnote")) {
13,491✔
1030
      // TODO: Some of these may not use footnotes yet.  They should.
1031
      for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) {
2,267✔
1032
        if (verbosity.count(flag)) {
5,880✔
1033
          verbosity.insert("footnote");
152✔
1034
          break;
76✔
1035
        }
1036
      }
1037
    }
1038

1039
    // Some flags imply "header" verbosity being active.  Make it so.
1040
    if (!verbosity.count("header")) {
13,491✔
1041
      for (auto flag : {"default"}) {
766✔
1042
        if (verbosity.count(flag)) {
1,149✔
1043
          verbosity.insert("header");
×
1044
          break;
×
1045
        }
1046
      }
1047
    }
1048
  }
1049

1050
  // rc.verbose=true|y|yes|1|on overrides all.
1051
  if (verbosity_legacy) return true;
45,252✔
1052

1053
  // rc.verbose=nothing overrides all.
1054
  if (verbosity.size() == 1 && *(verbosity.begin()) == "nothing") return false;
45,170✔
1055

1056
  // Specific token match.
1057
  if (verbosity.count(token)) return true;
40,635✔
1058

1059
  return false;
10,033✔
1060
}
69✔
1061

1062
////////////////////////////////////////////////////////////////////////////////
1063
const std::vector<std::string> Context::getColumns() const {
1✔
1064
  std::vector<std::string> output;
1✔
1065
  for (auto& col : columns) output.push_back(col.first);
25✔
1066

1067
  return output;
1✔
1068
}
×
1069

1070
////////////////////////////////////////////////////////////////////////////////
1071
// A value of zero mean unlimited.
1072
// A value of 'page' means however many screen lines there are.
1073
// A value of a positive integer is a row/task limit.
1074
void Context::getLimits(int& rows, int& lines) {
750✔
1075
  rows = 0;
750✔
1076
  lines = 0;
750✔
1077

1078
  // This is an integer specified as a filter (limit:10).
1079
  auto limit = config.get("limit");
1,500✔
1080
  if (limit != "") {
750✔
1081
    if (limit == "page") {
67✔
1082
      rows = 0;
58✔
1083
      lines = getHeight();
58✔
1084
    } else {
1085
      rows = (int)strtol(limit.c_str(), nullptr, 10);
9✔
1086
      lines = 0;
9✔
1087
    }
1088
  }
1089
}
750✔
1090

1091
////////////////////////////////////////////////////////////////////////////////
1092
// The 'Task' object, among others, is shared between projects.  To make this
1093
// easier, it has been decoupled from Context.
1094
void Context::staticInitialization() {
4,495✔
1095
  CLI2::minimumMatchLength = config.getInteger("abbreviation.minimum");
8,990✔
1096
  Lexer::minimumMatchLength = config.getInteger("abbreviation.minimum");
8,990✔
1097

1098
  Task::defaultProject = config.get("default.project");
8,990✔
1099
  Task::defaultDue = config.get("default.due");
8,990✔
1100
  Task::defaultScheduled = config.get("default.scheduled");
8,990✔
1101

1102
  Task::searchCaseSensitive = Variant::searchCaseSensitive =
4,495✔
1103
      config.getBoolean("search.case.sensitive");
8,990✔
1104
  Task::regex = Variant::searchUsingRegex = config.getBoolean("regex");
8,990✔
1105
  Lexer::dateFormat = Variant::dateFormat = config.get("dateformat");
8,990✔
1106

1107
  auto weekStart = Datetime::dayOfWeek(config.get("weekstart"));
8,990✔
1108
  if (weekStart != 0 && weekStart != 1)
4,495✔
1109
    throw std::string(
1110
        "The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'.");
×
1111
  Datetime::weekstart = weekStart;
4,495✔
1112
  Datetime::isoEnabled = config.getBoolean("date.iso");
8,990✔
1113
  Datetime::standaloneDateEnabled = false;
4,495✔
1114
  Datetime::standaloneTimeEnabled = false;
4,495✔
1115
  Duration::standaloneSecondsEnabled = false;
4,495✔
1116

1117
  TDB2::debug_mode = config.getBoolean("debug");
8,990✔
1118

1119
  for (auto& rc : config) {
1,105,111✔
1120
    if (rc.first.substr(0, 4) == "uda." && rc.first.substr(rc.first.length() - 7, 7) == ".values") {
1,100,616✔
1121
      std::string name = rc.first.substr(4, rc.first.length() - 7 - 4);
4,572✔
1122
      auto values = split(rc.second, ',');
4,572✔
1123

1124
      for (auto r = values.rbegin(); r != values.rend(); ++r) Task::customOrder[name].push_back(*r);
22,779✔
1125
    }
4,572✔
1126
  }
1127

1128
  for (auto& col : columns) {
113,077✔
1129
    Task::attributes[col.first] = col.second->type();
108,582✔
1130
    Lexer::attributes[col.first] = col.second->type();
108,582✔
1131
  }
1132

1133
  Task::urgencyProjectCoefficient = config.getReal("urgency.project.coefficient");
8,990✔
1134
  Task::urgencyActiveCoefficient = config.getReal("urgency.active.coefficient");
8,990✔
1135
  Task::urgencyScheduledCoefficient = config.getReal("urgency.scheduled.coefficient");
8,990✔
1136
  Task::urgencyWaitingCoefficient = config.getReal("urgency.waiting.coefficient");
8,990✔
1137
  Task::urgencyBlockedCoefficient = config.getReal("urgency.blocked.coefficient");
8,990✔
1138
  Task::urgencyAnnotationsCoefficient = config.getReal("urgency.annotations.coefficient");
8,990✔
1139
  Task::urgencyTagsCoefficient = config.getReal("urgency.tags.coefficient");
8,990✔
1140
  Task::urgencyDueCoefficient = config.getReal("urgency.due.coefficient");
8,990✔
1141
  Task::urgencyBlockingCoefficient = config.getReal("urgency.blocking.coefficient");
8,990✔
1142
  Task::urgencyAgeCoefficient = config.getReal("urgency.age.coefficient");
8,990✔
1143
  Task::urgencyAgeMax = config.getReal("urgency.age.max");
8,990✔
1144

1145
  // Tag- and project-specific coefficients.
1146
  for (auto& var : config.all())
1,105,111✔
1147
    if (var.substr(0, 13) == "urgency.user." || var.substr(0, 12) == "urgency.uda.")
1,100,616✔
1148
      Task::coefficients[var] = config.getReal(var);
22,713✔
1149
}
4,495✔
1150

1151
////////////////////////////////////////////////////////////////////////////////
1152
void Context::createDefaultConfig() {
4,496✔
1153
  // Do we need to create a default rc?
1154
  if (rc_file._data != "" && !rc_file.exists()) {
4,496✔
1155
    if (config.getBoolean("confirmation") &&
5✔
1156
        !confirm(format("A configuration file could not be found in {1}\n\nWould you like a sample "
4✔
1157
                        "{2} created, so Taskwarrior can proceed?",
1158
                        home_dir, rc_file._data)))
1✔
1159
      throw std::string("Cannot proceed without rc file.");
×
1160

1161
    Datetime now;
1✔
1162
    std::stringstream contents;
1✔
1163
    contents << "# [Created by " << PACKAGE_STRING << ' ' << now.toString("m/d/Y H:N:S") << "]\n"
2✔
1164
             << "data.location=" << data_dir._original << "\n"
1✔
1165
             << "news.version=" << Version::Current() << "\n"
3✔
1166
             << "\n# To use the default location of the XDG directories,\n"
1167
             << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and update "
1168
                "location config as follows:\n"
1169
             << "\n#data.location=~/.local/share/task\n"
1170
             << "#hooks.location=~/.config/task/hooks\n"
1171
             << "\n# Color theme (uncomment one to use)\n"
1172
             << "#include light-16.theme\n"
1173
             << "#include light-256.theme\n"
1174
             << "#include bubblegum-256.theme\n"
1175
             << "#include dark-16.theme\n"
1176
             << "#include dark-256.theme\n"
1177
             << "#include dark-red-256.theme\n"
1178
             << "#include dark-green-256.theme\n"
1179
             << "#include dark-blue-256.theme\n"
1180
             << "#include dark-violets-256.theme\n"
1181
             << "#include dark-yellow-green.theme\n"
1182
             << "#include dark-gray-256.theme\n"
1183
             << "#include dark-gray-blue-256.theme\n"
1184
             << "#include solarized-dark-256.theme\n"
1185
             << "#include solarized-light-256.theme\n"
1186
             << "#include no-color.theme\n"
1187
             << '\n';
1✔
1188

1189
    // Write out the new file.
1190
    if (!File::write(rc_file._data, contents.str()))
1✔
1191
      throw format("Could not write to '{1}'.", rc_file._data);
×
1192

1193
    // Load it so that it takes effect for this run.
1194
    config.load(rc_file);
1✔
1195
  }
1✔
1196
}
4,496✔
1197

1198
////////////////////////////////////////////////////////////////////////////////
1199
void Context::decomposeSortField(const std::string& field, std::string& key, bool& ascending,
5,088✔
1200
                                 bool& breakIndicator) {
1201
  int length = field.length();
5,088✔
1202

1203
  int decoration = 1;
5,088✔
1204
  breakIndicator = false;
5,088✔
1205
  if (field[length - decoration] == '/') {
5,088✔
1206
    breakIndicator = true;
18✔
1207
    ++decoration;
18✔
1208
  }
1209

1210
  if (field[length - decoration] == '+') {
5,088✔
1211
    ascending = true;
2,196✔
1212
    key = field.substr(0, length - decoration);
2,196✔
1213
  } else if (field[length - decoration] == '-') {
2,892✔
1214
    ascending = false;
2,395✔
1215
    key = field.substr(0, length - decoration);
2,395✔
1216
  } else {
1217
    ascending = true;
497✔
1218
    key = field;
497✔
1219
  }
1220
}
5,088✔
1221

1222
////////////////////////////////////////////////////////////////////////////////
1223
void Context::debugTiming(const std::string& details, const Timer& timer) {
4,497✔
1224
  std::stringstream out;
4,497✔
1225
  out << "Timer " << details << ' ' << std::setprecision(6) << std::fixed
4,497✔
1226
      << timer.total_us() / 1.0e6 << " sec";
4,497✔
1227
  debug(out.str());
4,497✔
1228
}
4,497✔
1229

1230
////////////////////////////////////////////////////////////////////////////////
1231
CurrentTask Context::withCurrentTask(const Task* task) { return CurrentTask(*this, task); }
10,878✔
1232

1233
////////////////////////////////////////////////////////////////////////////////
1234
// This capability is to answer the question of 'what did I just do to generate
1235
// this output?'.
1236
void Context::updateXtermTitle() {
4,490✔
1237
  if (config.getBoolean("xterm.title") && isatty(STDOUT_FILENO)) {
13,470✔
1238
    auto command = cli2.getCommand();
×
1239
    std::string title;
×
1240

1241
    for (auto a = cli2._args.begin(); a != cli2._args.end(); ++a) {
×
1242
      if (a != cli2._args.begin()) title += ' ';
×
1243

1244
      title += a->attribute("raw");
×
1245
    }
1246

1247
    std::cout << " ]0;task " << command << ' ' << title << " ";
×
1248
  }
×
1249
}
4,490✔
1250

1251
////////////////////////////////////////////////////////////////////////////////
1252
// This function allows a clean output if the command is a helper subcommand.
1253
void Context::updateVerbosity() {
4,490✔
1254
  auto command = cli2.getCommand();
4,490✔
1255
  if (command != "" && command[0] == '_') {
4,490✔
1256
    verbosity = {"nothing"};
720✔
1257
  }
1258
}
5,210✔
1259

1260
////////////////////////////////////////////////////////////////////////////////
1261
void Context::loadAliases() {
4,495✔
1262
  for (auto& i : config)
1,105,119✔
1263
    if (i.first.substr(0, 6) == "alias.") cli2.alias(i.first.substr(6), i.second);
1,100,624✔
1264
}
4,495✔
1265

1266
////////////////////////////////////////////////////////////////////////////////
1267
// Using the general rc.debug setting automaticalls sets debug.hooks
1268
// and debug.parser, unless they already have values, which by default they do
1269
// not.
1270
void Context::propagateDebug() {
4,495✔
1271
  if (config.getBoolean("debug")) {
13,485✔
1272
    if (!config.has("debug.hooks")) config.set("debug.hooks", 1);
20✔
1273

1274
    if (!config.has("debug.parser")) config.set("debug.parser", 1);
20✔
1275
  } else {
1276
    if ((config.has("debug.hooks") && config.getInteger("debug.hooks")) ||
22,457✔
1277
        (config.has("debug.parser") && config.getInteger("debug.parser")))
17,966✔
1278
      config.set("debug", true);
18✔
1279
  }
1280
}
4,495✔
1281

1282
////////////////////////////////////////////////////////////////////////////////
1283
// No duplicates.
1284
void Context::header(const std::string& input) {
8,242✔
1285
  if (input.length() && std::find(headers.begin(), headers.end(), input) == headers.end())
8,242✔
1286
    headers.push_back(input);
8,242✔
1287
}
8,242✔
1288

1289
////////////////////////////////////////////////////////////////////////////////
1290
// No duplicates.
1291
void Context::footnote(const std::string& input) {
2,231✔
1292
  if (input.length() && std::find(footnotes.begin(), footnotes.end(), input) == footnotes.end())
2,231✔
1293
    footnotes.push_back(input);
1,280✔
1294
}
2,231✔
1295

1296
////////////////////////////////////////////////////////////////////////////////
1297
// No duplicates.
1298
void Context::error(const std::string& input) {
492✔
1299
  if (input.length() && std::find(errors.begin(), errors.end(), input) == errors.end())
492✔
1300
    errors.push_back(input);
486✔
1301
}
492✔
1302

1303
////////////////////////////////////////////////////////////////////////////////
1304
void Context::debug(const std::string& input) {
16,398✔
1305
  if (input.length()) debugMessages.push_back(input);
16,398✔
1306
}
16,398✔
1307

1308
////////////////////////////////////////////////////////////////////////////////
1309
CurrentTask::CurrentTask(Context& context, const Task* task)
10,878✔
1310
    : context{context}, previous{context.currentTask} {
10,878✔
1311
  context.currentTask = task;
10,878✔
1312
}
10,878✔
1313

1314
////////////////////////////////////////////////////////////////////////////////
1315
CurrentTask::~CurrentTask() { context.currentTask = previous; }
10,878✔
1316

1317
////////////////////////////////////////////////////////////////////////////////
1318

1319
// vim ts=2:sw=2
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc