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 ( ) ) ;
32+ fn should_combine_code_blocks ( path : & Path ) -> io :: Result < bool > {
33+ const FLAG : & [ u8 ] = b"<!-- COMBINE CODE BLOCKS -->" ;
34+
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 ) ,
2240 }
41+ let mut buf = [ 0u8 ; 32 ] ;
42+ file. read_exact ( & mut buf) ?;
43+ Ok ( buf. trim_ascii_end ( ) . ends_with ( FLAG ) )
44+ }
2345
24- let mut level = Level :: default ( ) ;
46+ fn apply_diff ( src : & mut String , preamble : & str , added : & str , removed : & str )
47+ -> Result
48+ {
49+ assert ! (
50+ !preamble. is_empty( ) || !removed. is_empty( ) ,
51+ "Failure on applying a diff: \n \
52+ No preamble or text to remove provided, unable to find location to insert:\n {added}\n \
53+ In the following text:\n {src}",
54+ ) ;
2555
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 ( ) ;
56+ let mut matches = src. match_indices ( if preamble. is_empty ( ) {
57+ removed
58+ } else {
59+ & preamble
60+ } ) ;
61+ let Some ( ( preamble_start, _) ) = matches. next ( ) else {
62+ e ! ( "Failure on applying a diff: \n \
63+ couldn't find the following text:\n {preamble}\n \n In the following text:\n {src}")
64+ } ;
65+
66+ assert ! (
67+ matches. next( ) . is_none( ) ,
68+ "Failure on applying a diff: \n \
69+ Ambiguous preamble:\n {preamble}\n \
70+ In the following text:\n {src}\n \
71+ While trying to remove the following text:\n {removed}\n \
72+ And add the following:\n {added}\n "
73+ ) ;
3174
32- let mut parts = vec ! [ ] ;
75+ let preamble_end = preamble_start + preamble. len ( ) ;
76+ assert ! (
77+ src. get( preamble_end .. preamble_end + removed. len( ) ) == Some ( removed) ,
78+ "Failure on applying a diff: \n \
79+ Text to remove not found:\n {removed}\n \n In the following text:\n {src}",
80+ ) ;
3381
34- for part in rel {
35- parts. push ( part. to_str ( ) . unwrap ( ) ) ;
82+ src. replace_range ( preamble_end .. preamble_end + removed. len ( ) , added) ;
83+ Ok ( ( ) )
84+ }
85+
86+ fn combined_code_blocks ( path : & Path ) -> Result < String > {
87+ let file = BufReader :: new ( File :: open ( path) ?) ;
88+ let mut res = String :: new ( ) ;
89+
90+ let mut err = Ok ( ( ) ) ;
91+ let mut lines = file. lines ( ) . filter_map ( |i| {
92+ i. map_err ( |e| err = Err ( e) ) . ok ( )
93+ } ) . enumerate ( ) ;
94+ while let Some ( ( i, line) ) = lines. next ( ) {
95+ if !line. starts_with ( "```rust" ) {
96+ continue ;
3697 }
3798
38- level. insert ( path. clone ( ) , & parts[ ..] ) ;
99+ let mut preamble = String :: new ( ) ;
100+ let mut added = String :: new ( ) ;
101+ let mut removed = String :: new ( ) ;
102+ let mut diff_applied = false ;
103+ for ( i, line) in & mut lines {
104+ if line. starts_with ( "```" ) {
105+ if !added. is_empty ( ) || !removed. is_empty ( ) {
106+ apply_diff ( & mut res, & preamble, & added, & removed)
107+ . inspect_err ( |_| eprintln ! ( "Line {i}" ) ) ?;
108+ } else if !diff_applied { // if no diff markers were found, just add the contents
109+ eprintln ! ( "Shrimply added {preamble:?}, line {i}" ) ;
110+ res += & preamble;
111+ }
112+ break ;
113+ } else if let Some ( line) = line. strip_prefix ( '+' ) {
114+ if line. starts_with ( char:: is_whitespace) {
115+ added += " " ;
116+ }
117+ added += line;
118+ added += "\n " ;
119+ } else if let Some ( line) = line. strip_prefix ( '-' ) {
120+ if line. starts_with ( char:: is_whitespace) {
121+ removed += " " ;
122+ }
123+ removed += line;
124+ removed += "\n " ;
125+ } else if line. trim_ascii ( ) == "// ..." { // disregard the preamble
126+ preamble. clear ( ) ;
127+ } else {
128+ if !added. is_empty ( ) || !removed. is_empty ( ) {
129+ diff_applied = true ;
130+ apply_diff ( & mut res, & preamble, & added, & removed)
131+ . inspect_err ( |_| eprintln ! ( "Line {i}" ) ) ?;
132+ preamble += & added;
133+ added. clear ( ) ;
134+ removed. clear ( ) ;
135+ }
136+ preamble += & line;
137+ preamble += "\n " ;
138+ }
139+ }
39140 }
40141
41- let out = format ! ( "{}/website_tests.rs" , env:: var( "OUT_DIR" ) . unwrap( ) ) ;
42-
43- fs:: write ( out, level. to_contents ( ) ) . unwrap ( ) ;
142+ Ok ( res)
44143}
45144
46145impl Level {
@@ -53,14 +152,14 @@ impl Level {
53152 }
54153 }
55154
56- fn to_contents ( & self ) -> String {
155+ fn to_contents ( & self ) -> Result < String > {
57156 let mut dst = String :: new ( ) ;
58157
59- self . write_inner ( & mut dst, 0 ) . unwrap ( ) ;
60- dst
158+ self . write_inner ( & mut dst, 0 ) ? ;
159+ Ok ( dst)
61160 }
62161
63- fn write_into ( & self , dst : & mut String , name : & str , level : usize ) -> fmt :: Result {
162+ fn write_into ( & self , dst : & mut String , name : & str , level : usize ) -> Result {
64163 self . write_space ( dst, level) ;
65164 let name = name. replace ( [ '-' , '.' ] , "_" ) ;
66165 writeln ! ( dst, "pub mod {name} {{" ) ?;
@@ -73,24 +172,33 @@ impl Level {
73172 Ok ( ( ) )
74173 }
75174
76- fn write_inner ( & self , dst : & mut String , level : usize ) -> fmt :: Result {
175+ fn write_inner ( & self , dst : & mut String , level : usize ) -> Result {
77176 for ( name, nested) in & self . nested {
78177 nested. write_into ( dst, name, level) ?;
79178 }
80179
81- self . write_space ( dst, level) ;
82-
83180 for file in & self . files {
84- let stem = Path :: new ( file)
181+ let stem = file
85182 . file_stem ( )
86- . unwrap ( )
183+ . ok_or_else ( || format ! ( "no filename in path {file:?}" ) ) ?
87184 . to_str ( )
88- . unwrap ( )
185+ . ok_or_else ( || format ! ( "non-UTF8 path: {file:?}" ) ) ?
89186 . replace ( '-' , "_" ) ;
90187
91- self . write_space ( dst, level) ;
92-
93- writeln ! ( dst, "#[doc = include_str!(r\" {}\" )]" , file. display( ) ) ?;
188+ if should_combine_code_blocks ( file) ? {
189+ let res = combined_code_blocks ( file) ?;
190+ self . write_space ( dst, level) ;
191+ writeln ! ( dst, "/// ```rust, no_run" ) ?;
192+ for line in res. lines ( ) {
193+ self . write_space ( dst, level) ;
194+ writeln ! ( dst, "/// {line}" ) ?;
195+ }
196+ self . write_space ( dst, level) ;
197+ writeln ! ( dst, "/// ```" ) ?;
198+ } else {
199+ self . write_space ( dst, level) ;
200+ writeln ! ( dst, "#[doc = include_str!(r\" {}\" )]" , file. display( ) ) ?;
201+ }
94202 self . write_space ( dst, level) ;
95203 writeln ! ( dst, "pub fn {stem}_md() {{}}" ) ?;
96204 }
@@ -104,3 +212,45 @@ impl Level {
104212 }
105213 }
106214}
215+
216+ fn inner_main ( ) -> Result {
217+ let home = env:: var ( "CARGO_MANIFEST_DIR" ) ?;
218+ let pattern = format ! ( "{home}/../../website/docs/**/*.md*" ) ;
219+ let base = format ! ( "{home}/../../website" ) ;
220+ let base = Path :: new ( & base) . canonicalize ( ) ?;
221+ let dir_pattern = format ! ( "{home}/../../website/docs/**" ) ;
222+ for dir in glob ( & dir_pattern) ? {
223+ println ! ( "cargo:rerun-if-changed={}" , dir?. display( ) ) ;
224+ }
225+
226+ let mut level = Level :: default ( ) ;
227+
228+ for entry in glob ( & pattern) ? {
229+ let path = entry?. canonicalize ( ) ?;
230+ println ! ( "cargo:rerun-if-changed={}" , path. display( ) ) ;
231+ let rel = path. strip_prefix ( & base) ?;
232+
233+ let mut parts = vec ! [ ] ;
234+
235+ for part in rel {
236+ parts. push ( part. to_str ( ) . ok_or_else ( || format ! ( "Non-UTF8 path: {rel:?}" ) ) ?) ;
237+ }
238+
239+ level. insert ( path. clone ( ) , & parts[ ..] ) ;
240+ }
241+
242+ let out = format ! ( "{}/website_tests.rs" , env:: var( "OUT_DIR" ) ?) ;
243+
244+ fs:: write ( out, level. to_contents ( ) ?) ?;
245+ Ok ( ( ) )
246+ }
247+
248+ fn main ( ) -> ExitCode {
249+ match inner_main ( ) {
250+ Ok ( _) => ExitCode :: SUCCESS ,
251+ Err ( e) => {
252+ eprintln ! ( "{e}" ) ;
253+ ExitCode :: FAILURE
254+ }
255+ }
256+ }
0 commit comments