Skip to content

Commit ca29a21

Browse files
committed
parse plan items and extract team asks from those
1 parent afda4ec commit ca29a21

File tree

1 file changed

+153
-6
lines changed

1 file changed

+153
-6
lines changed

mdbook-goals/src/goal.rs

Lines changed: 153 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ pub struct GoalDocument {
2626
/// Metadata loaded from the header in the goal
2727
pub metadata: Metadata,
2828

29+
/// The "plan" for completing the goal (includes things owners will do as well as team asks)
30+
pub plan_items: Vec<PlanItem>,
31+
2932
/// List of team asks extracted from the goal
3033
pub team_asks: Vec<TeamAsk>,
3134
}
@@ -40,16 +43,25 @@ pub struct Metadata {
4043
pub status: Status,
4144
}
4245

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+
4355
/// Identifies a particular ask for a set of Rust teams
4456
#[derive(Debug)]
4557
pub struct TeamAsk {
4658
/// Path to the markdown file containing this ask (appropriate for a link)
4759
pub link_path: Arc<PathBuf>,
4860

49-
/// Title of the subgoal (or goal, if there are no subgoals)
61+
/// What the team is being asked for (e.g., RFC decision)
5062
pub subgoal: String,
5163

52-
/// What the team is being asked for (e.g., RFC decision)
64+
/// Title of the subgoal (or goal, if there are no subgoals)
5365
pub heading: String,
5466

5567
/// Name(s) of the teams being asked to do the thing
@@ -84,22 +96,29 @@ impl GoalDocument {
8496
};
8597

8698
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 {
88101
Status::Flagship | Status::Proposed | Status::Orphaned => {
89-
extract_team_asks(&link_path, &metadata, &sections)?
102+
extract_plan_items(&sections)?
90103
}
91104
Status::NotAccepted => vec![],
92105
};
93106

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+
)?);
96114
}
97115

98116
Ok(Some(GoalDocument {
99117
path: path.to_path_buf(),
100118
link_path,
101119
metadata,
102120
team_asks,
121+
plan_items,
103122
}))
104123
}
105124
}
@@ -261,6 +280,134 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result<Option<Metadata>> {
261280
}))
262281
}
263282

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+
264411
fn extract_team_asks<'i>(
265412
link_path: &Arc<PathBuf>,
266413
metadata: &Metadata,

0 commit comments

Comments
 (0)