Skip to content

Commit daa642d

Browse files
authored
Merge pull request #134 from nikomatsakis/main
link tracking issues
2 parents 45a1fac + fc6b535 commit daa642d

35 files changed

+427
-175
lines changed

mdbook-goals/src/gh.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::fmt::Display;
2+
3+
use crate::re::TRACKING_ISSUE;
4+
5+
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
6+
pub struct IssueId {
7+
/// Something like `rust-lang/rust-project-goals`
8+
pub repository: String,
9+
10+
/// Something like `22`
11+
pub number: u64,
12+
}
13+
14+
impl IssueId {
15+
pub fn new(repository: &(impl Display + ?Sized), number: u64) -> Self {
16+
Self {
17+
repository: repository.to_string(),
18+
number,
19+
}
20+
}
21+
}
22+
23+
impl std::fmt::Debug for IssueId {
24+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25+
write!(f, "{self}")
26+
}
27+
}
28+
29+
impl std::fmt::Display for IssueId {
30+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31+
write!(
32+
f,
33+
"[{repository}#{number}]",
34+
repository = self.repository,
35+
number = self.number,
36+
)
37+
}
38+
}
39+
40+
impl std::str::FromStr for IssueId {
41+
type Err = anyhow::Error;
42+
43+
fn from_str(s: &str) -> Result<Self, Self::Err> {
44+
let Some(c) = TRACKING_ISSUE.captures(s) else {
45+
anyhow::bail!("invalid issue-id")
46+
};
47+
48+
Ok(IssueId::new(&c[1], c[2].parse()?))
49+
}
50+
}

mdbook-goals/src/goal.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{collections::BTreeSet, path::PathBuf};
66
use anyhow::Context;
77
use regex::Regex;
88

9+
use crate::gh::IssueId;
910
use crate::re::USERNAME;
1011
use crate::team::{self, TeamName};
1112
use crate::util::{commas, markdown_files};
@@ -15,6 +16,7 @@ use crate::{
1516
};
1617

