extern crate regex;
use regex::Regex;
use std::fmt;
use super::Language;
use super::Translator;
const COLORS_ESCAPE_REGEX: &str = "\x1b\\[[0-9]{1,2}m";
pub struct IOProcessor {
translator: Box<dyn Translator>,
pub language: Language,
escape_colors_regex: Regex, }
#[derive(Copy, Clone, PartialEq, fmt::Debug)]
pub enum ExpressionParserError {
MissingToken,
}
struct ExpressionParserStates {
text: String, expression_token: String, escape_block: bool, backslash: bool, in_expression: bool, previous_state: Option<Box<ExpressionParserStates>>, }
#[allow(dead_code)]
enum ExpressionConversion {
ToLatin,
ToCyrillic,
}
impl IOProcessor {
pub fn new(language: Language, translator: Box<dyn Translator>) -> IOProcessor {
let this_lang_regex: String =
String::from(translator.to_cyrillic(&String::from(COLORS_ESCAPE_REGEX)));
let re: Regex = Regex::new(this_lang_regex.as_str()).unwrap();
IOProcessor {
translator: translator,
language: language,
escape_colors_regex: re,
}
}
pub fn expression_to_latin(&self, expression: &String) -> Result<String, ExpressionParserError> {
self.translate_expression(&expression, ExpressionConversion::ToLatin)
}
#[allow(dead_code)]
pub fn expression_to_cyrillic(&self, expression: &String) -> Result<String, ExpressionParserError> {
self.translate_expression(expression, ExpressionConversion::ToCyrillic)
}
pub fn text_to_latin(&self, text: &String) -> String {
self.translator.to_latin(text)
}
pub fn text_to_cyrillic(&self, text: &String) -> String {
self.escape_cyrillic(self.translator.to_cyrillic(text))
}
fn translate_expression(&self, expression: &String, conversion: ExpressionConversion) -> Result<String, ExpressionParserError> {
let mut states: ExpressionParserStates = ExpressionParserStates::new(None);
for c in expression.chars() {
if c == '(' && !states.backslash {
states.escape_block = false;
states.text.push_str(
match conversion {
ExpressionConversion::ToLatin => self.translator.to_latin(&states.expression_token),
ExpressionConversion::ToCyrillic => {
self.translator.to_cyrillic(&states.expression_token)
}
}.as_str(),
);
states.expression_token = String::new();
states = ExpressionParserStates::new(Some(states));
states.in_expression = true;
states.expression_token.push(c);
continue;
}
if c == ')' && !states.backslash {
states.in_expression = false;
states.expression_token.push(c);
states.text.push_str(
match conversion {
ExpressionConversion::ToLatin => {
self.translator.to_latin(&states.expression_token.clone())
}
ExpressionConversion::ToCyrillic => {
self.translator.to_cyrillic(&states.expression_token.clone())
}
}.as_str(),
);
let expression_output: String = states.text.clone();
if states.backslash || states.in_expression || states.escape_block {
return Err(ExpressionParserError::MissingToken);
}
states = match states.previous_state {
Some(_) => states.restore_previous_state(),
None => return Err(ExpressionParserError::MissingToken),
};
states.text.push_str(expression_output.as_str());
continue;
} if c == '"' && !states.backslash {
if states.escape_block {
states.expression_token.push(c);
states.text.push_str(states.expression_token.as_str());
states.expression_token = String::new();
} else {
states.text.push_str(
match conversion {
ExpressionConversion::ToLatin => self.translator.to_latin(&states.expression_token),
ExpressionConversion::ToCyrillic => {
self.translator.to_cyrillic(&states.expression_token)
}
}.as_str(),
);
states.expression_token = String::new();
states.expression_token.push(c);
}
states.escape_block = !states.escape_block;
continue;
}
if c == '\\' {
states.backslash = true;
states.expression_token.push(c);
continue;
} else {
states.backslash = false; }
states.expression_token.push(c);
} states.text.push_str(
match conversion {
ExpressionConversion::ToLatin => self.translator.to_latin(&states.expression_token),
ExpressionConversion::ToCyrillic => self.translator.to_cyrillic(&states.expression_token),
}.as_str(),
);
if states.backslash || states.in_expression || states.escape_block || states.previous_state.is_some() {
return Err(ExpressionParserError::MissingToken);
}
Ok(states.text)
}
fn escape_cyrillic(&self, cyrillic_text: String) -> String {
self.escape_colors(cyrillic_text)
}
fn escape_colors(&self, cyrillic_text: String) -> String {
let mut res: String = cyrillic_text.clone();
for regex_match in self.escape_colors_regex.captures_iter(cyrillic_text.clone().as_str()) {
let mtch: String = String::from(®ex_match[0]);
let replace_with: String = self.text_to_latin(&mtch);
res = res.replace(mtch.as_str(), replace_with.as_str());
}
res
}
}
impl ExpressionParserStates {
fn new(previous_state: Option<ExpressionParserStates>) -> ExpressionParserStates {
ExpressionParserStates {
text: String::new(),
expression_token: String::new(),
escape_block: false,
backslash: false,
in_expression: false,
previous_state: match previous_state {
None => None,
Some(prev_state) => Some(Box::new(prev_state)),
},
}
}
fn clone(strref: &ExpressionParserStates) -> ExpressionParserStates {
ExpressionParserStates {
text: strref.text.clone(), expression_token: strref.expression_token.clone(), escape_block: strref.escape_block,
backslash: strref.backslash,
in_expression: strref.in_expression,
previous_state: match &strref.previous_state {
None => None,
Some(state_box) => Some(Box::new(ExpressionParserStates::clone(state_box.as_ref()))),
},
}
}
fn restore_previous_state(&mut self) -> ExpressionParserStates {
match &self.previous_state {
None => panic!("ParserState has no previous state"),
Some(prev_state) => ExpressionParserStates::clone(prev_state.as_ref()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::translator::{new_translator, Language};
#[test]
fn to_cyrillic_simple() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("Привет Мир!");
assert_eq!(iop.text_to_latin(&input), String::from("Privet Mir!"));
}
#[test]
fn to_cyrillic_expressions() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("экхо фообар");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo foobar")
);
let input: String = String::from("echo foobar");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo foobar")
);
let input: String = String::from("экхо \"привет\"");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo \"привет\"")
);
let input: String = String::from("экхо \\\"привет\\\"");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo \\\"privet\\\"")
);
let input: String = String::from("экхо ₽(хостнамэ)");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo $(hostname)")
);
let input: String = String::from("экхо ₽(кат \"/tmp/РЭАДМЭ.ткст\")");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo $(cat \"/tmp/РЭАДМЭ.ткст\")")
);
let input: String = String::from("экхо ₽(кат \"/tmp/Ивана_\\(дочка\\).ткст\")");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo $(cat \"/tmp/Ивана_\\(дочка\\).ткст\")")
);
let input: String = String::from("экхо ₽(хостнамэ) ₽(экхо ₽(хомэ)/₽(вьхоами))");
assert_eq!(
iop.expression_to_latin(&input).unwrap(),
String::from("echo $(hostname) $(echo $(home)/$(whoami))")
);
}
#[test]
#[should_panic]
fn to_cyrillic_missing_token_parenthesis() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("экхо ₽(хостнамэ");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
#[should_panic]
fn to_cyrillic_missing_token_quotes() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("экхо \"привет");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
#[should_panic]
fn to_cyrillic_missing_token_backslash() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("экхо \"привет\\");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
fn to_latin_simple() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("Hello World!");
assert_eq!(iop.text_to_cyrillic(&input), String::from("Хелло Уорлд!"));
}
#[test]
fn to_latin_expressions() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("echo foobar");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо фообар")
);
let input: String = String::from("echo \"hello world\"");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо \"hello world\"")
);
let input: String = String::from("echo \\\"Privet\\\"");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо \\\"Привет\\\"")
);
let input: String = String::from("echo $(hostname)");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо $(хостнаме)")
);
let input: String = String::from("echo $(cat \"/tmp/README.txt\")");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо $(кат \"/tmp/README.txt\")")
);
let input: String = String::from("echo $(cat \"/tmp/john\\(that_guy\\).txt\")");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо $(кат \"/tmp/john\\(that_guy\\).txt\")")
);
let input: String = String::from("echo $(hostname) $(echo $(home)/$(whoami))");
assert_eq!(
iop.expression_to_cyrillic(&input).unwrap(),
String::from("ечо $(хостнаме) $(ечо $(хоме)/$(ухоами))")
);
}
#[test]
#[should_panic]
fn to_latin_missing_token_parenthesis() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("echo $(hostname");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
#[should_panic]
fn to_latin_missing_token_quotes() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("echo \"hello");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
#[should_panic]
fn to_latin_missing_token_backslash() {
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
let input: String = String::from("echo \"hello\\");
assert!(iop.expression_to_latin(&input).is_ok());
}
#[test]
fn test_escapes() {
let latin_text: String = String::from("\x1b[31mRED\x1b[0m");
let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian));
assert_eq!(iop.language, Language::Russian);
assert_eq!(iop.text_to_cyrillic(&latin_text), String::from("\x1b[31mРЕД\x1b[0m"));
}
}