use crate::lexer::{Lexer, Token};
use crate::utils::build_string;
use log::{debug, info};
pub struct ConfigWriter {
tokens: Vec<Token>,
token: Token,
position: usize,
read_position: usize,
output: Vec<String>,
}
impl ConfigWriter {
pub fn new(config: &str) -> ConfigWriter {
let mut lexer = Lexer::new(config);
let tokens = lexer.tokenize();
debug!("Tokenization done!!\n\n\n");
info!("{:#?}", tokens);
let output: Vec<String> = Vec::new();
return ConfigWriter {
tokens: tokens,
token: Token::Start,
position: 0,
read_position: 0,
output: output,
};
}
pub fn write_configs(&mut self) -> String {
self.read_token();
let mut stanza_stack_record: Vec<Vec<String>> = Vec::new();
let mut stanza_stack: Vec<String> = Vec::new();
let mut stanza_pointer: usize = 0;
let mut config_line_stack: Vec<String> = Vec::new();
let mut inside_bracket_array: bool = false;
let mut next_inactive: bool = false;
let mut next_protect: bool = false;
while self.token != Token::Eof {
debug!("write_configs: {} {}", self.read_position, self.token);
match &self.token {
Token::LeftSquirly => {
debug!("LeftSquirly");
if next_inactive {
let addition = build_string(&stanza_stack_record, &config_line_stack)
.replace("set", "deactivate");
self.output.push(addition);
next_inactive = false;
} else if next_protect {
let addition = build_string(&stanza_stack_record, &config_line_stack)
.replace("set", "protect");
self.output.push(addition);
next_protect = false;
}
stanza_pointer += 1;
stanza_stack_record.push(stanza_stack.clone());
stanza_stack.clear();
config_line_stack.clear();
}
Token::LeftBracket => {
debug!("LeftBracket");
inside_bracket_array = true;
}
Token::RightBracket => {
debug!("RightBracket");
inside_bracket_array = false;
config_line_stack.clear();
}
Token::Semicolon => {
debug!("Semicolon");
if self.tokens[self.read_position - 2] == Token::RightBracket {
stanza_stack.clear();
}
config_line_stack.clear();
}
Token::RightSquirly => {
debug!("RightSquirly");
stanza_pointer -= 1;
stanza_stack.clear();
stanza_stack_record.pop();
}
Token::Pound => {
debug!("Pound");
self.move_past_comment();
}
Token::Identifier(string) => {
debug!("Identifier");
let statement = string;
if statement.ends_with(';') {
config_line_stack.push(statement.clone().to_owned());
let addition = build_string(&stanza_stack_record, &config_line_stack);
if next_inactive {
let deactivate = addition.clone().replace("set", "deactivate");
next_inactive = false;
self.output.push(addition);
self.output.push(deactivate);
} else if next_protect {
let protect = addition.clone().replace("set", "protect");
next_protect = false;
self.output.push(addition);
self.output.push(protect);
} else {
self.output.push(addition);
}
config_line_stack.clear();
stanza_stack.clear();
} else if inside_bracket_array {
config_line_stack.push(statement.clone().to_owned());
let addition = build_string(&stanza_stack_record, &config_line_stack);
self.output.push(addition);
config_line_stack.pop();
} else if statement == "inactive:" {
next_inactive = true;
} else if statement == "protect:" {
next_protect = true;
} else if statement == "replace:" {
()
} else {
debug!("non terminating statement {statement}");
stanza_stack.push(statement.clone().to_owned());
config_line_stack.push(statement.clone().to_owned());
}
}
_ => debug!("hit default case for {}", self.token),
}
self.read_token();
debug!("stanza_stack {:#?}", stanza_stack);
debug!("stanza_pointer {stanza_pointer}");
debug!("stanza_stack_record {:#?}", stanza_stack_record);
debug!("config_line_stack {:#?}", config_line_stack);
}
return self.output.join("\n");
}
fn read_token(&mut self) {
if self.read_position >= self.tokens.len() {
self.token = Token::Eof;
} else {
self.token = self.tokens[self.read_position].clone();
}
self.position = self.read_position;
self.read_position += 1;
}
fn move_past_comment(&mut self) {
info!("move_past_comment");
loop {
let next_token = self.tokens[self.read_position].clone();
if next_token == Token::NewLine || next_token == Token::RightSquirly {
break;
}
match &next_token {
Token::Identifier(string) => {
let statement = string;
if statement.ends_with(';') {
break;
}
}
_ => (),
}
self.token = self.tokens[self.read_position].clone();
info!("skip {}", self.token);
self.position = self.read_position;
self.read_position += 1;
}
}
}
#[cfg(test)]
mod test {
use super::{ConfigWriter, Lexer, Token};
#[test]
fn basic_identifiers_test() {
let input = String::from("{ as-path test another-statement-123 } } }");
let mut lexer = Lexer::new(&input);
let tokens: Vec<Token> = vec![
Token::LeftSquirly,
Token::Identifier("as-path".to_string()),
Token::Identifier("test".to_string()),
Token::Identifier("another-statement-123".to_string()),
Token::RightSquirly,
Token::RightSquirly,
Token::RightSquirly,
Token::Eof,
];
for token in tokens {
let next_token = lexer.next_token();
println!("expected: {:?}, received {:?}", token, next_token);
assert_eq!(token, next_token);
}
}
#[test]
fn basic_config_convert_1() {
let input = String::from(
"system {
host-name myrouter;
services {
ftp;
ssh;
telnet;
netconf {
ssh;
}
}
}",
);
let expected = String::from(
"set system host-name myrouter
set system services ftp
set system services ssh
set system services telnet
set system services netconf ssh",
);
let mut config_writer = ConfigWriter::new(&input);
let result = config_writer.write_configs();
assert_eq!(result, expected);
}
#[test]
fn basic_config_convert_2() {
let input = String::from(
"policy-options {
policy-statement directs {
term Lo0 {
from {
protocol direct;
route-filter 192.168.100.0/24 orlonger;
}
then accept;
}
}
}",
);
let expected = String::from(
"set policy-options policy-statement directs term Lo0 from protocol direct
set policy-options policy-statement directs term Lo0 from route-filter 192.168.100.0/24 orlonger
set policy-options policy-statement directs term Lo0 then accept",
);
let mut config_writer = ConfigWriter::new(&input);
let result = config_writer.write_configs();
assert_eq!(result, expected);
}
}