from __future__ import annotations import logging import re from typing import Optional, Type, TypeVar logger = logging.getLogger("TodoObject") logger.setLevel(logging.INFO) logger.addHandler(logging.StreamHandler()) class TodoObject: """Base TodoObject for all classes in the todo library.""" def __init__(self, level, text, parent = None): self.text: str = text self.level: int = level self.parents: list[TodoObject] = [] self.children: list[TodoObject] = [] self.parent: Optional[TodoObject] = parent if parent is not None: self._set_parents(parent) def get_children(self, immediate = False, obj_type: Optional[Type[T]] = None) -> list[T]: """Get all children of an object. Optionally specify a class of TodoObject to return. Args: immediate (bool, optional): Whether it should return immediate children only. Defaults to False. obj_type (Type[Note | Category | Task], optional): Specify the types of children to return. Defaults to None. Returns: list[Note | Category | Task]: Returns all children that match the criteria. """ output = [] for child in self.children: if obj_type is not None and isinstance(child, obj_type) and immediate and child.parent is self: output.append(child) elif obj_type is None and immediate and child.parent is self: output.append(child) elif obj_type is not None and isinstance(child, obj_type) and not immediate: output.append(child) elif obj_type is None and not immediate: output.append(child) return output def get_parents(self, obj_type: Optional[Type[T]] = None) -> list[T]: """Get all parents of an object. Optionally specify a class of TodoObject to return. Args: obj_type (Type[Note | Category | Task], optional): Specify the types of parents to return. Defaults to None. Returns: list[Note | Category | Task]: List of parents found. """ output = [] for parent in self.parents: if obj_type is not None and isinstance(parent, obj_type): output.append(parent) if obj_type is None: output.append(parent) return output def get_md(self, category_spacing: int = 1) -> str: """Gets the markdown text of the current data loaded into the object. Args: category_spacing (int, optional): Amount of newlines between categories. Defaults to 1. Returns: str: the markdown text generated """ output = "" for i in self.get_children(): if len(output) > 0: output += "\n" if isinstance(i, Category): if i.level > 0: output += "\n"*category_spacing output += f"{i}" elif isinstance(i, Task): output += f"{i.level*2*' '}{i}" elif isinstance(i, Note): output += f"{i.level*2*' '}{i}" return output def _set_parents(self, parent: TodoObject): """Set the parents of an object by supplying the first immediate parent. Args: parent (TodoObject): The immediate parent of the object. """ for p in parent.parents: logger.debug(f"adding parent '{p.text}' to '{self.text}'") self.parents.append(p) logger.debug(f"adding child '{self.text}' to '{p.text}'") p.children.append(self) logger.debug(f"adding parent '{parent.text}' to '{self.text}'") self.parents.append(parent) logger.debug(f"adding child '{self.text}' to '{parent.text}'") parent.children.append(self) self.parent = parent def get_tasks(self, immediate: bool = False, complete: Optional[bool] = None) -> list[Task]: """Get all tasks from the children of the TodoObject. Args: immediate (bool, optional): Whether it should return immediate children only. Defaults to False. complete (Optional[bool], optional): If true, return completed tasks only, inverse if False. Defaults to None. Returns: list[Task]: List of tasks found. """ if complete is None: return self.get_children(immediate, Task) else: output = [] for child in self.get_children(immediate, Task): if child.complete is complete: output.append(child) return output def get_task(self, regex: str, immediate: bool = False, complete: Optional[bool] = None) -> Task: """Get a specific task from a supplied regular expression. Args: regex (str): The regular expression to match for the text of the task. immediate (bool, optional): Whether or not it should return immediate children only. Defaults to False. complete (Optional[bool], optional): If true, return completed tasks only, inverse if False. Defaults to None. Returns: Task: Returns the task found """ for task in self.get_tasks(immediate, complete): if re.match(regex, task.text): return task def get_categories(self, immediate: bool = False) -> list[Category]: """Gets all categories from children of the TodoObject. Returns: list[Category]: Returns all categories found. """ return self.get_children(immediate, obj_type=Category) def get_category(self, regex: str, immediate: bool = False) -> Category: """Get a specific task from a supplied regular expression. Args: regex (str): The regular expression to match for the text of the category. immediate (bool, optional): Whether or not it should return immediate children only. Defaults to False. Returns: Category: Returns the category found. """ for category in self.get_categories(immediate): if re.match(regex, category.text): return category def get_notes(self, immediate: bool = False) -> list[Note]: """Get all notes from children of the TodoObject. Args: immediate (bool, optional): Whether or not it should return immeduate children only. Defaults to False. Returns: Note: Returns all notes found. """ return self.get_children(immediate, obj_type=Note) def get_note(self, regex: str, immediate: bool = False) -> Note: """Get a specific note from a supplied regular expression. Args: regex (str): The regular expression to match for the text of the note. immediate (bool, optional): Whether or not it should return immediate children only. Defaults to False. Returns: Note: Returns the note found. """ for note in self.get_notes(immediate): if re.match(regex, note.text): return note @property def has_children(self) -> bool: if len(self.children) > 0: return True else: return False from .Note import Note from .Category import Category from .Task import Task T = TypeVar("T", Note, Category, Task)