contack 0.9.2

A simple and easy contact library.
Documentation
//! These are functions todo with escaping the VCard properties.
//!
//! None of these need to be called directly.

use std::collections::HashMap;
use std::ops::Deref;

/// This will fold a line. This is run when generating a component.
///
/// Line folding requires that every 80 octets the line
/// is folded. Unforunately that is a pain to do
/// so I just to it every 75 unichars.
pub fn fold_line<T>(string: T) -> String
where
    T: Deref<Target = str>,
{
    let mut final_string = String::new();

    for (pos, chr) in string.char_indices() {
        if pos % 75 == 0 && pos != 0 {
            final_string.push_str("\r\n\t");
        }
        final_string.push(chr);
    }

    final_string
}

/// This will unfold a line. This is run when generating a component.
///
/// This implementation is not as good as it should be.
/// See issue
/// [#1](https://gitlab.com/john_t/contack/-/issues/8)
pub fn unfold_line<T>(string: T) -> String
where
    T: Deref<Target = str>,
{
    string.replace("\r\n\t", "").replace("\r\n ", "")
}

/// This will escape a property
pub fn escape_property<T>(string: T) -> String
where
    T: Deref<Target = str>,
{
    string
        .replace('\\', "\\\\")
        .replace(',', "\\,")
        .replace(';', "\\;")
        .replace('\n', "\\n")
}

/// This will unescape a property
pub fn unescape_property<T>(string: T) -> String
where
    T: Deref<Target = str>,
{
    string
        .replace("\\n", "\n")
        .replace("\\;", ";")
        .replace("\\,", ",")
        .replace("\\\\", "\\")
}

/// This will get all the parameters from a string of parameters.
#[must_use]
pub fn get_parameters(mut string: String) -> HashMap<String, String> {
    string.push(';');
    string.push(' ');

    // Create the map
    let mut map = HashMap::new();

    let mut name = String::new();
    let mut val = String::new();
    let mut on_val = false;
    let mut next_param = false;
    let mut in_string = false;

    for chr in string.chars() {
        // Add our values to the map and reset context
        if next_param {
            map.insert(std::mem::take(&mut name), std::mem::take(&mut val));
            next_param = false;
            on_val = false;
            in_string = false;
        }
        if on_val {
            // Strings!
            if chr == '"' {
                in_string = !in_string;
            }
            // If the character is not a semicolon than we can add the char
            // to the string.
            else if chr == ';' && !in_string {
                next_param = true;
            } else {
                val.push(chr);
            }
        } else {
            // If we have an equals then we move to the value, otherwise
            // we add it to the name
            if chr == '=' {
                on_val = true;
            } else {
                name.push(chr);
            }
        }
    }

    map
}

/// This will get all the values in a string.
#[must_use]
pub fn get_values(mut string: String) -> Vec<Vec<String>> {
    string = string.trim().to_string();
    string.push(';');

    let mut strings = Vec::new();

    let mut escape = false;

    strings.push(vec![String::new()]);
    for i in string.chars() {
        // Get the active string, where we need to append characters.
        if let Some(active_string) =
            strings.last_mut().and_then(|x| x.last_mut())
        {
            // Insert the character
            if escape {
                match i {
                    '\\' => active_string.push('\\'),
                    ';' => active_string.push(';'),
                    ',' => active_string.push(','),
                    'n' => active_string.push('\n'),
                    _ => (),
                }
                escape = false;
            } else {
                match i {
                    '\\' => escape = true,
                    ';' => strings.push(vec![String::new()]),
                    ',' => {
                        if let Some(active_val) = strings.last_mut() {
                            active_val.push(String::new());
                        }
                    }
                    _ => active_string.push(i),
                }
            }
        }
    }

    strings.pop();
    for i in &mut strings {
        i.retain(|x| !x.is_empty());
    }
    strings
}