taskman 0.1.1

Cli for the task management.
use std::fmt::Display;

pub(crate) fn serialize(strings: &Vec<Option<String>>) -> String {
    let mut result = String::new();
    for (i, str) in strings.iter().enumerate() {
        if let Some(s) = str {
            if s.contains("\r")
                || s.contains(" ")
                || s.contains("\n")
                || s.contains(",")
                || s.contains("\"")
            {
                result.push('"');
                if s.contains("\"") {
                    let replaced = s.replace("\"", "\"\"");
                    result.push_str(&replaced);
                } else {
                    result.push_str(&s);
                }
                result.push('"');
            } else {
                result.push_str(&s);
            }
        }
        if i != strings.len() - 1 {
            result.push(',');
        }
    }
    result
}

pub(crate) fn deserialize(str: &str) -> Result<Vec<Option<String>>, ParserError> {
    validate_string(str)?;
    let strings = divide(str);
    let strings = replace_double_quotes(strings);
    Ok(strings)
}

fn validate_string(str: &str) -> Result<(), ParserError> {
    let mut quotes_are_open = false;
    let chars = str.chars();
    let chars = chars.collect::<Vec<char>>();
    let mut count_of_quotes_in_raw = 0;
    let mut last_index = 0;
    for (i, c) in chars.iter().enumerate() {
        if *c == '"' {
            if count_of_quotes_in_raw == 0 && !quotes_are_open && i != 0 {
                let previous_char = chars.get(i - 1).unwrap();
                if *previous_char != ',' {
                    return Err(ParserError::ValidationError("quotes_error1".to_string()));
                }
            }
            count_of_quotes_in_raw += 1;
        } else {
            if (*c == ' ' || *c == '\r' || *c == '\n') && !quotes_are_open {
                return Err(ParserError::ValidationError("quotes_error2".to_string()));
            }
            if quotes_are_open {
                if count_of_quotes_in_raw % 2 != 0 {
                    if *c != ',' {
                        return Err(ParserError::ValidationError("quotes_error3".to_string()));
                    }
                    quotes_are_open = false;
                }
                count_of_quotes_in_raw = 0;
            }
            if !quotes_are_open {
                if count_of_quotes_in_raw != 0 {
                    if count_of_quotes_in_raw % 2 == 0 {
                        return Err(ParserError::ValidationError("quotes_error4".to_string()));
                    }
                    if *chars.get(last_index).unwrap() != ',' && last_index != 0 {
                        return Err(ParserError::ValidationError("quotes_error5".to_string()));
                    }
                    count_of_quotes_in_raw = 0;
                    quotes_are_open = true;
                }
            }
            last_index = i;
        }
    }
    Ok(())
}

fn replace_double_quotes(vec: Vec<Option<String>>) -> Vec<Option<String>> {
    vec.iter()
        .map(|s| match s {
            Some(str) => {
                if str.chars().next().unwrap() == '"' {
                    let str = &str[1..str.len() - 1];
                    return Some(str.replace("\"\"", "\""));
                }
                Some(str.replace("\"\"", "\""))
            }
            None => None,
        })
        .collect()
}
// text,"text text, text",text
fn divide(str: &str) -> Vec<Option<String>> {
    let mut quotes_are_open = false;
    let mut strings = Vec::new();
    let mut begin = 0;
    let chars = str.chars();
    let chars = chars.collect::<Vec<char>>();
    for (i, c) in str.chars().enumerate() {
        if c == '"' {
            quotes_are_open = !quotes_are_open;
        }
        if !quotes_are_open {
            if c == ',' {
                if begin == i {
                    strings.push(None);
                } else {
                    let mut times = String::new();
                    for (s, t) in str.chars().enumerate().skip(begin) {
                        if s >= i {
                            break;
                        }
                        times.push(t);
                    }
                    strings.push(Some(times));
                }
                begin = i + 1;
            }
        }
        if chars.get(i + 1).is_none() {
            if begin == i + 1 {
                strings.push(None);
            } else {
                let mut times = String::new();
                for (_, t) in str.chars().enumerate().skip(begin) {
                    times.push(t);
                }
                strings.push(Some(times));
            }
        }
    }
    strings
}

