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

GothenburgBitFactory / taskwarrior / 27952146060

22 Jun 2026 12:19PM UTC coverage: 85.053% (+0.1%) from 84.958%
27952146060

push

github

web-flow
Bump actions/checkout from 6 to 7 in the github-actions group (#4130)

Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

19620 of 23068 relevant lines covered (85.05%)

23532.36 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
#ifdef TASK_TEST_RCDIR
201
    "include " TASK_TEST_RCDIR
202
    "/default.theme\n"
203
#else
204
    "include default.theme\n"
205
#endif
206
    "\n"
207
    "# Here is the rule precedence order, highest to lowest.\n"
208
    "# Note that these are just the color rule names, without the leading 'color.'\n"
209
    "#      and any trailing '.value'.\n"
210
    "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due."
211
    "today,due,blocked,blocking,recurring,tagged,uda.\n"
212
    "\n"
213
    "# UDA priority\n"
214
    "uda.priority.type=string                       # UDA priority is a string type\n"
215
    "uda.priority.label=Priority                    # UDA priority has a display label'\n"
216
    "uda.priority.values=H,M,L,                     # UDA priority values are 'H', 'M', 'L' or ''\n"
217
    "                                               # UDA priority sorting is 'H' > 'M' > 'L' > '' "
218
    "(highest to lowest)\n"
219
    "#uda.priority.default=M                        # UDA priority default value of 'M'\n"
220
    "urgency.uda.priority.H.coefficient=6.0         # UDA priority coefficient for value 'H'\n"
221
    "urgency.uda.priority.M.coefficient=3.9         # UDA priority coefficient for value 'M'\n"
222
    "urgency.uda.priority.L.coefficient=1.8         # UDA priority coefficient for value 'L'\n"
223
    "\n"
224
    "#default.project=foo                           # Default project for 'add' command\n"
225
    "#default.due=eom                               # Default due date for 'add' command\n"
226
    "#default.scheduled=eom                         # Default scheduled date for 'add' command\n"
227
    "default.command=next                           # When no arguments are specified\n"
228
    "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and "
229
    "end.after:now-4wks )\n"
230
    "\n"
231
    "_forcecolor=0                                  # Forces color to be on, even for non TTY "
232
    "output\n"
233
    "complete.all.tags=0                            # Include old tag names in '_ags' command\n"
234
    "list.all.projects=0                            # Include old project names in 'projects' "
235
    "command\n"
236
    "summary.all.projects=0                         # Include old project names in 'summary' "
237
    "command\n"
238
    "list.all.tags=0                                # Include old tag names in 'tags' command\n"
239
    "print.empty.columns=0                          # Print columns which have no data for any "
240
    "task\n"
241
    "debug=0                                        # Display diagnostics\n"
242
    "sugar=1                                        # Syntactic sugar\n"
243
    "obfuscate=0                                    # Obfuscate data for error reporting\n"
244
    "fontunderline=1                                # Uses underlines rather than -------\n"
245
    "\n"
246
    "# WARNING: Please read the documentation (man task-sync) before setting up\n"
247
    "#          Taskwarrior for Taskserver synchronization.\n"
248
    "\n"
249
    "#sync.encryption_secret                        # Encryption secret for sync to a server\n"
250
    "#sync.server.client_id                         # Client ID for sync to a server\n"
251
    "#sync.server.url                               # URL of the sync server\n"
252
    "#sync.local.server_dir                         # Directory for local sync\n"
253
    "#sync.aws.region                               # region for AWS sync\n"
254
    "#sync.aws.bucket                               # bucket for AWS sync\n"
255
    "#sync.aws.access_key_id                        # access_key_id for AWS sync\n"
256
    "#sync.aws.secret_access_key                    # secret_access_key for AWS sync\n"
257
    "#sync.aws.profile                              # profile name for AWS sync\n"
258
    "#sync.aws.default_credentials                  # use default credentials for AWS sync\n"
259
    "#sync.gcp.credential_path                      # Path to JSON file containing credentials to "
260
    "authenticate GCP Sync\n"
261
    "#sync.gcp.bucket                               # Bucket for sync to GCP\n"
262
    "\n"
263
    "# Aliases - alternate names for commands\n"
264
    "alias.rm=delete                                # Alias for the delete command\n"
265
    "alias.history=history.monthly                  # Prefer monthly over annual history reports\n"
266
    "alias.ghistory=ghistory.monthly                # Prefer monthly graphical over annual history "
267
    "reports\n"
268
    "alias.burndown=burndown.weekly                 # Prefer the weekly burndown chart\n"
269
    "\n"
270
    "# Reports\n"
271
    "\n"
272
    "report.long.description=All details of tasks\n"
273
    "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until,"
274
    "Description\n"
275
    "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur,"
276
    "wait.remaining,scheduled,due,until,description\n"
277
    "report.long.filter=+PENDING -WAITING\n"
278
    "report.long.sort=modified-\n"
279
    "report.long.context=1\n"
280
    "\n"
281
    "report.list.description=Most details of tasks\n"
282
    "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n"
283
    "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
284
    "indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n"
285
    "report.list.filter=+PENDING -WAITING\n"
286
    "report.list.sort=start-,due+,project+,urgency-\n"
287
    "report.list.context=1\n"
288
    "\n"
289
    "report.ls.description=Few details of tasks\n"
290
    "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n"
291
    "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait."
292
    "remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n"
293
    "report.ls.filter=+PENDING -WAITING\n"
294
    "report.ls.sort=start-,description+\n"
295
    "report.ls.context=1\n"
296
    "\n"
297
    "report.minimal.description=Minimal details of tasks\n"
298
    "report.minimal.labels=ID,Project,Tags,Description\n"
299
    "report.minimal.columns=id,project,tags.count,description.count\n"
300
    "report.minimal.filter=+PENDING -WAITING\n"
301
    "report.minimal.sort=project+/,description+\n"
302
    "report.minimal.context=1\n"
303
    "\n"
304
    "report.newest.description=Newest tasks\n"
305
    "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
306
    "Description\n"
307
    "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
308
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
309
    "report.newest.filter=+PENDING -WAITING\n"
310
    "report.newest.sort=entry-\n"
311
    "report.newest.context=1\n"
312
    "\n"
313
    "report.oldest.description=Oldest tasks\n"
314
    "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,"
315
    "Description\n"
316
    "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,"
317
    "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n"
318
    "report.oldest.filter=+PENDING -WAITING\n"
319
    "report.oldest.sort=entry+\n"
320
    "report.oldest.context=1\n"
321
    "\n"
322
    "report.overdue.description=Overdue tasks\n"
323
    "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n"
324
    "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator,"
325
    "scheduled.countdown,due,until,description,urgency\n"
326
    "report.overdue.filter=+PENDING -WAITING +OVERDUE\n"
327
    "report.overdue.sort=urgency-,due+\n"
328
    "report.overdue.context=1\n"
329
    "\n"
330
    "report.active.description=Active tasks\n"
331
    "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until,"
332
    "Description\n"
333
    "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags,"
334
    "recur,wait,scheduled.remaining,due,until,description\n"
335
    "report.active.filter=+PENDING -WAITING +ACTIVE\n"
336
    "report.active.sort=project+,start+\n"
337
    "report.active.context=1\n"
338
    "\n"
339
    "report.completed.description=Completed tasks\n"
340
    "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n"
341
    "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags,"
342
    "recur.indicator,due,description\n"
343
    "report.completed.filter=status:completed -WAITING \n"
344
    "report.completed.sort=end+\n"
345
    "report.completed.context=1\n"
346
    "\n"
347
    "report.recurring.description=Recurring Tasks\n"
348
    "report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description,"
349
    "Urg\n"
350
    "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short,"
351
    "project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n"
352
    "report.recurring.filter=(+PENDING -WAITING +CHILD) or (status:recurring -WAITING "
353
    "+PARENT)\n"
354
    "report.recurring.sort=due+,urgency-,entry+\n"
355
    "report.recurring.context=1\n"
356
    "\n"
357
    "report.waiting.description=Waiting (hidden) tasks\n"
358
    "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n"
359
    "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags,"
360
    "recur.indicator,wait,wait.remaining,scheduled,due,until,description\n"
361
    "report.waiting.filter=+WAITING\n"
362
    "report.waiting.sort=due+,wait+,entry+\n"
363
    "report.waiting.context=1\n"
364
    "\n"
365
    "report.all.description=All tasks\n"
366
    "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n"
367
    "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends."
368
    "indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled."
369
    "remaining,due,until.remaining,description\n"
370
    "report.all.sort=entry-\n"
371
    "report.all.context=1\n"
372
    "\n"
373
    "report.next.description=Most urgent tasks\n"
374
    "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n"
375
    "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled."
376
    "countdown,due.relative,until.remaining,description,urgency\n"
377
    "report.next.filter=+PENDING -WAITING limit:page\n"
378
    "report.next.sort=urgency-\n"
379
    "report.next.context=1\n"
380
    "\n"
381
    "report.ready.description=Most urgent actionable tasks\n"
382
    "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n"
383
    "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur."
384
    "indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n"
385
    "report.ready.filter=+READY\n"
386
    "report.ready.sort=start-,urgency-\n"
387
    "report.ready.context=1\n"
388
    "\n"
389
    "report.blocked.description=Blocked tasks\n"
390
    "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
391
    "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
392
    "report.blocked.sort=due+,priority-,start-,project+\n"
393
    "report.blocked.filter=+PENDING -WAITING +BLOCKED\n"
394
    "report.blocked.context=1\n"
395
    "\n"
396
    "report.unblocked.description=Unblocked tasks\n"
397
    "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n"
398
    "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n"
399
    "report.unblocked.sort=due+,priority-,start-,project+\n"
400
    "report.unblocked.filter=+PENDING -WAITING -BLOCKED\n"
401
    "report.unblocked.context=1\n"
402
    "\n"
403
    "report.blocking.description=Blocking tasks\n"
404
    "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n"
405
    "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled."
406
    "remaining,due.relative,until.remaining,description.count,urgency\n"
407
    "report.blocking.sort=urgency-,due+,entry+\n"
408
    "report.blocking.filter=+PENDING -WAITING +BLOCKING\n"
409
    "report.blocking.context=1\n"
410
    "\n"
411
    "report.timesheet.filter=(+PENDING -WAITING start.after:now-4wks) or (+COMPLETED -WAITING "
412
    "end.after:now-4wks)\n"
413
    "report.timesheet.context=0\n"
414
    "\n";
415

416
// Supported modifiers, synonyms on the same line.
417
static const char* modifierNames[] = {
418
    "before",     "under", "below",    "after", "over", "above", "by",       "none",
419
    "any",        "is",    "equals",   "isnt",  "not",  "has",   "contains", "hasnt",
420
    "startswith", "left",  "endswith", "right", "word", "noword"};
421

422
Context* Context::context;
423

424
////////////////////////////////////////////////////////////////////////////////
425
Context& Context::getContext() {
1,226,935✔
426
  assert(Context::context);
1,226,935✔
427
  return *Context::context;
1,226,935✔
428
}
429

430
////////////////////////////////////////////////////////////////////////////////
431
void Context::setContext(Context* context) { Context::context = context; }
4,624✔
432

433
////////////////////////////////////////////////////////////////////////////////
434
Context::~Context() {
4,655✔
435
  for (auto& com : commands) delete com.second;
425,365✔
436

437
  for (auto& col : columns) delete col.second;
116,202✔
438
}
4,655✔
439

440
////////////////////////////////////////////////////////////////////////////////
441
int Context::initialize(int argc, const char** argv) {
4,618✔
442
  timer_total.start();
4,618✔
443
  int rc = 0;
4,618✔
444
  home_dir = getenv("HOME");
4,618✔
445

446
  std::vector<std::string> searchPaths{TASK_RCDIR};
13,854✔
447

448
  try {
449
    ////////////////////////////////////////////////////////////////////////////
450
    //
451
    // [1] Load the correct config file.
452
    //     - Default to ~/.taskrc (ctor).
453
    //     - If no ~/.taskrc, use $XDG_CONFIG_HOME/task/taskrc if exists, or
454
    //       ~/.config/task/taskrc if $XDG_CONFIG_HOME is unset
455
    //     - Allow $TASKRC override.
456
    //     - Allow command line override rc:<file>
457
    //     - Load resultant file.
458
    //     - Apply command line overrides to the config.
459
    //
460
    ////////////////////////////////////////////////////////////////////////////
461

462
    bool taskrc_overridden = false;
4,618✔
463

464
    // XDG_CONFIG_HOME doesn't count as an override (no warning header)
465
    if (!rc_file.exists()) {
4,618✔
466
      // Use XDG_CONFIG_HOME if defined, otherwise default to ~/.config
467
      std::string xdg_config_home;
4,618✔
468
      const char* env_xdg_config_home = getenv("XDG_CONFIG_HOME");
4,618✔
469

470
      if (env_xdg_config_home)
4,618✔
471
        xdg_config_home = format("{1}", env_xdg_config_home);
9,236✔
472
      else
473
        xdg_config_home = format("{1}/.config", home_dir);
×
474

475
      // Ensure the path does not end with '/'
476
      if (xdg_config_home.back() == '/') xdg_config_home.pop_back();
4,618✔
477

478
      // https://github.com/GothenburgBitFactory/libshared/issues/32
479
      std::string rcfile_path = format("{1}/task/taskrc", xdg_config_home);
13,854✔
480

481
      File maybe_rc_file = File(rcfile_path);
4,618✔
482
      if (maybe_rc_file.exists()) rc_file = maybe_rc_file;
4,618✔
483
    }
4,618✔
484

485
    char* override = getenv("TASKRC");
4,618✔
486
    if (override) {
4,618✔
487
      rc_file = File(override);
4,507✔
488
      taskrc_overridden = true;
4,507✔
489
    }
490

491
    taskrc_overridden = CLI2::getOverride(argc, argv, rc_file) || taskrc_overridden;
4,618✔
492

493
    // Artificial scope for timing purposes.
494
    {
495
      Timer timer;
4,618✔
496
      config.parse(configurationDefaults, 1, searchPaths);
4,618✔
497
      config.load(rc_file._data, 1, searchPaths);
4,618✔
498
      debugTiming(format("Config::load ({1})", rc_file._data), timer);
13,854✔
499
    }
500

501
    CLI2::applyOverrides(argc, argv);
4,618✔
502

503
    if (taskrc_overridden && verbose("override"))
13,854✔
504
      header(format("TASKRC override: {1}", rc_file._data));
12,618✔
505

506
    ////////////////////////////////////////////////////////////////////////////
507
    //
508
    // [2] Locate the data directory.
509
    //     - Default to ~/.task (ctor).
510
    //     - Allow $TASKDATA override.
511
    //     - Allow command line override rc.data.location:<dir>
512
    //     - Inform TDB2 where to find data.
513
    //     - Create the rc_file and data_dir, if necessary.
514
    //
515
    ////////////////////////////////////////////////////////////////////////////
516

517
    bool taskdata_overridden = false;
4,618✔
518

519
    override = getenv("TASKDATA");
4,618✔
520
    if (override) {
4,618✔
521
      data_dir = Directory(override);
4,615✔
522
      config.set("data.location", data_dir._data);
9,230✔
523
      taskdata_overridden = true;
4,615✔
524
    }
525

526
    taskdata_overridden = CLI2::getDataLocation(argc, argv, data_dir) || taskdata_overridden;
4,618✔
527

528
    if (taskdata_overridden && verbose("override"))
13,850✔
529
      header(format("TASKDATA override: {1}", data_dir._data));
12,612✔
530

531
    createDefaultConfig();
4,618✔
532

533
    ////////////////////////////////////////////////////////////////////////////
534
    //
535
    // [3] Instantiate Command objects and capture command entities.
536
    //
537
    ////////////////////////////////////////////////////////////////////////////
538

539
    Command::factory(commands);
4,618✔
540
    for (auto& cmd : commands) cli2.entity("cmd", cmd.first);
846,038✔
541

542
    ////////////////////////////////////////////////////////////////////////////
543
    //
544
    // [4] Instantiate Column objects and capture column entities.
545
    //
546
    ////////////////////////////////////////////////////////////////////////////
547

548
    Column::factory(columns);
4,618✔
549
    for (auto& col : columns) cli2.entity("attribute", col.first);
227,712✔
550

551
    cli2.entity("pseudo", "limit");
18,472✔
552

553
    ////////////////////////////////////////////////////////////////////////////
554
    //
555
    // [5] Capture modifier and operator entities.
556
    //
557
    ////////////////////////////////////////////////////////////////////////////
558

559
    for (auto& modifierName : modifierNames) cli2.entity("modifier", modifierName);
411,002✔
560

561
    for (auto& op : Eval::getOperators()) cli2.entity("operator", op);
244,754✔
562

563
    for (auto& op : Eval::getBinaryOperators()) cli2.entity("binary_operator", op);
198,574✔
564

565
    ////////////////////////////////////////////////////////////////////////////
566
    //
567
    // [6] Complete the Context initialization.
568
    //
569
    ////////////////////////////////////////////////////////////////////////////
570

571
    initializeColorRules();
4,618✔
572
    staticInitialization();
4,618✔
573
    propagateDebug();
4,618✔
574
    loadAliases();
4,618✔
575

576
    ////////////////////////////////////////////////////////////////////////////
577
    //
578
    // [7] Parse the command line.
579
    //
580
    ////////////////////////////////////////////////////////////////////////////
581

582
    for (int i = 0; i < argc; i++) cli2.add(argv[i]);
40,220✔
583

584
    cli2.analyze();
4,618✔
585

586
    // Extract a recomposed command line.
587
    auto foundDefault = false;
4,616✔
588
    auto foundAssumed = false;
4,616✔
589
    std::string combined;
4,616✔
590
    for (auto& a : cli2._args) {
24,701✔
591
      if (combined.length()) combined += ' ';
20,085✔
592

593
      combined += a.attribute("raw");
40,170✔
594

595
      if (a.hasTag("DEFAULT")) foundDefault = true;
40,170✔
596

597
      if (a.hasTag("ASSUMED")) foundAssumed = true;
40,170✔
598
    }
599

600
    if (verbose("default")) {
9,232✔
601
      if (foundDefault) header("[" + combined + "]");
8✔
602

603
      if (foundAssumed) header("No command specified - assuming 'information'.");
10✔
604
    }
605

606
    ////////////////////////////////////////////////////////////////////////////
607
    //
608
    // [7.5] Open the Replica.
609
    //
610
    ////////////////////////////////////////////////////////////////////////////
611

612
    bool create_if_missing = !config.getBoolean("exit.on.missing.db");
9,232✔
613
    Command* c = commands[cli2.getCommand()];
4,616✔
614

615
    // We must allow writes if either 'gc' is enabled and the command performs GC, or the command
616
    // itself is read-write.
617
    bool read_write =
618
        (config.getBoolean("gc") && (c->needs_gc() || c->needs_recur_update())) || !c->read_only();
9,232✔
619
    tdb2.open_replica(data_dir, create_if_missing, read_write);
4,617✔
620

621
    ////////////////////////////////////////////////////////////////////////////
622
    //
623
    // [8] Initialize hooks.
624
    //
625
    ////////////////////////////////////////////////////////////////////////////
626

627
    hooks.initialize();
4,615✔
628
  }
4,616✔
629

630
  catch (const std::string& message) {
3✔
631
    error(message);
2✔
632
    rc = 2;
2✔
633
  }
2✔
634

635
  catch (rust::Error& err) {
1✔
636
    error(err.what());
1✔
637
    rc = 2;
1✔
638
  }
1✔
639

640
  catch (int) {
×
641
    // Hooks can terminate processing by throwing integers.
642
    rc = 4;
×
643
  }
×
644

645
  catch (const std::regex_error& e) {
×
646
    std::cout << "regex_error caught: " << e.what() << '\n';
×
647
  } catch (...) {
×
648
    error("Unknown error. Please report.");
×
649
    rc = 3;
×
650
  }
×
651

652
  // On initialization failure...
653
  if (rc) {
4,618✔
654
    // Dump all debug messages, controlled by rc.debug.
655
    if (config.getBoolean("debug")) {
9✔
656
      for (auto& d : debugMessages)
×
657
        if (color())
×
658
          std::cerr << colorizeDebug(d) << '\n';
×
659
        else
660
          std::cerr << d << '\n';
×
661
    }
662

663
    // Dump all headers, controlled by 'header' verbosity token.
664
    if (verbose("header")) {
6✔
665
      for (auto& h : headers)
9✔
666
        if (color())
6✔
667
          std::cerr << colorizeHeader(h) << '\n';
×
668
        else
669
          std::cerr << h << '\n';
6✔
670
    }
671

672
    // Dump all footnotes, controlled by 'footnote' verbosity token.
673
    if (verbose("footnote")) {
6✔
674
      for (auto& f : footnotes)
4✔
675
        if (color())
1✔
676
          std::cerr << colorizeFootnote(f) << '\n';
×
677
        else
678
          std::cerr << f << '\n';
1✔
679
    }
680

681
    // Dump all errors, non-maskable.
682
    // Colorized as footnotes.
683
    for (auto& e : errors)
6✔
684
      if (color())
3✔
685
        std::cerr << colorizeFootnote(e) << '\n';
×
686
      else
687
        std::cerr << e << '\n';
3✔
688
  }
689

690
  time_init_us += timer_total.total_us();
4,618✔
691
  return rc;
4,618✔
692
}
18,472✔
693

694
////////////////////////////////////////////////////////////////////////////////
695
int Context::run() {
4,615✔
696
  int rc;
697
  std::string output;
4,615✔
698

699
  try {
700
    hooks.onLaunch();
4,615✔
701
    rc = dispatch(output);
4,612✔
702
    hooks.onExit();  // No chance to update data.
4,134✔
703

704
    timer_total.stop();
4,132✔
705
    time_total_us += timer_total.total_us();
4,132✔
706

707
    std::stringstream s;
4,132✔
708
    s << "Perf " << PACKAGE_STRING << ' '
709
#ifdef HAVE_COMMIT
710
      << COMMIT
711
#else
712
      << '-'
713
#endif
714
      << ' ' << Datetime().toISO()
8,264✔
715

716
      << " init:" << time_init_us << " load:" << time_load_us
8,264✔
717
      << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us)
4,132✔
718
      << " filter:" << time_filter_us << " commit:" << time_commit_us << " sort:" << time_sort_us
4,132✔
719
      << " render:" << time_render_us << " hooks:" << time_hooks_us << " other:"
4,132✔
720
      << time_total_us - time_init_us - time_gc_us - time_filter_us - time_commit_us -
4,132✔
721
             time_sort_us - time_render_us - time_hooks_us
4,132✔
722
      << " total:" << time_total_us << '\n';
4,132✔
723
    debug(s.str());
4,132✔
724
  }
4,132✔
725

726
  catch (const std::string& message) {
483✔
727
    error(message);
465✔
728
    rc = 2;
465✔
729
  }
465✔
730

731
  catch (rust::Error& err) {
×
732
    error(err.what());
×
733
    rc = 2;
×
734
  }
×
735

736
  catch (int) {
18✔
737
    // Hooks can terminate processing by throwing integers.
738
    rc = 4;
18✔
739
  }
18✔
740

741
  catch (...) {
×
742
    error("Unknown error. Please report.");
×
743
    rc = 3;
×
744
  }
×
745

746
  // Dump all debug messages, controlled by rc.debug.
747
  if (config.getBoolean("debug")) {
13,845✔
748
    for (auto& d : debugMessages)
166✔
749
      if (color())
156✔
750
        std::cerr << colorizeDebug(d) << '\n';
8✔
751
      else
752
        std::cerr << d << '\n';
148✔
753
  }
754

755
  // Dump all headers, controlled by 'header' verbosity token.
756
  if (verbose("header")) {
9,230✔
757
    for (auto& h : headers)
11,620✔
758
      if (color())
7,740✔
759
        std::cerr << colorizeHeader(h) << '\n';
364✔
760
      else
761
        std::cerr << h << '\n';
7,376✔
762
  }
763

764
  // Dump the report output.
765
  std::cout << output;
4,615✔
766

767
  // Dump all footnotes, controlled by 'footnote' verbosity token.
768
  if (verbose("footnote")) {
9,230✔
769
    for (auto& f : footnotes)
5,216✔
770
      if (color())
1,270✔
771
        std::cerr << colorizeFootnote(f) << '\n';
69✔
772
      else
773
        std::cerr << f << '\n';
1,201✔
774
  }
775

776
  // Dump all errors, non-maskable.
777
  // Colorized as footnotes.
778
  for (auto& e : errors)
5,100✔
779
    if (color())
485✔
780
      std::cerr << colorizeError(e) << '\n';
1✔
781
    else
782
      std::cerr << e << '\n';
484✔
783

784
  return rc;
4,615✔
785
}
4,615✔
786

