polib 0.3.0

Read, manipulate and store translation data in GNU gettext PO format.
Documentation
#[derive(Debug)]
pub(crate) struct UnescapeError {
    seq: String,
}

impl UnescapeError {
    fn new(seq: char) -> Self {
        Self {
            seq: seq.to_string(),
        }
    }
}

impl std::fmt::Display for UnescapeError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "invalid unescape sequence {}", self.seq)
    }
}

impl std::error::Error for UnescapeError {}

pub(super) fn escape(unescaped: &str) -> String {
    if unescaped.find('\\').is_none()
        && unescaped.find('"').is_none()
        && unescaped.find('\n').is_none()
        && unescaped.find('\r').is_none()
        && unescaped.find('\t').is_none()
    {
        return unescaped.to_string();
    }
    let mut escaped = String::new();
    for c in unescaped.to_string().chars() {
        match c {
            '\\' => {
                escaped.push_str("\\\\");
            }
            '"' => {
                escaped.push_str("\\\"");
            }
            '\n' => {
                escaped.push_str("\\n");
            }
            '\r' => {
                escaped.push_str("\\r");
            }
            '\t' => {
                escaped.push_str("\\t");
            }
            _ => {
                escaped.push(c);
            }
        }
    }
    escaped
}

pub(super) fn unescape(escaped: &str) -> Result<String, UnescapeError> {
    let first_backslash = escaped.find('\\');
    if let Some(i) = first_backslash {
        let mut unescaped = String::from(&escaped[0..i]);
        let escaped: Vec<char> = escaped[i..].to_string().chars().collect();
        let mut i = 0;
        while i < escaped.len() {
            if escaped[i] != '\\' || i == escaped.len() - 1 {
                unescaped.push(escaped[i]);
            } else {
                i += 1;
                match escaped[i] {
                    '\\' => {
                        unescaped.push('\\');
                    }
                    'n' => {
                        unescaped.push('\n');
                    }
                    'r' => {
                        unescaped.push('\r');
                    }
                    't' => {
                        unescaped.push('\t');
                    }
                    '"' => {
                        unescaped.push('"');
                    }
                    _ => {
                        return Err(UnescapeError::new(escaped[i]));
                    }
                }
            }
            i += 1;
        }
        Ok(unescaped)
    } else {
        Ok(escaped.to_string())
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_escape() {
        use crate::po_file::escape::escape;
        let unescaped = "1\n2\n3\n";
        let expected = r"1\n2\n3\n";
        assert_eq!(escape(unescaped), expected);
    }

    #[test]
    fn test_unescape() {
        use crate::po_file::escape::unescape;
        let raw = r"1\n2\n3\n";
        let expected = "1\n2\n3\n";
        assert_eq!(unescape(raw).unwrap(), expected);
    }
}