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

GothenburgBitFactory / taskwarrior / 15325732076

29 May 2025 02:06PM UTC coverage: 85.101% (-0.2%) from 85.252%
15325732076

Pull #3888

github

web-flow
Merge d67d7cf06 into f6824e90a
Pull Request #3888: Do not auto-create .taskrc when stdout is not a TTY

0 of 2 new or added lines in 1 file covered. (0.0%)

33 existing lines in 3 files now uncovered.

19552 of 22975 relevant lines covered (85.1%)

23454.4 hits per line

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

85.81
/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 <recur.h>
40
#include <rules.h>
41
#include <rust/cxx.h>
42
#include <shared.h>
43
#include <stdlib.h>
44
#include <taskchampion-cpp/lib.h>
45
#include <unistd.h>
46

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

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

57
#include <sys/ioctl.h>
58

59
#ifdef SOLARIS
60
#include <sys/termios.h>
61
#endif
62

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

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

488
Context* Context::context;
489

490
////////////////////////////////////////////////////////////////////////////////
491
Context& Context::getContext() {
1,224,061✔
492
  assert(Context::context);
1,224,061✔
493
  return *Context::context;
1,224,061✔
494
}
495

496
////////////////////////////////////////////////////////////////////////////////
497
void Context::setContext(Context* context) { Context::context = context; }
4,589✔
498

499
////////////////////////////////////////////////////////////////////////////////
500
Context::~Context() {
4,620✔
501
  for (auto& com : commands) delete com.second;
422,143✔
502

503
  for (auto& col : columns) delete col.second;
115,327✔
504
}
4,620✔
505

506
////////////////////////////////////////////////////////////////////////////////
507
int Context::initialize(int argc, const char** argv) {
4,583✔
508
  timer_total.start();
4,583✔
509
  int rc = 0;
4,583✔
510
  home_dir = getenv("HOME");
4,583✔
511

512
  std::vector<std::string> searchPaths{TASK_RCDIR};
13,749✔
513

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

528
    bool taskrc_overridden = false;
4,583✔
529

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

536
      if (env_xdg_config_home)
4,583✔
537
        xdg_config_home = format("{1}", env_xdg_config_home);
9,166✔
538
      else
539
        xdg_config_home = format("{1}/.config", home_dir);
×
540

541
      // Ensure the path does not end with '/'
542
      if (xdg_config_home.back() == '/') xdg_config_home.pop_back();
4,583✔
543

544
      // https://github.com/GothenburgBitFactory/libshared/issues/32
545
      std::string rcfile_path = format("{1}/task/taskrc", xdg_config_home);
13,749✔
546

547
      File maybe_rc_file = File(rcfile_path);
4,583✔
548
      if (maybe_rc_file.exists()) rc_file = maybe_rc_file;
4,583✔
549
    }
4,583✔
550

551
    char* override = getenv("TASKRC");
4,583✔
552
    if (override) {
4,583✔
553
      rc_file = File(override);
4,472✔
554
      taskrc_overridden = true;
4,472✔
555
    }
556

557
    taskrc_overridden = CLI2::getOverride(argc, argv, rc_file) || taskrc_overridden;
4,583✔
558

559
    // Artificial scope for timing purposes.
560
    {
561
      Timer timer;
4,583✔
562
      config.parse(configurationDefaults, 1, searchPaths);
4,583✔
563
      config.load(rc_file._data, 1, searchPaths);
4,583✔
564
      debugTiming(format("Config::load ({1})", rc_file._data), timer);
13,749✔
565
    }
566

567
    CLI2::applyOverrides(argc, argv);
4,583✔
568

569
    if (taskrc_overridden && verbose("override"))
13,749✔
570
      header(format("TASKRC override: {1}", rc_file._data));
12,513✔
571

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

583
    bool taskdata_overridden = false;
4,583✔
584

585
    override = getenv("TASKDATA");
4,583✔
586
    if (override) {
4,583✔
587
      data_dir = Directory(override);
4,580✔
588
      config.set("data.location", data_dir._data);
9,160✔
589
      taskdata_overridden = true;
4,580✔
590
    }
591

592
    taskdata_overridden = CLI2::getDataLocation(argc, argv, data_dir) || taskdata_overridden;
4,583✔
593

594
    if (taskdata_overridden && verbose("override"))
13,745✔
595
      header(format("TASKDATA override: {1}", data_dir._data));
12,507✔
596

597
    createDefaultConfig();
4,583✔
598

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

