import json import logging from typing import List, Union from bigtree.node.node import Node from bigtree.tree.construct import dict_to_tree from bigtree.tree.export import print_tree, tree_to_dict from bigtree.tree.search import find_children, find_name logging.getLogger(__name__).addHandler(logging.NullHandler()) class AppToDo: """ To-Do List Implementation with Big Tree. - To-Do List has three levels - app name, list name, and item name. - If list name is not given, item will be assigned to a `General` list. *Initializing and Adding Items* >>> from bigtree import AppToDo >>> app = AppToDo("To Do App") >>> app.add_item(item_name="Homework 1", list_name="School") >>> app.add_item(item_name=["Milk", "Bread"], list_name="Groceries", description="Urgent") >>> app.add_item(item_name="Cook") >>> app.show() To Do App ├── School │ └── Homework 1 ├── Groceries │ ├── Milk [description=Urgent] │ └── Bread [description=Urgent] └── General └── Cook *Reorder List and Item* >>> app.prioritize_list(list_name="General") >>> app.show() To Do App ├── General │ └── Cook ├── School │ └── Homework 1 └── Groceries ├── Milk [description=Urgent] └── Bread [description=Urgent] >>> app.prioritize_item(item_name="Bread") >>> app.show() To Do App ├── General │ └── Cook ├── School │ └── Homework 1 └── Groceries ├── Bread [description=Urgent] └── Milk [description=Urgent] *Removing Items* >>> app.remove_item("Homework 1") >>> app.show() To Do App ├── General │ └── Cook └── Groceries ├── Bread [description=Urgent] └── Milk [description=Urgent] *Exporting and Importing List* >>> app.save("list.json") >>> app2 = AppToDo.load("list.json") >>> app2.show() To Do App ├── General │ └── Cook └── Groceries ├── Bread [description=Urgent] └── Milk [description=Urgent] """ def __init__( self, app_name: str = "", ): """Initialize To-Do app Args: app_name (str): name of to-do app, optional """ self._root = Node(app_name) def add_list(self, list_name: str, **kwargs) -> Node: """Add list to app If list is present, return list node, else a new list will be created Args: list_name (str): name of list Returns: (Node) """ list_node = find_children(self._root, list_name) if not list_node: list_node = Node(list_name, parent=self._root, **kwargs) logging.info(f"Created list {list_name}") return list_node def prioritize_list(self, list_name: str): """Prioritize list in app, shift it to be the first list Args: list_name (str): name of list """ list_node = find_children(self._root, list_name) if not list_node: raise ValueError(f"List {list_name} not found") current_children = list(self._root.children) current_children.remove(list_node) current_children.insert(0, list_node) self._root.children = current_children def add_item(self, item_name: Union[str, List[str]], list_name: str = "", **kwargs): """Add items to list Args: item_name (str/List[str]): items to be added list_name (str): list to add items to, optional """ if not isinstance(item_name, str) and not isinstance(item_name, list): raise TypeError("Invalid data type for item") # Get list to add to if list_name: list_node = self.add_list(list_name) else: list_node = self.add_list("General") # Add items to list if isinstance(item_name, str): _ = Node(item_name, parent=list_node, **kwargs) logging.info(f"Created item {item_name}") elif isinstance(item_name, list): for _item in item_name: _ = Node(_item, parent=list_node, **kwargs) logging.info(f"Created items {', '.join(item_name)}") def remove_item(self, item_name: Union[str, List[str]], list_name: str = ""): """Remove items from list Args: item_name (str/List[str]): items to be added list_name (str): list to add items to, optional """ if not isinstance(item_name, str) and not isinstance(item_name, list): raise TypeError("Invalid data type for item") # Check if items can be found items_to_remove = [] parent_to_check = set() if list_name: list_node = find_children(self._root, list_name) if isinstance(item_name, str): item_node = find_children(list_node, item_name) items_to_remove.append(item_node) parent_to_check.add(item_node.parent) elif isinstance(item_name, list): for _item in item_name: item_node = find_children(list_node, _item) items_to_remove.append(item_node) parent_to_check.add(item_node.parent) else: if isinstance(item_name, str): item_node = find_name(self._root, item_name) items_to_remove.append(item_node) parent_to_check.add(item_node.parent) elif isinstance(item_name, list): for _item in item_name: item_node = find_name(self._root, _item) items_to_remove.append(item_node) parent_to_check.add(item_node.parent) # Remove items for item_node in items_to_remove: item_node.parent = None logging.info( f"Removed items {', '.join(item.node_name for item in items_to_remove)}" ) # Remove list if empty for list_node in parent_to_check: if not len(list(list_node.children)): list_node.parent = None logging.info(f"Removed list {list_node.node_name}") def prioritize_item(self, item_name: str): """Prioritize item in list, shift it to be the first item in list Args: item_name (str): name of item """ item_node = find_name(self._root, item_name) if not item_node: raise ValueError(f"Item {item_node} not found") current_parent = item_node.parent current_children = list(current_parent.children) current_children.remove(item_node) current_children.insert(0, item_node) current_parent.children = current_children def show(self, **kwargs): """Print tree to console""" print_tree(self._root, all_attrs=True, **kwargs) @staticmethod def load(json_path: str): """Load To-Do app from json Args: json_path (str): json load path Returns: (Self) """ if not json_path.endswith(".json"): raise ValueError("Path should end with .json") with open(json_path, "r") as fp: app_dict = json.load(fp) _app = AppToDo("dummy") AppToDo.__setattr__(_app, "_root", dict_to_tree(app_dict["root"])) return _app def save(self, json_path: str): """Save To-Do app as json Args: json_path (str): json save path """ if not json_path.endswith(".json"): raise ValueError("Path should end with .json") node_dict = tree_to_dict(self._root, all_attrs=True) app_dict = {"root": node_dict} with open(json_path, "w") as fp: json.dump(app_dict, fp)