@@ -5,6 +5,8 @@ use std::{
5
5
} ;
6
6
7
7
const LISTING_DELIMITER : & ' static str = "----" ;
8
+ const IMAGE_BLOCK_PREFIX : & ' static str = "image::" ;
9
+ const VIDEO_BLOCK_PREFIX : & ' static str = "video::" ;
8
10
9
11
struct Converter < ' a , ' b , R : BufRead > {
10
12
iter : & ' a mut Peekable < Lines < R > > ,
@@ -33,6 +35,12 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
33
35
self . process_source_code_block ( 0 ) ?;
34
36
} else if line. starts_with ( LISTING_DELIMITER ) {
35
37
self . process_listing_block ( None , 0 ) ?;
38
+ } else if line. starts_with ( '.' ) {
39
+ self . process_block_with_title ( 0 ) ?;
40
+ } else if line. starts_with ( IMAGE_BLOCK_PREFIX ) {
41
+ self . process_image_block ( None , 0 ) ?;
42
+ } else if line. starts_with ( VIDEO_BLOCK_PREFIX ) {
43
+ self . process_video_block ( None , 0 ) ?;
36
44
} else {
37
45
self . process_paragraph ( 0 ) ?;
38
46
}
@@ -95,6 +103,15 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
95
103
} else if line. starts_with ( LISTING_DELIMITER ) {
96
104
self . write_line ( "" , 0 ) ;
97
105
self . process_listing_block ( None , 1 ) ?;
106
+ } else if line. starts_with ( '.' ) {
107
+ self . write_line ( "" , 0 ) ;
108
+ self . process_block_with_title ( 1 ) ?;
109
+ } else if line. starts_with ( IMAGE_BLOCK_PREFIX ) {
110
+ self . write_line ( "" , 0 ) ;
111
+ self . process_image_block ( None , 1 ) ?;
112
+ } else if line. starts_with ( VIDEO_BLOCK_PREFIX ) {
113
+ self . write_line ( "" , 0 ) ;
114
+ self . process_video_block ( None , 1 ) ?;
98
115
} else {
99
116
self . write_line ( "" , 0 ) ;
100
117
self . process_paragraph ( 1 ) ?;
@@ -145,6 +162,75 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
145
162
bail ! ( "not a listing block" )
146
163
}
147
164
165
+ fn process_block_with_title ( & mut self , level : usize ) -> anyhow:: Result < ( ) > {
166
+ if let Some ( Ok ( line) ) = self . iter . next ( ) {
167
+ let title =
168
+ line. strip_prefix ( '.' ) . ok_or_else ( || anyhow ! ( "extraction of the title failed" ) ) ?;
169
+
170
+ let line = self
171
+ . iter
172
+ . peek ( )
173
+ . ok_or_else ( || anyhow ! ( "target block for the title is not found" ) ) ?;
174
+ let line = line. as_deref ( ) . map_err ( |e| anyhow ! ( "{e}" ) ) ?;
175
+ if line. starts_with ( IMAGE_BLOCK_PREFIX ) {
176
+ return self . process_image_block ( Some ( title) , level) ;
177
+ } else if line. starts_with ( VIDEO_BLOCK_PREFIX ) {
178
+ return self . process_video_block ( Some ( title) , level) ;
179
+ } else {
180
+ bail ! ( "title for that block type is not supported" ) ;
181
+ }
182
+ }
183
+ bail ! ( "not a title" )
184
+ }
185
+
186
+ fn process_image_block ( & mut self , caption : Option < & str > , level : usize ) -> anyhow:: Result < ( ) > {
187
+ if let Some ( Ok ( line) ) = self . iter . next ( ) {
188
+ if let Some ( ( url, attrs) ) = parse_media_block ( & line, IMAGE_BLOCK_PREFIX ) {
189
+ let alt = if let Some ( stripped) =
190
+ attrs. strip_prefix ( '"' ) . and_then ( |s| s. strip_suffix ( '"' ) )
191
+ {
192
+ stripped
193
+ } else {
194
+ attrs
195
+ } ;
196
+ if let Some ( caption) = caption {
197
+ self . write_caption_line ( caption, level) ;
198
+ }
199
+ self . write_indent ( level) ;
200
+ self . output . push_str ( " ;
203
+ self . output . push_str ( url) ;
204
+ self . output . push_str ( ")\n " ) ;
205
+ return Ok ( ( ) ) ;
206
+ }
207
+ }
208
+ bail ! ( "not a image block" )
209
+ }
210
+
211
+ fn process_video_block ( & mut self , caption : Option < & str > , level : usize ) -> anyhow:: Result < ( ) > {
212
+ if let Some ( Ok ( line) ) = self . iter . next ( ) {
213
+ if let Some ( ( url, attrs) ) = parse_media_block ( & line, VIDEO_BLOCK_PREFIX ) {
214
+ let html_attrs = match attrs {
215
+ "options=loop" => "controls loop" ,
216
+ r#"options="autoplay,loop""# => "autoplay controls loop" ,
217
+ _ => bail ! ( "unsupported video syntax" ) ,
218
+ } ;
219
+ if let Some ( caption) = caption {
220
+ self . write_caption_line ( caption, level) ;
221
+ }
222
+ self . write_indent ( level) ;
223
+ self . output . push_str ( r#"<video src=""# ) ;
224
+ self . output . push_str ( url) ;
225
+ self . output . push_str ( r#"" "# ) ;
226
+ self . output . push_str ( html_attrs) ;
227
+ self . output . push_str ( ">Your browser does not support the video tag.</video>\n " ) ;
228
+ return Ok ( ( ) ) ;
229
+ }
230
+ }
231
+ bail ! ( "not a video block" )
232
+ }
233
+
148
234
fn process_paragraph ( & mut self , level : usize ) -> anyhow:: Result < ( ) > {
149
235
while let Some ( line) = self . iter . peek ( ) {
150
236
let line = line. as_deref ( ) . map_err ( |e| anyhow ! ( "{e}" ) ) ?;
@@ -192,6 +278,13 @@ impl<'a, 'b, R: BufRead> Converter<'a, 'b, R> {
192
278
self . output . push ( '\n' ) ;
193
279
}
194
280
281
+ fn write_caption_line ( & mut self , caption : & str , level : usize ) {
282
+ self . write_indent ( level) ;
283
+ self . output . push ( '_' ) ;
284
+ self . output . push_str ( caption) ;
285
+ self . output . push_str ( "_\\ \n " ) ;
286
+ }
287
+
195
288
fn write_indent ( & mut self , level : usize ) {
196
289
for _ in 0 ..level {
197
290
self . output . push_str ( " " ) ;
@@ -249,6 +342,17 @@ fn get_list_item(line: &str) -> Option<&str> {
249
342
}
250
343
}
251
344
345
+ fn parse_media_block < ' a > ( line : & ' a str , prefix : & str ) -> Option < ( & ' a str , & ' a str ) > {
346
+ if let Some ( line) = line. strip_prefix ( prefix) {
347
+ if let Some ( ( url, rest) ) = line. split_once ( '[' ) {
348
+ if let Some ( attrs) = rest. strip_suffix ( ']' ) {
349
+ return Some ( ( url, attrs) ) ;
350
+ }
351
+ }
352
+ }
353
+ None
354
+ }
355
+
252
356
#[ cfg( test) ]
253
357
mod tests {
254
358
use super :: * ;
@@ -272,8 +376,18 @@ Release: release:2022-01-01[]
272
376
+
273
377
image::https://example.com/animation.gif[]
274
378
+
379
+ image::https://example.com/animation.gif[\" alt text\" ]
380
+ +
381
+ video::https://example.com/movie.mp4[options=loop]
382
+ +
275
383
video::https://example.com/movie.mp4[options=\" autoplay,loop\" ]
276
384
+
385
+ .Image
386
+ image::https://example.com/animation.gif[]
387
+ +
388
+ .Video
389
+ video::https://example.com/movie.mp4[options=loop]
390
+ +
277
391
[source,bash]
278
392
----
279
393
rustup update nightly
@@ -325,9 +439,19 @@ Release: release:2022-01-01[]
325
439
- pr:1111[] foo bar baz
326
440
- pr:2222[] foo bar baz
327
441
328
- image::https://example.com/animation.gif[]
442
+ 
443
+
444
+ 
445
+
446
+ <video src=\" https://example.com/movie.mp4\" controls loop>Your browser does not support the video tag.</video>
447
+
448
+ <video src=\" https://example.com/movie.mp4\" autoplay controls loop>Your browser does not support the video tag.</video>
449
+
450
+ _Image_\\
451
+ 
329
452
330
- video::https://example.com/movie.mp4[options=\" autoplay,loop\" ]
453
+ _Video_\\
454
+ <video src=\" https://example.com/movie.mp4\" controls loop>Your browser does not support the video tag.</video>
331
455
332
456
```bash
333
457
rustup update nightly
0 commit comments