use std::fmt::{self, Write};
use std::mem;
use std::collections::HashMap;
#[derive(Debug)]
pub struct Parameter {
key: String,
value: String,
}
impl Parameter {
pub fn new(key: &str, val: &str) -> Self {
Parameter {
key: key.to_owned(),
value: val.to_owned(),
}
}
}
type EntryParameters = HashMap<String,Parameter>;
#[derive(Debug)]
pub struct Property {
key: String,
value: String,
parameters: EntryParameters,
}
impl Property {
pub fn new(key: &str, val: &str) -> Self {
Property {
key: key.to_owned(),
value: val.replace('\n', "\\n"),
parameters: HashMap::new(),
}
}
pub fn key(&self) -> String {
self.key.clone()
}
pub fn append_parameter<I:Into<Parameter>>(&mut self, into_parameter: I) -> &mut Self {
let parameter = into_parameter.into();
self.parameters.insert(parameter.key.to_owned(), parameter);
self
}
pub fn add_parameter(&mut self, key: &str, val: &str) -> &mut Self {
self.append_parameter(Parameter::new(key, val));
self
}
pub fn done(&mut self) -> Self {
Property {
key: mem::replace(&mut self.key, String::new()),
value: mem::replace(&mut self.value, String::new()),
parameters: mem::replace(&mut self.parameters, HashMap::new()),
}
}
pub fn fmt_write<W: Write>(&self, out: &mut W) -> Result<(), fmt::Error> {
let mut line = String::with_capacity(150);
write!(line, "{}", self.key)?;
for &Parameter { ref key, ref value } in self.parameters.values() {
write!(line, ";{}={}", key, value)?;
}
write!(line, ":{}", self.value)?;
write_crlf!(out, "{}", fold_line(&line))?;
Ok(())
}
}
#[derive(Copy,Clone,Debug)]
pub enum Class {
Public,
Private,
Confidential
}
impl Into<Property> for Class {
fn into(self) -> Property {
Property {
key: String::from("CLASS"),
value: String::from(match self {
Class::Public => "PUBLIC",
Class::Private => "PRIVATE",
Class::Confidential => "CONFIDENTIAL",
}),
parameters: HashMap::new()
}
}
}
#[derive(Copy,Clone,Debug)]
pub enum ValueType{
Binary,
Boolean,
CalAddress,
Date,
DateTime,
Duration,
Float,
Integer,
Period,
Recur,
Text,
Time,
Uri,
UtcOffset,
}
impl Into<Parameter> for ValueType {
fn into(self) -> Parameter {
Parameter {
key: String::from("VALUE"),
value: String::from(match self {
ValueType::Binary => "BINARY",
ValueType::Boolean => "BOOLEAN",
ValueType::CalAddress => "CAL-ADDRESS",
ValueType::Date => "DATE",
ValueType::DateTime => "DATE-TIME",
ValueType::Duration => "DURATION",
ValueType::Float => "FLOAT",
ValueType::Integer => "INTEGER",
ValueType::Period => "PERIOD",
ValueType::Recur => "RECUR",
ValueType::Text => "TEXT",
ValueType::Time => "TIME",
ValueType::Uri => "URI",
ValueType::UtcOffset => "UTC-OFFSET"
})
}
}
}
#[derive(Copy,Clone,Debug)]
pub enum EventStatus {
Tentative,
Confirmed,
Cancelled,
}
#[derive(Copy,Clone,Debug)]
pub enum TodoStatus {
NeedsAction,
Completed,
InProcess,
Cancelled,
}
impl Into<Property> for EventStatus {
fn into(self) -> Property {
Property {
key: String::from("STATUS"),
value: String::from(match self {
EventStatus::Tentative => "TENTATIVE",
EventStatus::Confirmed => "CONFIRMED",
EventStatus::Cancelled => "CANCELLED",
}),
parameters: HashMap::new()
}
}
}
impl Into<Property> for TodoStatus {
fn into(self) -> Property {
Property {
key: String::from("STATUS"),
value: String::from(match self {
TodoStatus::NeedsAction => "NEEDS-ACTION",
TodoStatus::Completed => "COMPLETED",
TodoStatus::InProcess => "IN-PROCESS",
TodoStatus::Cancelled => "CANCELLED",
}),
parameters: HashMap::new()
}
}
}
fn fold_line(line: &str) -> String {
let limit = 75;
let len = line.len();
let mut ret = String::with_capacity(len + (len / limit * 3));
let mut bytes_remaining = len;
let mut pos = 0;
let mut next_pos = limit;
while bytes_remaining > limit {
while !line.is_char_boundary(next_pos) {
next_pos -= 1;
}
ret.push_str(&line[pos..next_pos]);
ret.push_str("\r\n ");
bytes_remaining -= next_pos - pos;
pos = next_pos;
next_pos += limit;
}
ret.push_str(&line[len - bytes_remaining..]);
ret
}
#[cfg(test)]
mod tests {
use std::string::String;
use super::*;
#[test]
fn fold_line_short() {
let line = String::from("This is a short line");
assert_eq!(line.clone(), fold_line(&line));
}
#[test]
fn fold_line_folds_on_char_boundary() {
let line = String::from("Content lines shouldn't be folded in the middle \
of a UTF-8 character. 老虎.");
let expected = String::from("Content lines shouldn't be folded in the middle \
of a UTF-8 character. 老\r\n 虎.");
assert_eq!(expected, fold_line(&line));
}
}