605
    Command::factory(commands);
4,583✔
606
    for (auto& cmd : commands) cli2.entity("cmd", cmd.first);
839,629✔
607

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

614
    Column::factory(columns);
4,583✔
615
    for (auto& col : columns) cli2.entity("attribute", col.first);
225,997✔
616

617
    cli2.entity("pseudo", "limit");
18,332✔
618

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

625
    for (auto& modifierName : modifierNames) cli2.entity("modifier", modifierName);
407,887✔
626

627
    for (auto& op : Eval::getOperators()) cli2.entity("operator", op);
242,899✔
628

629
    for (auto& op : Eval::getBinaryOperators()) cli2.entity("binary_operator", op);
197,069✔
630

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

637
    initializeColorRules();
4,583✔
638
    staticInitialization();
4,583✔
639
    propagateDebug();
4,583✔
640
    loadAliases();
4,583✔
641

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

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

650
    cli2.analyze();
4,583✔
651

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

659
      combined += a.attribute("raw");
39,892✔
660

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

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

666
    if (verbose("default")) {
9,162✔
667
      if (foundDefault) header("[" + combined + "]");
8✔
668

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

672
    ////////////////////////////////////////////////////////////////////////////
673
    //
674
    // [7.5] Open the Replica.
675
    //
676
    ////////////////////////////////////////////////////////////////////////////
677

678
    bool create_if_missing = !config.getBoolean("exit.on.missing.db");
9,162✔
679
    Command* c = commands[cli2.getCommand()];
4,581✔
680

681
    // We must allow writes if either 'gc' is enabled and the command performs GC, or the command
682
    // itself is read-write.
683
    bool read_write =
684
        (config.getBoolean("gc") && (c->needs_gc() || c->needs_recur_update())) || !c->read_only();
9,162✔
685
    tdb2.open_replica(data_dir, create_if_missing, read_write);
4,582✔
686

687
    ////////////////////////////////////////////////////////////////////////////
688
    //
689
    // [8] Initialize hooks.
690
    //
691
    ////////////////////////////////////////////////////////////////////////////
692

693
    hooks.initialize();
4,580✔
694
  }
4,581✔
695

696
  catch (const std::string& message) {
3✔
697
    error(message);
2✔
698
    rc = 2;
2✔
699
  }
2✔
700

701
  catch (rust::Error& err) {
1✔
702
    error(err.what());
1✔
703
    rc = 2;
1✔
704
  }
1✔
705

706
  catch (int) {
×
707
    // Hooks can terminate processing by throwing integers.
708
    rc = 4;
×
709
  }
×
710

711
  catch (const std::regex_error& e) {
×
712
    std::cout << "regex_error caught: " << e.what() << '\n';
×
713
  } catch (...) {
×
714
    error("Unknown error. Please report.");
×
715
    rc = 3;
×
716
  }
×
717

718
  // On initialization failure...
719
  if (rc) {
4,583✔
720
    // Dump all debug messages, controlled by rc.debug.
721
    if (config.getBoolean("debug")) {
9✔
722
      for (auto& d : debugMessages)
×
723
        if (color())
×
724
          std::cerr << colorizeDebug(d) << '\n';
×
725
        else
726
          std::cerr << d << '\n';
×
727
    }
728

729
    // Dump all headers, controlled by 'header' verbosity token.
730
    if (verbose("header")) {
6✔
731
      for (auto& h : headers)
9✔
732
        if (color())
6✔
733
          std::cerr << colorizeHeader(h) << '\n';
×
734
        else
735
          std::cerr << h << '\n';
6✔
736
    }
737

738
    // Dump all footnotes, controlled by 'footnote' verbosity token.
739
    if (verbose("footnote")) {
6✔
740
      for (auto& f : footnotes)
4✔
741
        if (color())
1✔
742
          std::cerr << colorizeFootnote(f) << '\n';
×
743
        else
744
          std::cerr << f << '\n';
1✔
745
    }
746

747
    // Dump all errors, non-maskable.
748
    // Colorized as footnotes.
749
    for (auto& e : errors)
6✔
750
      if (color())
3✔
751
        std::cerr << colorizeFootnote(e) << '\n';
×
752
      else
753
        std::cerr << e << '\n';
3✔
754
  }
755

756
  time_init_us += timer_total.total_us();
4,583✔
757
  return rc;
4,583✔
758
}
18,332✔
759

