Skip to content

Commit f18c6d8

Browse files
authored
finish preparing RFC (#373)
* Add champion metadata to goal files - Import champion assignments from CSV spreadsheet - Add [team] champion metadata rows to all goal files - Skip non-champion values (-, !, blank, minimal) - Include TC as valid username * WIP: fix re? * incorporate champions into team listing * wip * What goals were not accepted * improve "not accepted" goal listing * include champion table * opening RFC * turn this into a proper RFC format * give the PR number
1 parent ee51031 commit f18c6d8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+319
-151
lines changed

crates/mdbook-goals/src/mdbook_preprocessor.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use mdbook::BookItem;
1010
use regex::{Captures, Regex};
1111
use rust_project_goals::config::Configuration;
1212
use rust_project_goals::format_team_ask::format_team_asks;
13+
use rust_project_goals::format_champions::format_champions;
1314
use rust_project_goals::util::{self, GithubUserInfo};
1415

1516
use rust_project_goals::spanned::Spanned;
@@ -147,6 +148,7 @@ impl<'c> GoalPreprocessorWithContext<'c> {
147148
match book_item {
148149
BookItem::Chapter(chapter) => {
149150
self.replace_metadata_placeholders(chapter)?;
151+
self.replace_champions(chapter)?;
150152
self.replace_team_asks(chapter)?;
151153
self.replace_valid_team_asks(chapter)?;
152154
self.replace_goal_lists(chapter)?;
@@ -308,6 +310,26 @@ impl<'c> GoalPreprocessorWithContext<'c> {
308310
}
309311
}
310312

313+
/// Look for `(((CHAMPIONS)))` in the chapter content and replace it with the champions table.
314+
fn replace_champions(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> {
315+
let Some(m) = re::CHAMPIONS.find(&chapter.content) else {
316+
return Ok(());
317+
};
318+
let range = m.range();
319+
320+
let Some(path) = &chapter.path else {
321+
anyhow::bail!("found `(((CHAMPIONS)))` but chapter has no path")
322+
};
323+
324+
let goals = self.goal_documents(path)?;
325+
let goal_refs: Vec<&GoalDocument> = goals.iter().collect();
326+
let format_champions =
327+
format_champions(&goal_refs).map_err(|e| anyhow::anyhow!("{e}"))?;
328+
chapter.content.replace_range(range, &format_champions);
329+
330+
Ok(())
331+
}
332+
311333
/// Look for `<!-- TEAM ASKS -->` in the chapter content and replace it with the team asks.
312334
fn replace_team_asks(&mut self, chapter: &mut Chapter) -> anyhow::Result<()> {
313335
let Some(m) = re::TEAM_ASKS.find(&chapter.content) else {

crates/rust-project-goals-cli/src/csv_reports.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ fn champions(repository: &Repository, milestone: &str) -> Result<()> {
3939
let rows: Vec<ChampionRow> = goal_documents
4040
.iter()
4141
.map(|doc| ChampionRow {
42-
title: doc.metadata.title.clone(),
42+
title: doc.metadata.title.to_string(),
4343
url: format!(
4444
"https://github.com/{org}/{repo}/blob/main/{path}",
4545
org = repository.org,

crates/rust-project-goals-cli/src/rfc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ fn issue<'doc>(timeframe: &str, document: &'doc GoalDocument) -> Result<GithubIs
420420
}
421421

422422
Ok(GithubIssue {
423-
title: document.metadata.title.clone(),
423+
title: document.metadata.title.to_string(),
424424
assignees,
425425
body: issue_text(timeframe, document)?,
426426
labels,

crates/rust-project-goals/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub struct TeamAskDetails {
1818

1919
/// Longer description
2020
pub about: String,
21+
22+
/// If true, do not include in the RFC tables.
23+
#[serde(default)]
24+
pub elide: bool,
2125
}
2226

2327
impl Configuration {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::collections::{BTreeMap, BTreeSet};
2+
3+
use spanned::{Result, Spanned};
4+
5+
use crate::{goal::GoalDocument, util};
6+
7+
/// Format a champions table showing each champion and their goals.
8+
pub fn format_champions(goals: &[&GoalDocument]) -> Result<String> {
9+
use std::fmt::Write;
10+
11+
let mut output = String::new();
12+
13+
// Collect champions and their goals
14+
let mut champion_goals: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
15+
16+
for goal in goals {
17+
for champion in goal.metadata.champions.values() {
18+
let champion_name = champion.content.clone();
19+
let goal_link = format!("° [{}]({})", goal.metadata.title.content, goal.link_path.display());
20+
21+
champion_goals
22+
.entry(champion_name)
23+
.or_default()
24+
.insert(goal_link);
25+
}
26+
}
27+
28+
if champion_goals.is_empty() {
29+
return Ok("No champions found.".to_string());
30+
}
31+
32+
// Create the table
33+
let table = {
34+
let headings = vec![
35+
Spanned::here("Champion".to_string()),
36+
Spanned::here("#".to_string()),
37+
Spanned::here("Goals".to_string()),
38+
];
39+
40+
let rows = champion_goals.into_iter().map(|(champion, goals)| {
41+
let goals_vec: Vec<String> = goals.into_iter().collect();
42+
vec![
43+
Spanned::here(champion),
44+
Spanned::here(goals_vec.len().to_string()),
45+
Spanned::here(goals_vec.join("<br>")),
46+
]
47+
});
48+
49+
std::iter::once(headings).chain(rows).collect::<Vec<_>>()
50+
};
51+
52+
write!(output, "{}", util::format_table(&table))?;
53+
54+
Ok(output)
55+
}

crates/rust-project-goals/src/format_team_ask.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> Result<String> {
5959
// the configuration. We prune out the ones that do not appear in the asks for a particular team.
6060
let ask_headings = config
6161
.team_asks
62-
.keys()
63-
.filter(|&ask_kind| {
64-
asks_of_this_team
65-
.iter()
66-
.any(|a| &a.ask_description == ask_kind)
62+
.iter()
63+
.filter(|&(ask_kind, ask_details)| {
64+
!ask_details.elide
65+
&& asks_of_this_team
66+
.iter()
67+
.any(|a| &a.ask_description == ask_kind)
6768
})
69+
.map(|(ask_kind, _)| ask_kind)
6870
.collect::<Vec<_>>();
6971
let empty_row = || {
7072
(0..ask_headings.len())
@@ -79,10 +81,15 @@ pub fn format_team_asks(asks_of_any_team: &[&TeamAsk]) -> Result<String> {
7981

8082
let row = goal_rows.entry(goal_data).or_insert_with(empty_row);
8183

82-
let index = ask_headings
83-
.iter()
84-
.position(|&h| h == &ask.ask_description)
85-
.unwrap();
84+
let Some(index) = ask_headings.iter().position(|&h| h == &ask.ask_description) else {
85+
// Some asks are not included in the table
86+
assert!(
87+
config.team_asks[&ask.ask_description].elide,
88+
"ask {} has no index but is not elided",
89+
ask.ask_description
90+
);
91+
continue;
92+
};
8693

8794
let text = if !ask.notes.is_empty() {
8895
&ask.notes

crates/rust-project-goals/src/goal.rs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub struct GoalDocument {
4343
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
4444
pub struct Metadata {
4545
#[allow(unused)]
46-
pub title: String,
46+
pub title: Spanned<String>,
4747
pub short_title: Spanned<String>,
4848
pub pocs: String,
4949
pub status: Spanned<Status>,
@@ -117,7 +117,7 @@ pub fn goals_in_dir(directory_path: &Path) -> Result<Vec<GoalDocument>> {
117117
if path.file_name().unwrap() == "TEMPLATE.md" {
118118
continue;
119119
}
120-
120+
121121
if let Some(goal_document) = GoalDocument::load(&path, &link_path)? {
122122
goal_documents.push(goal_document);
123123
}
@@ -137,11 +137,7 @@ impl GoalDocument {
137137

138138
let link_path = Arc::new(link_path.to_path_buf());
139139

140-
let goal_plans = if metadata.status.is_not_not_accepted() {
141-
extract_plan_items(&sections)?
142-
} else {
143-
vec![]
144-
};
140+
let goal_plans = extract_plan_items(&sections)?;
145141

146142
let mut team_asks = vec![];
147143
for goal_plan in &goal_plans {
@@ -156,7 +152,10 @@ impl GoalDocument {
156152

157153
// Enforce that every goal has some team asks (unless it is not accepted)
158154
if metadata.status.is_not_not_accepted() && team_asks.is_empty() {
159-
spanned::bail_here!("no team asks in goal; did you include `![Team]` in the table?");
155+
spanned::bail!(
156+
metadata.title,
157+
"no team asks in goal; did you include `![Team]` in the table?"
158+
);
160159
}
161160

162161
let task_owners = goal_plans
@@ -168,7 +167,7 @@ impl GoalDocument {
168167
Ok(Some(GoalDocument {
169168
path: path.to_path_buf(),
170169
link_path,
171-
summary: summary.unwrap_or_else(|| metadata.title.clone()),
170+
summary: summary.unwrap_or_else(|| (*metadata.title).clone()),
172171
metadata,
173172
team_asks,
174173
goal_plans,
@@ -213,13 +212,14 @@ impl GoalDocument {
213212

214213
pub fn format_goal_table(goals: &[&GoalDocument]) -> Result<String> {
215214
// If any of the goals have tracking issues, include those in the table.
216-
let goals_are_proposed = goals
217-
.iter()
218-
.any(|g| g.metadata.status.acceptance == AcceptanceStatus::Proposed);
215+
let show_champions = goals.iter().any(|g| {
216+
g.metadata.status.acceptance == AcceptanceStatus::Proposed
217+
|| g.metadata.status.acceptance == AcceptanceStatus::NotAccepted
218+
});
219219

220220
let mut table;
221221

222-
if !goals_are_proposed {
222+
if !show_champions {
223223
table = vec![vec![
224224
Spanned::here("Goal".to_string()),
225225
Spanned::here("Point of contact".to_string()),
@@ -249,7 +249,7 @@ pub fn format_goal_table(goals: &[&GoalDocument]) -> Result<String> {
249249
table.push(vec![
250250
Spanned::here(format!(
251251
"[{}]({})",
252-
goal.metadata.title,
252+
*goal.metadata.title,
253253
goal.link_path.display()
254254
)),
255255
Spanned::here(goal.point_of_contact_for_goal_list()),
@@ -260,7 +260,7 @@ pub fn format_goal_table(goals: &[&GoalDocument]) -> Result<String> {
260260
table = vec![vec![
261261
Spanned::here("Goal".to_string()),
262262
Spanned::here("Point of contact".to_string()),
263-
Spanned::here("Team".to_string()),
263+
Spanned::here("Team(s) and Champion(s)".to_string()),
264264
]];
265265

266266
for goal in goals {
@@ -270,15 +270,27 @@ pub fn format_goal_table(goals: &[&GoalDocument]) -> Result<String> {
270270
.flat_map(|ask| &ask.teams)
271271
.copied()
272272
.collect();
273-
let teams: Vec<&TeamName> = teams.into_iter().collect();
273+
274+
// Format teams with champions in parentheses
275+
let teams_with_champions: Vec<String> = teams
276+
.into_iter()
277+
.map(|team| {
278+
if let Some(champion) = goal.metadata.champions.get(team) {
279+
format!("{} ({})", team, champion.content)
280+
} else {
281+
team.to_string()
282+
}
283+
})
284+
.collect();
285+
274286
table.push(vec![
275287
Spanned::here(format!(
276288
"[{}]({})",
277-
goal.metadata.title,
289+
*goal.metadata.title,
278290
goal.link_path.display()
279291
)),
280292
Spanned::here(goal.point_of_contact_for_goal_list()),
281-
Spanned::here(commas(&teams)),
293+
Spanned::here(teams_with_champions.join(", ")),
282294
]);
283295
}
284296
}
@@ -493,7 +505,7 @@ fn extract_metadata(sections: &[Section]) -> Result<Option<Metadata>> {
493505
.map(|row| row[1].clone());
494506

495507
Ok(Some(Metadata {
496-
title: title.to_string(),
508+
title: title.clone(),
497509
short_title: if let Some(row) = short_title_row {
498510
row[1].clone()
499511
} else {
@@ -508,8 +520,6 @@ fn extract_metadata(sections: &[Section]) -> Result<Option<Metadata>> {
508520
}))
509521
}
510522

511-
512-
513523
fn extract_summary(sections: &[Section]) -> Result<Option<String>> {
514524
let Some(ownership_section) = sections.iter().find(|section| section.title == "Summary") else {
515525
return Ok(None);
@@ -540,13 +550,6 @@ fn extract_plan_items<'i>(sections: &[Section]) -> Result<Vec<GoalPlan>> {
540550
goal_plans.extend(goal_plan(Some(subsection.title.clone()), subsection)?);
541551
}
542552

543-
if goal_plans.is_empty() {
544-
spanned::bail!(
545-
sections[ownership_index].title,
546-
"no goal table items found in the `Ownership and team asks` section or subsections"
547-
)
548-
}
549-
550553
Ok(goal_plans)
551554
}
552555

crates/rust-project-goals/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod config;
22
pub mod format_team_ask;
3+
pub mod format_champions;
34
pub mod gh;
45
pub mod goal;
56
pub mod markwaydown;

crates/rust-project-goals/src/re.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ lazy_static! {
55
pub static ref TEAM_ASKS: Regex = Regex::new(r"\(\(\(TEAM ASKS\)\)\)").unwrap();
66
}
77

8+
lazy_static! {
9+
pub static ref CHAMPIONS: Regex = Regex::new(r"\(\(\(CHAMPIONS\)\)\)").unwrap();
10+
}
11+
812
// List of all goals, flagship or otherwise
913
lazy_static! {
1014
pub static ref GOAL_LIST: Regex = Regex::new(r"\(\(\(GOALS\)\)\)").unwrap();
@@ -112,6 +116,6 @@ pub const TLDR: &str = "TL;DR:";
112116
lazy_static! {
113117
/// Metadata table rows like `[lang] champion` indicate the champion for the lang team
114118
pub static ref CHAMPION_METADATA: Regex =
115-
Regex::new(r"^\s*(?P<team>\[.*\]) champion)\s*$")
119+
Regex::new(r"^\s*\[(?P<team>.*)\] champion\s*$")
116120
.unwrap();
117121
}

rust-project-goals.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55

66
[team_asks]
77
"Allocate funds" = { short="Alloc funds", about="allocate funding" }
8-
"Discussion and moral support" = { short="Good vibes", about="approve of this direction and be prepared for light discussion on Zulip or elsewhere" }
8+
"Discussion and moral support" = { short="Good vibes", about="approve of this direction and be prepared for light discussion on Zulip or elsewhere", elide = true }
99
"Deploy to production" = { short="Deploy", about="deploy code to production (e.g., on crates.io" }
10-
"Standard reviews" = { short="r?", about="review PRs (PRs are not expected to be unduly large or complicated)" }
10+
"Standard reviews" = { short="r?", about="review PRs (PRs are not expected to be unduly large or complicated)", elide = true }
1111
"Dedicated reviewer" = { short="Ded. r?", about="assign a specific person (or people) to review a series of PRs, appropriate for large or complex asks" }
12-
"Lang-team champion" = { short="Champion", about="member of lang team or advisors who will champion the design within team" }
1312
"Lang-team experiment" = { short="Experiment", about="begin a [lang-team experiment](https://lang-team.rust-lang.org/how_to/experiment.html) authorizing experimental impl of lang changes before an RFC is written; limited to trusted contributors" }
1413
"Design meeting" = { short="Design mtg.", about="hold a synchronous meeting to review a proposal and provide feedback (no decision expected)" }
1514
"RFC decision" = { short="RFC", about="review an RFC and deciding whether to accept" }

0 commit comments

Comments
 (0)