Skip to content

Commit 2fd8433

Browse files
authored
Merge pull request #1733 from ehuss/mdbook-spec-warnings
Rework error handling in mdbook-spec
2 parents 4d5e128 + 6e31150 commit 2fd8433

File tree

3 files changed

+125
-72
lines changed

3 files changed

+125
-72
lines changed

mdbook-spec/src/lib.rs

+71-28
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use mdbook::BookItem;
99
use once_cell::sync::Lazy;
1010
use regex::{Captures, Regex};
1111
use semver::{Version, VersionReq};
12+
use std::fmt;
1213
use std::io;
1314
use std::ops::Range;
1415
use std::path::PathBuf;
@@ -46,15 +47,58 @@ pub fn handle_preprocessing() -> Result<(), Error> {
4647
Ok(())
4748
}
4849

49-
pub struct Spec {
50+
/// Handler for errors and warnings.
51+
pub struct Diagnostics {
5052
/// Whether or not warnings should be errors (set by SPEC_DENY_WARNINGS
5153
/// environment variable).
5254
deny_warnings: bool,
55+
/// Number of messages generated.
56+
count: u32,
57+
}
58+
59+
impl Diagnostics {
60+
fn new() -> Diagnostics {
61+
let deny_warnings = std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1");
62+
Diagnostics {
63+
deny_warnings,
64+
count: 0,
65+
}
66+
}
67+
68+
/// Displays a warning or error (depending on whether warnings are denied).
69+
///
70+
/// Usually you want the [`warn_or_err!`] macro.
71+
fn warn_or_err(&mut self, args: fmt::Arguments<'_>) {
72+
if self.deny_warnings {
73+
eprintln!("error: {args}");
74+
} else {
75+
eprintln!("warning: {args}");
76+
}
77+
self.count += 1;
78+
}
79+
}
80+
81+
/// Displays a warning or error (depending on whether warnings are denied).
82+
#[macro_export]
83+
macro_rules! warn_or_err {
84+
($diag:expr, $($arg:tt)*) => {
85+
$diag.warn_or_err(format_args!($($arg)*));
86+
};
87+
}
88+
89+
/// Displays a message for an internal error, and immediately exits.
90+
#[macro_export]
91+
macro_rules! bug {
92+
($($arg:tt)*) => {
93+
eprintln!("mdbook-spec internal error: {}", format_args!($($arg)*));
94+
std::process::exit(1);
95+
};
96+
}
97+
98+
pub struct Spec {
5399
/// Path to the rust-lang/rust git repository (set by SPEC_RUST_ROOT
54100
/// environment variable).
55101
rust_root: Option<PathBuf>,
56-
/// The git ref that can be used in a URL to the rust-lang/rust repository.
57-
git_ref: String,
58102
}
59103

60104
impl Spec {
@@ -64,30 +108,10 @@ impl Spec {
64108
/// the rust git checkout. If `None`, it will use the `SPEC_RUST_ROOT`
65109
/// environment variable. If the root is not specified, then no tests will
66110
/// be linked unless `SPEC_DENY_WARNINGS` is set in which case this will
67-
/// return an error..
111+
/// return an error.
68112
pub fn new(rust_root: Option<PathBuf>) -> Result<Spec> {
69-
let deny_warnings = std::env::var("SPEC_DENY_WARNINGS").as_deref() == Ok("1");
70113
let rust_root = rust_root.or_else(|| std::env::var_os("SPEC_RUST_ROOT").map(PathBuf::from));
71-
if deny_warnings && rust_root.is_none() {
72-
bail!("SPEC_RUST_ROOT environment variable must be set");
73-
}
74-
let git_ref = match git_ref(&rust_root) {
75-
Ok(s) => s,
76-
Err(e) => {
77-
if deny_warnings {
78-
eprintln!("error: {e:?}");
79-
std::process::exit(1);
80-
} else {
81-
eprintln!("warning: {e:?}");
82-
"master".into()
83-
}
84-
}
85-
};
86-
Ok(Spec {
87-
deny_warnings,
88-
rust_root,
89-
git_ref,
90-
})
114+
Ok(Spec { rust_root })
91115
}
92116

93117
/// Generates link references to all rules on all pages, so you can easily
@@ -180,9 +204,20 @@ impl Preprocessor for Spec {
180204
}
181205

182206
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
183-
let rules = self.collect_rules(&book);
207+
let mut diag = Diagnostics::new();
208+
if diag.deny_warnings && self.rust_root.is_none() {
209+
bail!("error: SPEC_RUST_ROOT environment variable must be set");
210+
}
211+
let rules = self.collect_rules(&book, &mut diag);
184212
let tests = self.collect_tests(&rules);
185213
let summary_table = test_links::make_summary_table(&book, &tests, &rules);
214+
let git_ref = match git_ref(&self.rust_root) {
215+
Ok(s) => s,
216+
Err(e) => {
217+
warn_or_err!(&mut diag, "{e:?}");
218+
"master".into()
219+
}
220+
};
186221

187222
book.for_each_mut(|item| {
188223
let BookItem::Chapter(ch) = item else {
@@ -193,15 +228,23 @@ impl Preprocessor for Spec {
193228
}
194229
ch.content = self.admonitions(&ch);
195230
ch.content = self.auto_link_references(&ch, &rules);
196-
ch.content = self.render_rule_definitions(&ch.content, &tests);
231+
ch.content = self.render_rule_definitions(&ch.content, &tests, &git_ref);
197232
if ch.name == "Test summary" {
198233
ch.content = ch.content.replace("{{summary-table}}", &summary_table);
199234
}
200235
});
201236

202237
// Final pass will resolve everything as a std link (or error if the
203238
// link is unknown).
204-
std_links::std_links(&mut book);
239+
std_links::std_links(&mut book, &mut diag);
240+
241+
if diag.count > 0 {
242+
if diag.deny_warnings {
243+
eprintln!("mdbook-spec exiting due to {} errors", diag.count);
244+
std::process::exit(1);
245+
}
246+
eprintln!("mdbook-spec generated {} warnings", diag.count);
247+
}
205248

206249
Ok(book)
207250
}

mdbook-spec/src/rules.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Handling for rule identifiers.
22
33
use crate::test_links::RuleToTests;
4-
use crate::Spec;
4+
use crate::{warn_or_err, Diagnostics, Spec};
55
use mdbook::book::Book;
66
use mdbook::BookItem;
77
use once_cell::sync::Lazy;
@@ -34,7 +34,7 @@ pub struct Rules {
3434

3535
impl Spec {
3636
/// Collects all rule definitions in the book.
37-
pub fn collect_rules(&self, book: &Book) -> Rules {
37+
pub fn collect_rules(&self, book: &Book, diag: &mut Diagnostics) -> Rules {
3838
let mut rules = Rules::default();
3939
for item in book.iter() {
4040
let BookItem::Chapter(ch) = item else {
@@ -53,16 +53,12 @@ impl Spec {
5353
.def_paths
5454
.insert(rule_id.to_string(), (source_path.clone(), path.clone()))
5555
{
56-
let message = format!(
56+
warn_or_err!(
57+
diag,
5758
"rule `{rule_id}` defined multiple times\n\
5859
First location: {old:?}\n\
5960
Second location: {source_path:?}"
6061
);
61-
if self.deny_warnings {
62-
panic!("error: {message}");
63-
} else {
64-
eprintln!("warning: {message}");
65-
}
6662
}
6763
let mut parts: Vec<_> = rule_id.split('.').collect();
6864
while !parts.is_empty() {
@@ -78,7 +74,12 @@ impl Spec {
7874

7975
/// Converts lines that start with `r[…]` into a "rule" which has special
8076
/// styling and can be linked to.
81-
pub fn render_rule_definitions(&self, content: &str, tests: &RuleToTests) -> String {
77+
pub fn render_rule_definitions(
78+
&self,
79+
content: &str,
80+
tests: &RuleToTests,
81+
git_ref: &str,
82+
) -> String {
8283
RULE_RE
8384
.replace_all(content, |caps: &Captures<'_>| {
8485
let rule_id = &caps[1];
@@ -96,7 +97,6 @@ impl Spec {
9697
test_html,
9798
"<li><a href=\"https://github.com/rust-lang/rust/blob/{git_ref}/{test_path}\">{test_path}</a></li>",
9899
test_path = test.path,
99-
git_ref = self.git_ref
100100
)
101101
.unwrap();
102102
}

0 commit comments

Comments
 (0)