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

GothenburgBitFactory / taskwarrior / 11335495770

14 Oct 2024 09:47PM UTC coverage: 84.223% (-0.6%) from 84.776%
11335495770

push

github

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

updates:
- [github.com/psf/black: 24.8.0 → 24.10.0](https://github.com/psf/black/compare/24.8.0...24.10.0)

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

19005 of 22565 relevant lines covered (84.22%)

23473.55 hits per line

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

89.02
/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
    "undo.style=side                                # Undo style - can be 'side', or 'diff'\n"
131
    "regex=1                                        # Assume all search/filter strings are "
132
    "regexes\n"
133
    "xterm.title=0                                  # Sets xterm title for some commands\n"
134
    "expressions=infix                              # Prefer infix over postfix expressions\n"
135
    "json.array=1                                   # Enclose JSON output in [ ]\n"
136
    "abbreviation.minimum=2                         # Shortest allowed abbreviation\n"
137
    "news.version=                                  # Latest version highlights read by the user\n"
138
    "purge.on-sync=0                                # Purge old tasks on sync\n"
139
    "\n"
140
    "# Dates\n"
141
    "dateformat=Y-M-D                               # Preferred input and display date format\n"
142
    "dateformat.holiday=YMD                         # Preferred input date format for holidays\n"
143
    "dateformat.edit=Y-M-D H:N:S                    # Preferred display date format when editing\n"
144
    "dateformat.info=Y-M-D H:N:S                    # Preferred display date format for "
145
    "information\n"
146
    "dateformat.report=                             # Preferred display date format for reports\n"
147
    "dateformat.annotation=                         # Preferred display date format for "
148
    "annotations\n"
149
    "date.iso=1                                     # Enable ISO date support\n"
150
    "weekstart=sunday                               # Sunday or Monday only\n"
151
    "displayweeknumber=1                            # Show week numbers on calendar\n"
152
    "due=7                                          # Task is considered due in 7 days\n"
153
    "\n"
154
    "# Calendar controls\n"
155
    "calendar.legend=1                              # Display the legend on calendar\n"
156
    "calendar.details=sparse                        # Calendar shows information for tasks w/due "
157
    "dates: full, sparse or none\n"
158
    "calendar.details.report=list                   # Report to use when showing task information "
159
    "in cal\n"
160
    "calendar.offset=0                              # Apply an offset value to control the first "
161
    "month of the calendar\n"
162
    "calendar.offset.value=-1                       # The number of months the first month of the "
163
    "calendar is moved\n"
164
    "calendar.holidays=none                         # Show public holidays on calendar:full, "
165
    "sparse or none\n"
166
    "#calendar.monthsperline=3                      # Number of calendar months on a line\n"
167
    "\n"
168
    "# Journal controls\n"
169
    "journal.time=0                                 # Record start/stop commands as annotation\n"
170
    "journal.time.start.annotation=Started task     # Annotation description for the start journal "
171
    "entry\n"
172
    "journal.time.stop.annotation=Stopped task      # Annotation description for the stop  journal "
173
    "entry\n"
174
    "journal.info=1                                 # Display task journal with info command\n"
175
    "\n"
176
    "# Dependency controls\n"
177
    "dependency.reminder=1                          # Nags on dependency chain violations\n"
178
    "dependency.confirmation=1                      # Should dependency chain repair be "
179
    "confirmed?\n"
180
    "\n"
181
    "# Urgency Coefficients\n"
182
    "urgency.user.tag.next.coefficient=15.0         # Urgency coefficient for 'next' special tag\n"
183
    "urgency.due.coefficient=12.0                   # Urgency coefficient for due dates\n"
184
    "urgency.blocking.coefficient=8.0               # Urgency coefficient for blocking tasks\n"
185
    "urgency.active.coefficient=4.0                 # Urgency coefficient for active tasks\n"
186
    "urgency.scheduled.coefficient=5.0              # Urgency coefficient for scheduled tasks\n"
187
    "urgency.age.coefficient=2.0                    # Urgency coefficient for age\n"
188
    "urgency.annotations.coefficient=1.0            # Urgency coefficient for annotations\n"
189
    "urgency.tags.coefficient=1.0                   # Urgency coefficient for tags\n"
190
    "urgency.project.coefficient=1.0                # Urgency coefficient for projects\n"
191
    "urgency.blocked.coefficient=-5.0               # Urgency coefficient for blocked tasks\n"
192
    "urgency.waiting.coefficient=-3.0               # Urgency coefficient for waiting status\n"
193
    "urgency.inherit=0                              # Recursively inherit highest urgency value "
194
    "from blocked tasks\n"
195
    "urgency.age.max=365                            # Maximum age in days\n"
196
    "\n"
197
    "#urgency.user.project.foo.coefficient=5.0      # Urgency coefficients for 'foo' project\n"
198
    "#urgency.user.tag.foo.coefficient=5.0          # Urgency coefficients for 'foo' tag\n"
199
    "#urgency.uda.foo.coefficient=5.0               # Urgency coefficients for UDA 'foo'\n"
200
    "\n"
201
    "# Color controls.\n"
202
    "color=1                                        # Enable color\n"
203
    "\n"
204
    "# Here is the rule precedence order, highest to lowest.\n"
205
    "# Note that these are just the color rule names, without the leading 'color.'\n"
206
    "#      and any trailing '.value'.\n"
207
    "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due."
208
    "today,due,blocked,blocking,recurring,tagged,uda.\n"
209
    "\n"
210
    "# General decoration\n"
211
    "rule.color.merge=1\n"
212
    "color.label=\n"
213
    "color.label.sort=\n"
214
    "color.alternate=on gray2\n"
215
    "color.header=color3\n"
216
    "color.footnote=color3\n"
217
    "color.warning=bold red\n"
218
    "color.error=white on red\n"
219
    "color.debug=color4\n"
220
    "\n"
221
    "# Task state\n"
222
    "color.completed=\n"
223
    "color.deleted=\n"
224
    "color.active=rgb555 on rgb410\n"
225
    "color.recurring=rgb013\n"
226
    "color.scheduled=on rgb001\n"
227
    "color.until=\n"
228
    "color.blocked=white on color8\n"
229
    "color.blocking=black on color15\n"
230
    "\n"
231
    "# Project\n"
232
    "color.project.none=\n"
233
    "\n"
234
    "# Priority UDA\n"
235
    "color.uda.priority.H=color255\n"
236
    "color.uda.priority.L=color245\n"
237
    "color.uda.priority.M=color250\n"
238
    "\n"
239
    "# Tags\n"
240
    "color.tag.next=rgb440\n"
241
    "color.tag.none=\n"
242
    "color.tagged=rgb031\n"
243
    "\n"
244
    "# Due\n"
245
    "color.due.today=rgb400\n"
246
    "color.due=color1\n"
247
    "color.overdue=color9\n"
248
    "\n"
249
    "# Report: burndown\n"
250
    "color.burndown.done=on rgb010\n"
251
    "color.burndown.pending=on color9\n"
252
    "color.burndown.started=on color11\n"
253
    "\n"
254
    "# Report: history\n"
255
    "color.history.add=color0 on rgb500\n"
256
    "color.history.delete=color0 on rgb550\n"
257
    "color.history.done=color0 on rgb050\n"
258
    "\n"
259
    "# Report: summary\n"
260
    "color.summary.background=white on color0\n"
261
    "color.summary.bar=black on rgb141\n"
262
    "\n"
263
    "# Command: calendar\n"
264
    "color.calendar.due.today=color15 on color1\n"
265
    "color.calendar.due=color0 on color1\n"
266
    "color.calendar.holiday=color0 on color11\n"
267
    "color.calendar.scheduled=rgb013 on color15\n"
268
    "color.calendar.overdue=color0 on color9\n"
269
    "color.calendar.today=color15 on rgb013\n"
270
    "color.calendar.weekend=on color235\n"
271
    "color.calendar.weeknumber=rgb013\n"
272
    "\n"
273
    "# Command: sync\n"
274
    "color.sync.added=rgb010\n"
275
    "color.sync.changed=color11\n"
276
    "color.sync.rejected=color9\n"
277
    "\n"
278
    "# Command: undo\n"
279
    "color.undo.after=color2\n"
280
    "color.undo.before=color1\n"
281
    "\n"
282
    "# UDA priority\n"
283
    "uda.priority.type=string                       # UDA priority is a string type\n"
284
    "uda.priority.label=Priority                    # UDA priority has a display label'\n"
285
    "uda.priority.values=H,M,L,                     # UDA priority values are 'H', 'M', 'L' or ''\n"
286
    "                                               # UDA priority sorting is 'H' > 'M' > 'L' > '' "
287
    "(highest to lowest)\n"
288
    "#uda.priority.default=M                        # UDA priority default value of 'M'\n"
289
    "urgency.uda.priority.H.coefficient=6.0         # UDA priority coefficient for value 'H'\n"
290
    "urgency.uda.priority.M.coefficient=3.9         # UDA priority coefficient for value 'M'\n"
291
    "urgency.uda.priority.L.coefficient=1.8         # UDA priority coefficient for value 'L'\n"
292
    "\n"
293
    "#default.project=foo                           # Default project for 'add' command\n"
294
    "#default.due=eom                               # Default due date for 'add' command\n"
295
    "#default.scheduled=eom                         # Default scheduled date for 'add' command\n"
296
    "default.command=next                           # When no arguments are specified\n"
297
    "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and "
298
    "end.after:now-4wks )\n"
299
    "\n"
300
    "_forcecolor=0                                  # Forces color to be on, even for non TTY "
301
    "output\n"
302
    "complete.all.tags=0                            # Include old tag names in '_ags' command\n"
303
    "list.all.projects=0                            # Include old project names in 'projects' "
304
    "command\n"
305
    "summary.all.projects=0                         # Include old project names in 'summary' "
306
    "command\n"
307
    "list.all.tags=0                                # Include old tag names in 'tags' command\n"
308
    "print.empty.columns=0                          # Print columns which have no data for any "
309
    "task\n"
310
    "debug=0                                        # Display diagnostics\n"
311
    "sugar=1                                        # Syntactic sugar\n"
312
    "obfuscate=0                                    # Obfuscate data for error reporting\n"
313
    "fontunderline=1                                # Uses underlines rather than -------\n"
314
    "\n"
315
    "# WARNING: Please read the documentation (man task-sync) before setting up\n"
316
    "#          Taskwarrior for Taskserver synchronization.\n"
317
    "\n"
318
    "#sync.encryption_secret                        # Encryption secret for sync to a server\n"
319
    "#sync.server.client_id                         # Client ID for sync to a server\n"
320
    "#sync.server.url                               # URL of the sync server\n"
321
    "#sync.local.server_dir                         # Directory for local sync\n"
322
    "#sync.gcp.credential_path                      # Path to JSON file containing credentials to "
323
    "authenticate GCP Sync\n"
324
    "#sync.gcp.bucket                               # Bucket for sync to GCP\n"
325
    "\n"
326
    "# Aliases - alternate names for commands\n"
327
    "alias.rm=delete                                # Alias for the delete command\n"
328
    "alias.history=history.monthly                  # Prefer monthly over annual history reports\n"
329
    "alias.ghistory=ghistory.monthly                # Prefer monthly graphical over annual history "
330
    "reports\n"
331
    "alias.burndown=burndown.weekly                 # Prefer the weekly burndown chart\n"
332
    "\n"
333
    "# Reports\n"
334
    "\n"
335
    "report.long.description=All details of tasks\n"
336
    "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until,"
337
    "Description\n"
338
    "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur,"
339
    "wait.remaining,scheduled,due,until,description\n"
340
    "report.long.filter=status:pending -WAITING\n"
341
    "report.long.sort=modified-\n"
342
    "report.long.context=1\n"
343
    "\n"
344
    "report.list.description=Most details of tasks\n"
345
    "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n"
346
    "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
347
    "indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n"
348
    "report.list.filter=status:pending -WAITING\n"
349
    "report.list.sort=start-,due+,project+,urgency-\n"
350
    "report.list.context=1\n"
351
    "\n"
352
    "report.ls.description=Few details of tasks\n"
353
    "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n"
354
    "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait."
355
    "remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n"
356
    "report.ls.filter=status:pending -WAITING\n"
357
    "report.ls.sort=start-,description+\n"
358
    "report.ls.context=1\n"
359
    "\n"
360
    "report.minimal.description=Minimal details of tasks\n"
361
    "report.minimal.labels=ID,Project,Tags,Description\n"
362
    "report.minimal.columns=id,project,tags.count,description.count\n"
363
    "report.minimal.filter=status:pending -WAITING\n"
364
    "report.minimal.sort=project+/,description+\n"
365
    "report.minimal.context=1\n"
366
    "\n"
367
    "report.newest.description=Newest tasks\n"
368
    "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
369
    "Description\n"
370
    "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
371
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
372
    "report.newest.filter=status:pending -WAITING\n"
373
    "report.newest.sort=entry-\n"
374
    "report.newest.context=1\n"
375
    "\n"
376
    "report.oldest.description=Oldest tasks\n"
377
    "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
378
    "Description\n"
379
    "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
380
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
381
    "report.oldest.filter=status:pending -WAITING\n"
382
    "report.oldest.sort=entry+\n"
383
    "report.oldest.context=1\n"
384
    "\n"
385
    "report.overdue.description=Overdue tasks\n"
386
    "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n"
387
    "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator,"
388
    "scheduled.countdown,due,until,description,urgency\n"
389
    "report.overdue.filter=status:pending -WAITING +OVERDUE\n"
390
    "report.overdue.sort=urgency-,due+\n"
391
    "report.overdue.context=1\n"
392
    "\n"
393
    "report.active.description=Active tasks\n"
394
    "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until,"
395
    "Description\n"
396
    "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags,"
397
    "recur,wait,scheduled.remaining,due,until,description\n"
398
    "report.active.filter=status:pending -WAITING +ACTIVE\n"
399
    "report.active.sort=project+,start+\n"
400
    "report.active.context=1\n"
401
    "\n"
402
    "report.completed.description=Completed tasks\n"
403
    "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n"
404
    "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags,"
405
    "recur.indicator,due,description\n"
406
    "report.completed.filter=status:completed -WAITING \n"
407
    "report.completed.sort=end+\n"
408
    "report.completed.context=1\n"
409
    "\n"
410
    "report.recurring.description=Recurring Tasks\n"
411
    "report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description,"
412
    "Urg\n"
413
    "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short,"
414
    "project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
415
    "report.recurring.filter=(status:pending -WAITING +CHILD) or (status:recurring -WAITING "
416
    "+PARENT)\n"
417
    "report.recurring.sort=due+,urgency-,entry+\n"
418
    "report.recurring.context=1\n"
419
    "\n"
420
    "report.waiting.description=Waiting (hidden) tasks\n"
421
    "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n"
422
    "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags,"
423
    "recur.indicator,wait,wait.remaining,scheduled,due,until,description\n"
424
    "report.waiting.filter=+WAITING\n"
425
    "report.waiting.sort=due+,wait+,entry+\n"
426
    "report.waiting.context=1\n"
427
    "\n"
428
    "report.all.description=All tasks\n"
429
    "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
430
    "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends."
431
    "indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled."
432
    "remaining,due,until.remaining,description\n"
433
    "report.all.sort=entry-\n"
434
    "report.all.context=1\n"
435
    "\n"
436
    "report.next.description=Most urgent tasks\n"
437
    "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n"
438
    "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled."
439
    "countdown,due.relative,until.remaining,description,urgency\n"
440
    "report.next.filter=status:pending -WAITING limit:page\n"
441
    "report.next.sort=urgency-\n"
442
    "report.next.context=1\n"
443
    "\n"
444
    "report.ready.description=Most urgent actionable tasks\n"
445
    "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n"
446
    "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
447
    "indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n"
448
    "report.ready.filter=+READY\n"
449
    "report.ready.sort=start-,urgency-\n"
450
    "report.ready.context=1\n"
451
    "\n"
452
    "report.blocked.description=Blocked tasks\n"
453
    "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
454
    "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
455
    "report.blocked.sort=due+,priority-,start-,project+\n"
456
    "report.blocked.filter=status:pending -WAITING +BLOCKED\n"
457
    "report.blocked.context=1\n"
458
    "\n"
459
    "report.unblocked.description=Unblocked tasks\n"
460
    "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
461
    "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
462
    "report.unblocked.sort=due+,priority-,start-,project+\n"
463
    "report.unblocked.filter=status:pending -WAITING -BLOCKED\n"
464
    "report.unblocked.context=1\n"
465
    "\n"
466
    "report.blocking.description=Blocking tasks\n"
467
    "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n"
468
    "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled."
469
    "remaining,due.relative,until.remaining,description.count,urgency\n"
470
    "report.blocking.sort=urgency-,due+,entry+\n"
471
    "report.blocking.filter=status:pending -WAITING +BLOCKING\n"
472
    "report.blocking.context=1\n"
473
    "\n"
474
    "report.timesheet.filter=(+PENDING -WAITING start.after:now-4wks) or (+COMPLETED -WAITING "
475
    "end.after:now-4wks)\n"
476
    "report.timesheet.context=0\n"
477
    "\n";
478

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

485
Context* Context::context;
486

487
////////////////////////////////////////////////////////////////////////////////
488
Context& Context::getContext() {
1,215,673✔
489
  assert(Context::context);
1,215,673✔
490
  return *Context::context;
1,215,673✔
491
}
492

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

496
////////////////////////////////////////////////////////////////////////////////
497
Context::~Context() {
4,535✔
498
  for (auto& com : commands) delete com.second;
409,690✔
499

500
  for (auto& col : columns) delete col.second;
113,165✔
501
}
4,535✔
502

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

509
  std::vector<std::string> searchPaths{TASK_RCDIR};
13,494✔
510

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

525
    bool taskrc_overridden = false;
4,498✔
526

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

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

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

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

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

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

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

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

564
    CLI2::applyOverrides(argc, argv);
4,498✔
565

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

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

580
    bool taskdata_overridden = false;
4,498✔
581

582
    override = getenv("TASKDATA");
4,498✔
583
    if (override) {
4,498✔
584
      data_dir = Directory(override);
4,495✔
585
      config.set("data.location", data_dir._data);
8,990✔
586
      taskdata_overridden = true;
4,495✔
587
    }
588

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

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

594
    createDefaultConfig();
4,498✔
595

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

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

605
    Command::factory(commands);
4,497✔
606
    for (auto& cmd : commands) cli2.entity("cmd", cmd.first);
814,807✔
607

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

614
    Column::factory(columns);
4,497✔
615
    for (auto& col : columns) cli2.entity("attribute", col.first);
221,757✔
616

617
    cli2.entity("pseudo", "limit");
17,988✔
618

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

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

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

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

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

637
    initializeColorRules();
4,497✔
638
    staticInitialization();
4,497✔
639
    propagateDebug();
4,497✔
640
    loadAliases();
4,497✔
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,185✔
649

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

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

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

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

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

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

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

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

678
    hooks.initialize();
4,495✔
679
  }
