11use std:: collections:: HashMap ;
2- use std:: fmt:: { self , Write } ;
2+ use std:: error:: Error ;
3+ use std:: fmt:: Write ;
4+ use std:: fs:: File ;
5+ use std:: io:: { self , BufRead , BufReader , ErrorKind , Read , Seek , SeekFrom } ;
36use std:: path:: { Path , PathBuf } ;
7+ use std:: process:: ExitCode ;
48use std:: { env, fs} ;
59
610use glob:: glob;
711
12+ type Result < T = ( ) > = core:: result:: Result < T , Box < dyn Error + ' static > > ;
13+
14+ macro_rules! e {
15+ ( $( $fmt: tt) ,* $( , ) ?) => {
16+ return Err ( format!( $( $fmt) ,* ) . into( ) )
17+ } ;
18+ }
19+
20+ macro_rules! assert {
21+ ( $condition: expr, $( $fmt: tt) ,* $( , ) ?) => {
22+ if !$condition { e!( $( $fmt) ,* ) }
23+ } ;
24+ }
25+
826#[ derive( Debug , Default ) ]
927struct Level {
1028 nested : HashMap < String , Level > ,
1129 files : Vec < PathBuf > ,
1230}
1331
14- fn main ( ) {
15- let home = env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap ( ) ;
16- let pattern = format ! ( "{home}/../../website/docs/**/*.md*" ) ;
17- let base = format ! ( "{home}/../../website" ) ;
18- let base = Path :: new ( & base) . canonicalize ( ) . unwrap ( ) ;
19- let dir_pattern = format ! ( "{home}/../../website/docs/**" ) ;
20- for dir in glob ( & dir_pattern) . unwrap ( ) {
21- println ! ( "cargo:rerun-if-changed={}" , dir. unwrap( ) . display( ) ) ;
22- }
23-
24- let mut level = Level :: default ( ) ;
32+ fn should_combine_code_blocks ( path : & Path ) -> io:: Result < bool > {
33+ const FLAG : & [ u8 ] = b"<!-- COMBINE CODE BLOCKS -->" ;
2534
26- for entry in glob ( & pattern) . unwrap ( ) {
27- let path = entry. unwrap ( ) ;
28- let path = Path :: new ( & path) . canonicalize ( ) . unwrap ( ) ;
29- println ! ( "cargo:rerun-if-changed={}" , path. display( ) ) ;
30- let rel = path. strip_prefix ( & base) . unwrap ( ) ;
35+ let mut file = File :: open ( path) ?;
36+ match file. seek ( SeekFrom :: End ( -32 ) ) {
37+ Ok ( _) => ( ) ,
38+ Err ( e) if e. kind ( ) == ErrorKind :: InvalidInput => return Ok ( false ) ,
39+ Err ( e) => return Err ( e) ,
40+ }
41+ let mut buf = [ 0u8 ; 32 ] ;
42+ file. read_exact ( & mut buf) ?;
43+ Ok ( buf. trim_ascii_end ( ) . ends_with ( FLAG ) )
44+ }
3145
32- let mut parts = vec ! [ ] ;
46+ fn apply_diff ( src : & mut String , preamble : & str , added : & str , removed : & str ) -> Result {
47+ assert ! (
48+ !preamble. is_empty( ) || !removed. is_empty( ) ,
49+ "Failure on applying a diff: \n No preamble or text to remove provided, unable to find \
50+ location to insert:\n {added}\n In the following text:\n {src}",
51+ ) ;
52+
53+ let mut matches = src. match_indices ( if preamble. is_empty ( ) {
54+ removed
55+ } else {
56+ preamble
57+ } ) ;
58+ let Some ( ( preamble_start, _) ) = matches. next ( ) else {
59+ e ! (
60+ "Failure on applying a diff: \n couldn't find the following text:\n {preamble}\n \n In \
61+ the following text:\n {src}"
62+ )
63+ } ;
64+
65+ assert ! (
66+ matches. next( ) . is_none( ) ,
67+ "Failure on applying a diff: \n Ambiguous preamble:\n {preamble}\n In the following \
68+ text:\n {src}\n While trying to remove the following text:\n {removed}\n And add the \
69+ following:\n {added}\n "
70+ ) ;
71+
72+ let preamble_end = preamble_start + preamble. len ( ) ;
73+ assert ! (
74+ src. get( preamble_end..preamble_end + removed. len( ) ) == Some ( removed) ,
75+ "Failure on applying a diff: \n Text to remove not found:\n {removed}\n \n In the following \
76+ text:\n {src}",
77+ ) ;
78+
79+ src. replace_range ( preamble_end..preamble_end + removed. len ( ) , added) ;
80+ Ok ( ( ) )
81+ }
3382
34- for part in rel {
35- parts. push ( part. to_str ( ) . unwrap ( ) ) ;
83+ fn combined_code_blocks ( path : & Path ) -> Result < String > {
84+ let file = BufReader :: new ( File :: open ( path) ?) ;
85+ let mut res = String :: new ( ) ;
86+
87+ let mut err = Ok ( ( ) ) ;
88+ let mut lines = file
89+ . lines ( )
90+ . filter_map ( |i| i. map_err ( |e| err = Err ( e) ) . ok ( ) ) ;
91+ while let Some ( line) = lines. next ( ) {
92+ if !line. starts_with ( "```rust" ) {
93+ continue ;
3694 }
3795
38- level. insert ( path. clone ( ) , & parts[ ..] ) ;
96+ let mut preamble = String :: new ( ) ;
97+ let mut added = String :: new ( ) ;
98+ let mut removed = String :: new ( ) ;
99+ let mut diff_applied = false ;
100+ for line in & mut lines {
101+ if line. starts_with ( "```" ) {
102+ if !added. is_empty ( ) || !removed. is_empty ( ) {
103+ apply_diff ( & mut res, & preamble, & added, & removed) ?;
104+ } else if !diff_applied {
105+ // if no diff markers were found, just add the contents
106+ res += & preamble;
107+ }
108+ break ;
109+ } else if let Some ( line) = line. strip_prefix ( '+' ) {
110+ if line. starts_with ( char:: is_whitespace) {
111+ added += " " ;
112+ }
113+ added += line;
114+ added += "\n " ;
115+ } else if let Some ( line) = line. strip_prefix ( '-' ) {
116+ if line. starts_with ( char:: is_whitespace) {
117+ removed += " " ;
118+ }
119+ removed += line;
120+ removed += "\n " ;
121+ } else if line. trim_ascii ( ) == "// ..." {
122+ // disregard the preamble
123+ preamble. clear ( ) ;
124+ } else {
125+ if !added. is_empty ( ) || !removed. is_empty ( ) {
126+ diff_applied = true ;
127+ apply_diff ( & mut res, & preamble, & added, & removed) ?;
128+ preamble += & added;
129+ added. clear ( ) ;
130+ removed. clear ( ) ;
131+ }
132+ preamble += & line;
133+ preamble += "\n " ;
134+ }
135+ }
39136 }
40137
41- let out = format ! ( "{}/website_tests.rs" , env:: var( "OUT_DIR" ) . unwrap( ) ) ;
42-
43- fs:: write ( out, level. to_contents ( ) ) . unwrap ( ) ;
138+ Ok ( res)
44139}
45140
46141impl Level {
@@ -53,14 +148,14 @@ impl Level {
53148 }
54149 }
55150
56- fn to_contents ( & self ) -> String {
151+ fn to_contents ( & self ) -> Result < String > {
57152 let mut dst = String :: new ( ) ;
58153
59- self . write_inner ( & mut dst, 0 ) . unwrap ( ) ;
60- dst
154+ self . write_inner ( & mut dst, 0 ) ? ;
155+ Ok ( dst)
61156 }
62157
63- fn write_into ( & self , dst : & mut String , name : & str , level : usize ) -> fmt :: Result {
158+ fn write_into ( & self , dst : & mut String , name : & str , level : usize ) -> Result {
64159 self . write_space ( dst, level) ;
65160 let name = name. replace ( [ '-' , '.' ] , "_" ) ;
66161 writeln ! ( dst, "pub mod {name} {{" ) ?;
@@ -73,24 +168,33 @@ impl Level {
73168 Ok ( ( ) )
74169 }
75170
76- fn write_inner ( & self , dst : & mut String , level : usize ) -> fmt :: Result {
171+ fn write_inner ( & self , dst : & mut String , level : usize ) -> Result {
77172 for ( name, nested) in & self . nested {
78173 nested. write_into ( dst, name, level) ?;
79174 }
80175
81- self . write_space ( dst, level) ;
82-
83176 for file in & self . files {
84- let stem = Path :: new ( file)
177+ let stem = file
85178 . file_stem ( )
86- . unwrap ( )
179+ . ok_or_else ( || format ! ( "no filename in path {file:?}" ) ) ?
87180 . to_str ( )
88- . unwrap ( )
181+ . ok_or_else ( || format ! ( "non-UTF8 path: {file:?}" ) ) ?
89182 . replace ( '-' , "_" ) ;
90183
91- self . write_space ( dst, level) ;
92-
93- writeln ! ( dst, "#[doc = include_str!(r\" {}\" )]" , file. display( ) ) ?;
184+ if should_combine_code_blocks ( file) ? {
185+ let res = combined_code_blocks ( file) ?;
186+ self . write_space ( dst, level) ;
187+ writeln ! ( dst, "/// ```rust, no_run" ) ?;
188+ for line in res. lines ( ) {
189+ self . write_space ( dst, level) ;
190+ writeln ! ( dst, "/// {line}" ) ?;
191+ }
192+ self . write_space ( dst, level) ;
193+ writeln ! ( dst, "/// ```" ) ?;
194+ } else {
195+ self . write_space ( dst, level) ;
196+ writeln ! ( dst, "#[doc = include_str!(r\" {}\" )]" , file. display( ) ) ?;
197+ }
94198 self . write_space ( dst, level) ;
95199 writeln ! ( dst, "pub fn {stem}_md() {{}}" ) ?;
96200 }
@@ -104,3 +208,48 @@ impl Level {
104208 }
105209 }
106210}
211+
212+ fn inner_main ( ) -> Result {
213+ let home = env:: var ( "CARGO_MANIFEST_DIR" ) ?;
214+ let pattern = format ! ( "{home}/../../website/docs/**/*.md*" ) ;
215+ let base = format ! ( "{home}/../../website" ) ;
216+ let base = Path :: new ( & base) . canonicalize ( ) ?;
217+ let dir_pattern = format ! ( "{home}/../../website/docs/**" ) ;
218+ for dir in glob ( & dir_pattern) ? {
219+ println ! ( "cargo:rerun-if-changed={}" , dir?. display( ) ) ;
220+ }
221+
222+ let mut level = Level :: default ( ) ;
223+
224+ for entry in glob ( & pattern) ? {
225+ let path = entry?. canonicalize ( ) ?;
226+ println ! ( "cargo:rerun-if-changed={}" , path. display( ) ) ;
227+ let rel = path. strip_prefix ( & base) ?;
228+
229+ let mut parts = vec ! [ ] ;
230+
231+ for part in rel {
232+ parts. push (
233+ part. to_str ( )
234+ . ok_or_else ( || format ! ( "Non-UTF8 path: {rel:?}" ) ) ?,
235+ ) ;
236+ }
237+
238+ level. insert ( path. clone ( ) , & parts[ ..] ) ;
239+ }
240+
241+ let out = format ! ( "{}/website_tests.rs" , env:: var( "OUT_DIR" ) ?) ;
242+
243+ fs:: write ( out, level. to_contents ( ) ?) ?;
244+ Ok ( ( ) )
245+ }
246+
247+ fn main ( ) -> ExitCode {
248+ match inner_main ( ) {
249+ Ok ( _) => ExitCode :: SUCCESS ,
250+ Err ( e) => {
251+ eprintln ! ( "{e}" ) ;
252+ ExitCode :: FAILURE
253+ }
254+ }
255+ }
0 commit comments