Skip to content

Commit f7c61c5

Browse files
committed
generate goal text
1 parent ece4e6b commit f7c61c5

File tree

7 files changed

+254
-135
lines changed

7 files changed

+254
-135
lines changed

mdbook-goals/src/goal.rs

Lines changed: 63 additions & 90 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+
/// Text from the summary section
30+
pub summary: String,
31+
2932
/// The "plan" for completing the goal (includes things owners will do as well as team asks)
3033
pub plan_items: Vec<PlanItem>,
3134

@@ -52,6 +55,16 @@ pub struct PlanItem {
5255
pub children: Vec<PlanItem>,
5356
}
5457

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+
5568
/// Identifies a particular ask for a set of Rust teams
5669
#[derive(Debug)]
5770
pub struct TeamAsk {
@@ -95,6 +108,8 @@ impl GoalDocument {
95108
return Ok(None);
96109
};
97110

111+
let summary = extract_summary(&sections)?;
112+
98113
let link_path = Arc::new(link_path.to_path_buf());
99114

100115
let plan_items = match metadata.status {
@@ -116,11 +131,20 @@ impl GoalDocument {
116131
Ok(Some(GoalDocument {
117132
path: path.to_path_buf(),
118133
link_path,
134+
summary: summary.unwrap_or_else(|| metadata.title.clone()),
119135
metadata,
120136
team_asks,
121137
plan_items,
122138
}))
123139
}
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+
}
124148
}
125149

126150
/// 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>> {
286310
}))
287311
}
288312

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+
289321
fn extract_plan_items<'i>(sections: &[Section]) -> anyhow::Result<Vec<PlanItem>> {
290322
let Some(ownership_section) = sections
291323
.iter()
@@ -348,14 +380,35 @@ fn extract_plan_item(
348380
}
349381

350382
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+
351404
/// If true, this item is something being asked of a team.
352405
/// 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 {
354407
self.owners.contains("![Team]")
355408
}
356409

357410
/// 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>> {
359412
if !self.is_team_ask() {
360413
return Ok(vec![]);
361414
}
@@ -422,89 +475,6 @@ impl PlanItem {
422475
}
423476
}
424477

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-
508478
fn expect_headers(table: &Table, expected: &[&str]) -> anyhow::Result<()> {
509479
if table.header != expected {
510480
anyhow::bail!(
@@ -534,10 +504,13 @@ fn extract_identifiers(s: &str) -> Vec<&str> {
534504
impl Metadata {
535505
/// Extracts the `@abc` usernames found in the owner listing.
536506
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)
542508
}
543509
}
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+
}

mdbook-goals/src/main.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ enum Command {
4141
Issues {
4242
path: PathBuf,
4343

44-
#[structopt(short = "-n", long)]
45-
dry_run: bool,
44+
/// Without this option, no action is taken.
45+
#[structopt(long)]
46+
commit: bool,
4647
},
4748

4849
/// Checks that the goal documents are well-formed, intended for use within CI
@@ -69,8 +70,8 @@ fn main() -> anyhow::Result<()> {
6970
rfc::generate_rfc(&path)?;
7071
}
7172

72-
Some(Command::Issues { path, dry_run }) => {
73-
rfc::generate_issues(&opt.repository, path, *dry_run)?;
73+
Some(Command::Issues { path, commit }) => {
74+
rfc::generate_issues(&opt.repository, path, *commit)?;
7475
}
7576

7677
None => {

mdbook-goals/src/markwaydown.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::path::Path;
66
pub struct Section {
77
pub line_num: usize,
88
pub title: String,
9+
pub text: String,
910
pub tables: Vec<Table>,
1011
}
1112

@@ -32,16 +33,17 @@ pub fn parse(path: &Path) -> anyhow::Result<Vec<Section>> {
3233
open_section = Some(Section {
3334
line_num,
3435
title,
36+
text: String::new(),
3537
tables: vec![],
3638
});
3739
}
3840
CategorizeLine::TableRow(mut row) => {
3941
if open_section.is_none() {
40-
open_section = Some(Section {
41-
line_num,
42-
title: "".to_string(),
43-
tables: vec![],
44-
});
42+
anyhow::bail!(
43+
"{}:{}: markdowwn table outside of any section",
44+
path.display(),
45+
line_num
46+
);
4547
}
4648

4749
if let Some(table) = &mut open_table {
@@ -95,6 +97,10 @@ pub fn parse(path: &Path) -> anyhow::Result<Vec<Section>> {
9597
}
9698
CategorizeLine::Other => {
9799
close_table(&mut open_section, &mut open_table);
100+
if let Some(section) = open_section.as_mut() {
101+
section.text.push_str(line);
102+
section.text.push('\n');
103+
}
98104
}
99105
}
100106
}

0 commit comments

Comments
 (0)