codegenr_lib/processor/
mod.rs

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