4,495✔
680

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

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

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

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

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

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

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

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

741
  time_init_us += timer_total.total_us();
4,498✔
742
  return rc;
4,498✔
743
}
17,992✔
744

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

750
  try {
751
    hooks.onLaunch();
4,495✔
752
    rc = dispatch(output);
4,492✔
753
    hooks.onExit();  // No chance to update data.
4,016✔
754

755
    timer_total.stop();
4,014✔
756
    time_total_us += timer_total.total_us();
4,014✔
757

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

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

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

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

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

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

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

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

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

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

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

835
  return rc;
4,495✔
836
}
4,495✔
837

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

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

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

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

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

867
    return c->execute(out);
4,492✔
868
  }
869

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

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

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

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

891
    width = terminal_width;
1✔
892

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

899
  return width;
1,020✔
900
}
901

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

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

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

919
    height = terminal_height;
×
920
  }
921

922
  return height;
72✔
923
}
924

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

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

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

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

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

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

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

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

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

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

981
  // Cached result.
982
  return use_color;
27,302✔
983
}
984

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

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

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

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

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

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

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

1060
  return false;
10,035✔
1061
}
69✔
1062

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

1068
  return output;
1✔
1069
}
×
1070

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

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

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

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

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

1108
  Datetime::isoEnabled = config.getBoolean("date.iso");
