Skip to content

Commit 4fa352c

Browse files
committed
generate JSON for consumption by the website etc
1 parent ddd34d6 commit 4fa352c

File tree

4 files changed

+161
-2
lines changed

4 files changed

+161
-2
lines changed

mdbook-goals/src/gh/issues.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize};
77

88
use crate::util::comma;
99

10+
use super::labels::GhLabel;
11+
1012
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
1113
pub struct ExistingGithubIssue {
1214
pub number: u64,
@@ -15,13 +17,16 @@ pub struct ExistingGithubIssue {
1517
pub comments: Vec<ExistingGithubComment>,
1618
pub body: String,
1719
pub state: ExistingIssueState,
20+
pub labels: Vec<GhLabel>,
1821
}
1922

2023
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
2124
pub struct ExistingGithubComment {
2225
/// Just github username, no `@`
2326
pub author: String,
2427
pub body: String,
28+
pub created_at: String,
29+
pub url: String,
2530
}
2631

2732
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@@ -32,6 +37,7 @@ struct ExistingGithubIssueJson {
3237
comments: Vec<ExistingGithubCommentJson>,
3338
body: String,
3439
state: ExistingIssueState,
40+
labels: Vec<GhLabel>,
3541
}
3642

3743
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@@ -44,6 +50,9 @@ struct ExistingGithubAssigneeJson {
4450
struct ExistingGithubCommentJson {
4551
body: String,
4652
author: ExistingGithubAuthorJson,
53+
#[serde(rename = "createdAt")]
54+
created_at: String,
55+
url: String,
4756
}
4857

4958
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@@ -72,7 +81,7 @@ pub fn list_issue_titles_in_milestone(
7281
.arg("-s")
7382
.arg("all")
7483
.arg("--json")
75-
.arg("title,assignees,number,comments,body,state")
84+
.arg("title,assignees,number,comments,body,state,labels")
7685
.output()?;
7786

7887
let existing_issues: Vec<ExistingGithubIssueJson> = serde_json::from_slice(&output.stdout)?;
@@ -91,10 +100,13 @@ pub fn list_issue_titles_in_milestone(
91100
.map(|c| ExistingGithubComment {
92101
author: format!("@{}", c.author.login),
93102
body: c.body,
103+
url: c.url,
104+
created_at: c.created_at,
94105
})
95106
.collect(),
96107
body: e_i.body,
97108
state: e_i.state,
109+
labels: e_i.labels,
98110
},
99111
)
100112
})
@@ -221,3 +233,10 @@ pub fn lock_issue(repository: &str, number: u64) -> anyhow::Result<()> {
221233

222234
Ok(())
223235
}
236+
237+
impl ExistingGithubComment {
238+
/// True if this is one of the special comments that we put on issues.
239+
pub fn is_automated_comment(&self) -> bool {
240+
self.body.trim() == LOCK_TEXT
241+
}
242+
}

