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
use crate::prelude::*;
use ical::parser::vcard::component::*;
use ical::property::Property;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};

pub struct FromVcf;

impl WholeStreamCommand for FromVcf {
    fn name(&self) -> &str {
        "from vcf"
    }

    fn signature(&self) -> Signature {
        Signature::build("from vcf")
    }

    fn usage(&self) -> &str {
        "Parse text as .vcf and create table."
    }

    fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
        from_vcf(args)
    }
}

fn from_vcf(args: CommandArgs) -> Result<OutputStream, ShellError> {
    let tag = args.name_tag();
    let input = args.input;

    let input_string = input.collect_string(tag.clone())?.item;
    let input_bytes = input_string.into_bytes();
    let cursor = std::io::Cursor::new(input_bytes);
    let parser = ical::VcardParser::new(cursor);

    let iter = parser.map(move |contact| match contact {
        Ok(c) => contact_to_value(c, tag.clone()),
        Err(_) => Value::error(ShellError::labeled_error(
            "Could not parse as .vcf",
            "input cannot be parsed as .vcf",
            tag.clone(),
        )),
    });

    let collected: Vec<_> = iter.collect();

    Ok(collected.into_iter().into_output_stream())
}

fn contact_to_value(contact: VcardContact, tag: Tag) -> Value {
    let mut row = TaggedDictBuilder::new(tag.clone());
    row.insert_untagged("properties", properties_to_value(contact.properties, tag));
    row.into_value()
}

fn properties_to_value(properties: Vec<Property>, tag: Tag) -> UntaggedValue {
    UntaggedValue::table(
        &properties
            .into_iter()
            .map(|prop| {
                let mut row = TaggedDictBuilder::new(tag.clone());

                let name = UntaggedValue::string(prop.name);
                let value = match prop.value {
                    Some(val) => UntaggedValue::string(val),
                    None => UntaggedValue::Primitive(Primitive::Nothing),
                };
                let params = match prop.params {
                    Some(param_list) => params_to_value(param_list, tag.clone()).into(),
                    None => UntaggedValue::Primitive(Primitive::Nothing),
                };

                row.insert_untagged("name", name);
                row.insert_untagged("value", value);
                row.insert_untagged("params", params);
                row.into_value()
            })
            .collect::<Vec<Value>>(),
    )
}

fn params_to_value(params: Vec<(String, Vec<String>)>, tag: Tag) -> Value {
    let mut row = TaggedDictBuilder::new(tag);

    for (param_name, param_values) in params {
        let values: Vec<Value> = param_values.into_iter().map(|val| val.into()).collect();
        let values = UntaggedValue::table(&values);
        row.insert_untagged(param_name, values);
    }

    row.into_value()
}

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

    #[test]
    fn examples_work_as_expected() -> Result<(), ShellError> {
        use crate::examples::test as test_examples;

        test_examples(FromVcf {})
    }
}