From: Jacob Lifshay Date: Sat, 12 Sep 2020 01:29:01 +0000 (-0700) Subject: working on implementing write_budget_markdown X-Git-Url: https://git.libre-soc.org/?p=utils.git;a=commitdiff_plain;h=1408ab54f984e530d147ae60c5d35f7629daa75b;hp=29a149c9ed523d5bf47b0ec6020dbb82892a2a6b working on implementing write_budget_markdown --- diff --git a/.gitignore b/.gitignore index ae15ee2..3336860 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__ *.egg-info /.vscode -*.pyc \ No newline at end of file +*.pyc +/output \ No newline at end of file diff --git a/budget-sync-config.toml b/budget-sync-config.toml index 60ae440..9366f2f 100644 --- a/budget-sync-config.toml +++ b/budget-sync-config.toml @@ -3,22 +3,27 @@ bugzilla_url = "https://bugs.libre-soc.org" [people."Jacob R. Lifshay"] email = "programmerjake@gmail.com" aliases = ["programmerjake", "jacob", "Jacob", "Jacob Lifshay"] +output_markdown_file = "programmerjake.mdwn" [people."Luke Kenneth Casson Leighton"] email = "lkcl@lkcl.net" aliases = ["lkcl", "luke", "Luke", "Luke Leighton"] +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" [people."Vivek Pandya"] email = "vivekvpandya@gmail.com" aliases = ["vivekvpandya", "vivek pandya", "vivek", "Vivek"] +output_markdown_file = "vivek_pandya.mdwn" [people."Florent Kermarrec"] email = "florent@enjoy-digital.fr" aliases = ["florent", "Florent"] +output_markdown_file = "florent_kermarrec.mdwn" [milestones] "NLnet.2019.02" = { canonical_bug_id = 191 } diff --git a/src/budget_sync/budget_graph.py b/src/budget_sync/budget_graph.py index c6b5272..463c3e1 100644 --- a/src/budget_sync/budget_graph.py +++ b/src/budget_sync/budget_graph.py @@ -236,6 +236,11 @@ class Node: if self.milestone_str == "---": self.milestone_str = None + @cached_property + def bug_url(self) -> str: + return f"{self.graph.config.bugzilla_url_stripped}/show_bug.cgi?" \ + f"id={self.bug.id}" + @cached_property def milestone(self) -> Optional[Milestone]: try: diff --git a/src/budget_sync/config.py b/src/budget_sync/config.py index 1bebca1..8a490b5 100644 --- a/src/budget_sync/config.py +++ b/src/budget_sync/config.py @@ -13,10 +13,12 @@ class Person: email: Optional[str] def __init__(self, config: "Config", identifier: str, + output_markdown_file: str, aliases: Optional[Set[str]] = None, email: Optional[str] = None): self.config = config self.identifier = identifier + self.output_markdown_file = output_markdown_file if aliases is None: aliases = set() self.aliases = aliases @@ -35,8 +37,9 @@ class Person: return hash(self.identifier) def __repr__(self): - return f"Person(config=..., identifier={self.identifier!r}, " \ - f"aliases={self.aliases!r}, email={self.email!r})" + return (f"Person(config=..., identifier={self.identifier!r}, " + f"output_markdown_file={self.output_markdown_file!r}, " + f"aliases={self.aliases!r}, email={self.email!r})") class Milestone: @@ -70,6 +73,10 @@ class Config: f"people={self.people!r}, " \ f"milestones={self.milestones!r})" + @cached_property + def bugzilla_url_stripped(self): + return self.bugzilla_url.rstrip('/') + @cached_property def all_names(self) -> Dict[str, Person]: # also checks for any name clashes and raises @@ -124,6 +131,7 @@ class Config: f"person entry for {identifier!r} must be a table") aliases = set() email = None + output_markdown_file = None for k, v in value.items(): assert isinstance(k, str) if k == "aliases": @@ -144,10 +152,20 @@ class Config: f"`email` field in person entry for {identifier!r} " f"must be a string") email = v + elif k == "output_markdown_file": + if not isinstance(v, str): + raise ConfigParseError( + f"`output_markdown_file` field in person entry for " + f"{identifier!r} must be a string") + output_markdown_file = v else: raise ConfigParseError( f"unknown field in person entry for {identifier!r}: `{k}`") + if output_markdown_file is None: + raise ConfigParseError(f"`output_markdown_file` field is missing in " + f"person entry for {identifier!r}") return Person(config=self, identifier=identifier, + output_markdown_file=output_markdown_file, aliases=aliases, email=email) def _parse_people(self, people: Any): diff --git a/src/budget_sync/main.py b/src/budget_sync/main.py index d762f08..2726dbf 100644 --- a/src/budget_sync/main.py +++ b/src/budget_sync/main.py @@ -1,12 +1,11 @@ from bugzilla import Bugzilla import logging import argparse +from pathlib import Path from budget_sync.util import all_bugs from budget_sync.config import Config, ConfigParseError from budget_sync.budget_graph import BudgetGraph, BudgetGraphBaseError - - -BUGZILLA_URL = "https://bugs.libre-soc.org" +from budget_sync.write_budget_markdown import write_budget_markdown def main(): @@ -17,6 +16,11 @@ def main(): "-c", "--config", type=argparse.FileType('r'), required=True, help="The path to the configuration TOML file", dest="config", metavar="") + parser.add_argument( + "-o", "--output-dir", type=Path, default=None, + help="The path to the output directory, will be created if it " + "doesn't exist", + dest="output_dir", metavar="") args = parser.parse_args() try: with args.config as config_file: @@ -30,6 +34,8 @@ def main(): budget_graph = BudgetGraph(all_bugs(bz), config) for error in budget_graph.get_errors(): logging.error("%s", error) + if args.output_dir is not None: + write_budget_markdown(budget_graph, args.output_dir) if __name__ == "__main__": diff --git a/src/budget_sync/test/test_budget_graph.py b/src/budget_sync/test/test_budget_graph.py index 21786a1..a8857cb 100644 --- a/src/budget_sync/test/test_budget_graph.py +++ b/src/budget_sync/test/test_budget_graph.py @@ -127,12 +127,15 @@ EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2, EXAMPLE_CONFIG = Config.from_str( """ - bugzilla_url = "" + bugzilla_url = "https://bugzilla.example.com/" [people."person1"] aliases = ["person1_alias1", "alias1"] + output_markdown_file = "person1.mdwn" [people."person2"] aliases = ["person1_alias2", "alias2", "person 2"] + output_markdown_file = "person2.mdwn" [people."person3"] + output_markdown_file = "person3.mdwn" [milestones] "milestone 1" = { canonical_bug_id = 1 } "milestone 2" = { canonical_bug_id = 2 } @@ -176,6 +179,8 @@ class TestBudgetGraph(unittest.TestCase): self.assertIs(node.root, node) self.assertIsNone(node.parent_id) self.assertEqual(node.immediate_children, set()) + self.assertEqual(node.bug_url, + "https://bugzilla.example.com/show_bug.cgi?id=1") self.assertEqual(node.budget_excluding_subtasks, Money(cents=0)) self.assertEqual(node.budget_including_subtasks, Money(cents=0)) self.assertIsNone(node.milestone) @@ -212,6 +217,8 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(node1.budget_excluding_subtasks, Money(cents=1000)) self.assertEqual(node1.budget_including_subtasks, Money(cents=2000)) self.assertEqual(node1.milestone_str, "milestone 1") + self.assertEqual(node1.bug_url, + "https://bugzilla.example.com/show_bug.cgi?id=1") self.assertEqual(list(node1.children()), [node2]) self.assertEqual(list(node1.children_breadth_first()), [node2]) self.assertEqual(node1.payments, {}) @@ -225,6 +232,8 @@ class TestBudgetGraph(unittest.TestCase): self.assertEqual(node2.budget_including_subtasks, Money(cents=1000)) self.assertEqual(node2.milestone_str, "milestone 1") self.assertEqual(node2.payments, {}) + self.assertEqual(node2.bug_url, + "https://bugzilla.example.com/show_bug.cgi?id=2") def test_children(self): bg = BudgetGraph([ diff --git a/src/budget_sync/test/test_config.py b/src/budget_sync/test/test_config.py index 3b203a1..6a40c18 100644 --- a/src/budget_sync/test/test_config.py +++ b/src/budget_sync/test/test_config.py @@ -78,25 +78,45 @@ class TestConfig(unittest.TestCase): [people] """, "`milestones` table is missing") + check_error( + """ + bugzilla_url = "" + [people."person1"] + """, + "`output_markdown_file` field is missing in person entry for " + "'person1'") + check_error( + """ + bugzilla_url = "" + [people."person1"] + output_markdown_file = 1 + """, + "`output_markdown_file` field in person entry for 'person1' must " + "be a string") check( """ bugzilla_url = "" [milestones] [people."person1"] aliases = ["a"] + output_markdown_file = "person1.mdwn" [people."person2"] aliases = ["b"] + output_markdown_file = "person2.mdwn" """, "Config(bugzilla_url='', people={" "'person1': Person(config=..., identifier='person1', " + "output_markdown_file='person1.mdwn', " "aliases={'a'}, email=None), " "'person2': Person(config=..., identifier='person2', " + "output_markdown_file='person2.mdwn', " "aliases={'b'}, email=None)}, milestones={})") check_error( """ bugzilla_url = "" [people."person1"] email = 123 + output_markdown_file = "person1.mdwn" """, "`email` field in person entry for 'person1' must be a string") check( @@ -112,15 +132,18 @@ class TestConfig(unittest.TestCase): [milestones] [people."person1"] email = "email@example.com" + output_markdown_file = "person1.mdwn" """, "Config(bugzilla_url='', people={" "'person1': Person(config=..., identifier='person1', " + "output_markdown_file='person1.mdwn', " "aliases=set(), email='email@example.com')}, milestones={})") check_error( """ bugzilla_url = "" [people."person1"] blah = 123 + output_markdown_file = "person1.mdwn" """, "unknown field in person entry for 'person1': `blah`") check_error( @@ -128,8 +151,10 @@ class TestConfig(unittest.TestCase): bugzilla_url = "" [milestones] [people."person1"] + output_markdown_file = "person1.mdwn" [people."person2"] aliases = ["person1"] + output_markdown_file = "person2.mdwn" """, "alias is not allowed to be the same as any person's identifier: " "in person entry for 'person2': 'person1' is also the identifier " @@ -139,9 +164,11 @@ class TestConfig(unittest.TestCase): bugzilla_url = "" [milestones] [people."person1"] + output_markdown_file = "person1.mdwn" aliases = ["a"] [people."person2"] aliases = ["a"] + output_markdown_file = "person2.mdwn" """, "alias is not allowed to be the same as another person's alias: " "in person entry for 'person2': 'a' is also an alias for person " @@ -205,8 +232,10 @@ class TestConfig(unittest.TestCase): [milestones] [people."person1"] aliases = ["person1_alias1", "alias1"] + output_markdown_file = "person1.mdwn" [people."person2"] aliases = ["person2_alias2", "alias2"] + output_markdown_file = "person2.mdwn" """) person1 = config.people['person1'] person2 = config.people['person2'] @@ -237,6 +266,35 @@ class TestConfig(unittest.TestCase): 2: milestone2, }) + def test_bugzilla_url_stripped(self): + c = Config.from_str( + """ + bugzilla_url = "https://bugzilla.example.com/prefix" + [people] + [milestones] + """ + ) + self.assertEqual(c.bugzilla_url_stripped, + "https://bugzilla.example.com/prefix") + c = Config.from_str( + """ + bugzilla_url = "https://bugzilla.example.com/prefix/" + [people] + [milestones] + """ + ) + self.assertEqual(c.bugzilla_url_stripped, + "https://bugzilla.example.com/prefix") + c = Config.from_str( + """ + bugzilla_url = "https://bugzilla.example.com/" + [people] + [milestones] + """ + ) + self.assertEqual(c.bugzilla_url_stripped, + "https://bugzilla.example.com") + def test_from_file(self): def load(text): with io.StringIO(text) as file: @@ -257,11 +315,13 @@ class TestConfig(unittest.TestCase): [people."person1"] email = "person1@example.com" aliases = ["alias1"] + output_markdown_file = "person1.mdwn" [milestones] "Milestone 1" = { canonical_bug_id = 123 } """)), "Config(bugzilla_url='https://bugzilla.example.com/', " "people={'person1': Person(config=..., identifier='person1', " + "output_markdown_file='person1.mdwn', " "aliases={'alias1'}, email='person1@example.com')}, " "milestones={'Milestone 1': Milestone(config=..., " "identifier='Milestone 1', canonical_bug_id=123)})") diff --git a/src/budget_sync/write_budget_markdown.py b/src/budget_sync/write_budget_markdown.py new file mode 100644 index 0000000..efe3598 --- /dev/null +++ b/src/budget_sync/write_budget_markdown.py @@ -0,0 +1,48 @@ +from pathlib import Path +from typing import Dict, List, Any +from io import StringIO +from budget_sync.budget_graph import BudgetGraph, Node, Payment, PayeeState +from budget_sync.config import Person, Milestone, Config + + +def _markdown_escape_char(char: str) -> str: + if char == "<": + return "<" + if char == "&": + return "&" + if char in "\\`*_{}[]()#+-.!": + return "\\" + char + return char + + +def _markdown_escape(v: Any) -> str: + return "".join([char for char in str(v)]) + + +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: + 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}: " + f"{summary}]({payment.node.bug_url})", + file=buffer) + return 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")