mdbook-goals/src/json.rs

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,121 @@
1-
pub(super) fn generate_json() {
1+
//! Generate JSON summarizing the tracking issues.
22
3+
use std::path::PathBuf;
4+
5+
use serde::Serialize;
6+
7+
use crate::{
8+
gh::issues::{list_issue_titles_in_milestone, ExistingGithubComment, ExistingGithubIssue},
9+
re,
10+
};
11+
12+
pub(super) fn generate_json(
13+
repository: &str,
14+
milestone: &str,
15+
json_path: &Option<PathBuf>,
16+
) -> anyhow::Result<()> {
17+
let issues = list_issue_titles_in_milestone(repository, milestone)?;
18+
19+
let issues = TrackingIssues {
20+
issues: issues
21+
.into_iter()
22+
.map(|(title, issue)| {
23+
let (total_checkboxes, checked_checkboxes) = checkboxes(&issue);
24+
TrackingIssue {
25+
number: issue.number,
26+
title,
27+
flagship: is_flagship(&issue),
28+
total_checkboxes,
29+
checked_checkboxes,
30+
assignees: issue.assignees.into_iter().collect(),
31+
updates: updates(issue.comments),
32+
}
33+
})
34+
.collect(),
35+
repository: repository.to_string(),
36+
milestone: milestone.to_string(),
37+
};
38+
39+
if let Some(json_path) = json_path {
40+
let json = serde_json::to_string(&issues)?;
41+
std::fs::write(json_path, json)?;
42+
} else {
43+
println!("{}", serde_json::to_string_pretty(&issues)?);
44+
}
45+
46+
Ok(())
47+
}
48+
49+
#[derive(Serialize)]
50+
struct TrackingIssues {
51+
repository: String,
52+
milestone: String,
53+
issues: Vec<TrackingIssue>,
54+
}
55+
56+
#[derive(Serialize)]
57+
struct TrackingIssue {
58+
/// Issue number on the repository
59+
number: u64,
60+
61+
/// Title of the tracking issue
62+
title: String,
63+
64+
/// True if this is a flagship goal
65+
flagship: bool,
66+
67+
/// Total checkboxes appearing in the body (i.e., `* [ ]` or `* [x]`)
68+
total_checkboxes: u32,
69+
70+
/// Checked checkboxes appearing in the body (i.e., `* [x]`)
71+
checked_checkboxes: u32,
72+
73+
/// Set of assigned people
74+
assignees: Vec<String>,
75+
76+
/// Posts that we consider to be status updates, in chronological order
77+
updates: Vec<TrackingIssueUpdate>,
378
}
479

80+
#[derive(Serialize)]
81+
struct TrackingIssueUpdate {
82+
pub author: String,
83+
pub body: String,
84+
#[serde(rename = "createdAt")]
85+
pub created_at: String,
86+
pub url: String,
87+
}
88+
89+
fn checkboxes(issue: &ExistingGithubIssue) -> (u32, u32) {
90+
let mut total = 0;
91+
let mut checked = 0;
92+
93+
for line in issue.body.lines() {
94+
if re::CHECKBOX.is_match(line) {
95+
total += 1;
96+
}
97+
98+
if re::CHECKED_CHECKBOX.is_match(line) {
99+
checked += 1;
100+
}
101+
}
102+
103+
(total, checked)
104+
}
105+
106+
fn is_flagship(issue: &ExistingGithubIssue) -> bool {
107+
issue.labels.iter().any(|label| label.name == "flagship")
108+
}
109+
110+
fn updates(comments: Vec<ExistingGithubComment>) -> Vec<TrackingIssueUpdate> {
111+
comments
112+
.into_iter()
113+
.filter(|comment| !comment.is_automated_comment())
114+
.map(|comment| TrackingIssueUpdate {
115+
author: comment.author,
116+
body: comment.body,
117+
created_at: comment.created_at,
118+
url: comment.url,
119+
})
120+
.collect()
121+
}

mdbook-goals/src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use walkdir::WalkDir;
99

1010
mod gh;
1111
mod goal;
12+
mod json;
1213
mod markwaydown;
1314
mod mdbook_preprocessor;
1415
mod re;
@@ -67,6 +68,13 @@ enum Command {
6768

6869
/// Checks that the goal documents are well-formed, intended for use within CI
6970
Check {},
71+
72+
Json {
73+
milestone: String,
74+
75+
#[structopt(long)]
76+
json_path: Option<PathBuf>,
77+
},
7078
}
7179

7280
fn main() -> anyhow::Result<()> {
@@ -108,6 +116,13 @@ fn main() -> anyhow::Result<()> {
108116
} => {
109117
team_repo::generate_team_repo(&path, team_repo_path)?;
110118
}
119+
120+
Command::Json {
121+
milestone,
122+
json_path,
123+
} => {
124+
json::generate_json(&opt.repository, &milestone, json_path)?;
125+
}
111126
}
112127

113128
Ok(())

mdbook-goals/src/re.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ lazy_static! {
2020
lazy_static! {
2121
pub static ref TRACKING_ISSUE: Regex = Regex::new(r"\[([^#]*)#([0-9]+)\]").unwrap();
2222
}
23+
24+
lazy_static! {
25+
pub static ref CHECKBOX: Regex = Regex::new(r"\s*[-*] \[[ x]\] ").unwrap();
26+
}
27+
28+
lazy_static! {
29+
pub static ref CHECKED_CHECKBOX: Regex = Regex::new(r"\s*[-*] \[x\] ").unwrap();
30+
}

0 commit comments

Comments
 (0)