760
////////////////////////////////////////////////////////////////////////////////
761
int Context::run() {
4,580✔
762
  int rc;
763
  std::string output;
4,580✔
764

765
  try {
766
    hooks.onLaunch();
4,580✔
767
    rc = dispatch(output);
4,577✔
768
    hooks.onExit();  // No chance to update data.
4,099✔
769

770
    timer_total.stop();
4,097✔
771
    time_total_us += timer_total.total_us();
4,097✔
772

773
    std::stringstream s;
4,097✔
774
    s << "Perf " << PACKAGE_STRING << ' '
775
#ifdef HAVE_COMMIT
776
      << COMMIT
777
#else
778
      << '-'
779
#endif
780
      << ' ' << Datetime().toISO()
8,194✔
781

782
      << " init:" << time_init_us << " load:" << time_load_us
8,194✔
783
      << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us)
4,097✔
784
      << " filter:" << time_filter_us << " commit:" << time_commit_us << " sort:" << time_sort_us
4,097✔
785
      << " render:" << time_render_us << " hooks:" << time_hooks_us << " other:"
4,097✔
786
      << time_total_us - time_init_us - time_gc_us - time_filter_us - time_commit_us -
4,097✔
787
             time_sort_us - time_render_us - time_hooks_us
4,097✔
788
      << " total:" << time_total_us << '\n';
4,097✔
789
    debug(s.str());
4,097✔
790
  }
4,097✔
791

792
  catch (const std::string& message) {
483✔
793
    error(message);
465✔
794
    rc = 2;
465✔
795
  }
465✔
796

797
  catch (rust::Error& err) {
×
798
    error(err.what());
×
799
    rc = 2;
×
800
  }
×
801

802
  catch (int) {
18✔
803
    // Hooks can terminate processing by throwing integers.
804
    rc = 4;
18✔
805
  }
18✔
806

807
  catch (...) {
×
808
    error("Unknown error. Please report.");
×
809
    rc = 3;
×
810
  }
×
811

812
  // Dump all debug messages, controlled by rc.debug.
813
  if (config.getBoolean("debug")) {
13,740✔
814
    for (auto& d : debugMessages)
176✔
815
      if (color())
166✔
816
        std::cerr << colorizeDebug(d) << '\n';
8✔
817
      else
818
        std::cerr << d << '\n';
158✔
819
  }
820

821
  // Dump all headers, controlled by 'header' verbosity token.
822
  if (verbose("header")) {
9,160✔
823
    for (auto& h : headers)
11,535✔
824
      if (color())
7,684✔
825
        std::cerr << colorizeHeader(h) << '\n';
364✔
826
      else
827
        std::cerr << h << '\n';
7,320✔
828
  }
829

830
  // Dump the report output.
831
  std::cout << output;
4,580✔
832

833
  // Dump all footnotes, controlled by 'footnote' verbosity token.
834
  if (verbose("footnote")) {
9,160✔
835
    for (auto& f : footnotes)
5,181✔
836
      if (color())
1,264✔
837
        std::cerr << colorizeFootnote(f) << '\n';
69✔
838
      else
839
        std::cerr << f << '\n';
1,195✔
840
  }
841

842
  // Dump all errors, non-maskable.
843
  // Colorized as footnotes.
844
  for (auto& e : errors)
5,065✔
845
    if (color())
485✔
846
      std::cerr << colorizeError(e) << '\n';
1✔
847
    else
848
      std::cerr << e << '\n';
484✔
849

850
  return rc;
4,580✔
851
}
4,580✔
852

853
////////////////////////////////////////////////////////////////////////////////
854
// Dispatch to the command found by the CLI parser.
855
int Context::dispatch(std::string& out) {
4,577✔
856
  // Autocomplete args against keywords.
857
  std::string command = cli2.getCommand();
4,577✔
858
  if (command != "") {
4,577✔
859
    updateXtermTitle();
4,577✔
860
    updateVerbosity();
4,577✔
861

862
    Command* c = commands[command];
4,577✔
863
    assert(c);
4,577✔
864

865
    // The command know whether they need a GC or recurrence update.
866
    if (c->needs_gc()) {
4,577✔
867
      tdb2.gc();
930✔
868
    }
869

870
    // This is something that is only needed for write commands with no other
871
    // filter processing.
872
    if (c->accepts_modifications() && !c->accepts_filter()) {
4,577✔
873
      cli2.prepareFilter();
1,679✔
874
    }
875

876
    // With rc.debug.parser == 2, there are more tree dumps than you might want,
877
    // but we need the rc.debug.parser == 1 case covered also, with the final
878
    // tree.
879
    if (config.getBoolean("debug") && config.getInteger("debug.parser") == 1)
13,751✔
880
      debug(cli2.dump("Parse Tree (before command-specifÑ–c processing)"));
12✔
881

882
    if (c->needs_recur_update() && Context::getContext().config.getBoolean("gc")) {
6,349✔
883
      handleUntil();
881✔
884
      handleRecurrence();
881✔
885
    }
886

887
    return c->execute(out);
4,577✔
888
  }
889

890
  assert(commands["help"]);
×
891
  return commands["help"]->execute(out);
×
892
}
4,577✔
893

