From bdb37bdbfdc46a64631c0fb97d0d86540076165e Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Wed, 15 May 2024 03:42:18 +0200 Subject: [PATCH] Simplify Todo steps tree calculation/display. --- plomtask/http.py | 40 +++++----- plomtask/todos.py | 105 ++++----------------------- scripts/pre-commit | 3 +- templates/day.html | 177 +++++++++++++++++++++++++++++++++++---------- tests/todos.py | 73 +++---------------- 5 files changed, 181 insertions(+), 217 deletions(-) diff --git a/plomtask/http.py b/plomtask/http.py index adac957..a1f85fd 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -1,5 +1,5 @@ """Web server stuff.""" -from typing import Any, NamedTuple +from typing import Any from http.server import BaseHTTPRequestHandler from http.server import HTTPServer from urllib.parse import urlparse, parse_qs @@ -118,29 +118,25 @@ class TaskHandler(BaseHTTPRequestHandler): def do_GET_day(self) -> dict[str, object]: """Show single Day of ?date=.""" - - class ConditionListing(NamedTuple): - """Listing of Condition augmented with its enablers, disablers.""" - condition: Condition - enablers: list[Todo] - disablers: list[Todo] - date = self.params.get_str('date', todays_date()) - top_todos = [t for t in Todo.by_date(self.conn, date) if not t.parents] - todo_trees = [t.get_undone_steps_tree() for t in top_todos] - done_trees = [] - for t in top_todos: - done_trees += t.get_done_steps_tree() - condition_listings: list[ConditionListing] = [] - for cond in Condition.all(self.conn): - enablers = Todo.enablers_for_at(self.conn, cond, date) - disablers = Todo.disablers_for_at(self.conn, cond, date) - condition_listings += [ConditionListing(cond, enablers, disablers)] + todays_todos = Todo.by_date(self.conn, date) + conditions_present = [] + enablers_for = {} + for todo in todays_todos: + for condition in todo.conditions: + if condition not in conditions_present: + conditions_present += [condition] + enablers_for[condition.id_] = [p for p in + Process.all(self.conn) + if condition in p.enables] + seen_todos: set[int] = set() + top_nodes = [t.get_step_tree(seen_todos) + for t in todays_todos if not t.parents] return {'day': Day.by_id(self.conn, date, create=True), - 'todo_trees': todo_trees, - 'done_trees': done_trees, - 'processes': Process.all(self.conn), - 'condition_listings': condition_listings} + 'top_nodes': top_nodes, + 'enablers_for': enablers_for, + 'conditions_present': conditions_present, + 'processes': Process.all(self.conn)} def do_GET_todo(self) -> dict[str, object]: """Show single Todo of ?id=.""" diff --git a/plomtask/todos.py b/plomtask/todos.py index fcb8617..e42c484 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -11,13 +11,11 @@ from plomtask.exceptions import (NotFoundException, BadFormatException, @dataclass -class TodoStepsNode: +class TodoNode: """Collects what's useful to know for Todo/Condition tree display.""" - item: Todo | Condition - is_todo: bool - children: list[TodoStepsNode] + todo: Todo seen: bool - hide: bool + children: list[TodoNode] class Todo(BaseModel[int], ConditionsRelations): @@ -85,31 +83,6 @@ class Todo(BaseModel[int], ConditionsRelations): todos += [cls.by_id(db_conn, id_)] return todos - @staticmethod - def _x_ablers_for_at(db_conn: DatabaseConnection, name: str, - cond: Condition, date: str) -> list[Todo]: - """Collect all Todos of day that [name] condition.""" - assert isinstance(cond.id_, int) - x_ablers = [] - table = f'todo_{name}' - for id_ in db_conn.column_where(table, 'todo', 'condition', cond.id_): - todo = Todo.by_id(db_conn, id_) - if todo.date == date: - x_ablers += [todo] - return x_ablers - - @classmethod - def enablers_for_at(cls, db_conn: DatabaseConnection, - condition: Condition, date: str) -> list[Todo]: - """Collect all Todos of day that enable condition.""" - return cls._x_ablers_for_at(db_conn, 'enables', condition, date) - - @classmethod - def disablers_for_at(cls, db_conn: DatabaseConnection, - condition: Condition, date: str) -> list[Todo]: - """Collect all Todos of day that disable condition.""" - return cls._x_ablers_for_at(db_conn, 'disables', condition, date) - @property def is_doable(self) -> bool: """Decide whether .is_done settable based on children, Conditions.""" @@ -123,7 +96,7 @@ class Todo(BaseModel[int], ConditionsRelations): @property def process_id(self) -> int | str | None: - """Return ID of tasked Process.""" + """Needed for super().save to save Processes as attributes.""" return self.process.id_ @property @@ -169,69 +142,19 @@ class Todo(BaseModel[int], ConditionsRelations): todo.save(db_conn) self.add_child(todo) - def get_step_tree(self, seen_todos: set[int], - seen_conditions: set[int]) -> TodoStepsNode: - """Return tree of depended-on Todos and Conditions.""" + def get_step_tree(self, seen_todos: set[int]) -> TodoNode: + """Return tree of depended-on Todos.""" - def make_node(step: Todo | Condition) -> TodoStepsNode: - assert isinstance(step.id_, int) - is_todo = isinstance(step, Todo) + def make_node(todo: Todo) -> TodoNode: children = [] - if is_todo: - assert isinstance(step, Todo) - seen = step.id_ in seen_todos - seen_todos.add(step.id_) - potentially_enabled = set() - for child in step.children: - for condition in child.enables: - potentially_enabled.add(condition.id_) - children += [make_node(child)] - for condition in [c for c in step.conditions - if (not c.is_active) - and (c.id_ not in potentially_enabled)]: - children += [make_node(condition)] - else: - seen = step.id_ in seen_conditions - seen_conditions.add(step.id_) - return TodoStepsNode(step, is_todo, children, seen, False) - - node = make_node(self) - return node - - def get_undone_steps_tree(self) -> TodoStepsNode: - """Return tree of depended-on undone Todos and Conditions.""" - - def walk_tree(node: TodoStepsNode) -> None: - if isinstance(node.item, Todo) and node.item.is_done: - node.hide = True - for child in node.children: - walk_tree(child) - - seen_todos: set[int] = set() - seen_conditions: set[int] = set() - step_tree = self.get_step_tree(seen_todos, seen_conditions) - walk_tree(step_tree) - return step_tree - - def get_done_steps_tree(self) -> list[TodoStepsNode]: - """Return tree of depended-on done Todos.""" - - def make_nodes(node: TodoStepsNode) -> list[TodoStepsNode]: - children: list[TodoStepsNode] = [] - if not isinstance(node.item, Todo): - return children - for child in node.children: - children += make_nodes(child) - if node.item.is_done: - node.children = children - return [node] - return children + seen = todo.id_ in seen_todos + assert isinstance(todo.id_, int) + seen_todos.add(todo.id_) + for child in todo.children: + children += [make_node(child)] + return TodoNode(todo, seen, children) - seen_todos: set[int] = set() - seen_conditions: set[int] = set() - step_tree = self.get_step_tree(seen_todos, seen_conditions) - nodes = make_nodes(step_tree) - return nodes + return make_node(self) def add_child(self, child: Todo) -> None: """Add child to self.children, avoid recursion, update parenthoods.""" diff --git a/scripts/pre-commit b/scripts/pre-commit index 2aaccb0..6f84c41 100755 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -1,7 +1,6 @@ #!/bin/sh set -e -# for dir in $(echo '.' 'plomtask' 'tests'); do -for dir in $(echo 'tests'); do +for dir in $(echo '.' 'plomtask' 'tests'); do echo "Running mypy on ${dir}/ …." python3 -m mypy --strict ${dir}/*.py echo "Running flake8 on ${dir}/ …" diff --git a/templates/day.html b/templates/day.html index efa1c9b..f13eb5c 100644 --- a/templates/day.html +++ b/templates/day.html @@ -1,50 +1,101 @@ {% extends 'base.html' %} -{% macro show_node(node, indent) %} -{% if node.is_todo %} -{% for i in range(indent) %}  {% endfor %} + -{% if node.seen %}({% else %}{% endif %}{{node.item.process.title.newest|e}}{% if node.seen %}){% else %}{% endif %} -{% else %} -{% for i in range(indent) %} {% endfor %} + -{% if node.seen %}({% else %}{% endif %}{{node.item.title.newest|e}}{% if node.seen %}){% else %}{% endif %} -{% endif %} -{% endmacro %} +{% block css %} +td, th, tr, table { + padding: 0; + margin: 0; +} +th { + border: 1px solid black; +} +td.min_width { + min-width: 1em; +} +td.cond_line_0 { + background-color: #ffbbbb; +} +td.cond_line_1 { + background-color: #bbffbb; +} +td.cond_line_2 { + background-color: #bbbbff; +} +td.todo_line { + border-bottom: 1px solid #bbbbbb; +} +{% endblock %} -{% macro undone_with_children(node, indent) %} -{% if not node.hide %} + + +{% macro show_node_undone(node, indent) %} +{% if not node.todo.is_done %} - -{% if node.is_todo %} - -{% endif %} - - -{{ show_node(node, indent) }} + +{% for condition in conditions_present %} +{% if condition in node.todo.conditions %}{% if not condition.is_active %}O{% endif %}{% endif %} +{% endfor %} + +-> + + +{% for i in range(indent) %}  {% endfor %} + +{% if node.seen %}({% endif %}{{node.todo.process.title.newest|e}}{% if node.seen %}){% endif %} +-> + +{% for condition in conditions_present|reverse %} +{% if condition in node.todo.enables %}+{% elif condition in node.todo.disables %}!{% endif %} +{% endfor %} + + + {% endif %} + +{% if not node.seen %} {% for child in node.children %} -{{ undone_with_children(child, indent+1) }} +{{ show_node_undone(child, indent+1) }} {% endfor %} +{% endif %} + {% endmacro %} -{% macro done_with_children(node, indent) %} -{% if not node.hide %} + +{% macro show_node_done(node, indent, path) %} +{% if node.todo.is_done %} + +{% if path|length > 0 and not path[-1].todo.is_done %} -{{ show_node(node, indent) }} +({% for path_node in path %}{{path_node.todo.process.title.newest|e}} <- {% endfor %}) + + + +  + +{% else %} + +{% for i in range(indent) %}  {% endfor %} + +{% endif %} +{% if node.seen %}({% endif %}{{node.todo.process.title.newest|e}}{% if node.seen %}){% endif %} + + + {% endif %} +{% if not node.seen %} {% for child in node.children %} -{{ done_with_children(child, indent+1) }} +{{ show_node_done(child, indent+1, path + [node]) }} {% endfor %} +{% endif %} + {% endmacro %} + {% block content %}

{{day.date}} / {{day.weekday}}

@@ -59,30 +110,78 @@ add todo: {% endfor %} -

conditions

-