todo/todo/Todo.py

179 lines
5.5 KiB
Python

#!/bin/python3
import re
import logging
from pathlib import Path
from datetime import datetime
from typing import Optional
from .TodoObject import TodoObject
from .Category import Category
from .Note import Note
from .Task import Task
logger = logging.getLogger("Todo")
logger.addHandler(logging.StreamHandler())
class Todo(TodoObject):
def __init__(self, data_file : Path = Path.home().joinpath("todo.md")):
"""Todo class, reads and parses a todo.md from a file. Default file is 'todo.md' in the home directory.
Args:
data_file (Path, optional): Location of the markdown file to load from. Defaults to ~/todo.md.
"""
self.data_file = data_file
super().__init__(-1, "File")
self._load_data()
def __str__(self):
return self.get_md()
def _get_level(self, line: str) -> int:
"""Calculates the level at which this element lies.
Args:
line (str): The string to parse.
Returns:
int: The level, starting at 0.
"""
count = 0
if line[0] == "-" or line[0] == " ":
for char in line:
if char == " ":
count += 1
else:
break
return count // 2
elif line[0] == "#":
for char in line:
if char == "#":
count += 1
else:
break
return count - 1
else: # catch-all for unlisted notes
return 0
def _get_parent(self, level, object: TodoObject):
"""Gets the parent of an object at level X.
Args:
level (int): The level to find the parent for.
object (TodoObject): Object to grab parents from.
Returns:
TodoObject: The object found.
"""
if level > 0:
for child in object.children:
if child.level < level:
return child
return object
else:
return object
def _parse_note(self, line: str, parent: TodoObject):
"""Parses a markdown line representing a note.
Args:
line (str): The line to parse.
category (Category): The category this note belongs to.
"""
level = self._get_level(line)
parent = self._get_parent(level, parent)
if re.match(" * -", line):
Note(level, line[level*2+2:], parent)
else:
Note(level, line[level*2:], parent, False)
def _parse_task(self, line: str, parent: Category):
"""Parses the markdown line representing a task.
Args:
line (str): The line to parse.
category (Category): The category this task belongs to.
"""
level = self._get_level(line)
parent = self._get_parent(level, parent)
if "[ ]" in line:
complete = False
elif "[x]" in line:
complete = True
line = line[level*2+6:]
if "|" in line:
line = line.replace(" |", "|")
name = line.split("|")[0]
date = datetime.strptime(line.split("|")[1], '%b %d %Y')
else:
name = line
date = None
Task(name, date, complete, level, parent)
def _parse_category(self, line: str):
"""Parses the markdown line representing a category of todos.
Args:
line (str): The line to parse.
"""
level = self._get_level(line)
parent = self._get_parent(level, self)
line = line[level+2:]
Category(line, level, parent)
def _load_data(self):
"""Load categories and tasks from self.data_file."""
with self.data_file.open("r") as f:
for line in f:
line = line.rstrip()
if len(line.strip()) == 0: # skip empty lines
continue
elif line[0] == "#":
self._parse_category(line)
elif re.match(" *- \\[[x ]\\]", line):
self._parse_task(line, self.children[-1])
else:
self._parse_note(line, self.children[-1])
def get_md(self, category_spacing: int = 1, task_spacing: int = 0, note_spacing: int = 0) -> 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.
task_spacing (int, optional): Amount of newlines between tasks. Defaults to 0.
note_spacing (int, optional): Amount of newlines between category notes. Defaults to 0.
Returns:
str: the markdown text generated
"""
output = ""
for i in self.get_children(immediate=True):
if isinstance(i, Category):
output += f"{i}\n"
elif isinstance(i, Task):
output += f"{i.level*2*' '}{i}\n"
elif isinstance(i, Note):
output += f"{i.level*2*' '}{i}\n"
return output
def get_category(self, name: str) -> Optional[Category]:
"""Gets the requested category by name.
Args:
name (str): The name of the category to return.
Returns:
Optional[Category]: Returns the category if found.
"""
for i in self.children:
if isinstance(i, Category) and i.text == name:
return i
def main():
"""Generates a markdown of my todos"""
todo = Todo()
print(todo)
if __name__ == "__main__":
main()