taskcheck 0.2.1__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +8,7 @@ License-File: LICENSE
8
8
  Requires-Dist: appdirs>=1.4.4
9
9
  Requires-Dist: icalendar>=6.0.1
10
10
  Requires-Dist: requests>=2.32.3
11
+ Requires-Dist: rich>=13.9.4
11
12
 
12
13
 
13
14
  > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal/timehero/flowsavvy/reclaim/trevor/motion)_
@@ -32,13 +33,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
32
33
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
33
34
  It considers their estimated time to schedule all tasks starting from the most urgent one.
34
35
 
36
+ #### UDAs
37
+
35
38
  Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
36
39
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
37
40
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
38
41
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
39
42
  file of taskcheck. For instance:
40
43
 
41
- ```toml
42
44
  ```toml
43
45
  [time_maps]
44
46
  # get an error)
@@ -48,6 +50,8 @@ tuesday = [[9, 12.30], [14, 17]]
48
50
  # ...
49
51
  ```
50
52
 
53
+ #### They say it's an "AI"
54
+
51
55
  Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
52
56
  It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
53
57
  date of completion and the `scheduled` attribute with the date in which the task is expected to
@@ -58,6 +62,15 @@ It will also print a red line for every task whose `completion_date` is after it
58
62
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
59
63
  of your working day.
60
64
 
65
+ #### Reports
66
+
67
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
68
+ how much time you have to work on which task in which day. For
69
+ instance:
70
+
71
+ - `taskcheck -r today` will show the tasks planned for today
72
+ - `taskcheck -r 1w` will show the tasks planned for the next week,
73
+
61
74
  ## Algorithms
62
75
 
63
76
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
@@ -134,12 +147,11 @@ expiration = 720 # in hours (720 hours = 30 days)
134
147
 
135
148
  - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
136
149
  - You can see tasks that you can execute now with the `task ready` report.
137
- - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
138
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
139
150
 
140
151
  ## CLI Options
141
152
 
142
153
  ```
143
154
  -v, --verbose: increase output verbosity
144
155
  -i, --install: install taskcheck configuration
156
+ -r, --report: show tasks planned until a certain time
145
157
  ```
@@ -21,13 +21,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
21
21
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
22
22
  It considers their estimated time to schedule all tasks starting from the most urgent one.
23
23
 
24
+ #### UDAs
25
+
24
26
  Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
25
27
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
26
28
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
27
29
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
28
30
  file of taskcheck. For instance:
29
31
 
30
- ```toml
31
32
  ```toml
32
33
  [time_maps]
33
34
  # get an error)
@@ -37,6 +38,8 @@ tuesday = [[9, 12.30], [14, 17]]
37
38
  # ...
38
39
  ```
39
40
 
41
+ #### They say it's an "AI"
42
+
40
43
  Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
41
44
  It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
42
45
  date of completion and the `scheduled` attribute with the date in which the task is expected to
@@ -47,6 +50,15 @@ It will also print a red line for every task whose `completion_date` is after it
47
50
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
48
51
  of your working day.
49
52
 
53
+ #### Reports
54
+
55
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
56
+ how much time you have to work on which task in which day. For
57
+ instance:
58
+
59
+ - `taskcheck -r today` will show the tasks planned for today
60
+ - `taskcheck -r 1w` will show the tasks planned for the next week,
61
+
50
62
  ## Algorithms
51
63
 
52
64
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
@@ -123,12 +135,11 @@ expiration = 720 # in hours (720 hours = 30 days)
123
135
 
124
136
  - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
125
137
  - You can see tasks that you can execute now with the `task ready` report.
126
- - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
127
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
128
138
 
129
139
  ## CLI Options
130
140
 
131
141
  ```
132
142
  -v, --verbose: increase output verbosity
133
143
  -i, --install: install taskcheck configuration
144
+ -r, --report: show tasks planned until a certain time
134
145
  ```
@@ -1,10 +1,15 @@
1
1
  [project]
2
2
  name = "taskcheck"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  description = "Automatic scheduler for taskwarrior"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
- dependencies = ["appdirs>=1.4.4", "icalendar>=6.0.1", "requests>=2.32.3"]
7
+ dependencies = [
8
+ "appdirs>=1.4.4",
9
+ "icalendar>=6.0.1",
10
+ "requests>=2.32.3",
11
+ "rich>=13.9.4",
12
+ ]
8
13
 
9
14
  [project.scripts]
10
15
  taskcheck = "taskcheck.__main__:main"
@@ -7,13 +7,19 @@ from taskcheck.common import config_dir
7
7
 
8
8
  arg_parser = argparse.ArgumentParser()
9
9
  arg_parser.add_argument(
10
- "-v", "--verbose", action="store_true", help="increase output verbosity"
10
+ "-v", "--verbose", action="store_true", help="increase output verbosity."
11
11
  )
