codegenr_lib/processor/
mod.rs1use 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}