calcard 0.3.1

iCalendar/JSCalendar and vCard/JSContact parsing, building and conversion library for Rust
Documentation
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */
#![doc = include_str!("../README.md")]
#![deny(rust_2018_idioms)]
#![forbid(unsafe_code)]
use common::tokenizer::{StopChar, Token};
use icalendar::{ICalendar, ICalendarComponentType};
use std::{
    borrow::Cow,
    iter::{Enumerate, Peekable},
    slice::Iter,
};
use vcard::VCard;

pub mod common;
pub mod datecalc;
pub mod icalendar;
#[cfg(feature = "jmap")]
pub mod jscalendar;
#[cfg(feature = "jmap")]
pub mod jscontact;
pub mod vcard;

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Entry {
    VCard(VCard),
    ICalendar(ICalendar),
    InvalidLine(String),
    UnexpectedComponentEnd {
        expected: ICalendarComponentType,
        found: ICalendarComponentType,
    },
    UnterminatedComponent(Cow<'static, str>),
    TooManyComponents,
    Eof,
}

pub struct Parser<'x> {
    pub(crate) input: &'x [u8],
    pub(crate) iter: Peekable<Enumerate<Iter<'x, u8>>>,
    pub(crate) strict: bool,
    pub(crate) stop_colon: bool,
    pub(crate) stop_semicolon: bool,
    pub(crate) stop_comma: bool,
    pub(crate) stop_equal: bool,
    pub(crate) stop_dot: bool,
    pub(crate) unfold_qp: bool,
    pub(crate) unquote: bool,
    pub(crate) skip_ws: bool,
    pub(crate) token_buf: Vec<Token<'x>>,
}

impl<'x> Parser<'x> {
    pub fn new(input: &'x str) -> Self {
        let input = input.as_bytes();
        Self {
            input,
            iter: input.iter().enumerate().peekable(),
            strict: false,
            stop_colon: true,
            stop_semicolon: true,
            stop_comma: true,
            stop_equal: true,
            stop_dot: false,
            unfold_qp: false,
            unquote: true,
            skip_ws: false,
            token_buf: Vec::with_capacity(10),
        }
    }

    pub fn strict(mut self) -> Self {
        self.strict = true;
        self
    }

    pub fn entry(&mut self) -> Entry {
        self.expect_iana_token();

        loop {
            if let Some(token) = self.token() {
                if (token.text.eq_ignore_ascii_case(b"BEGIN")
                    || token.text.eq_ignore_ascii_case("\u{feff}BEGIN".as_bytes()))
                    && token.stop_char == StopChar::Colon
                {
                    if let Some(token) = self.token() {
                        if token.stop_char == StopChar::Lf {
                            hashify::fnc_map_ignore_case!(token.text.as_ref(),
                                b"VCARD" => { return self.vcard(); },
                                b"VCALENDAR" => { return self.icalendar(ICalendarComponentType::VCalendar); },
                                b"VEVENT" => { return self.icalendar(ICalendarComponentType::VEvent); },
                                b"VTODO" => { return self.icalendar(ICalendarComponentType::VTodo); },
                                b"VJOURNAL" => { return self.icalendar(ICalendarComponentType::VJournal); },
                                b"VFREEBUSY" => { return self.icalendar(ICalendarComponentType::VFreebusy); },
                                b"VTIMEZONE" => { return self.icalendar(ICalendarComponentType::VTimezone); },
                                b"VALARM" => { return self.icalendar(ICalendarComponentType::VAlarm); },
                                b"STANDARD" => { return self.icalendar(ICalendarComponentType::Standard); },
                                b"DAYLIGHT" => { return self.icalendar(ICalendarComponentType::Daylight); },
                                b"VAVAILABILITY" => { return self.icalendar(ICalendarComponentType::VAvailability); },
                                b"AVAILABLE" => { return self.icalendar(ICalendarComponentType::Available); },
                                b"PARTICIPANT" => { return self.icalendar(ICalendarComponentType::Participant); },
                                b"VLOCATION" => { return self.icalendar(ICalendarComponentType::VLocation); },
                                b"VRESOURCE" => { return self.icalendar(ICalendarComponentType::VResource); },
                                _ => {
                                    return self.icalendar(ICalendarComponentType::Other(token.into_string()));
                                }
                            )
                        }
                    } else {
                        return Entry::Eof;
                    }
                }

                let token_start = token.start;
                let mut token_end = token.end;

                if token.stop_char != StopChar::Lf {
                    self.expect_single_value();
                    while let Some(token) = self.token() {
                        token_end = token.end;
                        if token.stop_char == StopChar::Lf {
                            break;
                        }
                    }
                } else if token.text.is_empty() {
                    continue;
                }

                return Entry::InvalidLine(
                    std::str::from_utf8(
                        self.input.get(token_start..=token_end).unwrap_or_default(),
                    )
                    .unwrap_or_default()
                    .to_string(),
                );
            } else {
                return Entry::Eof;
            }
        }
    }
}