From 6a1ac46b59d482a132d9aae2abf9dd133e039733 Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 18:34:24 -0400 Subject: [PATCH 01/10] update todo.md --- todo.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/todo.md b/todo.md index 520907a..c6473a7 100644 --- a/todo.md +++ b/todo.md @@ -8,9 +8,8 @@ this is the to-do list for the `todo.md` project. *woah*. - category headers can go as many levels as necessary. - tasks can have due-dates, and they can either be complete, or incomplete. - [ ] this is an incomplete task. - - [x] this is a complete task. - - [ ] tasks can be nested on other tasks or notes. - + - [x] this is a complete task with a due date |Sep 30 2021. + - [x] tasks can be nested on other tasks or notes. - [ ] or they can be on the root of the category. - all tasks must belong under a category though. - so no line 1 tasks. @@ -32,4 +31,4 @@ this is the to-do list for the `todo.md` project. *woah*. - weekly - mon - biweekly - wed - monthly - 15 - - yearly - jan - 17 \ No newline at end of file + - yearly - jan - 17 From 6df8c6d11cc8707468a3622d7cc7ac5425e81e91 Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 18:35:46 -0400 Subject: [PATCH 02/10] update README.md --- README.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/README.md b/README.md index ba6819f..1dce8e0 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,3 @@ todo.py is my attempt to organize myself and get things straight. I disliked the ### Sub-Section - [ ] here's another task with a due date |Oct 13 2021 ``` - -## TODO -kinda funny how a todo tool has a todo list, anyways here's what I've got to do to finish this thing: - -```md -# todo.md - -## Features -- [ ] add recurring tasks - - thinking about having the following section for spec -- [ ] add subtasks -- [ ] add notes - -## RECURRING -- some recurring task |how often it should repeat |the last time completed - - possible values for repeat time: - - daily - - weekly - mon - - biweekly - wed - - monthly - 15 - - yearly - jan - 17 -``` From cb322bd0d4947b5bb9e43f7eef4575ca1b8f2188 Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 18:35:46 -0400 Subject: [PATCH 03/10] update README.md and todo.md --- README.md | 22 ---------------------- todo.md | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/README.md b/README.md index ba6819f..1dce8e0 100644 --- a/README.md +++ b/README.md @@ -15,25 +15,3 @@ todo.py is my attempt to organize myself and get things straight. I disliked the ### Sub-Section - [ ] here's another task with a due date |Oct 13 2021 ``` - -## TODO -kinda funny how a todo tool has a todo list, anyways here's what I've got to do to finish this thing: - -```md -# todo.md - -## Features -- [ ] add recurring tasks - - thinking about having the following section for spec -- [ ] add subtasks -- [ ] add notes - -## RECURRING -- some recurring task |how often it should repeat |the last time completed - - possible values for repeat time: - - daily - - weekly - mon - - biweekly - wed - - monthly - 15 - - yearly - jan - 17 -``` diff --git a/todo.md b/todo.md index c6473a7..55e1f53 100644 --- a/todo.md +++ b/todo.md @@ -8,7 +8,7 @@ this is the to-do list for the `todo.md` project. *woah*. - category headers can go as many levels as necessary. - tasks can have due-dates, and they can either be complete, or incomplete. - [ ] this is an incomplete task. - - [x] this is a complete task with a due date |Sep 30 2021. + - [x] this is a complete task with a due date |Sep 30 2021 - [x] tasks can be nested on other tasks or notes. - [ ] or they can be on the root of the category. - all tasks must belong under a category though. From a69639256010f44cfc6354ab178c4963753c782b Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 19:47:55 -0400 Subject: [PATCH 04/10] Fix get_md() and parent assignments. - now properly outputs category spacing in get_md() - removed task_spacing and note_spacing, may return later. - fixed parent assignment, now iterates through the reversed children list. - this finds the closest parent. - TodoObject now properly returns output with get_children() --- todo/Todo.py | 10 +++++----- todo/TodoObject.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/todo/Todo.py b/todo/Todo.py index 6f6af30..f4239f8 100644 --- a/todo/Todo.py +++ b/todo/Todo.py @@ -66,7 +66,7 @@ class Todo(TodoObject): TodoObject: The object found. """ if level > 0: - for child in object.children: + for child in object.children[::-1]: if child.level < level: return child return object @@ -135,20 +135,20 @@ class Todo(TodoObject): 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: + 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. - 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): + for i in self.get_children(): if isinstance(i, Category): + if i.level > 0: + output += "\n"*category_spacing output += f"{i}\n" elif isinstance(i, Task): output += f"{i.level*2*' '}{i}\n" diff --git a/todo/TodoObject.py b/todo/TodoObject.py index 4dbab1d..b541067 100644 --- a/todo/TodoObject.py +++ b/todo/TodoObject.py @@ -23,6 +23,7 @@ class TodoObject: output.append(child) elif not immediate: output.append(child) + return output def set_parents(self, parent): parent: TodoObject From e768dae9783fead11918358e97144eb0f8b1a041 Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 20:16:08 -0400 Subject: [PATCH 05/10] Add \t support to parsing. --- todo/Todo.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/todo/Todo.py b/todo/Todo.py index f4239f8..1ab6272 100644 --- a/todo/Todo.py +++ b/todo/Todo.py @@ -15,13 +15,15 @@ logger = logging.getLogger("Todo") logger.addHandler(logging.StreamHandler()) class Todo(TodoObject): - def __init__(self, data_file : Path = Path.home().joinpath("todo.md")): + def __init__(self, data_file : Path = Path.home().joinpath("todo.md"), tab_spacing = 2): """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. + tab_spacing (int, optional): How many levels a tab character should represent. Defaults to 2. """ self.data_file = data_file + self.tab_spacing = tab_spacing super().__init__(-1, "File") self._load_data() @@ -125,6 +127,7 @@ class Todo(TodoObject): """Load categories and tasks from self.data_file.""" with self.data_file.open("r") as f: for line in f: + line = line.replace("\t", self.tab_spacing*" ") line = line.rstrip() if len(line.strip()) == 0: # skip empty lines continue From ada7812e3b2fed4ffaf7543578e6b74d5e7361f9 Mon Sep 17 00:00:00 2001 From: riley Date: Sat, 25 Sep 2021 23:25:30 -0400 Subject: [PATCH 06/10] Parent/Child assignment & fetching refactor - TodoObject.get_children accepts an obj_type to filter by - Todo._get_parent accepts an obj_type to filter potential parents - Todo._parse_category now properly assigns parent categories - Reworked get_* methods in Task, Category, and Note to use get_children --- todo/Category.py | 22 +++++++++------------- todo/Note.py | 7 +++++++ todo/Task.py | 17 +++++------------ todo/Todo.py | 38 +++++++++++++++++--------------------- todo/TodoObject.py | 13 +++++++++---- 5 files changed, 47 insertions(+), 50 deletions(-) diff --git a/todo/Category.py b/todo/Category.py index 4d9f294..3d65e6b 100644 --- a/todo/Category.py +++ b/todo/Category.py @@ -1,25 +1,21 @@ from __future__ import annotations -from .Task import Task from .TodoObject import TodoObject class Category(TodoObject): def __init__(self, name : str, level: int, parent = None): super().__init__(level, name, parent) - def get_subcategories(self) -> list[Category]: - categories = [] - for child in self.children: - if isinstance(child, Category): - categories.append(child) - return categories + def get_notes(self, immediate = True): + from .Note import Note + return self.get_children(immediate, Note) - def get_tasks(self) -> list[Task]: - tasks = [] - for child in self.children: - if isinstance(child, Task): - tasks.append(child) - return tasks + def get_subcategories(self, immediate = True): + return self.get_children(immediate, Category) + + def get_tasks(self, immediate = True): + from .Task import Task + return self.get_children(immediate, Task) def __str__(self): return "#"*(self.level+1) + f" {self.text}" diff --git a/todo/Note.py b/todo/Note.py index d7e0b1c..f47660e 100644 --- a/todo/Note.py +++ b/todo/Note.py @@ -7,6 +7,13 @@ class Note(TodoObject): self.listed = listed super().__init__(level, text, parent) + def get_subnotes(self, immediate = True): + return self.get_children(immediate, Note) + + def get_tasks(self, immediate = True): + from .Task import Task + return self.get_children(immediate, Task) + def __str__(self): if self.listed: return f"- {self.text}" diff --git a/todo/Task.py b/todo/Task.py index 7f15c14..e6e7d87 100644 --- a/todo/Task.py +++ b/todo/Task.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import Union from datetime import datetime -from .Note import Note from .TodoObject import TodoObject class Task(TodoObject): @@ -12,18 +11,12 @@ class Task(TodoObject): self.complete = complete super().__init__(level, name, parent) - def get_subtasks(self) -> list[Task]: - tasks = [] - for child in self.children: - if isinstance(child, Task): - tasks.append(child) - return tasks + def get_subtasks(self, immediate = True): + return self.get_children(immediate, Task) - def get_notes(self): - notes = [] - for child in self.children: - if isinstance(child, Note): - notes.append(child) + def get_notes(self, immediate = True): + from .Note import Note + return self.get_children(immediate, Note) def __str__(self): output = "" diff --git a/todo/Todo.py b/todo/Todo.py index 1ab6272..feaf599 100644 --- a/todo/Todo.py +++ b/todo/Todo.py @@ -4,7 +4,7 @@ import logging from pathlib import Path from datetime import datetime -from typing import Optional +from typing import Optional, Type from .TodoObject import TodoObject from .Category import Category @@ -57,23 +57,27 @@ class Todo(TodoObject): else: # catch-all for unlisted notes return 0 - def _get_parent(self, level, object: TodoObject): - """Gets the parent of an object at level X. + def _get_parent(self, level, obj: TodoObject, obj_type: Type[TodoObject] = None): + """Gets the parent for an object at level X. + If an object with a lower level cannot be found within the obj, it will return the obj. Args: level (int): The level to find the parent for. - object (TodoObject): Object to grab parents from. + obj (TodoObject): Object to grab parents from. + obj_type (Type[TodoObject]): The type of the object the parent should be. Default is None. Returns: TodoObject: The object found. """ if level > 0: - for child in object.children[::-1]: - if child.level < level: + for child in obj.children[::-1]: + if obj_type is not None and isinstance(child, obj_type) and child.level < level: return child - return object + if obj_type is None and child.level < level: + return child + return obj else: - return object + return obj def _parse_note(self, line: str, parent: TodoObject): """Parses a markdown line representing a note. @@ -119,7 +123,7 @@ class Todo(TodoObject): line (str): The line to parse. """ level = self._get_level(line) - parent = self._get_parent(level, self) + parent = self._get_parent(level, self, obj_type=Category) line = line[level+2:] Category(line, level, parent) @@ -134,9 +138,9 @@ class Todo(TodoObject): elif line[0] == "#": self._parse_category(line) elif re.match(" *- \\[[x ]\\]", line): - self._parse_task(line, self.children[-1]) + self._parse_task(line, self.get_children(obj_type=Category)[-1]) else: - self._parse_note(line, self.children[-1]) + self._parse_note(line, self.get_children(obj_type=Category)[-1]) def get_md(self, category_spacing: int = 1) -> str: """Gets the markdown text of the current data loaded into the object. @@ -168,14 +172,6 @@ class Todo(TodoObject): Returns: Optional[Category]: Returns the category if found. """ - for i in self.children: - if isinstance(i, Category) and i.text == name: + for i in self.get_children(obj_type=Category): + if i.text == name: return i - -def main(): - """Generates a markdown of my todos""" - todo = Todo() - print(todo) - -if __name__ == "__main__": - main() diff --git a/todo/TodoObject.py b/todo/TodoObject.py index b541067..a6fbe1d 100644 --- a/todo/TodoObject.py +++ b/todo/TodoObject.py @@ -1,6 +1,7 @@ +from __future__ import annotations import logging -from typing import Optional +from typing import Optional, Type logger = logging.getLogger("TodoObject") logger.setLevel(logging.INFO) @@ -16,12 +17,16 @@ class TodoObject: if parent is not None: self.set_parents(parent) - def get_children(self, immediate = False): + def get_children(self, immediate = False, obj_type: Type[TodoObject] = None) -> list[TodoObject]: output = [] for child in self.children: - if immediate and child.parent is self: + if obj_type is not None and isinstance(child, obj_type) and immediate and child.parent is self: output.append(child) - elif not immediate: + 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 From ed0cb5ebc625748b9708f9829ae136754f210954 Mon Sep 17 00:00:00 2001 From: riley Date: Tue, 28 Sep 2021 00:20:20 -0400 Subject: [PATCH 07/10] Refactor for CLI General: + Improved documentation cli.py: + added basic CLI, will likely be rewritten later. TodoObject: ~ improved type-hinting for get_children + added get_parents method + moved get_md method from Todo + added get_* methods to get Tasks, Categories, and Notes from children + added property has_children Todo: ~ changed string output to "File: {self.data_file.name}" - moved get_md method to TodoObject + added write_data method Task: - removed get_* methods + added task_category method to get parent category. + added toggle_complete method. - removed __add__ method. Note: - removed get_* methods. - removed __add__ method. Category: - removed get_* methods. - removed __add__ method. --- cli.py | 78 ++++++++++++++++++++++ todo/Category.py | 14 ---- todo/Note.py | 11 ---- todo/Task.py | 18 +++-- todo/Todo.py | 42 ++---------- todo/TodoObject.py | 160 +++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 250 insertions(+), 73 deletions(-) create mode 100755 cli.py diff --git a/cli.py b/cli.py new file mode 100755 index 0000000..c2c3426 --- /dev/null +++ b/cli.py @@ -0,0 +1,78 @@ +#!/bin/python3 +import argparse +import re + +from todo.TodoObject import TodoObject + +from todo import Todo, Category, Task + +parser = argparse.ArgumentParser("Todo.md CLI Application", description="Access your todos via the command line!") + +parser.add_argument("-md", "--markdown", action="store_true", help="Show markdown.") +parser.add_argument("-cat", "--category", action="store", help="Category the command should target.") +parser.add_argument("-t", "--task", action="store", help="Target a specific task in a category.") +parser.add_argument("-i", "--info", action="store_true", help="Print information about the given object.") +parser.add_argument("-x", "--complete", action="store_true", help="Toggle a task's completed status.") # x = done, right? + +todo = Todo() + +args = parser.parse_args() + +def print_incomplete_tasks(data: TodoObject): + if len(data.get_tasks(complete=False)) > 0: + print("\n--- Incomplete Task(s) ---") + for category in [data, *data.get_categories()]: # allows us to catch tasks in subcategories and the main data object. + if len(category.get_tasks(immediate=False, complete=False)) > 0: + print(f"\n{category}") + for task in category.get_tasks(immediate=True, complete=False): + print(task) + if task.has_children: + print(task.get_md()) + +data = todo # start with base todo object as input data. + +# --category handler +if args.category is not None: + args.category = f".*{args.category}" + data = data.get_category(args.category) + +# --task handler +if args.task is not None: + for i in data.get_tasks(): + if re.match(args.task, i.text, re.IGNORECASE): + data = i + +# --done handler +if args.complete: + if isinstance(data, Task): + data.toggle_complete() + print(f"Set complete status for '{data.text}' to '{data.complete}'.") + todo.write_data() + else: + print("-d is only applicable to tasks.") + +## --markdown handler +if args.markdown: + print(data) + print(data.get_md()) + +## --info handler +if args.info: + task_count = len(data.get_tasks()) + incomplete_count = len(data.get_tasks(complete=False)) + if isinstance(data, Todo): + print(data) + print("Categories: ", ", ".join([x.text for x in data.get_children(obj_type=Category)])) + print(f"Tasks: \n\tTotal: {task_count}\n\tIncomplete: {incomplete_count}") + print_incomplete_tasks(data) + elif isinstance(data, Category): + print("Category:", data.text) + print(f"Tasks: \n\tTotal: {task_count}\n\tIncomplete: {incomplete_count}") + print_incomplete_tasks(data) + elif isinstance(data, Task): + print("Category: ", data.task_category().text) + print("Task:", data.text) + print("Due Date:", data.date.strftime("%b %d %Y")) + print("Complete:", data.complete) + else: + print("Don't know how we ended up here. :/") diff --git a/todo/Category.py b/todo/Category.py index 3d65e6b..b604e3b 100644 --- a/todo/Category.py +++ b/todo/Category.py @@ -5,20 +5,6 @@ from .TodoObject import TodoObject class Category(TodoObject): def __init__(self, name : str, level: int, parent = None): super().__init__(level, name, parent) - - def get_notes(self, immediate = True): - from .Note import Note - return self.get_children(immediate, Note) - - def get_subcategories(self, immediate = True): - return self.get_children(immediate, Category) - - def get_tasks(self, immediate = True): - from .Task import Task - return self.get_children(immediate, Task) def __str__(self): return "#"*(self.level+1) + f" {self.text}" - - def __add__(self, other): - return other + self.__str__() diff --git a/todo/Note.py b/todo/Note.py index f47660e..91e8c8a 100644 --- a/todo/Note.py +++ b/todo/Note.py @@ -7,19 +7,8 @@ class Note(TodoObject): self.listed = listed super().__init__(level, text, parent) - def get_subnotes(self, immediate = True): - return self.get_children(immediate, Note) - - def get_tasks(self, immediate = True): - from .Task import Task - return self.get_children(immediate, Task) - def __str__(self): if self.listed: return f"- {self.text}" else: return self.text - - def __add__(self, other: str): - return other + self.__str__() - diff --git a/todo/Task.py b/todo/Task.py index e6e7d87..d89dc98 100644 --- a/todo/Task.py +++ b/todo/Task.py @@ -11,13 +11,18 @@ class Task(TodoObject): self.complete = complete super().__init__(level, name, parent) - def get_subtasks(self, immediate = True): - return self.get_children(immediate, Task) + def task_category(self) -> Category: + """Get the category this task belongs to. - def get_notes(self, immediate = True): - from .Note import Note - return self.get_children(immediate, Note) + Returns: + Category: The category this task belongs to. + """ + return self.get_parents(Category)[-1] + def toggle_complete(self): + """Toggle this task's complete value.""" + self.complete = not self.complete + def __str__(self): output = "" if self.complete: @@ -31,5 +36,4 @@ class Task(TodoObject): output += f" {self.text}" return output - def __add__(self, other): - return other + self.__str__() +from .Category import Category diff --git a/todo/Todo.py b/todo/Todo.py index feaf599..5ff1415 100644 --- a/todo/Todo.py +++ b/todo/Todo.py @@ -27,11 +27,11 @@ class Todo(TodoObject): super().__init__(-1, "File") self._load_data() - def __str__(self): - return self.get_md() + def __str__(self) -> str: + return f"File: {self.data_file.name}" def _get_level(self, line: str) -> int: - """Calculates the level at which this element lies. + """Calculates the level at which this element lies by parsing the given markdown line. Args: line (str): The string to parse. @@ -142,36 +142,6 @@ class Todo(TodoObject): else: self._parse_note(line, self.get_children(obj_type=Category)[-1]) - 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 isinstance(i, Category): - if i.level > 0: - output += "\n"*category_spacing - 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.get_children(obj_type=Category): - if i.text == name: - return i + def write_data(self): + """Write the Todo data to the datafile.""" + self.data_file.write_text(self.get_md()) diff --git a/todo/TodoObject.py b/todo/TodoObject.py index a6fbe1d..02c27bf 100644 --- a/todo/TodoObject.py +++ b/todo/TodoObject.py @@ -1,13 +1,15 @@ from __future__ import annotations import logging +import re -from typing import Optional, Type +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 @@ -15,9 +17,18 @@ class TodoObject: self.children: list[TodoObject] = [] self.parent: Optional[TodoObject] = parent if parent is not None: - self.set_parents(parent) + self._set_parents(parent) - def get_children(self, immediate = False, obj_type: Type[TodoObject] = None) -> list[TodoObject]: + 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 (Optional[Type[T]], optional): Specify the types of children to return. Defaults to None. + + Returns: + list[T]: [description] + """ output = [] for child in self.children: if obj_type is not None and isinstance(child, obj_type) and immediate and child.parent is self: @@ -30,8 +41,53 @@ class TodoObject: output.append(child) return output - def set_parents(self, parent): - parent: TodoObject + 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 (Optional[Type[T]], optional): Specify the types of parents to return. Defaults to None. + + Returns: + list[T]: 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) @@ -42,3 +98,97 @@ class TodoObject: logger.debug(f"adding child '{self.text}' to '{parent.text}'") parent.children.append(self) self.parent = parent + + def get_tasks(self, immediate = 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 = 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: [description] + """ + for task in self.get_tasks(self, immediate, complete): + if re.match(regex, task.text): + return Task + + + def get_categories(self, immediate = 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: [description] + """ + 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: [description] + """ + for note in self.get_notes(): + 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) From ff0f47ae88d6f6dde946825132a0f4ad62b70cad Mon Sep 17 00:00:00 2001 From: riley Date: Tue, 28 Sep 2021 00:22:00 -0400 Subject: [PATCH 08/10] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c01bd8c..5420d1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ */__pycache__/ +.vscode/ \ No newline at end of file From 134d71f44dcb90f5f411d61f3a26c15a4ea522e9 Mon Sep 17 00:00:00 2001 From: riley Date: Tue, 28 Sep 2021 00:54:49 -0400 Subject: [PATCH 09/10] Add --file argument & fixes cli.py: + added --file argument to specify the todo file. ~ changed the --task handler to use get_task instead. ~ fixed bug w/ printing information on tasks w/ no due date. TodoObject.py: ~ modified type-hinting on get_tasks. ~ modified type-hinting on get_task. ~ fixed bug with get_task returning type Task rather than found task. ~ fixed bug with get_note not listening to the immediate parameter. --- cli.py | 18 ++++++++++++------ todo/TodoObject.py | 14 +++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cli.py b/cli.py index c2c3426..0e349be 100755 --- a/cli.py +++ b/cli.py @@ -1,4 +1,6 @@ #!/bin/python3 +from pathlib import Path + import argparse import re @@ -13,11 +15,15 @@ parser.add_argument("-cat", "--category", action="store", help="Category the com parser.add_argument("-t", "--task", action="store", help="Target a specific task in a category.") parser.add_argument("-i", "--info", action="store_true", help="Print information about the given object.") parser.add_argument("-x", "--complete", action="store_true", help="Toggle a task's completed status.") # x = done, right? - -todo = Todo() +parser.add_argument("-f", "--file", action="store", help="Specify the file to load into a Todo.") args = parser.parse_args() +if args.file is not None: + todo = Todo(Path(args.file)) +else: + todo = Todo() + def print_incomplete_tasks(data: TodoObject): if len(data.get_tasks(complete=False)) > 0: print("\n--- Incomplete Task(s) ---") @@ -38,9 +44,8 @@ if args.category is not None: # --task handler if args.task is not None: - for i in data.get_tasks(): - if re.match(args.task, i.text, re.IGNORECASE): - data = i + args.task = f".*{args.task}" + data = data.get_task(args.task) # --done handler if args.complete: @@ -72,7 +77,8 @@ if args.info: elif isinstance(data, Task): print("Category: ", data.task_category().text) print("Task:", data.text) - print("Due Date:", data.date.strftime("%b %d %Y")) + if data.date is not None: + print("Due Date:", data.date.strftime("%b %d %Y")) print("Complete:", data.complete) else: print("Don't know how we ended up here. :/") diff --git a/todo/TodoObject.py b/todo/TodoObject.py index 02c27bf..f99fddb 100644 --- a/todo/TodoObject.py +++ b/todo/TodoObject.py @@ -99,7 +99,7 @@ class TodoObject: parent.children.append(self) self.parent = parent - def get_tasks(self, immediate = False, complete: Optional[bool] = None) -> list[Task]: + def get_tasks(self, immediate: bool = False, complete: Optional[bool] = None) -> list[Task]: """Get all tasks from the children of the TodoObject. Args: @@ -118,7 +118,7 @@ class TodoObject: output.append(child) return output - def get_task(self, regex: str, immediate = False, complete: Optional[bool] = None) -> Task: + def get_task(self, regex: str, immediate: bool = False, complete: Optional[bool] = None) -> Task: """Get a specific task from a supplied regular expression. Args: @@ -129,12 +129,12 @@ class TodoObject: Returns: Task: [description] """ - for task in self.get_tasks(self, immediate, complete): + for task in self.get_tasks(immediate, complete): if re.match(regex, task.text): - return Task + return task - def get_categories(self, immediate = False) -> list[Category]: + def get_categories(self, immediate: bool = False) -> list[Category]: """Gets all categories from children of the TodoObject. Returns: @@ -161,7 +161,7 @@ class TodoObject: Args: immediate (bool, optional): Whether or not it should return immeduate children only. Defaults to False. - +c Returns: Note: Returns all notes found. """ @@ -177,7 +177,7 @@ class TodoObject: Returns: Note: [description] """ - for note in self.get_notes(): + for note in self.get_notes(immediate): if re.match(regex, note.text): return note From 9686e43ec26a4733bb4125232cf5c01cb736f58d Mon Sep 17 00:00:00 2001 From: riley Date: Tue, 28 Sep 2021 01:36:12 -0400 Subject: [PATCH 10/10] Documentation and formatting fixes --- todo/Todo.py | 4 ++-- todo/TodoObject.py | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/todo/Todo.py b/todo/Todo.py index 5ff1415..104ea5a 100644 --- a/todo/Todo.py +++ b/todo/Todo.py @@ -57,14 +57,14 @@ class Todo(TodoObject): else: # catch-all for unlisted notes return 0 - def _get_parent(self, level, obj: TodoObject, obj_type: Type[TodoObject] = None): + def _get_parent(self, level: int, obj: TodoObject, obj_type: Optional[Type[TodoObject]] = None): """Gets the parent for an object at level X. If an object with a lower level cannot be found within the obj, it will return the obj. Args: level (int): The level to find the parent for. obj (TodoObject): Object to grab parents from. - obj_type (Type[TodoObject]): The type of the object the parent should be. Default is None. + obj_type (Type[TodoObject], optional): The type of the object the parent should be. Default is None. Returns: TodoObject: The object found. diff --git a/todo/TodoObject.py b/todo/TodoObject.py index f99fddb..585107f 100644 --- a/todo/TodoObject.py +++ b/todo/TodoObject.py @@ -24,10 +24,10 @@ class TodoObject: Args: immediate (bool, optional): Whether it should return immediate children only. Defaults to False. - obj_type (Optional[Type[T]], optional): Specify the types of children to return. Defaults to None. + obj_type (Type[Note | Category | Task], optional): Specify the types of children to return. Defaults to None. Returns: - list[T]: [description] + list[Note | Category | Task]: Returns all children that match the criteria. """ output = [] for child in self.children: @@ -45,10 +45,10 @@ class TodoObject: """Get all parents of an object. Optionally specify a class of TodoObject to return. Args: - obj_type (Optional[Type[T]], optional): Specify the types of parents to return. Defaults to None. + obj_type (Type[Note | Category | Task], optional): Specify the types of parents to return. Defaults to None. Returns: - list[T]: List of parents found. + list[Note | Category | Task]: List of parents found. """ output = [] for parent in self.parents: @@ -67,7 +67,6 @@ class TodoObject: Returns: str: the markdown text generated """ - output = "" for i in self.get_children(): if len(output) > 0: @@ -127,13 +126,12 @@ class TodoObject: complete (Optional[bool], optional): If true, return completed tasks only, inverse if False. Defaults to None. Returns: - Task: [description] + 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. @@ -150,7 +148,7 @@ class TodoObject: immediate (bool, optional): Whether or not it should return immediate children only. Defaults to False. Returns: - Category: [description] + Category: Returns the category found. """ for category in self.get_categories(immediate): if re.match(regex, category.text): @@ -161,7 +159,7 @@ class TodoObject: Args: immediate (bool, optional): Whether or not it should return immeduate children only. Defaults to False. -c + Returns: Note: Returns all notes found. """ @@ -175,7 +173,7 @@ c immediate (bool, optional): Whether or not it should return immediate children only. Defaults to False. Returns: - Note: [description] + Note: Returns the note found. """ for note in self.get_notes(immediate): if re.match(regex, note.text):