extern crate ical; use ical::parser::ical::component::*; use ical::property::Property; use indexmap::map::IndexMap; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, Type, Value, }; use std::io::BufReader; #[derive(Clone)] pub struct FromIcs; impl Command for FromIcs { fn name(&self) -> &str { "from ics" } fn signature(&self) -> Signature { Signature::build("from ics") .input_output_types(vec![(Type::String, Type::Table(vec![]))]) .category(Category::Formats) } fn usage(&self) -> &str { "Parse text as .ics and create table." } fn run( &self, _engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; from_ics(input, head) } fn examples(&self) -> Vec { vec![Example { example: "'BEGIN:VCALENDAR END:VCALENDAR' | from ics", description: "Converts ics formatted string to table", result: Some(Value::List { vals: vec![Value::Record { cols: vec![ "properties".to_string(), "events".to_string(), "alarms".to_string(), "to-Dos".to_string(), "journals".to_string(), "free-busys".to_string(), "timezones".to_string(), ], vals: vec![ Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, Value::List { vals: vec![], span: Span::test_data(), }, ], span: Span::test_data(), }], span: Span::test_data(), }), }] } } fn from_ics(input: PipelineData, head: Span) -> Result { let (input_string, metadata) = input.collect_string_strict(head)?; let input_string = input_string .lines() .map(|x| x.trim().to_string()) .collect::>() .join("\n"); let input_bytes = input_string.as_bytes(); let buf_reader = BufReader::new(input_bytes); let parser = ical::IcalParser::new(buf_reader); let mut output = vec![]; for calendar in parser { match calendar { Ok(c) => output.push(calendar_to_value(c, head)), Err(e) => output.push(Value::Error { error: ShellError::UnsupportedInput( format!("input cannot be parsed as .ics ({})", e), head, ), }), } } Ok(Value::List { vals: output, span: head, } .into_pipeline_data_with_metadata(metadata)) } fn calendar_to_value(calendar: IcalCalendar, span: Span) -> Value { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(calendar.properties, span), ); row.insert("events".to_string(), events_to_value(calendar.events, span)); row.insert("alarms".to_string(), alarms_to_value(calendar.alarms, span)); row.insert("to-Dos".to_string(), todos_to_value(calendar.todos, span)); row.insert( "journals".to_string(), journals_to_value(calendar.journals, span), ); row.insert( "free-busys".to_string(), free_busys_to_value(calendar.free_busys, span), ); row.insert( "timezones".to_string(), timezones_to_value(calendar.timezones, span), ); Value::from(Spanned { item: row, span }) } fn events_to_value(events: Vec, span: Span) -> Value { Value::List { vals: events .into_iter() .map(|event| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(event.properties, span), ); row.insert("alarms".to_string(), alarms_to_value(event.alarms, span)); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn alarms_to_value(alarms: Vec, span: Span) -> Value { Value::List { vals: alarms .into_iter() .map(|alarm| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(alarm.properties, span), ); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn todos_to_value(todos: Vec, span: Span) -> Value { Value::List { vals: todos .into_iter() .map(|todo| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(todo.properties, span), ); row.insert("alarms".to_string(), alarms_to_value(todo.alarms, span)); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn journals_to_value(journals: Vec, span: Span) -> Value { Value::List { vals: journals .into_iter() .map(|journal| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(journal.properties, span), ); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn free_busys_to_value(free_busys: Vec, span: Span) -> Value { Value::List { vals: free_busys .into_iter() .map(|free_busy| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(free_busy.properties, span), ); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn timezones_to_value(timezones: Vec, span: Span) -> Value { Value::List { vals: timezones .into_iter() .map(|timezone| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(timezone.properties, span), ); row.insert( "transitions".to_string(), timezone_transitions_to_value(timezone.transitions, span), ); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn timezone_transitions_to_value(transitions: Vec, span: Span) -> Value { Value::List { vals: transitions .into_iter() .map(|transition| { let mut row = IndexMap::new(); row.insert( "properties".to_string(), properties_to_value(transition.properties, span), ); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn properties_to_value(properties: Vec, span: Span) -> Value { Value::List { vals: properties .into_iter() .map(|prop| { let mut row = IndexMap::new(); let name = Value::String { val: prop.name, span, }; let value = match prop.value { Some(val) => Value::String { val, span }, None => Value::nothing(span), }; let params = match prop.params { Some(param_list) => params_to_value(param_list, span), None => Value::nothing(span), }; row.insert("name".to_string(), name); row.insert("value".to_string(), value); row.insert("params".to_string(), params); Value::from(Spanned { item: row, span }) }) .collect::>(), span, } } fn params_to_value(params: Vec<(String, Vec)>, span: Span) -> Value { let mut row = IndexMap::new(); for (param_name, param_values) in params { let values: Vec = param_values .into_iter() .map(|val| Value::string(val, span)) .collect(); let values = Value::List { vals: values, span }; row.insert(param_name, values); } Value::from(Spanned { item: row, span }) } #[cfg(test)] mod tests { use super::*; #[test] fn test_examples() { use crate::test_examples; test_examples(FromIcs {}) } }