12
12
  arg_parser.add_argument(
13
13
  "-i",
14
14
  "--install",
15
15
  action="store_true",
16
- help="install the UDAs, required settings, and default config file",
16
+ help="install the UDAs, required settings, and default config file.",
17
+ )
18
+ arg_parser.add_argument(
19
+ "-r",
20
+ "--report",
21
+ action="store",
22
+ 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.",
17
23
  )
18
24
 
19
25
  args = arg_parser.parse_args()
@@ -34,6 +40,13 @@ def main():
34
40
  install()
35
41
  return
36
42
 
43
+ if args.report:
44
+ from taskcheck.report import generate_report
45
+
46
+ config = load_config()
47
+ generate_report(config, args.report, args.verbose)
48
+ return
49
+
37
50
  config = load_config()
38
51
  algo = config["scheduler"].get("algorithm", "parallel")
39
52
  if algo == "parallel":
@@ -91,19 +91,36 @@ def pdth_to_hours(duration_str):
91
91
  # string format is P#DT#H
92
92
  # with D and H optional
93
93
  duration_str = duration_str[1:] # Remove leading "P"
94
- days, hours = 0, 0
94
+ days, hours, minutes = 0, 0, 0
95
95
  if "D" in duration_str:
96
96
  days, duration_str = duration_str.split("D")
97
97
  days = int(days)
98
98
  if "H" in duration_str:
99
99
  hours = int(duration_str.split("T")[1].split("H")[0])
100
- return days * 24 + hours
100
+ if "M" in duration_str:
101
+ minutes = int(duration_str.split("H")[1].split("M")[0])
102
+ return days * 24 + hours + minutes / 60
101
103
 
102
104
 
103
105
  def hours_to_pdth(hours):
104
- days = hours // 24
105
- hours = hours % 24
106
- return f"P{days}DT{hours}H"
106
+ days_ = hours / 24
107
+ hours_ = hours % 24
108
+ minutes_ = int(round((hours_ - int(hours_)) * 60))
109
+ days_ = int(days_)
110
+ hours_ = int(hours_)
111
+ minutes_ = int(minutes_)
112
+ ret = "P"
113
+ if days_ == 0:
114
+ ret += "T"
115
+ else:
116
+ ret += f"{days_}DT"
117
+ if hours_ > 0:
118
+ ret += f"{hours_}H"
119
+ if minutes_ > 0:
120
+ ret += f"{minutes_}M"
121
+ if ret == "PT":
122
+ print("Warning: hours_to_pdth() returned 'PT' for hours = ", hours)
123
+ return ret
107
124
 
108
125
 
109
126
  def get_long_range_time_map(
@@ -57,12 +57,10 @@ def compute_estimated_urgency(remaining_hours, urgency_coefficients):
57
57
  """
58
58
  Computes the estimated urgency for the given remaining hours using the coefficients.
59
59
  """
60
- # Convert remaining hours to PDTH format used in the coefficients
61
- pdth_value = hours_to_pdth(remaining_hours)
62
60
  # Find the closest match (e.g., if '2h' is not available, use '1h' or '3h')
63
61
  closest_match = min(
64
62
  urgency_coefficients.estimated.keys(),
65
- key=lambda x: abs(int(pdth_value[1]) - int(pdth_to_hours(x[1]))),
63
+ key=lambda x: abs(pdth_to_hours(x) - remaining_hours),
66
64
  )
67
65
  coefficient = urgency_coefficients.estimated[closest_match]
68
66
  # Compute the urgency
@@ -246,7 +244,7 @@ def allocate_time_to_task(info, day_offset, day_remaining_hours):
246
244
  hours_to_decimal(info["task"].get("min_block", 2)),
247
245
  )
248
246
 
249
- if allocation <= 0:
247
+ if allocation <= 0.05:
250
248
  return 0
251
249
 
252
250
  info["remaining_hours"] -= allocation
@@ -273,7 +271,7 @@ def update_tasks_with_scheduling_info(task_info, verbose):
273
271
  end_date = scheduled_dates[-1]
274
272
  for date_str in scheduled_dates:
275
273
  hours = info["scheduling"][date_str]
276
- scheduling_note += f"{date_str}T00:00:00 - {hours:.2f} hours\n"
274
+ scheduling_note += f"{date_str} - {hours_to_pdth(hours)}\n"
277
275
 
