From 8a801cccee28c8060cef9690355a3f35c124384c Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Thu, 17 Sep 2020 23:26:03 -0700 Subject: [PATCH] generating output markdown files works --- budget-sync-config.toml | 67 +++++++- src/budget_sync/budget_graph.py | 7 + .../test/test_write_budget_markdown.py | 19 +- src/budget_sync/write_budget_markdown.py | 162 +++++++++++++++--- 4 files changed, 230 insertions(+), 25 deletions(-) diff --git a/budget-sync-config.toml b/budget-sync-config.toml index 9366f2f..fce36e3 100644 --- a/budget-sync-config.toml +++ b/budget-sync-config.toml @@ -13,7 +13,7 @@ output_markdown_file = "lkcl.mdwn" [people."Samuel A. Falvo II"] email = "kc5tja@arrl.net" aliases = ["kc5tja", "samuel", "Samuel", "Samuel Falvo II", "sam.falvo"] -output_markdown_file = "samuel_falvo_ii.mdwn" +output_markdown_file = "Samuel_A_Falvo_II.mdwn" [people."Vivek Pandya"] email = "vivekvpandya@gmail.com" @@ -25,6 +25,71 @@ email = "florent@enjoy-digital.fr" aliases = ["florent", "Florent"] output_markdown_file = "florent_kermarrec.mdwn" +[people."Michael Nolan"] +email = "mtnolan2640@gmail.com" +aliases = ["michael", "Michael", "mtnolan", "mtnolan2640"] +output_markdown_file = "michael_nolan.mdwn" + +[people."Alain D D Williams"] +email = "addw@phcomp.co.uk" +aliases = ["alain", "Alain", "Alain Williams", "addw"] +output_markdown_file = "addw.mdwn" + +[people."Jock Tanner"] +email = "tanner.of.kha@gmail.com" +aliases = [] +output_markdown_file = "jock_tanner.mdwn" + +[people."R Veera Kumar"] +email = "vklr@vkten.in" +aliases = ["Veera", "veera", "Veera Kumar"] +output_markdown_file = "veera.mdwn" + +[people."Jean-Paul Chaput"] +email = "Jean-Paul.Chaput@lip6.fr" +aliases = ["Jean Paul Chaput", "Jean-Paul", "jean-paul", "jean paul"] +output_markdown_file = "jean-paul_chaput.mdwn" + +[people."Staf Verhaegen"] +email = "staf@fibraservi.eu" +aliases = ["Staf", "staf"] +output_markdown_file = "staf_verhaegen.mdwn" + +[people."Lauri Kasanen"] +email = "cand@gmx.com" +aliases = ["Lauri", "lauri"] +output_markdown_file = "lauri_kasanen.mdwn" + +[people."Yehowshua Immanuel"] +email = "yimmanuel3@gatech.edu" +aliases = ["Yehowshua", "yehowshua"] +output_markdown_file = "yehowshua_immanuel.mdwn" + +[people."whitequark"] +email = "whitequark@whitequark.org" +aliases = [] +output_markdown_file = "whitequark.mdwn" + +[people."Tobias Platen"] +email = "libre-soc@platen-software.de" +aliases = ["Tobias", "tobias", "tplaten"] +output_markdown_file = "tplaten.mdwn" + +[people."Cole Poirier"] +email = "colepoirier@gmail.com" +aliases = ["Cole", "cole"] +output_markdown_file = "cole.mdwn" + +[people."Aleksandar Kostovic"] +email = "alexandar.kostovic@gmail.com" +aliases = ["alexandar", "aleksandar"] +output_markdown_file = "aleksandar_kostovic.mdwn" + +[people."Cesar Strauss"] +email = "cestrauss@gmail.com" +aliases = ["Cesar", "cesar", "cestrauss"] +output_markdown_file = "cesar_strauss.mdwn" + [milestones] "NLnet.2019.02" = { canonical_bug_id = 191 } "NLnet.2019.10.Cells" = { canonical_bug_id = 153 } diff --git a/src/budget_sync/budget_graph.py b/src/budget_sync/budget_graph.py index 074447d..dccf4e2 100644 --- a/src/budget_sync/budget_graph.py +++ b/src/budget_sync/budget_graph.py @@ -717,6 +717,13 @@ class BudgetGraph: errors.append(e) return errors + @cached_property + def assigned_nodes(self) -> Dict[Person, List[Node]]: + retval = {person: [] for person in self.config.people.values()} + for node in self.nodes.values(): + retval[node.assignee].append(node) + return retval + @cached_property def payments(self) -> Dict[Person, Dict[Milestone, List[Payment]]]: retval = {} diff --git a/src/budget_sync/test/test_write_budget_markdown.py b/src/budget_sync/test/test_write_budget_markdown.py index 1691edc..b035a39 100644 --- a/src/budget_sync/test/test_write_budget_markdown.py +++ b/src/budget_sync/test/test_write_budget_markdown.py @@ -4,10 +4,22 @@ from budget_sync.test.mock_bug import MockBug from budget_sync.test.mock_path import MockPath, DIR from budget_sync.test.test_mock_path import make_filesystem_and_report_if_error from budget_sync.budget_graph import BudgetGraph -from budget_sync.write_budget_markdown import write_budget_markdown +from budget_sync.write_budget_markdown import ( + write_budget_markdown, DisplayStatus, markdown_escape) +from budget_sync.util import BugStatus class TestWriteBudgetMarkdown(unittest.TestCase): + maxDiff = None + + def test_display_status(self): + for status in BugStatus: + DisplayStatus.from_status(status) + + def test_markdown_escape(self): + self.assertEqual(markdown_escape("abc * def_k < &k"), + r"abc \* def\_k < &k") + def test(self): config = Config.from_str( """ @@ -37,10 +49,13 @@ class TestWriteBudgetMarkdown(unittest.TestCase): "/": DIR, "/output_dir": DIR, '/output_dir/person1.mdwn': b'\n# person1\n\n# Status Tracking\n', + b'budget-sync -->\n# person1\n\n# Status Tracking\n## Not yet ' + b'started\n* [Bug #1](https://bugzilla.example.com/show_bug.c' + b'gi?id=1): \n', '/output_dir/person2.mdwn': b'\n# person2\n\n# Status Tracking\n', }, filesystem.files) + # TODO: add more test cases if __name__ == "__main__": diff --git a/src/budget_sync/write_budget_markdown.py b/src/budget_sync/write_budget_markdown.py index 6780a10..aefc04a 100644 --- a/src/budget_sync/write_budget_markdown.py +++ b/src/budget_sync/write_budget_markdown.py @@ -1,8 +1,10 @@ from pathlib import Path -from typing import Dict, List, Any +from typing import Dict, List, Any, Optional from io import StringIO +import enum from budget_sync.budget_graph import BudgetGraph, Node, Payment, PayeeState from budget_sync.config import Person, Milestone, Config +from budget_sync.util import BugStatus def _markdown_escape_char(char: str) -> str: @@ -15,34 +17,150 @@ def _markdown_escape_char(char: str) -> str: return char -def _markdown_escape(v: Any) -> str: - return "".join([char for char in str(v)]) +def markdown_escape(v: Any) -> str: + return "".join([_markdown_escape_char(char) for char in str(v)]) + + +class DisplayStatus(enum.Enum): + Hidden = "Hidden" + NotYetStarted = "Not yet started" + InProgress = "Currently working on" + Completed = "Completed but not yet added to payees list" + + @staticmethod + def from_status(status: BugStatus) -> "DisplayStatus": + return _DISPLAY_STATUS_MAP[status] + + +_DISPLAY_STATUS_MAP = { + BugStatus.UNCONFIRMED: DisplayStatus.Hidden, + BugStatus.CONFIRMED: DisplayStatus.NotYetStarted, + BugStatus.IN_PROGRESS: DisplayStatus.InProgress, + BugStatus.DEFERRED: DisplayStatus.Hidden, + BugStatus.RESOLVED: DisplayStatus.Completed, + BugStatus.VERIFIED: DisplayStatus.Completed, + BugStatus.PAYMENTPENDING: DisplayStatus.Completed, +} + + +class MarkdownWriter: + last_headers: List[str] + + def __init__(self): + self.buffer = StringIO() + self.last_headers = [] + + def write_headers(self, headers: List[str]): + if headers == self.last_headers: + return + for i in range(len(headers)): + if i >= len(self.last_headers): + print(headers[i], file=self.buffer) + self.last_headers.append(headers[i]) + elif headers[i] != self.last_headers[i]: + del self.last_headers[i:] + print(headers[i], file=self.buffer) + self.last_headers.append(headers[i]) + if len(self.last_headers) > len(headers): + raise ValueError("tried to go from deeper header scope stack to " + "ancestor scope without starting a new header, " + "which is not supported by markdown", + self.last_headers, headers) + assert headers == self.last_headers + + def write_node(self, + headers: List[str], + node: Node, + payment: Optional[Payment]): + self.write_headers(headers) + summary = markdown_escape(node.bug.summary) + print(f"* [Bug #{node.bug.id}]({node.bug_url}): {summary}", + file=self.buffer) + if payment is not None: + if node.fixed_budget_excluding_subtasks \ + != node.budget_excluding_subtasks: + total = (f"€{node.fixed_budget_excluding_subtasks} (" + f"total is fixed from amount appearing in bug report," + f" which is €{node.budget_excluding_subtasks})") + else: + total = f"€{node.fixed_budget_excluding_subtasks}" + if payment.amount != node.fixed_budget_excluding_subtasks \ + or payment.amount != node.budget_excluding_subtasks: + print(f" * €{payment.amount} out of total of {total}", + file=self.buffer) + else: + print(f" * €{payment.amount} which is the total amount", + file=self.buffer) def _markdown_for_person(person: Person, - payments_dict: Dict[Milestone, List[Payment]]) -> str: - buffer = StringIO() - print(f"", file=buffer) - print(f"# {person.identifier}", file=buffer) - print(file=buffer) - print(f"# Status Tracking", file=buffer) - for milestone, payments_list in payments_dict.items(): - if len(payments_list) == 0: + payments_dict: Dict[Milestone, List[Payment]], + assigned_nodes: List[Node]) -> str: + writer = MarkdownWriter() + print(f"", file=writer.buffer) + writer.write_headers([f"# {person.identifier}"]) + print(file=writer.buffer) + status_tracking_header = "# Status Tracking" + writer.write_headers([status_tracking_header]) + displayed_nodes_dict: Dict[DisplayStatus, List[Node]] + displayed_nodes_dict = {i: [] for i in DisplayStatus} + for node in assigned_nodes: + display_status = DisplayStatus.from_status(node.status) + displayed_nodes_dict[display_status].append(node) + + def write_display_status_chunk(display_status: DisplayStatus): + display_status_header = f"## {display_status.value}" + for node in displayed_nodes_dict[display_status]: + if display_status == DisplayStatus.Completed: + payment_found = False + for payment in node.payments.values(): + if payment.payee == person: + payment_found = True + break + if payment_found: + continue + if len(node.payments) == 0 \ + and node.budget_excluding_subtasks == 0 \ + and node.budget_including_subtasks == 0: + continue + writer.write_node( + headers=[status_tracking_header, display_status_header], + node=node, payment=None) + + for display_status in DisplayStatus: + if display_status == DisplayStatus.Hidden \ + or display_status == DisplayStatus.NotYetStarted: continue - print(f"## {milestone.identifier}", file=buffer) - for payment in payments_list: - # TODO: finish - summary = _markdown_escape(payment.node.bug.summary) - print(f"* [Bug #{payment.node.bug.id}]({payment.node.bug_url}): " - f"{summary}", - file=buffer) - return buffer.getvalue() + write_display_status_chunk(display_status) + + for payee_state in PayeeState: + if payee_state == PayeeState.NotYetSubmitted: + display_status_header = f"## Completed but not yet paid" + elif payee_state == PayeeState.Submitted: + display_status_header = f"## Submitted to NLNet but not yet paid" + else: + assert payee_state == PayeeState.Paid + display_status_header = f"## Paid by NLNet" + for milestone, payments_list in payments_dict.items(): + milestone_header = f"### {milestone.identifier}" + for payment in payments_list: + if payment.state == payee_state: + writer.write_node(headers=[status_tracking_header, + display_status_header, + milestone_header], + node=payment.node, payment=payment) + + write_display_status_chunk(DisplayStatus.NotYetStarted) + + return writer.buffer.getvalue() def write_budget_markdown(budget_graph: BudgetGraph, output_dir: Path): output_dir.mkdir(parents=True, exist_ok=True) for person, payments_dict in budget_graph.payments.items(): - output_dir.joinpath(person.output_markdown_file) \ - .write_text(_markdown_for_person(person, payments_dict), - encoding="utf-8") + markdown = _markdown_for_person(person, + payments_dict, + budget_graph.assigned_nodes[person]) + output_file = output_dir.joinpath(person.output_markdown_file) + output_file.write_text(markdown, encoding="utf-8") -- 2.30.2