use std::error::Error;
use std::fmt::Debug;
use std::result::Result;
pub struct SyntaxError {
pos: usize,
msg: String,
}
impl std::fmt::Display for SyntaxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Syntax error at {}: {}", self.pos, self.msg)
}
}
impl std::fmt::Debug for SyntaxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Syntax error at {}: {}", self.pos, self.msg)
}
}
impl Error for SyntaxError {}
#[derive(Debug, Clone)]
pub struct Span {
_start: usize,
_end: usize,
}
#[derive(Debug)]
pub enum Token {
ROOT(Vec<Token>, Option<Span>),
Comment(String, Option<Span>),
Variable(String, String, Option<Span>),
}
impl Token {
pub fn create_root<T>(tokens: T) -> Token
where
T: IntoIterator<Item = Token>,
{
Token::ROOT(tokens.into_iter().collect(), None)
}
pub fn create_comment<T>(comment: T) -> Token
where
T: ToString,
{
Token::Comment(comment.to_string(), None)
}
pub fn create_variable<T, U>(name: T, value: U) -> Token
where
T: ToString,
U: ToString,
{
Token::Variable(name.to_string(), value.to_string(), None)
}
fn append(&mut self, token: Token) {
match self {
Token::ROOT(tokens, _) => tokens.push(token),
_ => {}
}
}
pub fn parse<C: AsRef<[u8]>>(content: C) -> Result<Token, Box<dyn Error>> {
let mut root_token = Token::ROOT(Vec::new(), None);
let content_ref = content.as_ref();
let mut key_characters: Vec<u8> = vec![];
key_characters.append(&mut (b'a'..b'z').collect::<Vec<u8>>());
key_characters.append(&mut (b'A'..b'Z').collect::<Vec<u8>>());
key_characters.append(&mut (b'0'..b'9').collect::<Vec<u8>>());
key_characters.push(b'_');
let mut pos_current = 0;
let mut acumulator_type = AcumulatorKind::ROOT;
let mut collector_variable_key = String::new();
let mut collector_variable_value = String::new();
let mut collector_comment = String::new();
loop {
if pos_current >= content_ref.len() {
break;
}
let char = content_ref[pos_current];
pos_current += 1;
match acumulator_type {
AcumulatorKind::ROOT => match char {
b'#' => {
acumulator_type = AcumulatorKind::COMMENT;
continue;
}
b'\n' | b'\t' | b' ' | b'\r' => {
continue;
}
char if key_characters.contains(&char) => {
acumulator_type = AcumulatorKind::VariableKey;
collector_variable_key = String::from_utf8(vec![char]).unwrap();
continue;
}
_ => {
return Err(Box::new(SyntaxError {
pos: pos_current,
msg: "Unexpected character".to_string(),
}));
}
},
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue) => {
match char {
b' ' | b'\t' | b'\r' | b'\n' => {
continue;
}
b'"' => {
acumulator_type = AcumulatorKind::VariableValue(
AcumulatorVariableValueKind::StringDoubleQuote,
);
collector_variable_value = String::new();
continue;
}
b'\'' => {
acumulator_type = AcumulatorKind::VariableValue(
AcumulatorVariableValueKind::StringSimpleQuote,
);
collector_variable_value = String::new();
continue;
}
char if char != b' ' => {
acumulator_type = AcumulatorKind::VariableValue(
AcumulatorVariableValueKind::StringWithoutQuotes,
);
collector_variable_value = String::from(char as char);
continue;
}
_ => {
return Err(Box::new(SyntaxError {
pos: pos_current,
msg: "Unexpected character".to_string(),
}));
}
}
}
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringWithoutQuotes) => {
match char {
b'\n' => {
acumulator_type = AcumulatorKind::ROOT;
root_token.append(Token::Variable(
collector_variable_key.clone(),
collector_variable_value.clone().trim().to_string(),
None,
));
continue;
}
b'#' => {
acumulator_type = AcumulatorKind::COMMENT;
root_token.append(Token::Variable(
collector_variable_key.clone(),
collector_variable_value.clone().trim().to_string(),
None,
));
continue;
}
_ => {
collector_variable_value.push(char as char);
continue;
}
}
}
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringSimpleQuote) => {
match char {
b'\'' if content_ref[pos_current - 2] != b'\\' => {
acumulator_type = AcumulatorKind::ROOT;
root_token.append(Token::Variable(
collector_variable_key.clone(),
collector_variable_value.replace("\\'", "'").clone(),
None,
));
continue;
}
_ => {
collector_variable_value.push(char as char);
continue;
}
}
}
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StringDoubleQuote) => {
match char {
b'\"' if content_ref[pos_current - 2] != b'\\' => {
acumulator_type = AcumulatorKind::ROOT;
root_token.append(Token::Variable(
collector_variable_key.clone(),
collector_variable_value.replace("\\\"", "\"").clone(),
None,
));
continue;
}
_ => {
collector_variable_value.push(char as char);
continue;
}
}
}
AcumulatorKind::VariableEqual => match char {
b' ' | b'\t' => {
continue;
}
b'=' => {
acumulator_type =
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue);
continue;
}
_ => {
return Err(Box::new(SyntaxError {
pos: pos_current,
msg: "Unexpected character".to_string(),
}));
}
},
AcumulatorKind::VariableKey => match char {
b' ' | b'\t' => {
acumulator_type = AcumulatorKind::VariableEqual;
continue;
}
b'=' => {
acumulator_type =
AcumulatorKind::VariableValue(AcumulatorVariableValueKind::StartValue);
continue;
}
char if key_characters.contains(&char) => {
collector_variable_key.push(char as char);
continue;
}
_ => {
return Err(Box::new(SyntaxError {
pos: pos_current,
msg: "Unexpected character".to_string(),
}));
}
},
AcumulatorKind::COMMENT => match char {
b'\n' => {
acumulator_type = AcumulatorKind::ROOT;
root_token.append(Token::Comment(
collector_comment.clone().trim().to_string(),
None,
));
continue;
}
_ => {
collector_comment.push(char as char);
continue;
}
},
}
}
Ok(root_token)
}
pub fn serialize(token: Token) -> Result<String, Box<String>> {
let tokens = match token {
Token::ROOT(tokens, _) => tokens,
_ => return Err(Box::new("Invalid token require a ROOT token".to_string())),
};
let mut serialized_string = String::new();
for token in tokens {
match token {
Token::Variable(key, value, _) => {
let next_string = value.replace("'", "\\'");
serialized_string.push_str(&format!("{}='{}'\n", key, next_string));
}
Token::Comment(comment, _) => {
serialized_string.push_str(&format!("# {}\n", comment));
}
_ => {}
}
}
Ok(serialized_string)
}
}
#[derive(Debug)]
pub struct EnvMap {}
enum AcumulatorVariableValueKind {
StartValue,
StringDoubleQuote,
StringWithoutQuotes,
StringSimpleQuote,
}
enum AcumulatorKind {
ROOT,
COMMENT,
VariableKey,
VariableEqual,
VariableValue(AcumulatorVariableValueKind),
}
impl Default for AcumulatorKind {
fn default() -> Self {
Self::ROOT
}
}
#[cfg(test)]
mod tests_env_map {
use super::*;
#[test]
fn parse_from_binary() {
let payload = b"
# Comment
key = value
KEY = 'val\\'ue' # inline comment
KEY2=\"VALUE2\"
KEY3 = ABC ASDF # inline comment
";
let envs = Token::parse(payload).unwrap();
match envs {
Token::ROOT(tokens, _) => {
match &tokens[0] {
Token::Comment(comment, _) => assert_eq!(comment, "Comment"),
_ => panic!("Unexpected token"),
}
match &tokens[1] {
Token::Variable(key, value, _) => {
assert_eq!(key, "key");
assert_eq!(value, "value");
}
_ => panic!("Unexpected token"),
}
match &tokens[2] {
Token::Variable(key, value, _) => {
assert_eq!(key, "KEY");
assert_eq!(value, "val'ue");
}
_ => panic!("Unexpected token"),
}
match &tokens[4] {
Token::Variable(key, value, _) => {
assert_eq!(key, "KEY2");
assert_eq!(value, "VALUE2");
}
_ => panic!("Unexpected token"),
}
match &tokens[5] {
Token::Variable(key, value, _) => {
assert_eq!(key, "KEY3");
assert_eq!(value, "ABC ASDF");
}
_ => panic!("Unexpected token"),
}
}
_ => panic!("Unexpected token"),
}
}
#[test]
fn serialize_to_binary() {
let payload = Token::create_root([
Token::create_comment("Comment"),
Token::create_variable("KEY1", "value"),
Token::create_variable("KEY2", "VALUE2"),
Token::create_variable("KEY3", "ABC ASDF"),
]);
let body_payload = Token::serialize(payload).unwrap();
assert_eq!(
"# Comment\nKEY1='value'\nKEY2='VALUE2'\nKEY3='ABC ASDF'\n",
body_payload
);
}
}