8,994✔
1109
  Datetime::standaloneDateEnabled = false;
4,497✔
1110
  Datetime::standaloneTimeEnabled = false;
4,497✔
1111
  Duration::standaloneSecondsEnabled = false;
4,497✔
1112

1113
  TDB2::debug_mode = config.getBoolean("debug");
8,994✔
1114

1115
  for (auto& rc : config) {
1,110,096✔
1116
    if (rc.first.substr(0, 4) == "uda." && rc.first.substr(rc.first.length() - 7, 7) == ".values") {
1,105,599✔
1117
      std::string name = rc.first.substr(4, rc.first.length() - 7 - 4);
4,574✔
1118
      auto values = split(rc.second, ',');
4,574✔
1119

1120
      for (auto r = values.rbegin(); r != values.rend(); ++r) Task::customOrder[name].push_back(*r);
22,789✔
1121
    }
4,574✔
1122
  }
1123

1124
  for (auto& col : columns) {
113,127✔
1125
    Task::attributes[col.first] = col.second->type();
108,630✔
1126
    Lexer::attributes[col.first] = col.second->type();
108,630✔
1127
  }
1128

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

1141
  // Tag- and project-specific coefficients.
1142
  for (auto& var : config.all())
1,110,096✔
1143
    if (var.substr(0, 13) == "urgency.user." || var.substr(0, 12) == "urgency.uda.")
