Added the management of completed tasks

This commit is contained in:
2025-12-31 15:50:45 +01:00
parent 91e1a83bf0
commit c901ce7653
3 changed files with 96 additions and 9 deletions
+80 -9
View File
@@ -43,18 +43,48 @@ load_dotenv()
# ----------------------------
def slug_tag(name: str) -> str:
"""
Convert label/tag to Tududi-safe ASCII slug:
- removes emoji/non-ascii
- removes accents
- keeps a-z0-9_- and collapses spaces to _
Convert label/tag to Tududi-safe slug while preserving emojis:
- emojis at the beginning are moved to the end
- text is ASCII-normalized
- result: <slug>_<emoji>
Example:
"💡 IDEAS" -> "ideas_💡"
"📚 Reading list" -> "reading_list_📚"
"""
s = unicodedata.normalize("NFKD", name)
if not name:
return "tag"
# 1) Extract emojis (very permissive, Unicode-aware)
emojis = re.findall(
r"[\U0001F300-\U0001FAFF\U00002600-\U000027BF]",
name
)
# 2) Remove emojis from text
text = re.sub(
r"[\U0001F300-\U0001FAFF\U00002600-\U000027BF]",
"",
name
)
# 3) Normalize text → ASCII slug
s = unicodedata.normalize("NFKD", text)
s = "".join(ch for ch in s if not unicodedata.combining(ch))
s = s.encode("ascii", "ignore").decode("ascii")
s = s.strip().lower()
s = re.sub(r"\s+", "_", s)
s = re.sub(r"[^a-z0-9_-]", "", s)
return s or "tag"
if not s:
s = "tag"
# 4) Append emojis at the end (if any)
if emojis:
s = f"{s}_{''.join(emojis)}"
return s
def normalize_list(resp: Any) -> List[Dict[str, Any]]:
"""
@@ -661,18 +691,40 @@ def main() -> None:
tududi_key = os.getenv("TUDUDI_API_KEY")
tududi_email = os.getenv("TUDUDI_EMAIL")
tududi_password = os.getenv("TUDUDI_PASSWORD")
dry_run = getenv_bool("DRY_RUN", False)
clean_first = getenv_bool("CLEAN_FIRST", False)
use_areas = getenv_bool("USE_AREAS", False)
request_sleep = getenv_float("REQUEST_SLEEP", 0.25)
skip_completed_tasks = getenv_bool("SKIP_COMPLETED_TASKS", False)
skip_archived_projects = getenv_bool("SKIP_ARCHIVED_PROJECTS", False)
skip_empty_projects = getenv_bool("SKIP_EMPTY_PROJECTS", False)
print(f"[INFO] Starting migration @ {iso_now()} (dry_run={dry_run})")
print(f"[INFO] SKIP_COMPLETED_TASKS={skip_completed_tasks}")
print(f"[INFO] Options: "
f"SKIP_ARCHIVED_PROJECTS={skip_archived_projects}, "
f"SKIP_EMPTY_PROJECTS={skip_empty_projects}")
print("[INFO] Exporting from Todoist (sync full + completed best-effort)...")
tdc = TodoistClient(todoist_token)
data = tdc.export_everything()
# Build project -> task count map (active + completed)
project_task_count: Dict[str, int] = {}
for t in data.items:
pid = t.get("project_id") or t.get("projectId")
if pid is not None:
project_task_count[str(pid)] = project_task_count.get(str(pid), 0) + 1
for t in data.completed_tasks:
pid = t.get("project_id") or t.get("projectId")
if pid is not None:
project_task_count[str(pid)] = project_task_count.get(str(pid), 0) + 1
print(
f"[INFO] Todoist: projects={len(data.projects)} labels={len(data.labels)} "
f"sections={len(data.sections)} active_items={len(data.items)} "
@@ -754,6 +806,17 @@ def main() -> None:
name = p.get("name")
if not name:
continue
# --- SKIP ARCHIVED PROJECTS ---
if skip_archived_projects and p.get("is_archived"):
print(f"[SKIP] Archived project: {name}")
continue
# --- SKIP EMPTY PROJECTS ---
if skip_empty_projects and project_task_count.get(tid, 0) == 0:
print(f"[SKIP] Empty project: {name}")
continue
if name in proj_by_name:
# prendi ID numerico se esiste, fallback su uid se proprio manca (ma dovrebbe esserci)
@@ -794,12 +857,15 @@ def main() -> None:
# Comments index
comments_idx = build_comments_index(data.notes)
# Merge active + completed tasks
# Merge active + (if requested) completed tasks
all_tasks_src: List[Tuple[Dict[str, Any], bool]] = []
for i in data.items:
all_tasks_src.append((i, False))
for c in data.completed_tasks:
all_tasks_src.append((c, True))
if skip_completed_tasks:
print(f"[INFO] Skipping completed tasks from Todoist export: {len(data.completed_tasks)}")
else:
for c in data.completed_tasks:
all_tasks_src.append((c, True))
items_like = [x for (x, _done) in all_tasks_src if "id" in x]
items_sorted = topo_sort_items(items_like)
@@ -878,6 +944,11 @@ def main() -> None:
due = normalize_due(task)
prio = map_priority(task.get("priority"))
project_uid = get_project_uid_for_task(task)
# Skip task if project was skipped
if project_uid is None and (task.get("project_id") or task.get("projectId")):
print(f"[SKIP] Task {tid} belongs to skipped project")
continue
# Tududi note: Todoist link + Todoist description
tududi_description = build_tududi_description_from_todoist(task, name)