taskcheck 0.4.0__tar.gz → 1.0.0.post1__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.post1/PKG-INFO +217 -0
  2. taskcheck-1.0.0.post1/README.md +205 -0
  3. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/pyproject.toml +21 -1
  4. taskcheck-1.0.0.post1/taskcheck/__main__.py +144 -0
  5. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck/common.py +25 -6
  6. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck/ical.py +2 -2
  7. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck/install.py +7 -0
  8. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck/parallel.py +178 -23
  9. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck/report.py +28 -10
  10. taskcheck-1.0.0.post1/taskcheck.egg-info/PKG-INFO +217 -0
  11. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck.egg-info/SOURCES.txt +7 -1
  12. taskcheck-1.0.0.post1/tests/test_common.py +140 -0
  13. taskcheck-1.0.0.post1/tests/test_ical.py +171 -0
  14. taskcheck-1.0.0.post1/tests/test_install.py +78 -0
  15. taskcheck-1.0.0.post1/tests/test_main.py +586 -0
  16. taskcheck-1.0.0.post1/tests/test_parallel.py +522 -0
  17. taskcheck-1.0.0.post1/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.post1}/LICENSE +0 -0
  23. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/setup.cfg +0 -0
  24. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck.egg-info/dependency_links.txt +0 -0
  25. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck.egg-info/entry_points.txt +0 -0
  26. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck.egg-info/requires.txt +0 -0
  27. {taskcheck-0.4.0 → taskcheck-1.0.0.post1}/taskcheck.egg-info/top_level.txt +0 -0
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: taskcheck
3
+ Version: 1.0.0.post1
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
+ <div align="center">
14
+
15
+ ```
16
+ ######## ### ###### ## ## ###### ## ## ######## ###### ## ##
17
+ ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
18
+ ## ## ## ## ## ## ## ## ## ## ## ## ##
19
+ ## ## ## ###### ##### ## ######### ###### ## #####
20
+ ## ######### ## ## ## ## ## ## ## ## ## ##
21
+ ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
22
+ ## ## ## ###### ## ## ###### ## ## ######## ###### ## ##
23
+ ```
24
+
25
+ > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
26
+
27
+ ![immagine](https://github.com/user-attachments/assets/27b83bb1-7a50-4923-a453-0a958fbe11ed)
28
+
29
+ </div>
30
+
31
+ This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
32
+ estimated time, and calendar events, finding an optimal time to work on each task and match all your
33
+ deadlines.
34
+
35
+ ## Features
36
+
37
+ - [x] **Use arbitrarily complex time maps for working hours**
38
+ - [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
39
+ - [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
40
+ - [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
41
+ - [x] Custom urgency weighting for scheduling (via CLI or config)
42
+ - [x] **Auto-fix scheduling to mach due dates**
43
+ - [x] Force update of iCal calendars, bypassing cache
44
+ - [x] Simple, customizable reports for planned and unplanned tasks
45
+ - [x] Emoji and attribute customization in reports
46
+ - [ ] Use Google API to access calendars
47
+ - [ ] Export tasks to iCal calendar and API calendars
48
+
49
+ ## Install
50
+
51
+ 1. `pipx install taskcheck`
52
+ 2. `taskcheck --install`
53
+
54
+ ## How does it work
55
+
56
+ This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
57
+ It considers their estimated time to schedule all tasks starting from the most urgent one.
58
+
59
+ #### UDAs
60
+
61
+ Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
62
+ the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
63
+ that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
64
+ The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
65
+ file of taskcheck. For instance:
66
+
67
+ ```toml
68
+ [time_maps]
69
+ # get an error)
70
+ [time_maps.work]
71
+ monday = [[9, 12.30], [14, 17]]
72
+ tuesday = [[9, 12.30], [14, 17]]
73
+ # ...
74
+ ```
75
+
76
+ #### They say it's an "AI"
77
+
78
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
79
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
80
+ date of completion and the `scheduled` attribute with the date in which the task is expected to
81
+ start.
82
+
83
+ It will also print a red line for every task whose `completion_date` is after its `due_date`.
84
+
85
+ In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
86
+ of your working day.
87
+
88
+ #### Reports
89
+
90
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
91
+ how much time you have to work on which task in which day. For
92
+ instance:
93
+
94
+ - `taskcheck -r today` will show the tasks planned for today
95
+ - `taskcheck -r 1w` will show the tasks planned for the next week
96
+
97
+ ## Configuration
98
+
99
+ `taskcheck --install` allows you to create required and recommended configurations for
100
+ Taskwarrior. It will also generate a default configuration file for taskcheck.
101
+
102
+ Below is an example of a taskcheck configuration file, with all relevant options:
103
+
104
+ ```toml
105
+ [time_maps]
106
+ # Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
107
+ [time_maps.work]
108
+ monday = [[9, 12.30], [14, 17]]
109
+ tuesday = [[9, 12.30], [14, 17]]
110
+ wednesday = [[9, 12.30], [14, 17]]
111
+ thursday = [[9, 12.30], [14, 17]]
112
+ friday = [[9, 12.30], [14, 17]]
113
+
114
+ [time_maps.weekend]
115
+ saturday = [[9, 12.30]]
116
+ sunday = [[9, 12.30]]
117
+
118
+ [scheduler]
119
+ days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
120
+ weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
121
+ # if weight_urgency is set to 0, only due urgency is considered
122
+ # by default, this factor is automatically reduced if some task cannot be scheduled in time,
123
+ # leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
124
+
125
+ [calendars]
126
+ # iCal calendars can be used to block your time and make the scheduling more precise
127
+ [calendars.1]
128
+ url = "https://your/url/to/calendar.ics"
129
+ expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
130
+ timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
131
+
132
+ [calendars.holidays]
133
+ url = "https://www.officeholidays.com/ics-clean/italy/milan"
134
+ event_all_day_is_blocking = true
135
+ expiration = 720 # In hours (720 hours = 30 days)
136
+
137
+ [report]
138
+ include_unplanned = true
139
+ additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
140
+ additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
141
+ emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
142
+ ```
143
+
144
+ ### Configuration Options
145
+
146
+ - **[scheduler]**
147
+ - `days_ahead`: How many days ahead to schedule tasks.
148
+ - `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
149
+ - **[calendars]**
150
+ - `url`: iCal URL to block time.
151
+ - `expiration`: Cache expiration in hours.
152
+ - `timezone`: (Optional) Force a timezone for this calendar.
153
+ - `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
154
+ - **[report]**
155
+ - `include_unplanned`: Show unplanned tasks in a separate section.
156
+ - `additional_attributes`: Extra columns to show in the report.
157
+ - `additional_attributes_unplanned`: Extra columns for unplanned tasks.
158
+ - `emoji_keywords`: Map keywords in task descriptions to emoji.
159
+
160
+ ## Algorithm
161
+
162
+ The algorithm simulates what happens if you work on a task for a certain time on a given day.
163
+
164
+ For each day X starting from today, it sorts the tasks by decreasing urgency.
165
+ It start from the most urgent tasks that can be allocated on day X depending on the task's
166
+ `time_map` and on your calendars. It allocates a few number of hours to the task,
167
+ then recomputes the urgencies exactly as Taskwarrior would do
168
+ if it was running on day X. Having recomputed the urgencies, it restarts.
169
+
170
+ If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
171
+ task will get scheduled in its place.
172
+
173
+ For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
174
+ skip all the available slots until 12 pm.
175
+
176
+ The maximum time that is allocated at each attempt is by default 2 hours
177
+ (or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
178
+
179
+ After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
180
+ `weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
181
+ are scheduled before their due dates or the `weight_urgency` factor reaches 0.
182
+
183
+ ## Tips and Tricks
184
+
185
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
186
+ - You can see tasks that you can execute now with the `task ready` report.
187
+
188
+ ## CLI Options
189
+
190
+ ```
191
+ -v, --verbose Increase output verbosity
192
+ -i, --install Install taskcheck configuration
193
+ -r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
194
+ -s, --schedule Perform the scheduling algorithm and update tasks
195
+ -f, --force-update Force update of all iCal calendars, ignoring cache expiration
196
+ --taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
197
+ --urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
198
+ --dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
199
+ --no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
200
+ ```
201
+
202
+ ### Examples
203
+
204
+ - `taskcheck --schedule`
205
+ Run the scheduler and update your Taskwarrior tasks.
206
+ - `taskcheck --schedule --dry-run`
207
+ Preview the schedule without modifying your database.
208
+ - `taskcheck --schedule --urgency-weight 0.5`
209
+ Use a custom urgency weighting for this run.
210
+ - `taskcheck --schedule --no-auto-adjust-urgency`
211
+ Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
212
+ - `taskcheck --report today`
213
+ Show the schedule for today.
214
+ - `taskcheck --report 1w`
215
+ Show the schedule for the next week.
216
+ - `taskcheck --force-update`
217
+ Force refresh of all iCal calendars, ignoring cache.
@@ -0,0 +1,205 @@
1
+ <div align="center">
2
+
3
+ ```
4
+ ######## ### ###### ## ## ###### ## ## ######## ###### ## ##
5
+ ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6
+ ## ## ## ## ## ## ## ## ## ## ## ## ##
7
+ ## ## ## ###### ##### ## ######### ###### ## #####
8
+ ## ######### ## ## ## ## ## ## ## ## ## ##
9
+ ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
10
+ ## ## ## ###### ## ## ###### ## ## ######## ###### ## ##
11
+ ```
12
+
13
+ > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal / timehero / flowsavvy / reclaim / trevor / motion)_
14
+
15
+ ![immagine](https://github.com/user-attachments/assets/27b83bb1-7a50-4923-a453-0a958fbe11ed)
16
+
17
+ </div>
18
+
19
+ This is a taskwarrior extension that automatically schedule your tasks based on your working hours,
20
+ estimated time, and calendar events, finding an optimal time to work on each task and match all your
21
+ deadlines.
22
+
23
+ ## Features
24
+
25
+ - [x] **Use arbitrarily complex time maps for working hours**
26
+ - [x] Block scheduling time using iCal calendars (meetings, vacations, holidays, etc.)
27
+ - [x] **Parallel scheduling algorithm for multiple tasks, considering urgency and dependencies**
28
+ - [x] Dry-run mode: preview scheduling without modifying your Taskwarrior database
29
+ - [x] Custom urgency weighting for scheduling (via CLI or config)
30
+ - [x] **Auto-fix scheduling to mach due dates**
31
+ - [x] Force update of iCal calendars, bypassing cache
32
+ - [x] Simple, customizable reports for planned and unplanned tasks
33
+ - [x] Emoji and attribute customization in reports
34
+ - [ ] Use Google API to access calendars
35
+ - [ ] Export tasks to iCal calendar and API calendars
36
+
37
+ ## Install
38
+
39
+ 1. `pipx install taskcheck`
40
+ 2. `taskcheck --install`
41
+
42
+ ## How does it work
43
+
44
+ This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
45
+ It considers their estimated time to schedule all tasks starting from the most urgent one.
46
+
47
+ #### UDAs
48
+
49
+ Taskcheck leverages two UDAs, `estimated` and `time_map`. The `estimated` attribute is
50
+ the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
51
+ that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
52
+ The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
53
+ file of taskcheck. For instance:
54
+
55
+ ```toml
56
+ [time_maps]
57
+ # get an error)
58
+ [time_maps.work]
59
+ monday = [[9, 12.30], [14, 17]]
60
+ tuesday = [[9, 12.30], [14, 17]]
61
+ # ...
62
+ ```
63
+
64
+ #### They say it's an "AI"
65
+
66
+ Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
67
+ It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
68
+ date of completion and the `scheduled` attribute with the date in which the task is expected to
69
+ start.
70
+
71
+ It will also print a red line for every task whose `completion_date` is after its `due_date`.
72
+
73
+ In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
74
+ of your working day.
75
+
76
+ #### Reports
77
+
78
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
79
+ how much time you have to work on which task in which day. For
80
+ instance:
81
+
82
+ - `taskcheck -r today` will show the tasks planned for today
83
+ - `taskcheck -r 1w` will show the tasks planned for the next week
84
+
85
+ ## Configuration
86
+
87
+ `taskcheck --install` allows you to create required and recommended configurations for
88
+ Taskwarrior. It will also generate a default configuration file for taskcheck.
89
+
90
+ Below is an example of a taskcheck configuration file, with all relevant options:
91
+
92
+ ```toml
93
+ [time_maps]
94
+ # Define your working hours for each named time map (in 24h format, e.g. 9.5 = 9:30)
95
+ [time_maps.work]
96
+ monday = [[9, 12.30], [14, 17]]
97
+ tuesday = [[9, 12.30], [14, 17]]
98
+ wednesday = [[9, 12.30], [14, 17]]
99
+ thursday = [[9, 12.30], [14, 17]]
100
+ friday = [[9, 12.30], [14, 17]]
101
+
102
+ [time_maps.weekend]
103
+ saturday = [[9, 12.30]]
104
+ sunday = [[9, 12.30]]
105
+
106
+ [scheduler]
107
+ days_ahead = 1000 # How far to go with the schedule (lower values = faster computation)
108
+ weight_urgency = 1.0 # Default weight for urgency in scheduling (overridable via CLI)
109
+ # if weight_urgency is set to 0, only due urgency is considered
110
+ # by default, this factor is automatically reduced if some task cannot be scheduled in time,
111
+ # leading to tasks with due dates being prioritized (see --no-auto-adjust-urgency)
112
+
113
+ [calendars]
114
+ # iCal calendars can be used to block your time and make the scheduling more precise
115
+ [calendars.1]
116
+ url = "https://your/url/to/calendar.ics"
117
+ expiration = 0.08 # In hours (0.08 hours ≈ 5 minutes)
118
+ timezone = "Europe/Rome" # If set, force timezone for this calendar (see TZ database)
119
+
120
+ [calendars.holidays]
121
+ url = "https://www.officeholidays.com/ics-clean/italy/milan"
122
+ event_all_day_is_blocking = true
123
+ expiration = 720 # In hours (720 hours = 30 days)
124
+
125
+ [report]
126
+ include_unplanned = true
127
+ additional_attributes = ["estimated", "due", "urgency"] # Extra attributes to show in the report
128
+ additional_attributes_unplanned = ["due", "urgency"] # Extra attributes for unplanned tasks
129
+ emoji_keywords = {"meet"=":busts_in_silhouette:", "review"=":mag_right:"} # Map keywords to emoji
130
+ ```
131
+
132
+ ### Configuration Options
133
+
134
+ - **[scheduler]**
135
+ - `days_ahead`: How many days ahead to schedule tasks.
136
+ - `weight_urgency`: Default weight for urgency in scheduling (0.0 to 1.0). Can be overridden with `--urgency-weight`.
137
+ - **[calendars]**
138
+ - `url`: iCal URL to block time.
139
+ - `expiration`: Cache expiration in hours.
140
+ - `timezone`: (Optional) Force a timezone for this calendar.
141
+ - `event_all_day_is_blocking`: (Optional, bool) Treat all-day events as blocking.
142
+ - **[report]**
143
+ - `include_unplanned`: Show unplanned tasks in a separate section.
144
+ - `additional_attributes`: Extra columns to show in the report.
145
+ - `additional_attributes_unplanned`: Extra columns for unplanned tasks.
146
+ - `emoji_keywords`: Map keywords in task descriptions to emoji.
147
+
148
+ ## Algorithm
149
+
150
+ The algorithm simulates what happens if you work on a task for a certain time on a given day.
151
+
152
+ For each day X starting from today, it sorts the tasks by decreasing urgency.
153
+ It start from the most urgent tasks that can be allocated on day X depending on the task's
154
+ `time_map` and on your calendars. It allocates a few number of hours to the task,
155
+ then recomputes the urgencies exactly as Taskwarrior would do
156
+ if it was running on day X. Having recomputed the urgencies, it restarts.
157
+
158
+ If after 2 hours a long task has decreased its urgency, it will be noticed and the newer most urgent
159
+ task will get scheduled in its place.
160
+
161
+ For `today`, taskcheck will skip the hours in the past -- i.e. if you're running at 12 pm, it will
162
+ skip all the available slots until 12 pm.
163
+
164
+ The maximum time that is allocated at each attempt is by default 2 hours
165
+ (or less if the task is shorter), but you can change it by tuning the Taskwarrior UDA `min_block`.
166
+
167
+ After the scheduling is done, if any task has a `completion_date` after its `due_date`, the
168
+ `weight_urgency` factor is reduced by 0.1 and the scheduling is repeated, until all tasks
169
+ are scheduled before their due dates or the `weight_urgency` factor reaches 0.
170
+
171
+ ## Tips and Tricks
172
+
173
+ - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
174
+ - You can see tasks that you can execute now with the `task ready` report.
175
+
176
+ ## CLI Options
177
+
178
+ ```
179
+ -v, --verbose Increase output verbosity
180
+ -i, --install Install taskcheck configuration
181
+ -r, --report CONSTRAINT Show tasks planned until a certain time (e.g. 'today', '1w', 'eow')
182
+ -s, --schedule Perform the scheduling algorithm and update tasks
183
+ -f, --force-update Force update of all iCal calendars, ignoring cache expiration
184
+ --taskrc PATH Set custom TASKRC directory (and TASKDATA) for debugging or alternate environments
185
+ --urgency-weight FLOAT Weight for urgency in scheduling (0.0 to 1.0), overrides config value. When 0, only due urgency is considered.
186
+ --dry-run Perform scheduling without modifying the Taskwarrior database (useful for testing)
187
+ --no-auto-adjust-urgency Disable auto-adjustment of urgency weight (default: enabled)
188
+ ```
189
+
190
+ ### Examples
191
+
192
+ - `taskcheck --schedule`
193
+ Run the scheduler and update your Taskwarrior tasks.
194
+ - `taskcheck --schedule --dry-run`
195
+ Preview the schedule without modifying your database.
196
+ - `taskcheck --schedule --urgency-weight 0.5`
197
+ Use a custom urgency weighting for this run.
198
+ - `taskcheck --schedule --no-auto-adjust-urgency`
199
+ Avoid the automatically reduction of urgency weight if tasks can't be scheduled before their due dates.
200
+ - `taskcheck --report today`
201
+ Show the schedule for today.
202
+ - `taskcheck --report 1w`
203
+ Show the schedule for the next week.
204
+ - `taskcheck --force-update`
205
+ 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-1"
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.no_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)