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
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
,
19 parser
= argparse
.ArgumentParser(
20 description
="Check for errors in "
21 "Libre-SOC's style of budget tracking in Bugzilla.")
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>")
27 "-o", "--output-dir", type=Path
, default
=None,
28 help="The path to the output directory, will be created if it "
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",
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()
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
)
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
)
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
:
60 logging
.fatal("must use --subset-person with --subset option")
62 print_markdown_for_person(budget_graph
, config
,
63 args
.person
, args
.subset
)
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
)
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
)
75 logging
.fatal("--subset-person: unknown person: %s", person_str
)
79 nodes_subset
= OrderedSet()
80 for bug_id
in re
.split(r
"[\s,]+", subset_str
):
82 node
= budget_graph
.nodes
[int(bug_id
)]
83 except (ValueError, KeyError):
84 logging
.fatal("--subset: unknown bug: %s", bug_id
)
86 nodes_subset
.add(node
)
87 print(markdown_for_person(budget_graph
, person
, nodes_subset
))
90 def print_budget_then_children(indent
, nodes
, bug_id
):
91 """recursive indented printout of budgets
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
)))
103 for child
in bug
.immediate_children
:
104 if (str(child
.budget_including_subtasks
) == "0" and
105 str(child
.budget_excluding_subtasks
) == "0"):
107 print_budget_then_children(indent
+1, nodes
, child
.bug
.id)
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()
118 print("not submitted", not_submitted
)
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
))
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
)
132 print ("```") # for using the output as markdown
135 if __name__
== "__main__":