exemplify_lib/layers/domain/transforms/
asciidoctor_transform.rs1use std::pin::Pin;
2
3use futures::{Stream, StreamExt};
4use crate::layers::domain::entities::{Example, Printable};
5
6pub struct AsciidoctorSettings {
7 pub callout_token: String
8}
9
10pub struct AsciidoctorExample {
11 inner: Example
12}
13
14impl Printable for AsciidoctorExample {
15 fn print(&self) -> String {
16 self.inner.print()
17 }
18
19 fn file_name(&self) -> String {
20 format!("{}.adoc", self.inner.name)
21 }
22}
23
24pub fn map_to_asciidoctor(input: Pin<Box<dyn Stream<Item=Example>>>, settings: AsciidoctorSettings) -> Pin<Box<dyn Stream<Item=Result<AsciidoctorExample, String>>>> {
25 Box::pin(input.map(move |example| {
26 let header = create_asciidoc_source_header(&settings, &example);
27 let footer = create_asciidoc_source_footer(&settings);
28
29 let content = transform_callouts(example.content, &settings.callout_token)?;
30 let callouts = content.1
31 .into_iter()
32 .map(|callout| format!("<{}> {}", callout.number, callout.text))
33 .collect();
34
35 Ok(AsciidoctorExample {
36 inner: Example {
37 name: example.name,
38 content: vec![
39 header,
40 content.0,
41 footer,
42 callouts
43 ].into_iter().flatten().collect(),
44 title: example.title,
45 language: example.language,
46 id: example.id
47 }
48 })
49 }))
50}
51
52fn create_asciidoc_source_header(_settings: &AsciidoctorSettings, example: &Example) -> Vec<String> {
53 let title = match &example.title {
54 Some(title) => vec![format!(".{}", title)],
55 _ => vec![]
56 };
57
58 let id = match &example.id {
59 Some(id) => vec![format!("[#{}]", id)],
60 _ => vec![]
61 };
62
63 vec![
64 title,
65 id,
66 vec![format!("[source{}]", match &example.language {
67 Some(language) => format!(",{}", language),
68 _ => "".into()
69 })],
70 vec!["----".into()]
71 ].into_iter().flatten().collect()
72}
73
74fn create_asciidoc_source_footer(_settings: &AsciidoctorSettings) -> Vec<String> {
75 vec![
76 "----".into()
77 ]
78}
79
80struct Callout {
81 text: String,
82 number: usize
83}
84
85fn transform_callouts(input: Vec<String>, callout_token: &String) -> Result<(Vec<String>, Vec<Callout>), String> {
86 let mut callout_number = 1;
87 let mut output = Vec::new();
88 let mut callouts= Vec::new();
89
90 for mut line in input {
91 loop {
92 if line.contains(callout_token) {
93 line = line.replacen(callout_token, format!("<{}>", callout_number).as_str(), 1);
94 let extract = extract_first_callout(line, callout_number)
95 .map_err(|e| format!("Failed extracting callout from {}", e))?;
96
97 line = extract.0;
98 callouts.push(extract.1);
99
100 callout_number += 1;
101 } else {
102 break;
103 }
104 }
105
106 output.push(line);
107 }
108
109 Ok((output, callouts))
110}
111
112fn extract_first_callout(mut input: String, idx: usize) -> Result<(String, Callout), String> {
113 lazy_static::lazy_static! {
114 static ref CALLOUT_RE: regex::Regex = regex::Regex::new("\\{value=\"(.+)\"\\}").unwrap();
115 }
116
117 for val in CALLOUT_RE.captures_iter(input.clone().as_str()) {
118 let value = val.get(1);
119
120 if let Some(value) = value {
121 input = CALLOUT_RE.replace(input.as_str(), "").to_string();
122
123 return Ok((input, Callout {
124 text: value.as_str().to_string().clone(),
125 number: idx
126 }))
127 }
128 }
129
130 Err(input)
131}