taskcheck 0.4.0__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. taskcheck-1.0.0/PKG-INFO +216 -0
  2. taskcheck-1.0.0/README.md +204 -0
  3. {taskcheck-0.4.0 → taskcheck-1.0.0}/pyproject.toml +21 -1
  4. taskcheck-1.0.0/taskcheck/__main__.py +144 -0
  5. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck/common.py +25 -6
  6. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck/ical.py +2 -2
  7. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck/install.py +7 -0
  8. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck/parallel.py +178 -23
  9. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck/report.py +28 -10
  10. taskcheck-1.0.0/taskcheck.egg-info/PKG-INFO +216 -0
  11. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck.egg-info/SOURCES.txt +7 -1
  12. taskcheck-1.0.0/tests/test_common.py +140 -0
  13. taskcheck-1.0.0/tests/test_ical.py +171 -0
  14. taskcheck-1.0.0/tests/test_install.py +78 -0
  15. taskcheck-1.0.0/tests/test_main.py +586 -0
  16. taskcheck-1.0.0/tests/test_parallel.py +522 -0
  17. taskcheck-1.0.0/tests/test_report.py +203 -0
  18. taskcheck-0.4.0/PKG-INFO +0 -154
  19. taskcheck-0.4.0/README.md +0 -142
  20. taskcheck-0.4.0/taskcheck/__main__.py +0 -66
  21. taskcheck-0.4.0/taskcheck.egg-info/PKG-INFO +0 -154
  22. {taskcheck-0.4.0 → taskcheck-1.0.0}/LICENSE +0 -0
  23. {taskcheck-0.4.0 → taskcheck-1.0.0}/setup.cfg +0 -0
  24. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck.egg-info/dependency_links.txt +0 -0
  25. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck.egg-info/entry_points.txt +0 -0
  26. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck.egg-info/requires.txt +0 -0
  27. {taskcheck-0.4.0 → taskcheck-1.0.0}/taskcheck.egg-info/top_level.txt +0 -0
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: taskcheck
3
+ Version: 1.0.0
4
+ Summary: Automatic scheduler for taskwarrior
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: appdirs>=1.4.4
8
+ Requires-Dist: icalendar>=6.0.1
9
+ Requires-Dist: random-unicode-emoji>=2.8
10
+ Requires-Dist: requests>=2.32.3
11
+ Requires-Dist: rich>=13.9.4
12
+
13
+
14
+ ```
15
+
16
+ 888888888888 88 ,ad8888ba, 88 88
17
+ 88 88 d8"' `"8b 88 88
18
+ 88 88 d8' 88 88
19
+ 88 ,adPPYYba, ,adPPYba, 88 ,d8 88 88,dPPYba, ,adPPYba, ,adPPYba, 88 ,d8
20
+ 88 "" `Y8 I8[ "" 88 ,a8" 88 88P' "8a a8P_____88 a8" "" 88 ,a8"
21
+ 88 ,adPPPPP88 `"Y8ba, 8888[ Y8, 88 88 8PP""""""" 8b 8888[
22
+ 88 88, ,88 aa ]8I 88`"Yba, Y8a. .a8P 88 88 "8b, ,aa "8a, ,aa 88`"Yba,
23
+ 88 `"8bbdP"Y8 `"YbbdP"' 88 `Y8a `"Y8888Y"' 88 88 `"Ybbd8"' `"Ybbd8"' 88 `Y8a
24
+
25
+
26
+ ```
27
+
28
+ > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
29
+
30
+ This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
31
+ estimated time, and calendar events, finding an optimal time to work on each task and match all your
32
+ deadlines.
33
+
34
+ ## Features
35
+
36
+ - [x] **Use arbitrarily complex time maps for working hours**
37
+ - [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
38
+ - [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
39
+ - [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
40
+ - [x] Custom urgency weighting for scheduling (via CLI or config)
41
+ - [x] **Auto-fix scheduling to mach due dates**
42
+ - [x] Force update of iCal calendars, bypassing cache
43
+ - [x] Simple, customizable reports for planned and unplanned tasks
44
+ - [x] Emoji and attribute customization in reports
45
+ - [ ] Use Google API to access calendars
46
+ - [ ] Export tasks to iCal calendar and API calendars
47
+
48
+ ## Install
49
+
50
+ 1. `pipx install taskcheck`
51
+ 2. `taskcheck --install`
52
+
53
+ ## How does it work
54
+
55
+ This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
56
+ It considers their estimated time to schedule all tasks starting from the most urgent one.
57
+
58
+ #### UDAs
59
+
60
+ Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
61
+ the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
62
+ that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
63
+ The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
64
+ file of taskcheck. For instance:
65
+
66
+ ```toml
67
+ [time_maps]
68
+ # get an error)
69
+ [time_maps.work]
70
+ monday = [[9, 12.30], [14, 17]]
71
+ tuesday = [[9, 12.30], [14, 17]]
72
+ # ...
73
+ ```
74
+
75
+ #### They say it's an "AI"
76
+
77
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
78
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
79
+ date of completion and the `scheduled` attribute with the date in which the task is expected to
80
+ start.
81
+
82
+ It will also print a red line for every task whose `completion_date` is after its `due_date`.
83
+
84
+ In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
85
+ of your working day.
86
+
87
+ #### Reports
88
+
89
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
90
+ how much time you have to work on which task in which day. For
91
+ instance:
92
+
93
+ - `taskcheck -r today` will show the tasks planned for today
94
+ - `taskcheck -r 1w` will show the tasks planned for the next week
95
+
96
+ ## Configuration
97
+
98
+ `taskcheck --install` allows you to create required and recommended configurations for
99
+ Taskwarrior. It will also generate a default configuration file for taskcheck.
100
+
101
+ Below is an example of a taskcheck configuration file, with all relevant options:
102
+
103
+ ```toml
104
+ [time_maps]
105
+ # Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
106
+ [time_maps.work]
107
+ monday = [[9, 12.30], [14, 17]]
108
+ tuesday = [[9, 12.30], [14, 17]]
109
+ wednesday = [[9, 12.30], [14, 17]]
110
+ thursday = [[9, 12.30], [14, 17]]
111
+ friday = [[9, 12.30], [14, 17]]
112
+
113
+ [time_maps.weekend]
114
+ saturday = [[9, 12.30]]
115
+ sunday = [[9, 12.30]]
116
+
117
+ [scheduler]
118
+ days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
119
+ weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
120
+ # if weight_urgency is set to 0, only due urgency is considered
121
+ # by default, this factor is automatically reduced if some task cannot be scheduled in time,
122
+ # leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
123
+
124
+ [calendars]
125
+ # iCal calendars can be used to block your time and make the scheduling more precise
126
+ [calendars.1]
127
+ url = "https://your/url/to/calendar.ics"
128
+ expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
129
+ timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
130
+
131
+ [calendars.holidays]
132
+ url = "https://www.officeholidays.com/ics-clean/italy/milan"
133
+ event_all_day_is_blocking = true
134
+ expiration = 720 # In hours (720 hours = 30 days)
135
+
136
+ [report]
137
+ include_unplanned = true
138
+ additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
139
+ additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
140
+ emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
141
+ ```
142
+
143
+ ### Configuration Options
144
+
145
+ - **[scheduler]**
146
+ - `days_ahead`: How many days ahead to schedule tasks.
147
+ - `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
148
+ - **[calendars]**
149
+ - `url`: iCal URL to block time.
150
+ - `expiration`: Cache expiration in hours.
151
+ - `timezone`: (Optional) Force a timezone for this calendar.
152
+ - `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
153
+ - **[report]**
154
+ - `include_unplanned`: Show unplanned tasks in a separate section.
155
+ - `additional_attributes`: Extra columns to show in the report.
156
+ - `additional_attributes_unplanned`: Extra columns for unplanned tasks.
157
+ - `emoji_keywords`: Map keywords in task descriptions to emoji.
158
+
159
+ ## Algorithm
160
+
161
+ The algorithm simulates what happens if you work on a task for a certain time on a given day.
162
+
163
+ For each day X starting from today, it sorts the tasks by decreasing urgency.
164
+ It start from the most urgent tasks that can be allocated on day X depending on the task's
165
+ `time_map` and on your calendars. It allocates a few number of hours to the task,
166
+ then recomputes the urgencies exactly as Taskwarrior would do
167
+ if it was running on day X. Having recomputed the urgencies, it restarts.
168
+
169
+ If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
170
+ task will get scheduled in its place.
171
+
172
+ For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
173
+ skip all the available slots until 12 pm.
174
+
175
+ The maximum time that is allocated at each attempt is by default 2 hours
176
+ (or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
177
+
178
+ After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
179
+ `weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
180
+ are scheduled before their due dates or the `weight_urgency` factor reaches 0.
181
+
182
+ ## Tips and Tricks
183
+
184
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
185
+ - You can see tasks that you can execute now with the `task ready` report.
186
+
187
+ ## CLI Options
188
+
189
+ ```
190
+ -v, --verbose Increase output verbosity
191
+ -i, --install Install taskcheck configuration
192
+ -r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
193
+ -s, --schedule Perform the scheduling algorithm and update tasks
194
+ -f, --force-update Force update of all iCal calendars, ignoring cache expiration
195
+ --taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
196
+ --urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
197
+ --dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
198
+ --no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
199
+ ```
200
+
201
+ ### Examples
202
+
203
+ - `taskcheck --schedule`
204
+ Run the scheduler and update your Taskwarrior tasks.
205
+ - `taskcheck --schedule --dry-run`
206
+ Preview the schedule without modifying your database.
207
+ - `taskcheck --schedule --urgency-weight 0.5`
208
+ Use a custom urgency weighting for this run.
209
+ - `taskcheck --schedule --no-auto-adjust-urgency`
210
+ Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
211
+ - `taskcheck --report today`
212
+ Show the schedule for today.
213
+ - `taskcheck --report 1w`
214
+ Show the schedule for the next week.
215
+ - `taskcheck --force-update`
216
+ Force refresh of all iCal calendars, ignoring cache.
@@ -0,0 +1,204 @@
1
+
2
+ ```
3
+
4
+ 888888888888 88 ,ad8888ba, 88 88
5
+ 88 88 d8"' `"8b 88 88
6
+ 88 88 d8' 88 88
7
+ 88 ,adPPYYba, ,adPPYba, 88 ,d8 88 88,dPPYba, ,adPPYba, ,adPPYba, 88 ,d8
8
+ 88 "" `Y8 I8[ "" 88 ,a8" 88 88P' "8a a8P_____88 a8" "" 88 ,a8"
9
+ 88 ,adPPPPP88 `"Y8ba, 8888[ Y8, 88 88 8PP""""""" 8b 8888[
10
+ 88 88, ,88 aa ]8I 88`"Yba, Y8a. .a8P 88 88 "8b, ,aa "8a, ,aa 88`"Yba,
11
+ 88 `"8bbdP"Y8 `"YbbdP"' 88 `Y8a `"Y8888Y"' 88 88 `"Ybbd8"' `"Ybbd8"' 88 `Y8a
12
+
13
+
14
+ ```
15
+
16
+ > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
17
+
18
+ This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
19
+ estimated time, and calendar events, finding an optimal time to work on each task and match all your
20
+ deadlines.
21
+
22
+ ## Features
23
+
24
+ - [x] **Use arbitrarily complex time maps for working hours**
25
+ - [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
26
+ - [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
27
+ - [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
28
+ - [x] Custom urgency weighting for scheduling (via CLI or config)
29
+ - [x] **Auto-fix scheduling to mach due dates**
30
+ - [x] Force update of iCal calendars, bypassing cache
31
+ - [x] Simple, customizable reports for planned and unplanned tasks
32
+ - [x] Emoji and attribute customization in reports
33
+ - [ ] Use Google API to access calendars
34
+ - [ ] Export tasks to iCal calendar and API calendars
35
+
36
+ ## Install
37
+
38
+ 1. `pipx install taskcheck`
39
+ 2. `taskcheck --install`
40
+
41
+ ## How does it work
42
+
43
+ This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
44
+ It considers their estimated time to schedule all tasks starting from the most urgent one.
45
+
46
+ #### UDAs
47
+
48
+ Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
49
+ the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
50
+ that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
51
+ The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
52
+ file of taskcheck. For instance:
53
+
54
+ ```toml
55
+ [time_maps]
56
+ # get an error)
57
+ [time_maps.work]
58
+ monday = [[9, 12.30], [14, 17]]
59
+ tuesday = [[9, 12.30], [14, 17]]
60
+ # ...
61
+ ```
62
+
63
+ #### They say it's an "AI"
64
+
65
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
66
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
67
+ date of completion and the `scheduled` attribute with the date in which the task is expected to
68
+ start.
69
+
70
+ It will also print a red line for every task whose `completion_date` is after its `due_date`.
71
+
72
+ In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
73
+ of your working day.
74
+
75
+ #### Reports
76
+
77
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
78
+ how much time you have to work on which task in which day. For
79
+ instance:
80
+
81
+ - `taskcheck -r today` will show the tasks planned for today
82
+ - `taskcheck -r 1w` will show the tasks planned for the next week
83
+
84
+ ## Configuration
85
+
86
+ `taskcheck --install` allows you to create required and recommended configurations for
87
+ Taskwarrior. It will also generate a default configuration file for taskcheck.
88
+
89
+ Below is an example of a taskcheck configuration file, with all relevant options:
90
+
91
+ ```toml
92
+ [time_maps]
93
+ # Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
94
+ [time_maps.work]
95
+ monday = [[9, 12.30], [14, 17]]
96
+ tuesday = [[9, 12.30], [14, 17]]
97
+ wednesday = [[9, 12.30], [14, 17]]
98
+ thursday = [[9, 12.30], [14, 17]]
99
+ friday = [[9, 12.30], [14, 17]]
100
+
101
+ [time_maps.weekend]
102
+ saturday = [[9, 12.30]]
103
+ sunday = [[9, 12.30]]
104
+
105
+ [scheduler]
106
+ days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
107
+ weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
108
+ # if weight_urgency is set to 0, only due urgency is considered
109
+ # by default, this factor is automatically reduced if some task cannot be scheduled in time,
110
+ # leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
111
+
112
+ [calendars]
113
+ # iCal calendars can be used to block your time and make the scheduling more precise
114
+ [calendars.1]
115
+ url = "https://your/url/to/calendar.ics"
116
+ expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
117
+ timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
118
+
119
+ [calendars.holidays]
120
+ url = "https://www.officeholidays.com/ics-clean/italy/milan"
121
+ event_all_day_is_blocking = true
122
+ expiration = 720 # In hours (720 hours = 30 days)
123
+
124
+ [report]
125
+ include_unplanned = true
126
+ additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
127
+ additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
128
+ emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
129
+ ```
130
+
131
+ ### Configuration Options
132
+
133
+ - **[scheduler]**
134
+ - `days_ahead`: How many days ahead to schedule tasks.
135
+ - `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
136
+ - **[calendars]**
137
+ - `url`: iCal URL to block time.
138
+ - `expiration`: Cache expiration in hours.
139
+ - `timezone`: (Optional) Force a timezone for this calendar.
140
+ - `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
141
+ - **[report]**
142
+ - `include_unplanned`: Show unplanned tasks in a separate section.
143
+ - `additional_attributes`: Extra columns to show in the report.
144
+ - `additional_attributes_unplanned`: Extra columns for unplanned tasks.
145
+ - `emoji_keywords`: Map keywords in task descriptions to emoji.
146
+
147
+ ## Algorithm
148
+
149
+ The algorithm simulates what happens if you work on a task for a certain time on a given day.
150
+
151
+ For each day X starting from today, it sorts the tasks by decreasing urgency.
152
+ It start from the most urgent tasks that can be allocated on day X depending on the task's
153
+ `time_map` and on your calendars. It allocates a few number of hours to the task,
154
+ then recomputes the urgencies exactly as Taskwarrior would do
155
+ if it was running on day X. Having recomputed the urgencies, it restarts.
156
+
157
+ If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
158
+ task will get scheduled in its place.
159
+
160
+ For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
161
+ skip all the available slots until 12 pm.
162
+
163
+ The maximum time that is allocated at each attempt is by default 2 hours
164
+ (or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
165
+
166
+ After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
167
+ `weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
168
+ are scheduled before their due dates or the `weight_urgency` factor reaches 0.
169
+
170
+ ## Tips and Tricks
171
+
172
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
173
+ - You can see tasks that you can execute now with the `task ready` report.
174
+
175
+ ## CLI Options
176
+
177
+ ```
178
+ -v, --verbose Increase output verbosity
179
+ -i, --install Install taskcheck configuration
180
+ -r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
181
+ -s, --schedule Perform the scheduling algorithm and update tasks
182
+ -f, --force-update Force update of all iCal calendars, ignoring cache expiration
183
+ --taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
184
+ --urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
185
+ --dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
186
+ --no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
187
+ ```
188
+
189
+ ### Examples
190
+
191
+ - `taskcheck --schedule`
192
+ Run the scheduler and update your Taskwarrior tasks.
193
+ - `taskcheck --schedule --dry-run`
194
+ Preview the schedule without modifying your database.
195
+ - `taskcheck --schedule --urgency-weight 0.5`
196
+ Use a custom urgency weighting for this run.
197
+ - `taskcheck --schedule --no-auto-adjust-urgency`
198
+ Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
199
+ - `taskcheck --report today`
200
+ Show the schedule for today.
201
+ - `taskcheck --report 1w`
202
+ Show the schedule for the next week.
203
+ - `taskcheck --force-update`
204
+ Force refresh of all iCal calendars, ignoring cache.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "taskcheck"
3
- version = "0.4.0"
3
+ version = "1.0.0"
4
4
  description = "Automatic scheduler for taskwarrior"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -15,5 +15,25 @@ dependencies = [
15
15
  [project.scripts]
16
16
  taskcheck = "taskcheck.__main__:main"
17
17
 
18
+ [build-system]
19
+ requires = ["setuptools>=45", "wheel"]
20
+ build-backend = "setuptools.build_meta"
21
+
18
22
  [tool.setuptools]
19
23
  license-files = []
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["."]
27
+ include = ["taskcheck*"]
28
+
29
+ [tool.uv]
30
+ package = true
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "freezegun>=1.5.2",
35
+ "pytest>=8.3.5",
36
+ "pytest-cov>=6.1.1",
37
+ "pytest-mock>=3.14.1",
38
+ "responses>=0.25.7",
39
+ ]
@@ -0,0 +1,144 @@
1
+ import tomllib
2
+ import argparse
3
+
4
+ from taskcheck.parallel import check_tasks_parallel
5
+ from taskcheck.common import config_dir
6
+
7
+ arg_parser = argparse.ArgumentParser()
8
+ arg_parser.add_argument(
9
+ "-v", "--verbose", action="store_true", help="increase output verbosity."
10
+ )
11
+ arg_parser.add_argument(
12
+ "-i",
13
+ "--install",
14
+ action="store_true",
15
+ help="install the UDAs, required settings, and default config file.",
16
+ )
17
+ arg_parser.add_argument(
18
+ "-r",
19
+ "--report",
20
+ action="store",
21
+ help="generate a report of the tasks based on the scheduling; can be any Taskwarrior datetime specification (e.g. today, tomorrow, eom, som, 1st, 2nd, etc.). It is considered as `by`, meaning that the report will be generated for all the days until the specified date and including it.",
22
+ )
23
+ arg_parser.add_argument(
24
+ "-s",
25
+ "--schedule",
26
+ action="store_true",
27
+ help="perform the scheduling algorithm, giving a schedule and a scheduling UDA and alerting for not completable tasks",
28
+ )
29
+ arg_parser.add_argument(
30
+ "-f",
31
+ "--force-update",
32
+ action="store_true",
33
+ help="force update of all ical calendars by ignoring cache expiration",
34
+ )
35
+ arg_parser.add_argument(
36
+ "--taskrc",
37
+ action="store",
38
+ help="set custom TASKRC directory for debugging purposes",
39
+ )
40
+ arg_parser.add_argument(
41
+ "--urgency-weight",
42
+ type=float,
43
+ help="weight for urgency in scheduling (0.0 to 1.0), overrides config value. Not aaplie to due urgency, i.e. when 0, only due urgency is considered.",
44
+ )
45
+ arg_parser.add_argument(
46
+ "--dry-run",
47
+ action="store_true",
48
+ help="perform scheduling without modifying the Taskwarrior database, useful for testing",
49
+ )
50
+ arg_parser.add_argument(
51
+ "--no-auto-adjust-urgency",
52
+ action="store_true",
53
+ help="disable automatically reduction of urgency weight when tasks cannot be completed on time",
54
+ )
55
+
56
+
57
+ # Load working hours and exceptions from TOML file
58
+ def load_config():
59
+ """
60
+ Loads the configuration from the TOML file located at config_dir / "taskcheck.toml".
61
+ Returns a dictionary with the config structure, e.g.:
62
+ {
63
+ "time_maps": {
64
+ "work": {
65
+ "monday": [[9, 12.30], [14, 17]],
66
+ "tuesday": [[9, 12.30], [14, 17]],
67
+ ...
68
+ }
69
+ },
70
+ "scheduler": {
71
+ "days_ahead": 365,
72
+ "weight_urgency": 1.0,
73
+ ...
74
+ },
75
+ "report": {
76
+ "include_unplanned": true,
77
+ "additional_attributes": [],
78
+ "additional_attributes_unplanned": [],
79
+ "emoji_keywords": {},
80
+ ...
81
+ }
82
+ }
83
+ """
84
+ with open(config_dir / "taskcheck.toml", "rb") as f:
85
+ config = tomllib.load(f)
86
+ return config
87
+
88
+
89
+ def main():
90
+ args = arg_parser.parse_args()
91
+
92
+ # Load data and check tasks
93
+ print_help = True
94
+ result = None
95
+ if args.install:
96
+ from taskcheck.install import install
97
+
98
+ install()
99
+ return
100
+
101
+ if args.schedule:
102
+ config = load_config()
103
+ check_tasks_kwargs = dict(
104
+ verbose=args.verbose,
105
+ force_update=args.force_update,
106
+ taskrc=args.taskrc,
107
+ urgency_weight_override=args.urgency_weight,
108
+ dry_run=args.dry_run,
109
+ )
110
+ # Only add auto_adjust_urgency if it is present and a real bool (not a mock)
111
+ if hasattr(args, "no_auto_adjust_urgency") and isinstance(
112
+ args.no_auto_adjust_urgency, bool
113
+ ):
114
+ check_tasks_kwargs["auto_adjust_urgency"] = not args.auto_adjust_urgency
115
+ result = check_tasks_parallel(
116
+ config,
117
+ **check_tasks_kwargs,
118
+ )
119
+ print_help = False
120
+
121
+ if args.report:
122
+ from taskcheck.report import generate_report
123
+
124
+ config = load_config()
125
+ scheduling_results = None
126
+ if args.schedule and args.dry_run:
127
+ # If we just did a dry-run schedule, use those results
128
+ scheduling_results = result
129
+ generate_report(
130
+ config,
131
+ args.report,
132
+ args.verbose,
133
+ force_update=args.force_update,
134
+ taskrc=args.taskrc,
135
+ scheduling_results=scheduling_results,
136
+ )
137
+ print_help = False
138
+
139
+ if print_help:
140
+ arg_parser.print_help()
141
+
142
+
143
+ if __name__ == "__main__":
144
+ main()
@@ -17,9 +17,19 @@ AVOID_STATUS = ["completed", "deleted", "recurring"]
17
17
  long_range_time_map = {}
