6b37d03bf2569d56ce84bc5ac36ee528d063b2b6
[utils.git] / src / budget_sync / main.py
1 import os
2 import re
3 import sys
4 from typing import Optional
5 from budget_sync.ordered_set import OrderedSet
6 from budget_sync.write_budget_csv import write_budget_csv
7 from bugzilla import Bugzilla
8 import logging
9 import argparse
10 from pathlib import Path
11 from budget_sync.util import all_bugs
12 from budget_sync.config import Config, ConfigParseError
13 from budget_sync.budget_graph import BudgetGraph, PaymentSummary
14 from budget_sync.write_budget_markdown import (write_budget_markdown,
15 markdown_for_person)
16
17
18 def main():
19 parser = argparse.ArgumentParser(
20 description="Check for errors in "
21 "Libre-SOC's style of budget tracking in Bugzilla.")
22 parser.add_argument(
23 "-c", "--config", type=argparse.FileType('r'),
24 required=True, help="The path to the configuration TOML file",
25 dest="config", metavar="<path/to/budget-sync-config.toml>")
26 parser.add_argument(
27 "-o", "--output-dir", type=Path, default=None,
28 help="The path to the output directory, will be created if it "
29 "doesn't exist",
30 dest="output_dir", metavar="<path/to/output/dir>")
31 parser.add_argument('--subset',
32 help="write the output for this subset of bugs",
33 metavar="<bug-id>,<bug-id>,...")
34 parser.add_argument('--subset-person',
35 help="write the output for this person",
36 dest="person")
37 parser.add_argument('--username',
38 help="Log in with this Bugzilla username")
39 parser.add_argument('--password',
40 help="Log in with this Bugzilla password")
41 args = parser.parse_args()
42 try:
43 with args.config as config_file:
44 config = Config.from_file(config_file)
45 except (IOError, ConfigParseError) as e:
46 logging.error("Failed to parse config file: %s", e)
47 return
48 print ("```") # for using the output as markdown
49 logging.info("Using Bugzilla instance at %s", config.bugzilla_url)
50 bz = Bugzilla(config.bugzilla_url)
51 if args.username:
52 logging.debug("logging in...")
53 bz.interactive_login(args.username, args.password)
54 logging.debug("Connected to Bugzilla")
55 budget_graph = BudgetGraph(all_bugs(bz), config)
56 for error in budget_graph.get_errors():
57 logging.error("%s", error)
58 if args.person or args.subset:
59 if not args.person:
60 logging.fatal("must use --subset-person with --subset option")
61 sys.exit(1)
62 print_markdown_for_person(budget_graph, config,
63 args.person, args.subset)
64 return
65 if args.output_dir is not None:
66 write_budget_markdown(budget_graph, args.output_dir)
67 write_budget_csv(budget_graph, args.output_dir)
68 summarize_milestones(budget_graph)
69
70
71 def print_markdown_for_person(budget_graph: BudgetGraph, config: Config,
72 person_str: str, subset_str: Optional[str]):
73 person = config.all_names.get(person_str)
74 if person is None:
75 logging.fatal("--subset-person: unknown person: %s", person_str)
76 sys.exit(1)
77 nodes_subset = None
78 if subset_str:
79 nodes_subset = OrderedSet()
80 for bug_id in re.split(r"[\s,]+", subset_str):
81 try:
82 node = budget_graph.nodes[int(bug_id)]
83 except (ValueError, KeyError):
84 logging.fatal("--subset: unknown bug: %s", bug_id)
85 sys.exit(1)
86 nodes_subset.add(node)
87 print(markdown_for_person(budget_graph, person, nodes_subset))
88
89
90 def print_budget_then_children(indent, nodes, bug_id):
91 """recursive indented printout of budgets
92 """
93
94 bug = nodes[bug_id]
95 print("bug #%5d %s budget %6s excltasks %6s s %s p %s" %
96 (bug.bug.id, ' | ' * indent,
97 str(bug.fixed_budget_including_subtasks),
98 str(bug.fixed_budget_excluding_subtasks),
99 str(bug.submitted_including_subtasks),
100 str(bug.paid_including_subtasks)))
101 # print(repr(bug))
102
103 for child in bug.immediate_children:
104 if (str(child.budget_including_subtasks) == "0" and
105 str(child.budget_excluding_subtasks) == "0"):
106 continue
107 print_budget_then_children(indent+1, nodes, child.bug.id)
108
109
110 def summarize_milestones(budget_graph: BudgetGraph):
111 for milestone, payments in budget_graph.milestone_payments.items():
112 summary = PaymentSummary(payments)
113 print(f"{milestone.identifier}")
114 print(f"\t{summary.total} submitted: "
115 f"{summary.total_submitted} paid: {summary.total_paid}")
116 not_submitted = summary.get_not_submitted()
117 if not_submitted:
118 print("not submitted", not_submitted)
119
120 # and one to display people
121 for person in budget_graph.milestone_people[milestone]:
122 print(f"\t%-30s - %s" % (person.identifier, person.full_name))
123 print()
124
125 # now do trees
126 for milestone, payments in budget_graph.milestone_payments.items():
127 print("%s %d" % (milestone.identifier, milestone.canonical_bug_id))
128 print_budget_then_children(0, budget_graph.nodes,
129 milestone.canonical_bug_id)
130 print()
131
132 print ("```") # for using the output as markdown
133
134
135 if __name__ == "__main__":
136 main()