1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
const SEPARATOR: char = '=';

#[derive(Debug, Copy, Clone)]
pub struct Property<'a> {
    key: &'a str,
    value: &'a str,
}

impl<'a> Property<'a> {
    pub fn new() -> Self {
        Property { key: "", value: "" }
    }
    pub fn init(key: &'a str, value: &'a str) -> Self {
        Property { key, value }
    }
    pub fn key(self) -> String {
        self.key.to_string()
    }
    pub fn value(self) -> String {
        self.value.to_string()
    }
}

impl<'a> PartialEq for Property<'a> {
    fn eq(&self, other: &Property) -> bool {
        self.key == other.key && self.value == other.value
    }
    fn ne(&self, other: &Property) -> bool {
        self.key != other.key || self.value != other.value
    }
}

/// Split a line based and return a property
///
/// line expects there to be only one separator
///
/// Default separator is '='
pub fn split(line: &str, separator: Option<char>) -> Property {
    let mut property: Property = Property::new();
    let sep: char = match separator {
        None => SEPARATOR,
        Some(c) => c,
    };

    let split: Vec<&str> = line.split(sep).collect();
    assert_eq!(split.len(), 2, "Invalid property line. Expected format: \"key{}value\"", sep);
    property.key = split.first().unwrap().trim();
    property.value = split.last().unwrap().trim();
    return property;
}

fn check_line(line: &str, separator: char) -> bool {
    let mut i = 0;
    line.chars().for_each(|c| {
        if c == separator {
            i += 1;
        }
    });
    if i != 1 {
        return false;
    }
    return true;
}

/// Attempt to parse a line, returns None if the following are true:
/// * line is empty
/// * comment is not None and line begins with comment
/// * separator is not None and line contains more than one separator
pub fn try_split<'a>(
    line: &'a str,
    separator: Option<char>,
    comment: Option<&'a str>,
) -> Option<Property<'a>> {
    if line.is_empty() {
        return None;
    }

    match comment {
        None => {}
        Some(c) => {
            if line.starts_with(c) {
                return None;
            }
        }
    }

    if separator == None && !check_line(line, SEPARATOR) {
        return None;
    } else {
        if !check_line(line, separator.unwrap()) {
            return None;
        }
    }

    return Some(split(line, separator));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let property: Property = Property::new();
        assert_eq!(property.key(), String::new());
        assert_eq!(property.value(), String::new());
    }

    #[test]
    fn test_init() {
        let property: Property = Property::init("foo", "bar");
        assert_eq!(property.key(), String::from("foo"));
        assert_eq!(property.value(), String::from("bar"));
    }

    #[test]
    fn test_split() {
        let expected: Property = Property::init("foo", "bar");
        assert_eq!(split("foo: bar", Some(':')), expected);
    }

    #[test]
    fn test_try_split() {
        assert_eq!(try_split("", None, None), None);
        assert_eq!(try_split("foo:bar:baz", Some(':'), None), None);
        assert_eq!(try_split("//foo:bar", Some(':'), Some("//")), None);

        let expected: Property = Property::init("foo", "bar");
        assert_eq!(try_split("foo:bar", Some(':'), Some("//")), Some(expected));
    }
}