@@ -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
+ /// Text from the summary section
30
+ pub summary : String ,
31
+
29
32
/// The "plan" for completing the goal (includes things owners will do as well as team asks)
30
33
pub plan_items : Vec < PlanItem > ,
31
34
@@ -52,6 +55,16 @@ pub struct PlanItem {
52
55
pub children : Vec < PlanItem > ,
53
56
}
54
57
58
+ /// Returns the "owner(s)" of a plan-item, which can be
59
+ ///
60
+ /// * users, if this is something that users have to do
61
+ /// * teams, if this is a team ask
62
+ #[ derive( Debug ) ]
63
+ pub enum ParsedOwners {
64
+ TeamAsks ( Vec < & ' static TeamName > ) ,
65
+ Usernames ( Vec < String > ) ,
66
+ }
67
+
55
68
/// Identifies a particular ask for a set of Rust teams
56
69
#[ derive( Debug ) ]
57
70
pub struct TeamAsk {
@@ -95,6 +108,8 @@ impl GoalDocument {
95
108
return Ok ( None ) ;
96
109
} ;
97
110
111
+ let summary = extract_summary ( & sections) ?;
112
+
98
113
let link_path = Arc :: new ( link_path. to_path_buf ( ) ) ;
99
114
100
115
let plan_items = match metadata. status {
@@ -116,11 +131,20 @@ impl GoalDocument {
116
131
Ok ( Some ( GoalDocument {
117
132
path : path. to_path_buf ( ) ,
118
133
link_path,
134
+ summary : summary. unwrap_or_else ( || metadata. title . clone ( ) ) ,
119
135
metadata,
120
136
team_asks,
121
137
plan_items,
122
138
} ) )
123
139
}
140
+
141
+ pub fn teams_with_asks ( & self ) -> BTreeSet < & ' static TeamName > {
142
+ self . team_asks
143
+ . iter ( )
144
+ . flat_map ( |ask| & ask. teams )
145
+ . copied ( )
146
+ . collect ( )
147
+ }
124
148
}
125
149
126
150
/// Format a set of team asks into a table, with asks separated by team and grouped by kind.
@@ -286,6 +310,14 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result<Option<Metadata>> {
286
310
} ) )
287
311
}
288
312
313
+ fn extract_summary ( sections : & [ Section ] ) -> anyhow:: Result < Option < String > > {
314
+ let Some ( ownership_section) = sections. iter ( ) . find ( |section| section. title == "Summary" ) else {
315
+ return Ok ( None ) ;
316
+ } ;
317
+
318
+ Ok ( Some ( ownership_section. text . trim ( ) . to_string ( ) ) )
319
+ }
320
+
289
321
fn extract_plan_items < ' i > ( sections : & [ Section ] ) -> anyhow:: Result < Vec < PlanItem > > {
290
322
let Some ( ownership_section) = sections
291
323
. iter ( )
@@ -348,14 +380,35 @@ fn extract_plan_item(
348
380
}
349
381
350
382
impl PlanItem {
383
+ /// Parses the owners of this plan item.
384
+ pub fn parse_owners ( & self ) -> anyhow:: Result < Option < ParsedOwners > > {
385
+ if self . owners . is_empty ( ) {
386
+ Ok ( None )
387
+ } else if self . is_team_ask ( ) {
388
+ Ok ( Some ( ParsedOwners :: TeamAsks ( self . teams_being_asked ( ) ?) ) )
389
+ } else {
390
+ Ok ( Some ( ParsedOwners :: Usernames (
391
+ owner_usernames ( & self . owners )
392
+ . iter ( )
393
+ . map ( |s| s. to_string ( ) )
394
+ . collect ( ) ,
395
+ ) ) )
396
+ }
397
+ }
398
+
399
+ /// True if the plan item is noted as being completed
400
+ pub fn is_complete ( & self ) -> bool {
401
+ self . notes . contains ( "![Complete]" )
402
+ }
403
+
351
404
/// If true, this item is something being asked of a team.
352
405
/// If false, it's something the goal owner(s) are proposing to do.
353
- fn is_team_ask ( & self ) -> bool {
406
+ pub fn is_team_ask ( & self ) -> bool {
354
407
self . owners . contains ( "![Team]" )
355
408
}
356
409
357
410
/// Return the set of teams being asked to do things by this item, or empty vector if this is not a team ask.
358
- fn teams_being_asked ( & self ) -> anyhow:: Result < Vec < & ' static TeamName > > {
411
+ pub fn teams_being_asked ( & self ) -> anyhow:: Result < Vec < & ' static TeamName > > {
359
412
if !self . is_team_ask ( ) {
360
413
return Ok ( vec ! [ ] ) ;
361
414
}
@@ -422,89 +475,6 @@ impl PlanItem {
422
475
}
423
476
}
424
477
425
- fn extract_team_asks < ' i > (
426
- link_path : & Arc < PathBuf > ,
427
- metadata : & Metadata ,
428
- sections : & [ Section ] ,
429
- ) -> anyhow:: Result < Vec < TeamAsk > > {
430
- let Some ( ownership_section) = sections
431
- . iter ( )
432
- . find ( |section| section. title == "Ownership and team asks" )
433
- else {
434
- anyhow:: bail!( "no `Ownership and team asks` section found" )
435
- } ;
436
-
437
- let Some ( table) = ownership_section. tables . first ( ) else {
438
- anyhow:: bail!(
439
- "on line {}, no table found in `Ownership and team asks` section" ,
440
- ownership_section. line_num
441
- )
442
- } ;
443
-
444
- expect_headers ( table, & [ "Subgoal" , "Owner(s) or team(s)" , "Notes" ] ) ?;
445
-
446
- let mut heading = "" ;
447
- let mut heading_owners: & str = & metadata. owners [ ..] ;
448
-
449
- let mut tasks = vec ! [ ] ;
450
- for row in & table. rows {
451
- let subgoal;
452
- let owners;
453
- if row[ 0 ] . starts_with ( ARROW ) {
454
- // e.g., "↳ stabilization" is a subtask of the metagoal
455
- subgoal = row[ 0 ] [ ARROW . len ( ) ..] . trim ( ) ;
456
- owners = heading_owners;
457
- } else {
458
- // remember the last heading
459
- heading = & row[ 0 ] ;
460
- heading_owners = if row[ 1 ] . is_empty ( ) {
461
- & metadata. owners [ ..]
462
- } else {
463
- & row[ 1 ]
464
- } ;
465
-
466
- subgoal = heading;
467
- owners = & metadata. owners ;
468
- } ;
469
-
470
- if !row[ 1 ] . contains ( "![Team]" ) {
471
- continue ;
472
- }
473
-
474
- let mut teams = vec ! [ ] ;
475
- for team_name in extract_team_names ( & row[ 1 ] ) {
476
- let Some ( team) = team:: get_team_name ( & team_name) ? else {
477
- anyhow:: bail!(
478
- "no Rust team named `{}` found (valid names are {})" ,
479
- team_name,
480
- commas( team:: get_team_names( ) ?) ,
481
- ) ;
482
- } ;
483
-
484
- teams. push ( team) ;
485
- }
486
-
487
- if teams. is_empty ( ) {
488
- anyhow:: bail!( "team ask for \" {subgoal}\" does not list any teams" ) ;
489
- }
490
-
491
- tasks. push ( TeamAsk {
492
- link_path : link_path. clone ( ) ,
493
- subgoal_title : if subgoal == heading {
494
- metadata. short_title . to_string ( )
495
- } else {
496
- heading. to_string ( )
497
- } ,
498
- ask_description : subgoal. to_string ( ) ,
499
- teams,
500
- owners : owners. to_string ( ) ,
501
- notes : row[ 2 ] . to_string ( ) ,
502
- } ) ;
503
- }
504
-
505
- Ok ( tasks)
506
- }
507
-
508
478
fn expect_headers ( table : & Table , expected : & [ & str ] ) -> anyhow:: Result < ( ) > {
509
479
if table. header != expected {
510
480
anyhow:: bail!(
@@ -534,10 +504,13 @@ fn extract_identifiers(s: &str) -> Vec<&str> {
534
504
impl Metadata {
535
505
/// Extracts the `@abc` usernames found in the owner listing.
536
506
pub fn owner_usernames ( & self ) -> Vec < & str > {
537
- self . owners
538
- . split ( char:: is_whitespace)
539
- . filter_map ( |owner| USERNAME . captures ( owner) )
540
- . map ( |captures| captures. get ( 0 ) . unwrap ( ) . as_str ( ) )
541
- . collect ( )
507
+ owner_usernames ( & self . owners )
542
508
}
543
509
}
510
+
511
+ fn owner_usernames ( text : & str ) -> Vec < & str > {
512
+ text. split ( char:: is_whitespace)
513
+ . filter_map ( |owner| USERNAME . captures ( owner) )
514
+ . map ( |captures| captures. get ( 0 ) . unwrap ( ) . as_str ( ) )
515
+ . collect ( )
516
+ }
0 commit comments