1,105,599✔
1144
      Task::coefficients[var] = config.getReal(var);
22,723✔
1145
}
4,497✔
1146

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

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

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

1188
    // Load it so that it takes effect for this run.
1189
    config.load(rc_file);
1✔
1190
  }
1✔
1191
}
4,498✔
1192

1193
////////////////////////////////////////////////////////////////////////////////
1194
void Context::decomposeSortField(const std::string& field, std::string& key, bool& ascending,
5,069✔
1195
                                 bool& breakIndicator) {
1196
  int length = field.length();
5,069✔
1197

1198
  int decoration = 1;
5,069✔
1199
  breakIndicator = false;
5,069✔
1200
  if (field[length - decoration] == '/') {
5,069✔
1201
    breakIndicator = true;
18✔
1202
    ++decoration;
18✔
1203
  }
1204

1205
  if (field[length - decoration] == '+') {
5,069✔
1206
    ascending = true;
2,188✔
1207
    key = field.substr(0, length - decoration);
2,188✔
1208
  } else if (field[length - decoration] == '-') {
2,881✔
1209
    ascending = false;
2,410✔
1210
    key = field.substr(0, length - decoration);
2,410✔
1211
  } else {
1212
    ascending = true;
471✔
1213
    key = field;
471✔
1214
  }
1215
}
5,069✔
1216