278
276
  subprocess.run(
279
277
  [
@@ -0,0 +1,122 @@
1
+ import sys
2
+ from datetime import timedelta
3
+ from datetime import datetime
4
+ import json
5
+ import re
6
+ import subprocess
7
+
8
+
9
+ from rich import print
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+ from rich.panel import Panel
14
+
15
+
16
+ def get_tasks(tasks, year, month, day):
17
+ regex = re.compile(rf"{year}-{month}-{day} - ([PDTHM0-9]+)")
18
+ valid_tasks = []
19
+ for task in tasks:
20
+ for line in task["scheduling"].split("\n"):
21
+ m = regex.match(line)
22
+ if m:
23
+ due = task.get("due", "")
24
+ if due:
25
+ due = datetime.strptime(due, "%Y%m%dT%H%M%SZ")
26
+ due = due - datetime.today()
27
+ valid_tasks.append(
28
+ {
29
+ "id": task["id"],
30
+ "project": task.get("project", ""),
31
+ "description": task["description"],
32
+ "scheduling_day": f"{year}-{month}-{day}",
33
+ "scheduling_hours": m.group(1),
34
+ "urgency": task["urgency"],
35
+ "due": due,
36
+ }
37
+ )
38
+ valid_tasks = sorted(valid_tasks, key=lambda x: x["urgency"], reverse=True)
39
+ return valid_tasks
40
+
41
+
42
+ def get_taskwarrior_date(date, _retry=True):
43
+ date = subprocess.run(
44
+ ["task", "calc", date],
45
+ capture_output=True,
46
+ text=True,
47
+ )
48
+ date = date.stdout.strip()
49
+ try:
50
+ date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")
51
+ except Exception as _:
52
+ if _retry:
53
+ return get_taskwarrior_date("today+" + date, False)
54
+ else:
55
+ print(
56
+ "Please provide a valid date. Check with `task calc` that your date is in the format YYYY-MM-DDTHH:MM:SS",
57
+ file=sys.stderr,
58
+ )
59
+ exit(2)
60
+ return date
61
+
62
+
63
+ def get_days_in_constraint(constraint):
64
+ current_date = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
65
+ constraint = get_taskwarrior_date(constraint)
66
+ while current_date <= constraint:
67
+ yield current_date.year, current_date.month, current_date.day
68
+ current_date += timedelta(days=1)
69
+
70
+
71
+ def generate_report(config, constraint, verbose=False):
72
+ console = Console()
73
+ tasks = subprocess.run(
74
+ ["task", "scheduling~.", "export"], capture_output=True, text=True
75
+ )
76
+ tasks = json.loads(tasks.stdout)
77
+
78
+ for year, month, day in get_days_in_constraint(constraint):
79
+ this_day_tasks = get_tasks(tasks, year, month, day)
80
+
81
+ # Create a colorful date header with emojis and a panel
82
+ date_str = f":calendar: [bold cyan]{year}-{month}-{day}[/bold cyan]"
83
+ console.print(Panel(date_str, style="bold magenta", expand=False))
84
+
85
+ if this_day_tasks:
86
+ table = Table(show_header=True, header_style="bold blue")
87
+ table.add_column("Task", style="dim", width=12)
88
+ table.add_column("Project", style="dim", width=12)
89
+ table.add_column("Description")
90
+ table.add_column("Time", justify="right")
91
+ table.add_column("Urgency")
92
+ table.add_column("Due date")
93
+
94
+ for task in this_day_tasks:
95
+ task_id = f"[bold green]#{task['id']}[/bold green]"
96
+ project = task["project"]
97
+ description = Text(task["description"], style="white")
98
+ hours = f"[yellow]{task['scheduling_hours']}h[/yellow]"
99
+ urgency = f"[bold]{task['urgency']}[/bold]"
100
+ due = task["due"]
101
+ if due != "" and due < timedelta(days=5):
102
+ due = f"[red]{due.days}d[/red]"
103
+ elif due != "":
104
+ due = f"[green]{due.days}d[/green]"
105
+
106
+ # Add an emoji based on a keyword in the description
107
+ if "meeting" in task["description"].lower():
108
+ emoji = ":busts_in_silhouette:"
109
+ elif "review" in task["description"].lower():
110
+ emoji = ":mag_right:"
111
+ elif "write" in task["description"].lower():
112
+ emoji = ":pencil:"
113
+ else:
114
+ emoji = ":pushpin:"
115
+
116
+ table.add_row(
117
+ f"{emoji} {task_id}", project, description, hours, urgency, due
118
+ )
119
+
120
+ console.print(table)
121
+ else:
122
+ console.print("[italic dim]No tasks scheduled for this day.[/italic dim]")
@@ -5,6 +5,7 @@ from taskcheck.common import (
5
5
  get_tasks,
6
6
  mark_end_date,
7
7
  pdth_to_hours,
8
+ hours_to_pdth,
8
9
  AVOID_STATUS,
9
10
  )
10
11
 
@@ -43,7 +44,7 @@ def schedule_task_on_day(
43
44
  # consume all the remaining task's hours
44
45
  if scheduling_note != "":
45
46
  scheduling_note += "\n"
46
- scheduling_note += f"{current_date}: {task_remaining_hours:.2f} hours"
47
+ scheduling_note += f"{current_date} - {hours_to_pdth(task_remaining_hours)}\n"
47
48
  used_hours[day_offset] += task_remaining_hours
48
49
  task_remaining_hours = 0
49
50
  end_date = current_date
@@ -54,7 +55,7 @@ def schedule_task_on_day(
54
55
  # consume all the available hours on this task
55
56
  if scheduling_note != "":
56
57
  scheduling_note += "\n"
57
- scheduling_note += f"{current_date}T00:00:00 - {employable_hours:.2f} hours"
58
+ scheduling_note += f"{current_date} - {hours_to_pdth(employable_hours)}\n"
58
59
  task_remaining_hours -= employable_hours
59
60
  used_hours[day_offset] += employable_hours
60
61
  if verbose:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: taskcheck
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Automatic scheduler for taskwarrior
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -8,6 +8,7 @@ License-File: LICENSE
8
8
  Requires-Dist: appdirs>=1.4.4
9
9
  Requires-Dist: icalendar>=6.0.1
10
10
  Requires-Dist: requests>=2.32.3
11
+ Requires-Dist: rich>=13.9.4
11
12
 
12
13
 
13
14
  > _A non-AI automatic scheduler for taskwarrior (i.e. alternative to skedpal/timehero/flowsavvy/reclaim/trevor/motion)_
@@ -32,13 +33,14 @@ This is a taskwarrior extension checks if tasks can be completed on time, consid
32
33
  This extension parses your pending and waiting tasks sorted decreasingly by urgency and tries to schedule them in the future.
33
34
  It considers their estimated time to schedule all tasks starting from the most urgent one.
34
35
 
36
+ #### UDAs
37
+
35
38
  Taskcheck leverages to UDAs, `estimated` and `time_map`. The `estimated` attribute is
36
39
  the expected time to complete the task in hours. The `time_map` is a comma-separated list of strings
37
40
  that indicates the hours per day in which you will work on a task (e.g. `work`, `weekend`, etc.).
38
41
  The exact correspondence between the `time_map` and the hours of the day is defined in the configuration
39
42
  file of taskcheck. For instance:
40
43
 
41
- ```toml
42
44
  ```toml
43
45
  [time_maps]
44
46
  # get an error)
@@ -48,6 +50,8 @@ tuesday = [[9, 12.30], [14, 17]]
48
50
  # ...
49
51
  ```
50
52
 
53
+ #### They say it's an "AI"
54
+
51
55
  Taskcheck will also parse online iCal calendars (Google, Apple, etc.) and will match them with your time maps.
52
56
  It will then modify the Taskwarrior tasks by adding the `completion_date` attribute with the expected
53
57
  date of completion and the `scheduled` attribute with the date in which the task is expected to
@@ -58,6 +62,15 @@ It will also print a red line for every task whose `completion_date` is after it
58
62
  In general, it is recommended to run taskcheck rather frequently and at least once at the beginning
59
63
  of your working day.
60
64
 
65
+ #### Reports
66
+
67
+ You can also print simple reports that exploit the `scheduling` UDA filled by Taskcheck to grasp
68
+ how much time you have to work on which task in which day. For
69
+ instance:
70
+
71
+ - `taskcheck -r today` will show the tasks planned for today
72
+ - `taskcheck -r 1w` will show the tasks planned for the next week,
73
+
61
74
  ## Algorithms
62
75
 
63
76
  Taskcheck provides two scheduling algorithms: `parallel` and `sequential`.
@@ -134,12 +147,11 @@ expiration = 720 # in hours (720 hours = 30 days)
134
147
 
135
148
  - You can exclude a task from being scheduled by removing the `time_map` or `estimated` attributes.
136
149
  - You can see tasks that you can execute now with the `task ready` report.
137
- - You can see the schedule for a task in the `scheduling` UDA, e.g. `task scheduling:$(task calc
138
- tomorrow)` will show you the tasks that include a scheduling for tomorrow.
139
150
 
140
151
  ## CLI Options
141
152
 
142
153
  ```
143
154
  -v, --verbose: increase output verbosity
144
155
  -i, --install: install taskcheck configuration
156
+ -r, --report: show tasks planned until a certain time
145
157
  ```
@@ -6,6 +6,7 @@ taskcheck/common.py
6
6
  taskcheck/ical.py
7
7
  taskcheck/install.py
8
8
  taskcheck/parallel.py
9
+ taskcheck/report.py
9
10
  taskcheck/sequential.py
10
11
  taskcheck.egg-info/PKG-INFO
11
12
  taskcheck.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
1
1
  appdirs>=1.4.4
2
2
  icalendar>=6.0.1
3
3
  requests>=2.32.3
4
+ rich>=13.9.4
File without changes
File without changes
File without changes