1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::pin::Pin;

use futures::{Stream, StreamExt};
use crate::layers::domain::entities::{Example, Printable};

pub struct AsciidoctorSettings {
    pub callout_token: String
}

pub struct AsciidoctorExample {
    inner: Example
}

impl Printable for AsciidoctorExample {
    fn print(&self) -> String {
        self.inner.print()
    }

    fn file_name(&self) -> String {
        format!("{}.adoc", self.inner.name)
    }
}

pub fn map_to_asciidoctor(input: Pin<Box<dyn Stream<Item=Example>>>, settings: AsciidoctorSettings) -> Pin<Box<dyn Stream<Item=Result<AsciidoctorExample, String>>>> {
    Box::pin(input.map(move |example| {
        let header = create_asciidoc_source_header(&settings, &example);
        let footer = create_asciidoc_source_footer(&settings);

        let content = transform_callouts(example.content, &settings.callout_token)?;
        let callouts = content.1
            .into_iter()
            .map(|callout| format!("<{}> {}", callout.number, callout.text))
            .collect();

        Ok(AsciidoctorExample {
            inner: Example {
                name: example.name,
                content: vec![
                    header,
                    content.0,
                    footer,
                    callouts
                ].into_iter().flatten().collect(),
                title: example.title,
                language: example.language,
            }
        })
    }))
}

fn create_asciidoc_source_header(_settings: &AsciidoctorSettings, example: &Example) -> Vec<String> {
    let title = match &example.title {
        Some(title) => vec![format!(".{}", title)],
        _ => vec![]
    };

    vec![
        title,
        vec![format!("[source{}]", match &example.language {
            Some(language) => format!(",{}", language),
            _ => "".into()
        })],
        vec!["----".into()]
    ].into_iter().flatten().collect()
}

fn create_asciidoc_source_footer(_settings: &AsciidoctorSettings) -> Vec<String> {
    vec![
        "----".into()
    ]
}

struct Callout {
    text: String,
    number: usize
}

fn transform_callouts(input: Vec<String>, callout_token: &String) -> Result<(Vec<String>, Vec<Callout>), String> {
    let mut callout_number = 1;
    let mut output = Vec::new();
    let mut callouts= Vec::new();

    for mut line in input {
        loop {
            if line.contains(callout_token) {
                line = line.replacen(callout_token, format!("<{}>", callout_number).as_str(), 1);
                let extract = extract_first_callout(line, callout_number)
                    .map_err(|e| format!("Failed extracting callout from {}", e))?;

                line = extract.0;
                callouts.push(extract.1);

                callout_number += 1;
            } else {
                break;
            }
        }

        output.push(line);
    }

    Ok((output, callouts))
}

fn extract_first_callout(mut input: String, idx: usize) -> Result<(String, Callout), String> {
    lazy_static::lazy_static! {
        static ref CALLOUT_RE: regex::Regex = regex::Regex::new("\\{value=\"(.+)\"\\}").unwrap();
    }

    for val in CALLOUT_RE.captures_iter(input.clone().as_str()) {
        let value = val.get(1);

        if let Some(value) = value {
            input = CALLOUT_RE.replace(input.as_str(), "").to_string();

            return Ok((input, Callout {
                text: value.as_str().to_string().clone(),
                number: idx
            }))
        }
    }

    Err(input)
}