787
////////////////////////////////////////////////////////////////////////////////
788
// Dispatch to the command found by the CLI parser.
789
int Context::dispatch(std::string& out) {
4,612✔
790
  // Autocomplete args against keywords.
791
  std::string command = cli2.getCommand();
4,612✔
792
  if (command != "") {
4,612✔
793
    updateXtermTitle();
4,612✔
794
    updateVerbosity();
4,612✔
795

796
    Command* c = commands[command];
4,612✔
797
    assert(c);
4,612✔
798

799
    // The command know whether they need a GC or recurrence update.
800
    if (c->needs_gc()) {
4,612✔
801
      tdb2.gc();
937✔
802
    }
803

804
    // This is something that is only needed for write commands with no other
805
    // filter processing.
806
    if (c->accepts_modifications() && !c->accepts_filter()) {
4,612✔
807
      cli2.prepareFilter();
1,689✔
808
    }
809

810
    // With rc.debug.parser == 2, there are more tree dumps than you might want,
811
    // but we need the rc.debug.parser == 1 case covered also, with the final
812
    // tree.
813
    if (config.getBoolean("debug") && config.getInteger("debug.parser") == 1)
13,856✔
814
      debug(cli2.dump("Parse Tree (before command-specifÑ–c processing)"));
12✔
815

816
    if (c->needs_recur_update() && Context::getContext().config.getBoolean("gc")) {
6,392✔
817
      handleUntil();
885✔
818
      handleRecurrence();
885✔
819
    }
820

821
    return c->execute(out);
4,612✔
822
  }
823

824
  assert(commands["help"]);
×
825
  return commands["help"]->execute(out);
×
826
}
4,612✔
827

