1
- use std:: collections:: BTreeSet ;
1
+ use std:: { collections:: { BTreeMap , BTreeSet } , path :: PathBuf } ;
2
2
3
- use indexmap:: IndexMap ;
4
-
5
- use crate :: { config:: Configuration , goal:: TeamAsk , team:: TeamName , util} ;
3
+ use crate :: { config:: Configuration , goal:: TeamAsk , team:: TeamName , util:: { self , ARROW } } ;
6
4
7
5
/// Format a set of team asks into a table, with asks separated by team and grouped by kind.
8
6
///
@@ -65,22 +63,11 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> anyhow::Result<String>
65
63
} ;
66
64
67
65
// Collect the asks by goal. The `rows` map goes from goal title to a row with entries
68
- let mut goal_rows: IndexMap < String , Vec < String > > = IndexMap :: default ( ) ;
66
+ let mut goal_rows: BTreeMap < GoalData < ' _ > , Vec < String > > = BTreeMap :: default ( ) ;
69
67
for ask in & asks_of_this_team {
70
- let link = format ! ( "{}" , ask. link_path. display( ) ) ;
71
-
72
- let goal_title = match & ask. goal_titles [ ..] {
73
- [ goal_title] => format ! ( "[{goal_title}]({link}#ownership-and-team-asks)" ) ,
74
- [ goal_title, subgoal_title] => {
75
- format ! ( "[{subgoal_title}]({link}#ownership-and-team-asks) (part of [{goal_title}]({link}))" )
76
- }
77
- _ => anyhow:: bail!(
78
- "expected either 1 or 2 goal titles, not {:?}" ,
79
- ask. goal_titles
80
- ) ,
81
- } ;
68
+ let goal_data = GoalData :: new ( ask) ?;
82
69
83
- let row = goal_rows. entry ( goal_title ) . or_insert_with ( empty_row) ;
70
+ let row = goal_rows. entry ( goal_data ) . or_insert_with ( empty_row) ;
84
71
85
72
let index = ask_headings
86
73
. iter ( )
@@ -110,10 +97,12 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> anyhow::Result<String>
110
97
}
111
98
}
112
99
113
- // Sort the goal rows by name (ignoring case).
114
- goal_rows. sort_by_cached_key ( |ask_names, _ask_rows| {
115
- ask_names. clone ( ) . to_uppercase ( )
116
- } ) ;
100
+ // Ensure that we have an entry for the "meta-goal", even if there are no asks.
101
+ for ask in & asks_of_this_team {
102
+ let mut goal_data = GoalData :: new ( ask) ?;
103
+ goal_data. subgoal_title = None ;
104
+ goal_rows. entry ( goal_data) . or_insert_with ( empty_row) ;
105
+ }
117
106
118
107
// Create the table itself.
119
108
let table = {
@@ -128,8 +117,8 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> anyhow::Result<String>
128
117
) // e.g. "discussion and moral support"
129
118
. collect :: < Vec < String > > ( ) ;
130
119
131
- let rows = goal_rows. into_iter ( ) . map ( |( goal_title , goal_columns) | {
132
- std:: iter:: once ( goal_title)
120
+ let rows = goal_rows. into_iter ( ) . map ( |( goal_data , goal_columns) | {
121
+ std:: iter:: once ( goal_data . goal_title ( ) )
133
122
. chain ( goal_columns)
134
123
. collect :: < Vec < String > > ( )
135
124
} ) ;
@@ -146,3 +135,33 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> anyhow::Result<String>
146
135
147
136
Ok ( output)
148
137
}
138
+
139
+ #[ derive( Eq , PartialEq , PartialOrd , Ord , Hash , Copy , Clone , Debug ) ]
140
+ struct GoalData < ' g > {
141
+ goal_title : & ' g String ,
142
+ subgoal_title : Option < & ' g String > ,
143
+ link : & ' g PathBuf ,
144
+ }
145
+
146
+ impl < ' g > GoalData < ' g > {
147
+ fn new ( ask : & ' g TeamAsk ) -> anyhow:: Result < Self > {
148
+ match & ask. goal_titles [ ..] {
149
+ [ goal_title] => Ok ( Self { goal_title, subgoal_title : None , link : & ask. link_path } ) ,
150
+ [ goal_title, subgoal_title] => {
151
+ Ok ( Self { goal_title, subgoal_title : Some ( subgoal_title) , link : & ask. link_path } )
152
+ }
153
+ _ => anyhow:: bail!(
154
+ "expected either 1 or 2 goal titles, not {:?}" ,
155
+ ask. goal_titles
156
+ ) ,
157
+ }
158
+ }
159
+
160
+ fn goal_title ( & self ) -> String {
161
+ if let Some ( subgoal_title) = self . subgoal_title {
162
+ format ! ( "{} {}" , ARROW , subgoal_title)
163
+ } else {
164
+ format ! ( "[{}]({})" , self . goal_title, self . link. display( ) )
165
+ }
166
+ }
167
+ }
0 commit comments