@@ -26,6 +26,9 @@ pub struct GoalDocument {
26
26
/// Metadata loaded from the header in the goal
27
27
pub metadata : Metadata ,
28
28
29
+ /// The "plan" for completing the goal (includes things owners will do as well as team asks)
30
+ pub plan_items : Vec < PlanItem > ,
31
+
29
32
/// List of team asks extracted from the goal
30
33
pub team_asks : Vec < TeamAsk > ,
31
34
}
@@ -40,16 +43,25 @@ pub struct Metadata {
40
43
pub status : Status ,
41
44
}
42
45
46
+ /// Identifies a particular ask for a set of Rust teams
47
+ #[ derive( Debug ) ]
48
+ pub struct PlanItem {
49
+ pub text : String ,
50
+ pub owners : String ,
51
+ pub notes : String ,
52
+ pub children : Vec < PlanItem > ,
53
+ }
54
+
43
55
/// Identifies a particular ask for a set of Rust teams
44
56
#[ derive( Debug ) ]
45
57
pub struct TeamAsk {
46
58
/// Path to the markdown file containing this ask (appropriate for a link)
47
59
pub link_path : Arc < PathBuf > ,
48
60
49
- /// Title of the subgoal (or goal, if there are no subgoals )
61
+ /// What the team is being asked for (e.g., RFC decision )
50
62
pub subgoal : String ,
51
63
52
- /// What the team is being asked for (e.g., RFC decision )
64
+ /// Title of the subgoal (or goal, if there are no subgoals )
53
65
pub heading : String ,
54
66
55
67
/// Name(s) of the teams being asked to do the thing
@@ -84,22 +96,29 @@ impl GoalDocument {
84
96
} ;
85
97
86
98
let link_path = Arc :: new ( link_path. to_path_buf ( ) ) ;
87
- let team_asks = match metadata. status {
99
+
100
+ let plan_items = match metadata. status {
88
101
Status :: Flagship | Status :: Proposed | Status :: Orphaned => {
89
- extract_team_asks ( & link_path , & metadata , & sections) ?
102
+ extract_plan_items ( & sections) ?
90
103
}
91
104
Status :: NotAccepted => vec ! [ ] ,
92
105
} ;
93
106
94
- if metadata. status != Status :: NotAccepted && team_asks. is_empty ( ) {
95
- anyhow:: bail!( "no team asks found in goal file `{}`" , path. display( ) ) ;
107
+ let mut team_asks = vec ! [ ] ;
108
+ for plan_item in & plan_items {
109
+ team_asks. extend ( plan_item. team_asks (
110
+ & link_path,
111
+ & metadata. short_title ,
112
+ & metadata. owners ,
113
+ ) ?) ;
96
114
}
97
115
98
116
Ok ( Some ( GoalDocument {
99
117
path : path. to_path_buf ( ) ,
100
118
link_path,
101
119
metadata,
102
120
team_asks,
121
+ plan_items,
103
122
} ) )
104
123
}
105
124
}
@@ -261,6 +280,134 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result<Option<Metadata>> {
261
280
} ) )
262
281
}
263
282
283
+ fn extract_plan_items < ' i > ( sections : & [ Section ] ) -> anyhow:: Result < Vec < PlanItem > > {
284
+ let Some ( ownership_section) = sections
285
+ . iter ( )
286
+ . find ( |section| section. title == "Ownership and team asks" )
287
+ else {
288
+ anyhow:: bail!( "no `Ownership and team asks` section found" )
289
+ } ;
290
+
291
+ let Some ( table) = ownership_section. tables . first ( ) else {
292
+ anyhow:: bail!(
293
+ "on line {}, no table found in `Ownership and team asks` section" ,
294
+ ownership_section. line_num
295
+ )
296
+ } ;
297
+
298
+ expect_headers ( table, & [ "Subgoal" , "Owner(s) or team(s)" , "Notes" ] ) ?;
299
+
300
+ let mut rows = table. rows . iter ( ) . peekable ( ) ;
301
+ let mut plan_items = vec ! [ ] ;
302
+ while rows. peek ( ) . is_some ( ) {
303
+ plan_items. push ( extract_plan_item ( & mut rows) ?) ;
304
+ }
305
+ Ok ( plan_items)
306
+ }
307
+
308
+ fn extract_plan_item (
309
+ rows : & mut std:: iter:: Peekable < std:: slice:: Iter < Vec < String > > > ,
310
+ ) -> anyhow:: Result < PlanItem > {
311
+ let Some ( row) = rows. next ( ) else {
312
+ anyhow:: bail!( "unexpected end of table" ) ;
313
+ } ;
314
+
315
+ let mut subgoal = row[ 0 ] . trim ( ) ;
316
+ let mut is_child = false ;
317
+
318
+ if subgoal. starts_with ( ARROW ) {
319
+ // e.g., "↳ stabilization" is a subtask of the metagoal
320
+ subgoal = row[ 0 ] [ ARROW . len ( ) ..] . trim ( ) ;
321
+ is_child = true ;
322
+ }
323
+
324
+ let mut item = PlanItem {
325
+ text : subgoal. to_string ( ) ,
326
+ owners : row[ 1 ] . to_string ( ) ,
327
+ notes : row[ 2 ] . to_string ( ) ,
328
+ children : vec ! [ ] ,
329
+ } ;
330
+
331
+ if !is_child {
332
+ while let Some ( row) = rows. peek ( ) {
333
+ if !row[ 0 ] . starts_with ( ARROW ) {
334
+ break ;
335
+ }
336
+
337
+ item. children . push ( extract_plan_item ( rows) ?) ;
338
+ }
339
+ }
340
+
341
+ Ok ( item)
342
+ }
343
+
344
+ impl PlanItem {
345
+ fn teams ( & self ) -> anyhow:: Result < Vec < & ' static TeamName > > {
346
+ if !self . owners . contains ( "![Team]" ) {
347
+ return Ok ( vec ! [ ] ) ;
348
+ }
349
+
350
+ let mut teams = vec ! [ ] ;
351
+ for team_name in extract_team_names ( & self . owners ) {
352
+ let Some ( team) = team:: get_team_name ( & team_name) ? else {
353
+ anyhow:: bail!(
354
+ "no Rust team named `{}` found (valid names are {})" ,
355
+ team_name,
356
+ commas( team:: get_team_names( ) ?) ,
357
+ ) ;
358
+ } ;
359
+
360
+ teams. push ( team) ;
361
+ }
362
+
363
+ if teams. is_empty ( ) {
364
+ anyhow:: bail!( "team ask for \" {}\" does not list any teams" , self . text) ;
365
+ }
366
+
367
+ Ok ( teams)
368
+ }
369
+
370
+ /// Return a vector of all the team-asks from this item and its children
371
+ ///
372
+ /// # Parameters
373
+ ///
374
+ /// * `link_path`, the path to the document this plan item is found within
375
+ /// * `goal_title`, the title of the goal (or subgoal) this plan item is a part of
376
+ /// * `goal_owners`, the owners of the goal (or subgoal) this plan item is a part of
377
+ fn team_asks (
378
+ & self ,
379
+ link_path : & Arc < PathBuf > ,
380
+ goal_title : & str ,
381
+ goal_owners : & str ,
382
+ ) -> anyhow:: Result < Vec < TeamAsk > > {
383
+ let mut asks = vec ! [ ] ;
384
+
385
+ let teams = self . teams ( ) ?;
386
+ if !teams. is_empty ( ) {
387
+ asks. push ( TeamAsk {
388
+ link_path : link_path. clone ( ) ,
389
+ subgoal : self . text . clone ( ) ,
390
+ heading : goal_title. to_string ( ) ,
391
+ teams,
392
+ owners : goal_owners. to_string ( ) ,
393
+ notes : self . notes . clone ( ) ,
394
+ } ) ;
395
+ }
396
+
397
+ for child in & self . children {
398
+ // If this item has owners listed, they take precedence, otherwise use the owners in scope.
399
+ let owners = if self . owners . is_empty ( ) {
400
+ goal_owners
401
+ } else {
402
+ & self . owners
403
+ } ;
404
+ asks. extend ( child. team_asks ( link_path, & self . text , owners) ?) ;
405
+ }
406
+
407
+ Ok ( asks)
408
+ }
409
+ }
410
+
264
411
fn extract_team_asks < ' i > (
265
412
link_path : & Arc < PathBuf > ,
266
413
metadata : & Metadata ,
0 commit comments