#[derive(Debug)]
pub(crate) enum ParserError {
    ValidationError(String),
}
impl Display for ParserError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ValidationError(desc) => {
                write!(f, "ParseError:  {}.", desc)
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn deserialize_string_with_comma() {
        let input = "text,\"text with ,comma\"";
        let res = deserialize(input).unwrap();
        dbg!(&res);
        assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
        assert_eq!("text with ,comma", res.get(1).unwrap().as_ref().unwrap());
    }

    #[test]
    fn deserialize_string_with_comma_3_words() {
        let input = "text,\"text with ,comma\",text";
        let res = deserialize(input).unwrap();
        dbg!(&res);
        assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
        assert_eq!("text with ,comma", res.get(1).unwrap().as_ref().unwrap());
        assert_eq!("text", res.get(2).unwrap().as_ref().unwrap());
    }

    #[test]
    fn deserialize_simple() {
        let input = "text,text1,text2";
        let res = deserialize(input).unwrap();
        dbg!(&res);
        assert_eq!("text", res.get(0).unwrap().as_ref().unwrap());
        assert_eq!("text1", res.get(1).unwrap().as_ref().unwrap());
        assert_eq!("text2", res.get(2).unwrap().as_ref().unwrap());
    }
    #[test]
    fn validate_err_space_after_or_before_comma() {
        let input = "text,text1 ,text2";
        let res = validate_string(input);
        assert_eq!(true, res.is_err());

        let input = "text,text1, text2";
        let res = validate_string(input);
        assert_eq!(true, res.is_err());
        match res.err().unwrap() {
            ParserError::ValidationError(desc) => {
                assert_eq!("quotes_error2".to_string(), desc)
            }
        }
    }
    #[test]
    fn validate_err_quotes() {
        let input = "text,\"\"text1\",text2";
        let res = validate_string(input);
        assert_eq!(true, res.is_err());
    }
    #[test]
    fn unknown() {
        let input = "text,\"\"text1\"\",text2";
        let res = validate_string(input);
        dbg!(&res);
        assert_eq!(true, res.is_err());
        match res.err().unwrap() {
            ParserError::ValidationError(desc) => assert_eq!("quotes_error4".to_string(), desc),
        }
    }
    #[test]
    fn unknown2() {
        let input = "text,tex\"t1,text2";
        let res = validate_string(input);
        dbg!(&res);
        assert_eq!(true, res.is_err());
    }
    #[test]
    fn unknown3() {
        let input = "text,\"tex\"t1\",text2";
        let res = validate_string(input);
        dbg!(&res);
        assert_eq!(true, res.is_err());
    }
    #[test]
    fn unknown4() {
        let input = "text,\"tex\"\"t1\",text2";
        let res = validate_string(input);
        dbg!(&res);
        assert_eq!(true, res.is_ok());
    }
    #[test]
    fn unknown5() {
        let input = "text,\"tex\"\"\"t1\",text2";
        let res = validate_string(input);
        assert_eq!(true, res.is_err());
    }
    #[test]
    fn unknown6() {
        let input = "text,\"\"\"text1\"\"\",text2";
        let res = validate_string(input);
        dbg!(&res);
        assert_eq!(true, res.is_ok());
    }

    #[test]
    fn divide1() {
        let input = "text,\"text, text\",1";
        let result = divide(input);
        dbg!(&result);
        assert_eq!(3, result.len());
        assert_eq!("text", result[0].as_ref().unwrap());
        assert_eq!("\"text, text\"", result[1].as_ref().unwrap());
        assert_eq!("1", result[2].as_ref().unwrap());
    }
    #[test]
    fn divide2() {
        let input = ",\"text, text\",";
        let result = divide(input);
        dbg!(&result);
        assert_eq!(3, result.len());
        assert_eq!(true, result.get(0).unwrap().is_none());
        assert_eq!("\"text, text\"", result[1].as_ref().unwrap());
        assert_eq!(true, result.get(2).unwrap().is_none());
    }
    #[test]
    fn validate7() {
        let input = "text,\"text with ,comma\",text\r\n";
        let res = validate_string(input);
        assert_eq!(true, res.is_err());
    }
    #[test]
    fn validate8() {
        let input = "text,\"text with \r\n,comma\",text";
        let res = validate_string(input);
        assert_eq!(true, res.is_ok());
    }
    #[test]
    fn serialize_string_with_quotes() {
        let strs = vec![None, Some("te\"xt".to_string()), None, None];
        let str = serialize(&strs);
        assert_eq!(",\"te\"\"xt\",,", str);
    }
}