codegenr_lib/processor/
mod.rs

1use std::collections::HashMap;
2
3mod clean;
4mod console;
5mod file;
6
7use clean::*;
8use console::*;
9use file::*;
10
11static INSTRUCTION_LINE_REGEX: once_cell::sync::Lazy<regex::Regex> =
12  once_cell::sync::Lazy::new(|| regex::Regex::new("^###.*$").expect("The INSTRUCTION_LINE_REGEX regex did not compile."));
13
14pub trait Instruction {
15  fn command_name(&self) -> &'static str;
16  fn start(&self, params: Vec<String>) -> Result<Box<dyn InstructionLineHandler>, anyhow::Error>;
17  fn needs_closing(&self) -> bool {
18    false
19  }
20}
21
22pub trait InstructionLineHandler {
23  fn handle_line(&self, line: &str) -> Result<(), anyhow::Error>;
24}
25
26pub struct TranscientLineHandler;
27
28impl InstructionLineHandler for TranscientLineHandler {
29  fn handle_line(&self, _line: &str) -> Result<(), anyhow::Error> {
30    Ok(())
31  }
32}
33
34fn get_instructions(output: String) -> HashMap<&'static str, Box<dyn Instruction>> {
35  let mut hash: HashMap<&'static str, Box<dyn Instruction>> = HashMap::<_, _>::with_capacity(3);
36  hash.insert(CLEAN, Box::new(CleanInstruction::new(output.clone())) as Box<dyn Instruction>);
37  hash.insert(FILE, Box::new(FileInstruction::new(output)) as Box<dyn Instruction>);
38  hash.insert(CONSOLE, Box::new(ConsoleInstruction) as Box<dyn Instruction>);
39  hash
40}
41
42pub fn process(content: &str, output: String) -> Result<(), anyhow::Error> {
43  let instructions = get_instructions(output);
44  let mut active_handlers = HashMap::<String, Box<dyn InstructionLineHandler>>::new();
45
46  for (line_number, line) in content.lines().enumerate() {
47    let captures = INSTRUCTION_LINE_REGEX.find(line);
48    match captures {
49      None => {
50        for (_, h) in active_handlers.iter() {
51          h.handle_line(line)?;
52        }
53      }
54      Some(_match) => {
55        let net_line = line.trim_start_matches('#').trim_start();
56        let is_closing = net_line.starts_with('/');
57        let net_line = net_line.trim_start_matches('/').trim_start();
58
59        let mut words = net_line.split(' ').map(|s| s.trim()).filter(|s| !s.is_empty());
60        let instruction_name = words
61          .next()
62          .ok_or_else(|| anyhow::anyhow!("Instruction name not found on line {}: `{}`.", line_number, line))?
63          .to_uppercase();
64
65        let instruction = instructions.get(&instruction_name.as_ref()).ok_or_else(|| {
66          anyhow::anyhow!(
67            "Instruction `{}` doest not exist. Line {}: `{}`.",
68            instruction_name,
69            line_number,
70            line
71          )
72        })?;
73
74        match (is_closing, instruction.needs_closing()) {
75          (true, false) => {
76            return Err(anyhow::anyhow!(
77              "Closing tag found for `{}` instruction while it does not need it. Line {}: `{}`.",
78              instruction_name,
79              line_number,
80              line
81            ));
82          }
83          (true, true) => {
84            active_handlers.remove(&instruction_name).ok_or_else(|| {
85              anyhow::anyhow!(
86                "Missing openning tag for `{}` instruction. Line {}: `{}`.",
87                instruction_name,
88                line_number,
89                line
90              )
91            })?;
92          }
93          (false, _) => {
94            let handler = instruction.start(words.map(Into::into).collect())?;
95            if instruction.needs_closing() {
96              active_handlers.insert(instruction_name, handler);
97            }
98          }
99        }
100      }
101    }
102  }
103
104  Ok(())
105}
106
107#[cfg(test)]
108mod test {
109  use super::*;
110
111  #[test]
112  #[ignore]
113  fn process_test() -> Result<(), anyhow::Error> {
114    process(
115      r#"
116### FILE plop.rs
117test
118### /FILE
119### CONSOLE
120Hello
121###/ console
122### FILE plop2.rs
123test2
124### / FILE
125    "#,
126      ".".into(),
127    )?;
128
129    Ok(())
130  }
131}