1 from bugzilla
import Bugzilla
5 from pathlib
import Path
6 from budget_sync
.util
import all_bugs
7 from budget_sync
.config
import Config
, ConfigParseError
8 from budget_sync
.budget_graph
import BudgetGraph
, BudgetGraphBaseError
9 from budget_sync
.write_budget_markdown
import write_budget_markdown
10 from collections
import OrderedDict
13 def write_csv(name
, items
, headers
):
14 """ Write an array of dictionaries to the CSV file name """
15 with
open(name
, 'w') as csvfile
:
16 writer
= csv
.DictWriter(csvfile
, headers
, lineterminator
="\n")
18 writer
.writerows(items
)
21 mdwn_csv_template
= """\
24 [[!table format=csv file="%s"]]
27 mdwn_people_template
= """\
32 def write_budget_csv(budget_graph
: BudgetGraph
,
34 # quick hack to display total payment amounts per-milestone
35 for milestone
, payments
in budget_graph
.milestone_payments
.items():
41 for payment
in payments
:
42 # print("\t", payment)
43 total
+= payment
.amount
44 if payment
.submitted
is not None:
45 total_requested
+= payment
.amount
46 if payment
.paid
is not None:
47 total_paid
+= payment
.amount
48 if payment
.submitted
or payment
.paid
is not None:
49 total_req_or_paid
+= payment
.amount
51 print("\t %-9s" % total
,
52 "submitted %-9s" % total_requested
,
53 "paid %-9s" % total_paid
,
54 "submitted or paid %-9s" % total_req_or_paid
)
57 # and one to display peole
58 milestones_people
= budget_graph
.get_milestone_people()
59 for milestone
, people
in milestones_people
.items():
64 # even quicker hack to create something vaguely resembling a CSV file
66 milestone_headings
= {}
67 all_people
= OrderedDict()
68 for milestone
, nodes
in budget_graph
.assigned_nodes_for_milestones
.items():
69 milestone_csvs
[milestone
] = {} # rows in the CSV file
70 people
= milestones_people
[milestone
]
72 'budget_excluding_subtasks',
73 'budget_including_subtasks',
74 'fixed_budget_excluding_subtasks',
75 'fixed_budget_including_subtasks',
76 'submitted_excluding_subtasks',
77 'paid_excluding_subtasks']
79 name
= str(person
).replace(" ", "_")
80 all_people
[person
] = person
81 # name, amount, requested (submitted), paid
83 headings
.append(name
+"_req")
84 headings
.append(name
+"_paid")
85 milestone_headings
[milestone
] = headings
87 # skip uninteresting nodes
88 if len(node
.payments
) == 0 \
89 and node
.budget_excluding_subtasks
== 0 \
90 and node
.budget_including_subtasks
== 0:
92 row
= {'bug_id': node
.bug
.id,
93 'budget_excluding_subtasks': str(node
.budget_excluding_subtasks
),
94 'budget_including_subtasks': str(node
.budget_including_subtasks
),
95 'fixed_budget_excluding_subtasks': str(node
.fixed_budget_excluding_subtasks
),
96 'fixed_budget_including_subtasks': str(node
.fixed_budget_including_subtasks
),
97 'submitted_excluding_subtasks': str(node
.submitted_excluding_subtasks
),
98 'paid_excluding_subtasks': str(node
.paid_excluding_subtasks
)}
99 for payment
in node
.payments
.values():
100 short_name
= str(payment
.payee
.output_markdown_file
)
101 name
= short_name
.replace(".mdwn", "")
103 row
[name
] = str(payment
.amount
)
104 if payment
.submitted
is not None:
105 requested
= str(payment
.submitted
)
108 if payment
.paid
is not None:
109 paid
= str(payment
.paid
)
112 row
[name
+"_req"] = requested
113 row
[name
+"_paid"] = paid
116 milestone_csvs
[milestone
][node
.bug
.id] = row
118 with
open(output_dir
.joinpath("csvs.mdwn"), "w") as f
:
119 # write out the people pages
120 # TODO, has to be done by the markdown page name
121 # f.write("# People\n\n")
122 # for name, person in all_people.items():
123 # fname = output_dir.joinpath(f"{name}.csv")
124 # f.write(mdwn_people_template % (person, fname))
126 for milestone
, rows
in milestone_csvs
.items():
127 ident
= milestone
.identifier
128 header
= milestone_headings
[milestone
]
129 fname
= output_dir
.joinpath(f
"{ident}.csv")
130 rows
= rows
.values() # turn into list
131 write_csv(fname
, rows
, header
)
132 f
.write(mdwn_csv_template
% (ident
, fname
))
136 parser
= argparse
.ArgumentParser(
137 description
="Check for errors in "
138 "Libre-SOC's style of budget tracking in Bugzilla.")
140 "-c", "--config", type=argparse
.FileType('r'),
141 required
=True, help="The path to the configuration TOML file",
142 dest
="config", metavar
="<path/to/budget-sync-config.toml>")
144 "-o", "--output-dir", type=Path
, default
=None,
145 help="The path to the output directory, will be created if it "
147 dest
="output_dir", metavar
="<path/to/output/dir>")
148 args
= parser
.parse_args()
150 with args
.config
as config_file
:
151 config
= Config
.from_file(config_file
)
152 except (IOError, ConfigParseError
) as e
:
153 logging
.error("Failed to parse config file: %s", e
)
155 logging
.info("Using Bugzilla instance at %s", config
.bugzilla_url
)
156 bz
= Bugzilla(config
.bugzilla_url
)
157 logging
.debug("Connected to Bugzilla")
158 budget_graph
= BudgetGraph(all_bugs(bz
), config
)
159 for error
in budget_graph
.get_errors():
160 logging
.error("%s", error
)
161 if args
.output_dir
is not None:
162 write_budget_markdown(budget_graph
, args
.output_dir
)
163 write_budget_csv(budget_graph
, args
.output_dir
)
166 if __name__
== "__main__":