1718
/// Data parsed from a goal file in the expected format
19+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1820
pub struct GoalDocument {
1921
/// Path relative to the current directory (`book.toml`)
2022
pub path: PathBuf,
@@ -37,17 +39,21 @@ pub struct GoalDocument {
3739
}
3840

3941
/// Metadata loaded from the goal header
40-
#[derive(Debug)]
42+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
4143
pub struct Metadata {
4244
#[allow(unused)]
4345
pub title: String,
4446
pub short_title: String,
4547
pub owners: String,
4648
pub status: Status,
49+
pub tracking_issue: Option<IssueId>,
50+
pub table: Table,
4751
}
4852

53+
pub const TRACKING_ISSUE_ROW: &str = "Tracking issue";
54+
4955
/// Identifies a particular ask for a set of Rust teams
50-
#[derive(Debug)]
56+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
5157
pub struct PlanItem {
5258
pub text: String,
5359
pub owners: String,
@@ -66,7 +72,7 @@ pub enum ParsedOwners {
6672
}
6773

6874
/// Identifies a particular ask for a set of Rust teams
69-
#[derive(Debug)]
75+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
7076
pub struct TeamAsk {
7177
/// Path to the markdown file containing this ask (appropriate for a link)
7278
pub link_path: Arc<PathBuf>,
@@ -153,6 +159,16 @@ impl GoalDocument {
153159
Status::NotAccepted => false,
154160
}
155161
}
162+
163+
/// Modify the goal document on disk to link to the given issue number in the metadata.
164+
pub(crate) fn link_issue(&self, number: IssueId) -> anyhow::Result<()> {
165+
let mut metadata_table = self.metadata.table.clone();
166+
metadata_table.add_key_value_row(TRACKING_ISSUE_ROW, &number);
167+
self.metadata
168+
.table
169+
.overwrite_in_path(&self.path, &metadata_table)?;
170+
Ok(())
171+
}
156172
}
157173

158174
/// Format a set of team asks into a table, with asks separated by team and grouped by kind.
@@ -240,7 +256,7 @@ pub fn format_goal_table(goals: &[&GoalDocument]) -> anyhow::Result<String> {
240256
Ok(util::format_table(&table))
241257
}
242258

243-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
259+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
244260
pub enum Status {
245261
Flagship,
246262
Accepted,
@@ -308,6 +324,15 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result<Option<Metadata>> {
308324

309325
let status = Status::try_from(status_row[1].as_str())?;
310326

327+
let issue = match first_table
328+
.rows
329+
.iter()
330+
.find(|row| row[0] == TRACKING_ISSUE_ROW)
331+
{
332+
Some(r) => Some(r[1].parse()?),
333+
None => None,
334+
};
335+
311336
Ok(Some(Metadata {
312337
title: title.to_string(),
313338
short_title: if let Some(row) = short_title_row {
@@ -317,6 +342,8 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result<Option<Metadata>> {
317342
},
318343
owners: owners_row[1].to_string(),
319344
status,
345+
tracking_issue: issue,
346+
table: first_table.clone(),
320347
}))
321348
}
322349

mdbook-goals/src/json.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub(super) fn generate_json() {
2+
3+
}
4+

mdbook-goals/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{io, path::PathBuf};
77
use structopt::StructOpt;
88
use walkdir::WalkDir;
99

10+
mod gh;
1011
mod goal;
1112
mod markwaydown;
1213
mod mdbook_preprocessor;

mdbook-goals/src/markwaydown.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Arguably the worst markdown parser ever. Extracts exactly the things we care about.
22
3-
use std::path::Path;
3+
use std::{fmt::Display, path::Path};
4+
5+
use crate::util;
46

57
#[derive(Debug)]
68
pub struct Section {
@@ -10,7 +12,7 @@ pub struct Section {
1012
pub tables: Vec<Table>,
1113
}
1214

13-
#[derive(Debug)]
15+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1416
pub struct Table {
1517
pub line_num: usize,
1618
pub header: Vec<String>,
@@ -150,3 +152,56 @@ fn categorize_line(line: &str) -> CategorizeLine {
150152
CategorizeLine::Other
151153
}
152154
}
155+
156+
impl Table {
157+
/// For a "key-value" table (like metadata), find an existing row
158+
/// where the first column (the "key") is `row_key` and modify its second column (the "value")
159+
/// to be `row_value`. If no row exists with key `row_key`, then add a new row.
160+
pub fn add_key_value_row(&mut self, row_key: &str, row_value: &impl Display) {
161+
assert_eq!(self.header.len(), 2);
162+
163+
match self.rows.iter_mut().find(|row| row[0] == row_key) {
164+
Some(row) => {
165+
row[1] = row_value.to_string();
166+
}
167+
168+
None => {
169+
self.rows
170+
.push(vec![row_key.to_string(), row_value.to_string()]);
171+
}
172+
}
173+
}
174+
175+
/// Modify `path` to replace the lines containing this table with `new_table`.
176+
pub fn overwrite_in_path(&self, path: &Path, new_table: &Table) -> anyhow::Result<()> {
177+
let full_text = std::fs::read_to_string(path)?;
178+
179+
let mut new_lines = vec![];
180+
new_lines.extend(
181+
full_text
182+
.lines()
183+
.take(self.line_num - 1)
184+
.map(|s| s.to_string()),
185+
);
186+
187+
let table_text = {
188+
let mut new_rows = vec![new_table.header.clone()];
189+
new_rows.extend(new_table.rows.iter().cloned());
190+
util::format_table(&new_rows)
191+
};
192+
new_lines.push(table_text);
193+
194+
new_lines.extend(
195+
full_text
196+
.lines()
197+
.skip(self.line_num - 1)
198+
.skip(2 + self.rows.len())
199+
.map(|s| s.to_string()),
200+
);
201+
202+
let new_text = new_lines.join("\n");
203+
std::fs::write(path, new_text)?;
204+
205+
Ok(())
206+
}
207+
}

mdbook-goals/src/re.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ lazy_static! {
1616
lazy_static! {
1717
pub static ref USERNAME: Regex = Regex::new(r"@([-a-zA-Z0-9])+").unwrap();
1818
}
19+
20+
lazy_static! {
21+
pub static ref TRACKING_ISSUE: Regex = Regex::new(r"\[([^#]*)#([0-9]+)\]").unwrap();
22+
}

0 commit comments

Comments
 (0)