894
////////////////////////////////////////////////////////////////////////////////
895
int Context::getWidth() {
1,190✔
896
  // Determine window size.
897
  auto width = config.getInteger("defaultwidth");
2,380✔
898

899
  // A zero width value means 'infinity', which is approximated here by 2^16.
900
  if (width == 0) return 65536;
1,190✔
901

902
  if (config.getBoolean("detection")) {
3,531✔
903
    if (terminal_width == 0 && terminal_height == 0) {
1✔
904
      unsigned short buff[4];
905
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
1✔
906
        terminal_height = buff[0];
×
907
        terminal_width = buff[1];
×
908
      }
909
    }
910

911
    width = terminal_width;
1✔
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")) --width;
3✔
917
  }
918

919
  return width;
1,177✔
920
}
921

922
////////////////////////////////////////////////////////////////////////////////
923
int Context::getHeight() {
74✔
924
  // Determine window size.
925
  auto height = config.getInteger("defaultheight");
148✔
926

927
  // A zero height value means 'infinity', which is approximated here by 2^16.
928
  if (height == 0) return 65536;
74✔
929

930
  if (config.getBoolean("detection")) {
222✔
931
    if (terminal_width == 0 && terminal_height == 0) {
×
932
      unsigned short buff[4];
933
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
×
934
        terminal_height = buff[0];
×
935
        terminal_width = buff[1];
×
936
      }
937
    }
938

939
    height = terminal_height;
×
940
  }
941

942
  return height;
74✔
943
}
944