828
////////////////////////////////////////////////////////////////////////////////
829
int Context::getWidth() {
1,202✔
830
  // Determine window size.
831
  auto width = config.getInteger("defaultwidth");
2,404✔
832

833
  // A zero width value means 'infinity', which is approximated here by 2^16.
834
  if (width == 0) return 65536;
1,202✔
835

836
  if (config.getBoolean("detection")) {
3,567✔
837
    if (terminal_width == 0 && terminal_height == 0) {
1✔
838
      unsigned short buff[4];
839
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
1✔
840
        terminal_height = buff[0];
×
841
        terminal_width = buff[1];
×
842
      }
843
    }
844

845
    width = terminal_width;
1✔
846

847
    // Ncurses does this, and perhaps we need to as well, to avoid a problem on
848
    // Cygwin where the display goes right up to the terminal width, and causes
849
    // an odd color wrapping problem.
850
    if (config.getBoolean("avoidlastcolumn")) --width;
3✔
851
  }
852

853
  return width;
1,189✔
854
}
855

856
////////////////////////////////////////////////////////////////////////////////
857
int Context::getHeight() {
74✔
858
  // Determine window size.
859
  auto height = config.getInteger("defaultheight");
148✔
860

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

864
  if (config.getBoolean("detection")) {
222✔
865
    if (terminal_width == 0 && terminal_height == 0) {
×
866
      unsigned short buff[4];
867
      if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) {
×
868
        terminal_height = buff[0];
×
869
        terminal_width = buff[1];
×
870
      }
871
    }
872

873
    height = terminal_height;
×
874
  }
