exemplify_lib/layers/domain/transforms/
asciidoctor_transform.rs

1use 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}