945
////////////////////////////////////////////////////////////////////////////////
946
std::string Context::getTaskContext(const std::string& kind, std::string name,
3,170✔
947
                                    bool fallback /* = true */) {
948
  // Consider currently selected context, if none specified
949
  if (name.empty()) name = config.get("context");
9,498✔
950

951
  // Detect if any context is set, and bail out if not
952
  if (!name.empty())
3,170✔
953
    debug(format("Applying context '{1}'", name));
108✔
954
  else {
955
    debug("No context set");
6,268✔
956
    return "";
6,268✔
957
  }
958

959
  // Figure out the context string for this kind (read/write)
960
  std::string contextString = "";
36✔
961

962
  if (!config.has("context." + name + "." + kind) && kind == "read") {
36✔
963
    debug("Specific " + kind + " context for '" + name + "' not defined. ");
1✔
964
    if (fallback) {
1✔
965
      debug("Trying to interpret old-style context definition as read context.");
1✔
966
      contextString = config.get("context." + name);
1✔
967
    }
968
  } else
969
    contextString = config.get("context." + name + "." + kind);
35✔
970

971
  debug(format("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString));
111✔
972
  return contextString;
36✔
973
}
36✔
974

975
////////////////////////////////////////////////////////////////////////////////
976
bool Context::color() {
27,755✔
977
  if (determine_color_use) {
27,755✔
978
    // What the config says.
979
    use_color = config.getBoolean("color");
9,168✔
980

981
    // Only tty's support color.
982
    if (!isatty(STDOUT_FILENO)) {
4,584✔
983
      // No ioctl.
984
      config.set("detection", "off");
18,336✔
985
      config.set("color", "off");
18,336✔
986

987
      // Files don't get color.
988
      use_color = false;
4,584✔
989
    }
990

991
    // Override.
992
    if (config.getBoolean("_forcecolor")) {
13,752✔
993
      config.set("color", "on");
732✔
994
      use_color = true;
183✔
995
    }
996

997
    // No need to go through this again.
998
    determine_color_use = false;
4,584✔
999
  }
1000

1001
  // Cached result.
1002
  return use_color;
27,755✔
1003
}
1004

1005
////////////////////////////////////////////////////////////////////////////////
1006
// Support verbosity levels:
1007
//
1008
//   rc.verbose=1          Show all feedback.
1009
//   rc.verbose=0          Show regular feedback.
1010
//   rc.verbose=nothing    Show the absolute minimum.
1011
//   rc.verbose=one,two    Show verbosity for 'one' and 'two' only.
1012
//
1013
// TODO This mechanism is clunky, and should slowly evolve into something more
1014
//      logical and consistent.  This should probably mean that 'nothing' should
1015
//      take the place of '0'.
1016
bool Context::verbose(const std::string& token) {
46,033✔
1017
  if (verbosity.empty()) {
46,033✔
1018
    verbosity_legacy = config.getBoolean("verbose");
9,168✔
1019
    for (auto& token : split(config.get("verbose"), ',')) verbosity.insert(token);
72,462✔
1020

1021
    // Regular feedback means almost everything.
1022
    // This odd test is to see if a Boolean-false value is a real one, which
1023
    // means it is not 1/true/T/yes/on, but also should not be one of the
1024
    // valid tokens either.
1025
    if (!verbosity_legacy && !verbosity.empty()) {
4,584✔
1026
      std::string v = *(verbosity.begin());
4,577✔
1027
      if (v != "nothing" && v != "affected" &&  // This list must be complete.
8,822✔
1028
          v != "blank" &&                       //
37✔
1029
          v != "context" &&                     //
36✔
1030
          v != "default" &&                     //
36✔
1031
          v != "edit" &&                        //
35✔
1032
          v != "filter" &&                      //
35✔
1033
          v != "footnote" &&                    //
35✔
1034
          v != "header" &&                      //
33✔
1035
          v != "label" &&                       //
32✔
1036
          v != "new-id" &&                      //
31✔
1037
          v != "new-uuid" &&                    //
29✔
1038
          v != "news" &&                        //
26✔
1039
          v != "override" &&                    //
26✔
1040
          v != "project" &&                     //
25✔
1041
          v != "recur" &&                       //
24✔
1042
          v != "special" &&                     //
8,845✔
1043
          v != "sync") {
23✔
1044
        // This list emulates rc.verbose=off in version 1.9.4.
1045
        verbosity = {"blank", "label", "new-id", "edit"};
115✔
1046
      }
1047
    }
4,577✔
1048

1049
    // Some flags imply "footnote" verbosity being active.  Make it so.
1050
    if (!verbosity.count("footnote")) {
13,752✔
1051
      // TODO: Some of these may not use footnotes yet.  They should.
1052
      for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) {
2,519✔
1053
        if (verbosity.count(flag)) {
6,528✔
1054
          verbosity.insert("footnote");
152✔
1055
          break;
76✔
1056
        }
1057
      }
1058
    }
1059

1060
    // Some flags imply "header" verbosity being active.  Make it so.
1061
    if (!verbosity.count("header")) {
13,752✔
1062
      for (auto flag : {"default"}) {
838✔
1063
        if (verbosity.count(flag)) {
1,257✔
1064
          verbosity.insert("header");
×
1065
          break;
×
1066
        }
1067
      }
1068
    }
1069
  }
1070

1071
  // rc.verbose=true|y|yes|1|on overrides all.
1072
  if (verbosity_legacy) return true;
46,033✔
1073

1074
  // rc.verbose=nothing overrides all.
1075
  if (verbosity.size() == 1 && *(verbosity.begin()) == "nothing") return false;
45,951✔
1076

1077
  // Specific token match.
1078
  if (verbosity.count(token)) return true;
41,060✔
1079

1080
  return false;
10,117✔
1081
}
69✔
1082

1083
////////////////////////////////////////////////////////////////////////////////
1084
const std::vector<std::string> Context::getColumns() const {
1✔
1085
  std::vector<std::string> output;
1✔
1086
  for (auto& col : columns) output.push_back(col.first);
25✔
1087

1088
  return output;
1✔
1089
}
×
1090

1091
////////////////////////////////////////////////////////////////////////////////
1092
// A value of zero mean unlimited.
1093
// A value of 'page' means however many screen lines there are.
1094
// A value of a positive integer is a row/task limit.
1095
void Context::getLimits(int& rows, int& lines) {
764✔
1096
  rows = 0;
764✔
1097
  lines = 0;
764✔
1098

1099
  // This is an integer specified as a filter (limit:10).
1100
  auto limit = config.get("limit");
1,528✔
1101
  if (limit != "") {
764✔
1102
    if (limit == "page") {
67✔
1103
      rows = 0;
58✔
1104
      lines = getHeight();
58✔
1105
    } else {
1106
      rows = (int)strtol(limit.c_str(), nullptr, 10);
9✔
1107
      lines = 0;
9✔
1108
    }
1109
  }
1110
}
764✔
1111

1112
////////////////////////////////////////////////////////////////////////////////
1113
// The 'Task' object, among others, is shared between projects.  To make this
1114
// easier, it has been decoupled from Context.
1115
void Context::staticInitialization() {
4,583✔
1116
  CLI2::minimumMatchLength = config.getInteger("abbreviation.minimum");
9,166✔
1117
  Lexer::minimumMatchLength = config.getInteger("abbreviation.minimum");
9,166✔
1118

1119
  Task::defaultProject = config.get("default.project");
9,166✔
1120
  Task::defaultDue = config.get("default.due");
9,166✔
1121
  Task::defaultScheduled = config.get("default.scheduled");
9,166✔
1122

1123
  Task::searchCaseSensitive = Variant::searchCaseSensitive =
4,583✔
1124
      config.getBoolean("search.case.sensitive");
9,166✔
1125
  Task::regex = Variant::searchUsingRegex = config.getBoolean("regex");
9,166✔
1126
  Lexer::dateFormat = Variant::dateFormat = config.get("dateformat");
9,166✔
1127

1128
  auto weekStart = Datetime::dayOfWeek(config.get("weekstart"));
9,166✔
1129
  if (weekStart != 0 && weekStart != 1)
4,583✔
1130
    throw std::string(
1131
        "The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'.");
×
1132
  Datetime::weekstart = weekStart;
4,583✔
1133
  Datetime::isoEnabled = config.getBoolean("date.iso");
9,166✔
1134
  Datetime::standaloneDateEnabled = false;
4,583✔
1135
  Datetime::standaloneTimeEnabled = false;
4,583✔
1136
  Duration::standaloneSecondsEnabled = false;
4,583✔
1137

1138
  TDB2::debug_mode = config.getBoolean("debug");
9,166✔
1139

1140
  for (auto& rc : config) {
1,126,745✔
1141
    if (rc.first.substr(0, 4) == "uda." && rc.first.substr(rc.first.length() - 7, 7) == ".values") {
1,122,162✔
1142
      std::string name = rc.first.substr(4, rc.first.length() - 7 - 4);
4,660✔
1143
      auto values = split(rc.second, ',');
4,660✔
1144

1145
      for (auto r = values.rbegin(); r != values.rend(); ++r) Task::customOrder[name].push_back(*r);
23,219✔
1146
    }
4,660✔
1147
  }
1148

1149
  for (auto& col : columns) {
115,290✔
1150
    Task::attributes[col.first] = col.second->type();
110,707✔
1151
    Lexer::attributes[col.first] = col.second->type();
110,707✔
1152
  }
1153

1154
  Task::urgencyProjectCoefficient = config.getReal("urgency.project.coefficient");
9,166✔
1155
  Task::urgencyActiveCoefficient = config.getReal("urgency.active.coefficient");
9,166✔
1156
  Task::urgencyScheduledCoefficient = config.getReal("urgency.scheduled.coefficient");
9,166✔
1157
  Task::urgencyWaitingCoefficient = config.getReal("urgency.waiting.coefficient");
9,166✔
1158
  Task::urgencyBlockedCoefficient = config.getReal("urgency.blocked.coefficient");
9,166✔
1159
  Task::urgencyAnnotationsCoefficient = config.getReal("urgency.annotations.coefficient");
9,166✔
1160
  Task::urgencyTagsCoefficient = config.getReal("urgency.tags.coefficient");
9,166✔
1161
  Task::urgencyDueCoefficient = config.getReal("urgency.due.coefficient");
9,166✔
1162
  Task::urgencyBlockingCoefficient = config.getReal("urgency.blocking.coefficient");
9,166✔
1163
  Task::urgencyAgeCoefficient = config.getReal("urgency.age.coefficient");
9,166✔
1164
  Task::urgencyAgeMax = config.getReal("urgency.age.max");
9,166✔
1165

1166
  // Tag- and project-specific coefficients.
1167
  for (auto& var : config.all())
1,126,745✔
1168
    if (var.substr(0, 13) == "urgency.user." || var.substr(0, 12) == "urgency.uda.")
1,122,162✔
1169
      Task::coefficients[var] = config.getReal(var);
23,153✔
1170
}
4,583✔
1171

1172
////////////////////////////////////////////////////////////////////////////////
1173
void Context::createDefaultConfig() {
4,583✔
1174
  // Do we need to create a default rc?
1175
  if (rc_file._data != "" && !rc_file.exists()) {
4,583✔
1176
    // If stdout is not a file, we are probably executing in a completion context and should not
1177
    // prompt (as the user won't see it) or modify the config (as completion functions are typically
1178
    // read-only).
NEW
1179
    if (!isatty(STDOUT_FILENO)) {
×
NEW
1180
      throw std::string("Cannot proceed without rc file.");
×
1181
    }
1182

UNCOV
1183
    if (config.getBoolean("confirmation") &&
×
UNCOV
1184
        !confirm(format("A configuration file could not be found in {1}\n\nWould you like a sample "
×
1185
                        "{2} created, so Taskwarrior can proceed?",
UNCOV
1186
                        home_dir, rc_file._data)))
×
1187
      throw std::string("Cannot proceed without rc file.");
×
1188

UNCOV
1189
    Datetime now;
×
UNCOV
1190
    std::stringstream contents;
×
UNCOV
1191
    contents << "# [Created by " << PACKAGE_STRING << ' ' << now.toString("m/d/Y H:N:S") << "]\n"
×
UNCOV
1192
             << "data.location=" << data_dir._original << "\n"
×
UNCOV
1193
             << "news.version=" << Version::Current() << "\n"
×
1194
             << "\n# To use the default location of the XDG directories,\n"
1195
             << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and update "
1196
                "location config as follows:\n"
1197
             << "\n#data.location=~/.local/share/task\n"
1198
             << "#hooks.location=~/.config/task/hooks\n"
1199
             << "\n# Color theme (uncomment one to use)\n"
1200
             << "#include light-16.theme\n"
1201
             << "#include light-256.theme\n"
1202
             << "#include bubblegum-256.theme\n"
1203
             << "#include dark-16.theme\n"
1204
             << "#include dark-256.theme\n"
1205
             << "#include dark-red-256.theme\n"
1206
             << "#include dark-green-256.theme\n"
1207
             << "#include dark-blue-256.theme\n"
1208
             << "#include dark-violets-256.theme\n"
1209
             << "#include dark-yellow-green.theme\n"
1210
             << "#include dark-gray-256.theme\n"
1211
             << "#include dark-gray-blue-256.theme\n"
1212
             << "#include solarized-dark-256.theme\n"
1213
             << "#include solarized-light-256.theme\n"
1214
             << "#include no-color.theme\n"
UNCOV
1215
             << '\n';
×
1216

1217
    // Write out the new file.
UNCOV
1218
    if (!File::write(rc_file._data, contents.str()))
×
1219
      throw format("Could not write to '{1}'.", rc_file._data);
×
1220

1221
    // Load it so that it takes effect for this run.
UNCOV
1222
    config.load(rc_file);
×
UNCOV
1223
  }
×
1224
}
4,583✔
1225

1226
////////////////////////////////////////////////////////////////////////////////
1227
void Context::decomposeSortField(const std::string& field, std::string& key, bool& ascending,
5,079✔
1228
                                 bool& breakIndicator) {
1229
  int length = field.length();
5,079✔
1230

1231
  int decoration = 1;
5,079✔
1232
  breakIndicator = false;
5,079✔
1233
  if (field[length - decoration] == '/') {
5,079✔
1234
    breakIndicator = true;
18✔
1235
    ++decoration;
18✔
1236
  }
1237

1238
  if (field[length - decoration] == '+') {
5,079✔
1239
    ascending = true;
2,197✔
1240
    key = field.substr(0, length - decoration);
2,197✔
1241
  } else if (field[length - decoration] == '-') {
2,882✔
1242
    ascending = false;
2,417✔
1243
    key = field.substr(0, length - decoration);
2,417✔
1244
  } else {
1245
    ascending = true;
465✔
1246
    key = field;
465✔
1247
  }
1248
}
5,079✔
1249

1250
////////////////////////////////////////////////////////////////////////////////
1251
void Context::debugTiming(const std::string& details, const Timer& timer) {
4,584✔
1252
  std::stringstream out;
4,584✔
1253
  out << "Timer " << details << ' ' << std::setprecision(6) << std::fixed
4,584✔
1254
      << timer.total_us() / 1.0e6 << " sec";
4,584✔
1255
  debug(out.str());
4,584✔
1256
}
4,584✔
1257

1258
////////////////////////////////////////////////////////////////////////////////
1259
CurrentTask Context::withCurrentTask(const Task* task) { return CurrentTask(*this, task); }
10,937✔
1260

1261
////////////////////////////////////////////////////////////////////////////////
1262
// This capability is to answer the question of 'what did I just do to generate
1263
// this output?'.
1264
void Context::updateXtermTitle() {
4,577✔
1265
  if (config.getBoolean("xterm.title") && isatty(STDOUT_FILENO)) {
13,731✔
1266
    auto command = cli2.getCommand();
×
1267
    std::string title;
×
1268

1269
    for (auto a = cli2._args.begin(); a != cli2._args.end(); ++a) {
×
1270
      if (a != cli2._args.begin()) title += ' ';
×
1271

1272
      title += a->attribute("raw");
×
1273
    }
1274

1275
    std::cout << " ]0;task " << command << ' ' << title << " ";
×
1276
  }
×
1277
}
4,577✔
1278

1279
////////////////////////////////////////////////////////////////////////////////
1280
// This function allows a clean output if the command is a helper subcommand.
1281
void Context::updateVerbosity() {
4,577✔
1282
  auto command = cli2.getCommand();
4,577✔
1283
  if (command != "" && command[0] == '_') {
4,577✔
1284
    verbosity = {"nothing"};
724✔
1285
  }
1286
}
5,301✔
1287

1288
////////////////////////////////////////////////////////////////////////////////
1289
void Context::loadAliases() {
4,583✔
1290
  for (auto& i : config)
1,126,753✔
1291
    if (i.first.substr(0, 6) == "alias.") cli2.alias(i.first.substr(6), i.second);
1,122,170✔
1292
}
4,583✔
1293

1294
////////////////////////////////////////////////////////////////////////////////
1295
// Using the general rc.debug setting automaticalls sets debug.hooks
1296
// and debug.parser, unless they already have values, which by default they do
1297
// not.
1298
void Context::propagateDebug() {
4,583✔
1299
  if (config.getBoolean("debug")) {
13,749✔
1300
    if (!config.has("debug.hooks")) config.set("debug.hooks", 1);
20✔
1301

1302
    if (!config.has("debug.parser")) config.set("debug.parser", 1);
20✔
1303
  } else {
1304
    if ((config.has("debug.hooks") && config.getInteger("debug.hooks")) ||
22,897✔
1305
        (config.has("debug.parser") && config.getInteger("debug.parser")))
18,318✔
1306
      config.set("debug", true);
18✔
1307
  }
1308
}
4,583✔
1309

1310
////////////////////////////////////////////////////////////////////////////////
1311
// No duplicates.
1312
void Context::header(const std::string& input) {
8,344✔
1313
  if (input.length() && std::find(headers.begin(), headers.end(), input) == headers.end())
8,344✔
1314
    headers.push_back(input);
8,344✔
1315
}
8,344✔
1316

1317
////////////////////////////////////////////////////////////////////////////////
1318
// No duplicates.
1319
void Context::footnote(const std::string& input) {
2,262✔
1320
  if (input.length() && std::find(footnotes.begin(), footnotes.end(), input) == footnotes.end())
2,262✔
1321
    footnotes.push_back(input);
1,294✔
1322
}
2,262✔
1323

1324
////////////////////////////////////////////////////////////////////////////////
1325
// No duplicates.
1326
void Context::error(const std::string& input) {
494✔
1327
  if (input.length() && std::find(errors.begin(), errors.end(), input) == errors.end())
494✔
1328
    errors.push_back(input);
488✔
1329
}
494✔
1330

1331
////////////////////////////////////////////////////////////////////////////////
1332
void Context::debug(const std::string& input) {
16,662✔
1333
  if (input.length()) debugMessages.push_back(input);
16,662✔
1334
}
16,662✔
1335

1336
////////////////////////////////////////////////////////////////////////////////
1337
CurrentTask::CurrentTask(Context& context, const Task* task)
10,937✔
1338
    : context{context}, previous{context.currentTask} {
10,937✔
1339
  context.currentTask = task;
10,937✔
1340
}
10,937✔
1341

1342
////////////////////////////////////////////////////////////////////////////////
1343
CurrentTask::~CurrentTask() { context.currentTask = previous; }
10,937✔
1344

1345
////////////////////////////////////////////////////////////////////////////////
1346

1347
// 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

© 2026 Coveralls, Inc