Added list function and reworked tag parsing
Tag parsing now has updated time calculation and now requires the EstimatedTime tag to be set. You can also now interactivly decide if you want to sync your things tasks or list the tasks on reclaim.
This commit is contained in:
@@ -3,6 +3,14 @@
|
|||||||
import things
|
import things
|
||||||
from reclaim_sdk.models.task import ReclaimTask
|
from reclaim_sdk.models.task import ReclaimTask
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import argparse
|
||||||
|
from typing import Dict, List
|
||||||
|
import re
|
||||||
|
|
||||||
|
regex = (
|
||||||
|
r"((\d+\.?\d*) (hours|hrs|hour|hr|h))? ?((\d+\.?\d*) (mins|min|minutes|minute|m))?"
|
||||||
|
)
|
||||||
|
pattern = re.compile(regex)
|
||||||
|
|
||||||
|
|
||||||
def extract_uni_projects():
|
def extract_uni_projects():
|
||||||
@@ -10,52 +18,80 @@ def extract_uni_projects():
|
|||||||
return things.projects(area=uni_area["uuid"])
|
return things.projects(area=uni_area["uuid"])
|
||||||
|
|
||||||
|
|
||||||
def get_tasks_for_project(project):
|
def get_tasks_for_project(project) -> Dict | List[Dict]:
|
||||||
return things.tasks(project=project["uuid"], type="to-do")
|
return things.tasks(project=project["uuid"], type="to-do")
|
||||||
|
|
||||||
|
|
||||||
|
def get_task_tags(things_task: Dict) -> Dict[str, str]:
|
||||||
|
return {k: v for (k, v) in [tag.split(": ") for tag in things_task["tags"]]}
|
||||||
|
|
||||||
|
|
||||||
def set_default_reclaim_values(things_task, reclaim_task):
|
def set_default_reclaim_values(things_task, reclaim_task):
|
||||||
reclaim_task.min_work_duration = 0.5
|
tags_dict = get_task_tags(things_task)
|
||||||
reclaim_task.max_work_duration = 2
|
estimated_time = tags_dict.get("EstimatedTime")
|
||||||
reclaim_task.duration = 2
|
if estimated_time is None:
|
||||||
|
raise ValueError("EstimatedTime tag is required")
|
||||||
|
estimated_time = calculate_time_on_unit(estimated_time)
|
||||||
|
reclaim_task.min_work_duration = estimated_time
|
||||||
|
reclaim_task.max_work_duration = estimated_time
|
||||||
|
reclaim_task.duration = estimated_time
|
||||||
if things_task.get("start_date") is not None:
|
if things_task.get("start_date") is not None:
|
||||||
reclaim_task.start_date = datetime.strptime(
|
reclaim_task.start_date = datetime.strptime(
|
||||||
f"{things_task['start_date']} 08:00", "%Y-%m-%d %H:%M")
|
f"{things_task['start_date']} 08:00", "%Y-%m-%d %H:%M"
|
||||||
|
)
|
||||||
if things_task.get("deadline") is not None:
|
if things_task.get("deadline") is not None:
|
||||||
reclaim_task.due_date = datetime.strptime(
|
reclaim_task.due_date = datetime.strptime(
|
||||||
f"{things_task['deadline']} 22:00", "%Y-%m-%d %H:%M")
|
f"{things_task['deadline']} 22:00", "%Y-%m-%d %H:%M"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def calculate_time_on_unit(tag_value) -> float:
|
def calculate_time_on_unit(tag_value) -> float | None:
|
||||||
value, unit = tag_value.split(" ")
|
# This is a regex to match time in the format of 1h 30m
|
||||||
match unit:
|
# Minutes are optional if hours are present
|
||||||
case 'hours' | 'hrs' | 'hour' | 'hr' | "h":
|
# Hours are optional if minutes are present
|
||||||
return int(value)
|
# The regex will match two words when the correct format is found (format and emtpy word)
|
||||||
case "mins" | "min" | "minutes" | "minute" | "m":
|
# If more words are found the string is invalid and an Error is raised
|
||||||
return int(value) / 60
|
# We can extract hours and minutes based on the matching groups
|
||||||
|
values = pattern.findall(tag_value)
|
||||||
|
time = 0
|
||||||
|
if len(values) != 2:
|
||||||
|
raise ValueError("Invalid time format")
|
||||||
|
_, hours, _, _, mins, _ = values[0]
|
||||||
|
if "" == hours and "" == mins:
|
||||||
|
raise ValueError("Regex matched empty string")
|
||||||
|
if "" != hours:
|
||||||
|
time += float(hours)
|
||||||
|
if "" != mins:
|
||||||
|
time += float(mins) / 60
|
||||||
|
|
||||||
|
return time
|
||||||
|
|
||||||
|
|
||||||
|
def list_reclaim_tasks():
|
||||||
|
reclaim_tasks = ReclaimTask().search()
|
||||||
|
for id, task in enumerate(reclaim_tasks):
|
||||||
|
print(f"({id + 1}) {task.name} ")
|
||||||
|
|
||||||
|
|
||||||
def map_tag_values(things_task, reclaim_task):
|
def map_tag_values(things_task, reclaim_task):
|
||||||
tags_dict = {k: v for (k, v) in [tag.split(": ")
|
tags_dict = get_task_tags(things_task)
|
||||||
for tag in things_task["tags"]]}
|
|
||||||
for tag in tags_dict:
|
for tag in tags_dict:
|
||||||
match tag:
|
match tag:
|
||||||
case "MinTime":
|
case "MinTime":
|
||||||
reclaim_task.min_work_duration = calculate_time_on_unit(
|
reclaim_task.min_work_duration = calculate_time_on_unit(tags_dict[tag])
|
||||||
tags_dict[tag])
|
|
||||||
case "MaxTime":
|
case "MaxTime":
|
||||||
reclaim_task.max_work_duration = calculate_time_on_unit(
|
reclaim_task.max_work_duration = calculate_time_on_unit(tags_dict[tag])
|
||||||
tags_dict[tag])
|
|
||||||
case "EstimatedTime":
|
|
||||||
reclaim_task.duration = calculate_time_on_unit(tags_dict[tag])
|
|
||||||
case "DeadlineTime":
|
case "DeadlineTime":
|
||||||
if things_task.get('deadline') is not None:
|
if things_task.get("deadline") is not None:
|
||||||
reclaim_task.due_date = datetime.strptime(
|
reclaim_task.due_date = datetime.strptime(
|
||||||
f"{things_task['deadline']} {tags_dict[tag]}", "%Y-%m-%d %H:%M")
|
f"{things_task['deadline']} {tags_dict[tag]}", "%Y-%m-%d %H:%M"
|
||||||
|
)
|
||||||
case "StartTime":
|
case "StartTime":
|
||||||
if things_task.get('start_date') is not None:
|
if things_task.get("start_date") is not None:
|
||||||
reclaim_task.start_date = datetime.strptime(
|
reclaim_task.start_date = datetime.strptime(
|
||||||
f"{things_task['start_date']} {tags_dict[tag]}", "%Y-%m-%d %H:%M")
|
f"{things_task['start_date']} {tags_dict[tag]}",
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
)
|
||||||
case _:
|
case _:
|
||||||
print(f"Tag {tag} not recognized")
|
print(f"Tag {tag} not recognized")
|
||||||
|
|
||||||
@@ -63,8 +99,7 @@ def map_tag_values(things_task, reclaim_task):
|
|||||||
def things_to_reclaim(things_task, project_title):
|
def things_to_reclaim(things_task, project_title):
|
||||||
with ReclaimTask() as reclaim_task:
|
with ReclaimTask() as reclaim_task:
|
||||||
reclaim_task.name = "{} {}".format(project_title, things_task["title"])
|
reclaim_task.name = "{} {}".format(project_title, things_task["title"])
|
||||||
set_default_reclaim_values(
|
set_default_reclaim_values(things_task=things_task, reclaim_task=reclaim_task)
|
||||||
things_task=things_task, reclaim_task=reclaim_task)
|
|
||||||
map_tag_values(things_task=things_task, reclaim_task=reclaim_task)
|
map_tag_values(things_task=things_task, reclaim_task=reclaim_task)
|
||||||
reclaim_task_pretty_print(reclaim_task)
|
reclaim_task_pretty_print(reclaim_task)
|
||||||
|
|
||||||
@@ -85,21 +120,32 @@ def reclaim_task_pretty_print(task):
|
|||||||
print(f"\tDuration: {task.duration}")
|
print(f"\tDuration: {task.duration}")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def sync_things_to_reclaim():
|
||||||
projects = extract_uni_projects()
|
projects = extract_uni_projects()
|
||||||
reclaim_task_names = [task.name for task in ReclaimTask().search()]
|
reclaim_task_names = [task.name for task in ReclaimTask().search()]
|
||||||
for project in projects:
|
for project in projects:
|
||||||
things_tasks = get_tasks_for_project(project)
|
things_tasks = get_tasks_for_project(project)
|
||||||
for things_task in things_tasks:
|
for things_task in things_tasks:
|
||||||
full_task_name = "{} {}".format(
|
full_task_name = "{} {}".format(project["title"], things_task["title"])
|
||||||
project["title"], things_task["title"])
|
|
||||||
if full_task_name not in reclaim_task_names:
|
if full_task_name not in reclaim_task_names:
|
||||||
print(f"Creating task {full_task_name} in Reclaim")
|
print(f"Creating task {full_task_name} in Reclaim")
|
||||||
# things_task_pretty_print(things_task, project["title"])
|
|
||||||
things_to_reclaim(things_task, project["title"])
|
things_to_reclaim(things_task, project["title"])
|
||||||
else:
|
else:
|
||||||
print(f"Task {things_task['title']} already exists in Reclaim")
|
print(f"Task {things_task['title']} already exists in Reclaim")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Sync Things 3 tasks to Reclaim")
|
||||||
|
parser.add_argument("action", help="list, sync")
|
||||||
|
args = parser.parse_args()
|
||||||
|
match args.action:
|
||||||
|
case "list":
|
||||||
|
list_reclaim_tasks()
|
||||||
|
case "sync":
|
||||||
|
sync_things_to_reclaim()
|
||||||
|
case _:
|
||||||
|
print("Invalid action")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user