@@ -9,6 +9,7 @@ use mdbook::BookItem;
99use once_cell:: sync:: Lazy ;
1010use regex:: { Captures , Regex } ;
1111use semver:: { Version , VersionReq } ;
12+ use std:: fmt;
1213use std:: io;
1314use std:: ops:: Range ;
1415use 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
60104impl 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 }
0 commit comments