1217
////////////////////////////////////////////////////////////////////////////////
1218
void Context::debugTiming(const std::string& details, const Timer& timer) {
4,499✔
1219
  std::stringstream out;
4,499✔
1220
  out << "Timer " << details << ' ' << std::setprecision(6) << std::fixed
4,499✔
1221
      << timer.total_us() / 1.0e6 << " sec";
4,499✔
1222
  debug(out.str());
4,499✔
1223
}
4,499✔
1224

1225
////////////////////////////////////////////////////////////////////////////////
1226
CurrentTask Context::withCurrentTask(const Task* task) { return CurrentTask(*this, task); }
10,878✔
1227

1228
////////////////////////////////////////////////////////////////////////////////
1229
// This capability is to answer the question of 'what did I just do to generate
1230
// this output?'.
1231
void Context::updateXtermTitle() {
4,492✔
1232
  if (config.getBoolean("xterm.title") && isatty(STDOUT_FILENO)) {
13,476✔
1233
    auto command = cli2.getCommand();
×
1234
    std::string title;
×
1235

1236
    for (auto a = cli2._args.begin(); a != cli2._args.end(); ++a) {
×
1237
      if (a != cli2._args.begin()) title += ' ';
×
1238

1239
      title += a->attribute("raw");
×
1240
    }
1241

1242
    std::cout << " ]0;task " << command << ' ' << title << " ";
×
1243
  }
×
1244
}
4,492✔
1245