875

876
  return height;
74✔
877
}
878

879
////////////////////////////////////////////////////////////////////////////////
880
std::string Context::getTaskContext(const std::string& kind, std::string name,
3,189✔
881
                                    bool fallback /* = true */) {
882
  // Consider currently selected context, if none specified
883
  if (name.empty()) name = config.get("context");
9,555✔
884

885
  // Detect if any context is set, and bail out if not
886
  if (!name.empty())
3,189✔
887
    debug(format("Applying context '{1}'", name));
108✔
888
  else {
889
    debug("No context set");
6,306✔
890
    return "";
6,306✔
891
  }
892

893
  // Figure out the context string for this kind (read/write)
894
  std::string contextString = "";
36✔
895

896
  if (!config.has("context." + name + "." + kind) && kind == "read") {
36✔
897
    debug("Specific " + kind + " context for '" + name + "' not defined. ");
1✔
898
    if (fallback) {
1✔
899
      debug("Trying to interpret old-style context definition as read context.");
1✔
900
      contextString = config.get("context." + name);
1✔
901
    }
902
  } else
903
    contextString = config.get("context." + name + "." + kind);
35✔
904

905
  debug(format("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString));
111✔
906
  return contextString;
36✔
907
}
36✔
908

909
////////////////////////////////////////////////////////////////////////////////
910
bool Context::color() {
28,430✔
911
  if (determine_color_use) {
28,430✔
912
    // What the config says.
913
    use_color = config.getBoolean("color");
9,238✔
914

915
    // Only tty's support color.
916
    if (!isatty(STDOUT_FILENO)) {
4,619✔
917
      // No ioctl.
918
      config.set("detection", "off");
18,476✔
919
      config.set("color", "off");
18,476✔
920

921
      // Files don't get color.
922
      use_color = false;
4,619✔
923
    }
924

925
    // Override.
926
    if (config.getBoolean("_forcecolor")) {
13,857✔
927
      config.set("color", "on");
732✔
928
      use_color = true;
183✔
929
    }
930

931
    // No need to go through this again.
932
    determine_color_use = false;
4,619✔
933
  }
934

935
  // Cached result.
936
  return use_color;
28,430✔
937
}
938

939
////////////////////////////////////////////////////////////////////////////////
940
// Support verbosity levels:
941
//
942
//   rc.verbose=1          Show all feedback.
943
//   rc.verbose=0          Show regular feedback.
944
//   rc.verbose=nothing    Show the absolute minimum.
945
//   rc.verbose=one,two    Show verbosity for 'one' and 'two' only.
946
//
947
// TODO This mechanism is clunky, and should slowly evolve into something more
948
//      logical and consistent.  This should probably mean that 'nothing' should
949
//      take the place of '0'.
950
bool Context::verbose(const std::string& token) {
46,393✔
951
  if (verbosity.empty()) {
46,393✔
952
    verbosity_legacy = config.getBoolean("verbose");
9,238✔
953
    for (auto& token : split(config.get("verbose"), ',')) verbosity.insert(token);
73,057✔
954

955
    // Regular feedback means almost everything.
956
    // This odd test is to see if a Boolean-false value is a real one, which
957
    // means it is not 1/true/T/yes/on, but also should not be one of the
958
    // valid tokens either.
959
    if (!verbosity_legacy && !verbosity.empty()) {
4,619✔
960
      std::string v = *(verbosity.begin());
4,612✔
961
      if (v != "nothing" && v != "affected" &&  // This list must be complete.
8,892✔
962
          v != "blank" &&                       //
37✔
963
          v != "context" &&                     //
36✔
964
          v != "default" &&                     //
36✔
965
          v != "edit" &&                        //
35✔
966
          v != "filter" &&                      //
35✔
967
          v != "footnote" &&                    //
35✔
968
          v != "header" &&                      //
33✔
969
          v != "label" &&                       //
32✔
970
          v != "new-id" &&                      //
31✔
971
          v != "new-uuid" &&                    //
29✔
972
          v != "news" &&                        //
26✔
973
          v != "override" &&                    //
26✔
974
          v != "project" &&                     //
25✔
975
          v != "recur" &&                       //
24✔
976
          v != "special" &&                     //
8,915✔
977
          v != "sync") {
23✔
978
        // This list emulates rc.verbose=off in version 1.9.4.
979
        verbosity = {"blank", "label", "new-id", "edit"};
115✔
980
      }
981
    }
4,612✔
982

983
    // Some flags imply "footnote" verbosity being active.  Make it so.
984
    if (!verbosity.count("footnote")) {
13,857✔
985
      // TODO: Some of these may not use footnotes yet.  They should.
986
      for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) {
2,519✔
987
        if (verbosity.count(flag)) {
6,528✔
988
          verbosity.insert("footnote");
152✔
989
          break;
76✔
990
        }
991
      }
992
    }
993

994
    // Some flags imply "header" verbosity being active.  Make it so.
995
    if (!verbosity.count("header")) {
13,857✔
996
      for (auto flag : {"default"}) {
838✔
997
        if (verbosity.count(flag)) {
1,257✔
998
          verbosity.insert("header");
×
999
          break;
×
1000
        }
1001
      }
1002
    }
1003
  }
1004

1005
  // rc.verbose=true|y|yes|1|on overrides all.
1006
  if (verbosity_legacy) return true;
46,393✔
1007

1008
  // rc.verbose=nothing overrides all.
1009
  if (verbosity.size() == 1 && *(verbosity.begin()) == "nothing") return false;
46,311✔
1010

1011
  // Specific token match.
1012
  if (verbosity.count(token)) return true;
41,408✔
1013

1014
  return false;
10,194✔
1015
}
69✔
1016

1017
////////////////////////////////////////////////////////////////////////////////
1018
const std::vector<std::string> Context::getColumns() const {
1✔
1019
  std::vector<std::string> output;
1✔
1020
  for (auto& col : columns) output.push_back(col.first);
25✔
1021

1022
  return output;
1✔
1023
}
×
1024

1025
////////////////////////////////////////////////////////////////////////////////
1026
// A value of zero mean unlimited.
1027
// A value of 'page' means however many screen lines there are.
1028
// A value of a positive integer is a row/task limit.
1029
void Context::getLimits(int& rows, int& lines) {
766✔
1030
  rows = 0;
766✔
1031
  lines = 0;
766✔
1032

1033
  // This is an integer specified as a filter (limit:10).
1034
  auto limit = config.get("limit");
1,532✔
1035
  if (limit != "") {
766✔
1036
    if (limit == "page") {
67✔
1037
      rows = 0;
58✔
1038
      lines = getHeight();
58✔
1039
    } else {
1040
      rows = (int)strtol(limit.c_str(), nullptr, 10);
9✔
1041
      lines = 0;
9✔
1042
    }
1043
  }
1044
}
766✔
1045

1046
////////////////////////////////////////////////////////////////////////////////
1047
// The 'Task' object, among others, is shared between projects.  To make this
1048
// easier, it has been decoupled from Context.
1049
void Context::staticInitialization() {
4,618✔
1050
  CLI2::minimumMatchLength = config.getInteger("abbreviation.minimum");
9,236✔
1051
  Lexer::minimumMatchLength = config.getInteger("abbreviation.minimum");
9,236✔
1052

1053
  Task::defaultProject = config.get("default.project");
9,236✔
1054
  Task::defaultDue = config.get("default.due");
9,236✔
1055
  Task::defaultScheduled = config.get("default.scheduled");
9,236✔
1056

1057
  Task::searchCaseSensitive = Variant::searchCaseSensitive =
4,618✔
1058
      config.getBoolean("search.case.sensitive");
9,236✔
1059
  Task::regex = Variant::searchUsingRegex = config.getBoolean("regex");
9,236✔
1060
  Lexer::dateFormat = Variant::dateFormat = config.get("dateformat");
9,236✔
1061

1062
  auto weekStart = Datetime::dayOfWeek(config.get("weekstart"));
9,236✔
1063
  if (weekStart != 0 && weekStart != 1)
4,618✔
1064
    throw std::string(
1065
        "The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'.");
×
1066
  Datetime::weekstart = weekStart;
4,618✔
1067
  Datetime::isoEnabled = config.getBoolean("date.iso");
9,236✔
1068
  Datetime::standaloneDateEnabled = false;
4,618✔
1069
  Datetime::standaloneTimeEnabled = false;
4,618✔
1070
  Duration::standaloneSecondsEnabled = false;
4,618✔
1071

1072
  TDB2::debug_mode = config.getBoolean("debug");
9,236✔
1073

1074
  for (auto& rc : config) {
1,135,330✔
1075
    if (rc.first.substr(0, 4) == "uda." && rc.first.substr(rc.first.length() - 7, 7) == ".values") {
1,130,712✔
1076
      std::string name = rc.first.substr(4, rc.first.length() - 7 - 4);
4,695✔
1077
      auto values = split(rc.second, ',');
4,695✔
1078

1079
      for (auto r = values.rbegin(); r != values.rend(); ++r) Task::customOrder[name].push_back(*r);
23,394✔
1080
    }
4,695✔
1081
  }
1082

1083
  for (auto& col : columns) {
116,165✔
1084
    Task::attributes[col.first] = col.second->type();
111,547✔
1085
    Lexer::attributes[col.first] = col.second->type();
111,547✔
1086
  }
1087

1088
  Task::urgencyProjectCoefficient = config.getReal("urgency.project.coefficient");
9,236✔
1089
  Task::urgencyActiveCoefficient = config.getReal("urgency.active.coefficient");
9,236✔
1090
  Task::urgencyScheduledCoefficient = config.getReal("urgency.scheduled.coefficient");
9,236✔
1091
  Task::urgencyWaitingCoefficient = config.getReal("urgency.waiting.coefficient");
9,236✔
1092
  Task::urgencyBlockedCoefficient = config.getReal("urgency.blocked.coefficient");
9,236✔
1093
  Task::urgencyAnnotationsCoefficient = config.getReal("urgency.annotations.coefficient");
9,236✔
1094
  Task::urgencyTagsCoefficient = config.getReal("urgency.tags.coefficient");
9,236✔
1095
  Task::urgencyDueCoefficient = config.getReal("urgency.due.coefficient");
9,236✔
1096
  Task::urgencyBlockingCoefficient = config.getReal("urgency.blocking.coefficient");
9,236✔
1097
  Task::urgencyAgeCoefficient = config.getReal("urgency.age.coefficient");
9,236✔
1098
  Task::urgencyAgeMax = config.getReal("urgency.age.max");
9,236✔
1099

1100
  // Tag- and project-specific coefficients.
1101
  for (auto& var : config.all())
1,135,330✔
1102
    if (var.substr(0, 13) == "urgency.user." || var.substr(0, 12) == "urgency.uda.")
1,130,712✔
1103
      Task::coefficients[var] = config.getReal(var);
23,328✔
1104
}
4,618✔
1105

1106
////////////////////////////////////////////////////////////////////////////////
1107
void Context::createDefaultConfig() {
4,618✔
1108
  // Do we need to create a default rc?
1109
  if (rc_file._data != "" && !rc_file.exists()) {
4,618✔
1110
    // If stdout is not a file, we are probably executing in a completion context and should not
1111
    // prompt (as the user won't see it) or modify the config (as completion functions are typically
1112
    // read-only).
1113
    if (!isatty(STDOUT_FILENO)) {
×
1114
      throw std::string("Cannot proceed without rc file.");
×
1115
    }
1116

1117
    if (config.getBoolean("confirmation") &&
×
1118
        !confirm(format("A configuration file could not be found in {1}\n\nWould you like a sample "
×
1119
                        "{2} created, so Taskwarrior can proceed?",
1120
                        home_dir, rc_file._data)))
×
1121
      throw std::string("Cannot proceed without rc file.");
×
1122

1123
    Datetime now;
×
1124
    std::stringstream contents;
×
1125
    contents << "# [Created by " << PACKAGE_STRING << ' ' << now.toString("m/d/Y H:N:S") << "]\n"
×
1126
             << "data.location=" << data_dir._original << "\n"
×
1127
             << "news.version=" << Version::Current() << "\n"
×
1128
             << "\n# To use the default location of the XDG directories,\n"
1129
             << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and update "
1130
                "location config as follows:\n"
1131
             << "\n#data.location=~/.local/share/task\n"
1132
             << "#hooks.location=~/.config/task/hooks\n"
1133
             << "\n# Color theme (uncomment one to use)\n"
1134
             << "include default.theme\n"
1135
             << "#include light-16.theme\n"
1136
             << "#include light-256.theme\n"
1137
             << "#include bubblegum-256.theme\n"
1138
             << "#include dark-16.theme\n"
1139
             << "#include dark-256.theme\n"
1140
             << "#include dark-red-256.theme\n"
1141
             << "#include dark-green-256.theme\n"
1142
             << "#include dark-blue-256.theme\n"
1143
             << "#include dark-violets-256.theme\n"
1144
             << "#include dark-yellow-green.theme\n"
1145
             << "#include dark-gray-256.theme\n"
1146
             << "#include dark-gray-blue-256.theme\n"
1147
             << "#include solarized-dark-256.theme\n"
1148
             << "#include solarized-light-256.theme\n"
1149
             << "#include no-color.theme\n"
1150
             << '\n';
×
1151

1152
    // Write out the new file.
1153
    if (!File::write(rc_file._data, contents.str()))
×
1154
      throw format("Could not write to '{1}'.", rc_file._data);
×
1155

1156
    // Load it so that it takes effect for this run.
1157
    config.load(rc_file);
×
1158
  }
×
1159
}
4,618✔
1160

1161
////////////////////////////////////////////////////////////////////////////////
1162
void Context::decomposeSortField(const std::string& field, std::string& key, bool& ascending,
5,121✔
1163
                                 bool& breakIndicator) {
1164
  int length = field.length();
5,121✔
1165

1166
  int decoration = 1;
5,121✔
1167
  breakIndicator = false;
5,121✔
1168
  if (field[length - decoration] == '/') {
5,121✔
1169
    breakIndicator = true;
18✔
1170
    ++decoration;
18✔
1171
  }
1172

1173
  if (field[length - decoration] == '+') {
5,121✔
1174
    ascending = true;
2,205✔
1175
    key = field.substr(0, length - decoration);
2,205✔
1176
  } else if (field[length - decoration] == '-') {
2,916✔
1177
    ascending = false;
2,430✔
1178
    key = field.substr(0, length - decoration);
2,430✔
1179
  } else {
1180
    ascending = true;
486✔
1181
    key = field;
486✔
1182
  }
1183
}
5,121✔
1184

1185
////////////////////////////////////////////////////////////////////////////////
1186
void Context::debugTiming(const std::string& details, const Timer& timer) {
4,619✔
1187
  std::stringstream out;
4,619✔
1188
  out << "Timer " << details << ' ' << std::setprecision(6) << std::fixed
4,619✔
1189
      << timer.total_us() / 1.0e6 << " sec";
4,619✔
1190
  debug(out.str());
4,619✔
1191
}
4,619✔
1192

1193
////////////////////////////////////////////////////////////////////////////////
1194
CurrentTask Context::withCurrentTask(const Task* task) { return CurrentTask(*this, task); }
10,978✔
1195

1196
////////////////////////////////////////////////////////////////////////////////
1197
// This capability is to answer the question of 'what did I just do to generate
1198
// this output?'.
1199
void Context::updateXtermTitle() {
4,612✔
1200
  if (config.getBoolean("xterm.title") && isatty(STDOUT_FILENO)) {
13,836✔
1201
    auto command = cli2.getCommand();
×
1202
    std::string title;
×
1203

1204
    for (auto a = cli2._args.begin(); a != cli2._args.end(); ++a) {
×
1205
      if (a != cli2._args.begin()) title += ' ';
×
1206

1207
      title += a->attribute("raw");
×
1208
    }
1209

1210
    std::cout << " ]0;task " << command << ' ' << title << " ";
×
1211
  }
×
1212
}
4,612✔
1213

1214
////////////////////////////////////////////////////////////////////////////////
1215
// This function allows a clean output if the command is a helper subcommand.
1216
void Context::updateVerbosity() {
4,612✔
1217
  auto command = cli2.getCommand();
4,612✔
1218
  if (command != "" && command[0] == '_') {
4,612✔
1219
    verbosity = {"nothing"};
736✔
1220
  }
1221
}
5,348✔
1222

1223
////////////////////////////////////////////////////////////////////////////////
1224
void Context::loadAliases() {
4,618✔
1225
  for (auto& i : config)
1,135,338✔
1226
    if (i.first.substr(0, 6) == "alias.") cli2.alias(i.first.substr(6), i.second);
1,130,720✔
1227
}
4,618✔
1228

1229
////////////////////////////////////////////////////////////////////////////////
1230
// Using the general rc.debug setting automaticalls sets debug.hooks
1231
// and debug.parser, unless they already have values, which by default they do
1232
// not.
1233
void Context::propagateDebug() {
4,618✔
1234
  if (config.getBoolean("debug")) {
13,854✔
1235
    if (!config.has("debug.hooks")) config.set("debug.hooks", 1);
20✔
1236

1237
    if (!config.has("debug.parser")) config.set("debug.parser", 1);
20✔
1238
  } else {
1239
    if ((config.has("debug.hooks") && config.getInteger("debug.hooks")) ||
23,072✔
1240
        (config.has("debug.parser") && config.getInteger("debug.parser")))
18,458✔
1241
      config.set("debug", true);
18✔
1242
  }
1243
}
4,618✔
1244

1245
////////////////////////////////////////////////////////////////////////////////
1246
// No duplicates.
1247
void Context::header(const std::string& input) {
8,414✔
1248
  if (input.length() && std::find(headers.begin(), headers.end(), input) == headers.end())
8,414✔
1249
    headers.push_back(input);
8,414✔
1250
}
8,414✔
1251

1252
////////////////////////////////////////////////////////////////////////////////
1253
// No duplicates.
1254
void Context::footnote(const std::string& input) {
2,278✔
1255
  if (input.length() && std::find(footnotes.begin(), footnotes.end(), input) == footnotes.end())
2,278✔
1256
    footnotes.push_back(input);
1,300✔
1257
}
2,278✔
1258

1259
////////////////////////////////////////////////////////////////////////////////
1260
// No duplicates.
1261
void Context::error(const std::string& input) {
494✔
1262
  if (input.length() && std::find(errors.begin(), errors.end(), input) == errors.end())
494✔
1263
    errors.push_back(input);
488✔
1264
}
494✔
1265

1266
////////////////////////////////////////////////////////////////////////////////
1267
void Context::debug(const std::string& input) {
16,785✔
1268
  if (input.length()) debugMessages.push_back(input);
16,785✔
1269
}
16,785✔
1270

1271
////////////////////////////////////////////////////////////////////////////////
1272
CurrentTask::CurrentTask(Context& context, const Task* task)
10,978✔
1273
    : context{context}, previous{context.currentTask} {
10,978✔
1274
  context.currentTask = task;
10,978✔
1275
}
10,978✔
1276

1277
////////////////////////////////////////////////////////////////////////////////
1278
CurrentTask::~CurrentTask() { context.currentTask = previous; }
10,978✔
1279

1280
////////////////////////////////////////////////////////////////////////////////
1281

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