summaryrefslogtreecommitdiff
path: root/src/expand.rs
blob: 049b04d29a080640e0aaf1f63839382dd1e2e7a9 (plain)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::{collections::HashMap, mem::replace};

use just_fmt::snake_case;

use crate::template::Template;

const IMPL_AREA_BEGIN: &str = "@@@ >>> ";
const IMPL_AREA_END: &str = "@@@ <<<";

const IMPL_BEGIN: &str = ">>>>>>>>>>";

const PARAM_BEGIN: &str = "<<<";
const PARAM_BEND: &str = ">>>";

impl Template {
    pub fn expand(mut self) -> Option<String> {
        // Extract template text
        let expanded = replace(&mut self.template_str, String::default());

        let (expanded, impl_areas) = read_impl_areas(expanded)?;
        let expanded = apply_impls(&self, expanded, impl_areas)?;
        let expanded = apply_param(&self, expanded)?;
        Some(expanded.trim().to_string())
    }
}

/// Read all ImplAreas (HashMap<Name, Codes>)
fn read_impl_areas(content: String) -> Option<(String, HashMap<String, String>)> {
    let mut striped_content = String::new();
    let mut impl_areas: HashMap<String, String> = HashMap::new();

    let mut current_area_name = String::default();
    let mut current_area_codes: Vec<String> = Vec::new();

    for line in content.split("\n") {
        let trimmed_line = line.trim();

        // Implementation block end
        if trimmed_line.starts_with(IMPL_AREA_END) {
            // If the current ImplArea name length is less than 1, it means no block is being matched,
            // so matching fails, exit early
            if current_area_name.len() < 1 {
                return None;
            }

            // Submit Impl Area
            let name = replace(&mut current_area_name, String::default());
            impl_areas.insert(name, current_area_codes.join("\n"));
            current_area_codes.clear();
            continue;
        }

        // Implementation block start
        if trimmed_line.starts_with(IMPL_AREA_BEGIN) {
            // If the current ImplArea name length is greater than 0, it means we are already inside a block,
            // since nesting is not allowed, matching fails, exit early
            if current_area_name.len() > 0 {
                return None;
            }

            // Get a snake_case name
            let snake_name = snake_case!(line.trim_start_matches(IMPL_AREA_BEGIN).trim());
            current_area_name = snake_name;

            // Continue to next line
            continue;
        }

        // During implementation block
        if current_area_name.len() > 0 {
            // Add to current block code
            current_area_codes.push(line.to_string());
            continue;
        } else {
            // Add to remaining content
            striped_content += "\n";
            striped_content += line;
        }
    }

    Some((striped_content, impl_areas))
}

/// Apply Template parameters to implementation block areas
fn apply_impls(
    template: &Template,
    content: String,
    impl_areas: HashMap<String, String>,
) -> Option<String> {
    let mut applied_content = String::new();

    let mut impled_areas: HashMap<String, Vec<String>> = HashMap::new();
    for (impl_area_name, impl_area_template) in impl_areas {
        // Get user-provided parameters
        let impl_items = template.impl_params.get(&impl_area_name);

        // No parameters, return early
        let Some(impl_items) = impl_items else {
            impled_areas.insert(impl_area_name, Vec::new());
            continue;
        };

        let mut impled_area_code_applied = Vec::new();

        // Split items
        for item in impl_items {
            // Get base template
            let mut template = impl_area_template.clone();

            // Extract parameters
            for (param_name, param_value) in item {
                // Apply parameter
                template = template.replace(
                    &format!("{}{}{}", PARAM_BEGIN, param_name, PARAM_BEND),
                    param_value,
                );
            }

            // Add applied template
            impled_area_code_applied.push(template);
        }

        impled_areas.insert(impl_area_name, impled_area_code_applied);
    }

    for line in content.split("\n") {
        let trimmed_line = line.trim();

        // Recognize implementation line
        if trimmed_line.starts_with(IMPL_BEGIN) {
            let impl_name = snake_case!(trimmed_line.trim_start_matches(IMPL_BEGIN).trim());

            // Try to get implementation code block
            let Some(impled_code) = impled_areas.get(&impl_name) else {
                continue;
            };

            applied_content += "\n";
            applied_content += impled_code.join("\n").as_str();
        } else {
            // Other content directly appended
            applied_content += "\n";
            applied_content += line;
        }
    }

    Some(applied_content)
}

fn apply_param(template: &Template, content: String) -> Option<String> {
    let mut content = content;
    for (k, v) in template.params.iter() {
        content = content.replace(&format!("{}{}{}", PARAM_BEGIN, k, PARAM_BEND), v);
    }
    Some(content)
}