1246
////////////////////////////////////////////////////////////////////////////////
1247
// This function allows a clean output if the command is a helper subcommand.
1248
void Context::updateVerbosity() {
4,492✔
1249
  auto command = cli2.getCommand();
4,492✔
1250
  if (command != "" && command[0] == '_') {
4,492✔
1251
    verbosity = {"nothing"};
720✔
1252
  }
1253
}
5,212✔
1254

1255
////////////////////////////////////////////////////////////////////////////////
1256
void Context::loadAliases() {
4,497✔
1257
  for (auto& i : config)
1,110,104✔
1258
    if (i.first.substr(0, 6) == "alias.") cli2.alias(i.first.substr(6), i.second);
1,105,607✔
1259
}
4,497✔
1260

1261
////////////////////////////////////////////////////////////////////////////////
1262
// Using the general rc.debug setting automaticalls sets debug.hooks
1263
// and debug.parser, unless they already have values, which by default they do
1264
// not.
1265
void Context::propagateDebug() {
4,497✔
1266
  if (config.getBoolean("debug")) {
13,491✔
1267
    if (!config.has("debug.hooks")) config.set("debug.hooks", 1);
20✔
1268

1269
    if (!config.has("debug.parser")) config.set("debug.parser", 1);
20✔
1270
  } else {
1271
    if ((config.has("debug.hooks") && config.getInteger("debug.hooks")) ||
22,467✔
1272
        (config.has("debug.parser") && config.getInteger("debug.parser")))
17,974✔
1273
      config.set("debug", true);
18✔
1274
  }
1275
}
4,497✔
1276