18
18
 
19
19
 
20
+ def get_task_env(taskrc=None):
21
+ import os
22
+ env = os.environ.copy()
23
+ if taskrc:
24
+ env['TASKRC'] = taskrc
25
+ env['TASKDATA'] = taskrc
26
+ return env
27
+
28
+
20
29
  # Get tasks from Taskwarrior and sort by urgency
21
- def get_tasks():
22
- result = subprocess.run(["task", "export"], capture_output=True, text=True)
30
+ def get_tasks(taskrc=None):
31
+ env = get_task_env(taskrc)
32
+ result = subprocess.run(["task", "export"], capture_output=True, text=True, env=env)
23
33
  tasks = json.loads(result.stdout)
24
34
  return sorted(
25
35
  (task for task in tasks if "estimated" in task),
@@ -99,12 +109,18 @@ def pdth_to_hours(duration_str):
99
109
  days, duration_str = duration_str.split("D")
100
110
  days = int(days)
101
111
  if "H" in duration_str:
102
- hours = int(duration_str.split("T")[1].split("H")[0])
112
+ if "T" in duration_str:
113
+ hours = int(duration_str.split("T")[1].split("H")[0])
114
+ else:
115
+ hours = int(duration_str.split("H")[0])
103
116
  if "M" in duration_str:
104
117
  if "H" in duration_str:
105
118
  minutes = int(duration_str.split("H")[1].split("M")[0])
106
119
  else:
107
- minutes = int(duration_str.split("T")[1].split("M")[0])
120
+ if "T" in duration_str:
121
+ minutes = int(duration_str.split("T")[1].split("M")[0])
122
+ else:
123
+ minutes = int(duration_str.split("M")[0])
108
124
  return days * 24 + hours + minutes / 60
109
125
 
110
126
 
@@ -169,9 +185,10 @@ def get_long_range_time_map(
169
185
 
170
186
 
171
187
  def mark_end_date(
172
- due_date, end_date, start_date, scheduling_note, id, description=None
188
+ due_date, end_date, start_date, scheduling_note, id, description=None, taskrc=None
173
189
  ):
174
190
  start_end_fields = [f"scheduled:{start_date}", f"completion_date:{end_date}"]
191
+ env = get_task_env(taskrc)
175
192
 
176
193
  subprocess.run(
177
194
  [
@@ -183,6 +200,7 @@ def mark_end_date(
183
200
  ],
184
201
  stdout=subprocess.DEVNULL,
185
202
  stderr=subprocess.DEVNULL,
203
+ env=env,
186
204
  )
187
205
  if due_date is not None and end_date > due_date:
188
206
  # print in bold red using ANSI escape codes
@@ -190,7 +208,7 @@ def mark_end_date(
190
208
  print(f"\033[1;31mTask {id} {description} may not be completed on time\033[0m")
191
209
 
192
210
 
193
- def get_calendars(config, verbose=False):
211
+ def get_calendars(config, verbose=False, force_update=False):
194
212
  calendars = []
195
213
  for calname in config["calendars"]:
196
214
  calendar = config["calendars"][calname]
@@ -201,6 +219,7 @@ def get_calendars(config, verbose=False):
201
219
  expiration=calendar["expiration"],
202
220
  verbose=verbose,
203
221
  tz_name=calendar.get("timezone"),
222
+ force_update=force_update,
204
223
  )
205
224
  calendar.sort(key=lambda e: e["start"])
206
225
  calendars.append(calendar)