1277
////////////////////////////////////////////////////////////////////////////////
1278
// No duplicates.
1279
void Context::header(const std::string& input) {
8,246✔
1280
  if (input.length() && std::find(headers.begin(), headers.end(), input) == headers.end())
8,246✔
1281
    headers.push_back(input);
8,246✔
1282
}
8,246✔
1283

1284
////////////////////////////////////////////////////////////////////////////////
1285
// No duplicates.
1286
void Context::footnote(const std::string& input) {
2,256✔
1287
  if (input.length() && std::find(footnotes.begin(), footnotes.end(), input) == footnotes.end())
2,256✔
1288
    footnotes.push_back(input);
1,306✔
1289
}
2,256✔
1290

1291
////////////////////////////////////////////////////////////////////////////////
1292
// No duplicates.
1293
void Context::error(const std::string& input) {
492✔
1294
  if (input.length() && std::find(errors.begin(), errors.end(), input) == errors.end())
492✔
1295
    errors.push_back(input);
486✔
1296
}
492✔
1297

1298
////////////////////////////////////////////////////////////////////////////////
1299
void Context::debug(const std::string& input) {
16,409✔
1300
  if (input.length()) debugMessages.push_back(input);
16,409✔
1301
}
16,409✔
1302

1303
////////////////////////////////////////////////////////////////////////////////
1304
CurrentTask::CurrentTask(Context& context, const Task* task)
10,878✔
1305
    : context{context}, previous{context.currentTask} {
10,878✔
1306
  context.currentTask = task;
10,878✔
1307
}
10,878✔
1308

1309
////////////////////////////////////////////////////////////////////////////////
1310
CurrentTask::~CurrentTask() { context.currentTask = previous; }
10,878✔
1311

1312
////////////////////////////////////////////////////////////////////////////////
1313

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