use crate::common::property::Property;
use crate::datetime::DateTimeValue;
use crate::error::Result;
use crate::ical::alarm::Alarm;
use crate::ical::event::{escape_text, unescape_text};
use crate::ical::recurrence::RecurrenceRule;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Todo {
pub(crate) uid: Option<String>,
pub(crate) dtstamp: Option<DateTimeValue>,
pub(crate) dtstart: Option<DateTimeValue>,
pub(crate) due: Option<DateTimeValue>,
pub(crate) completed: Option<DateTimeValue>,
pub(crate) summary: Option<String>,
pub(crate) description: Option<String>,
pub(crate) location: Option<String>,
pub(crate) status: Option<String>,
pub(crate) categories: Vec<String>,
pub(crate) priority: Option<u8>,
pub(crate) percent_complete: Option<u8>,
pub(crate) rrule: Option<RecurrenceRule>,
pub(crate) alarms: Vec<Alarm>,
pub(crate) extra_properties: Vec<Property>,
}
impl Todo {
pub fn new() -> Self {
Self {
uid: None,
dtstamp: None,
dtstart: None,
due: None,
completed: None,
summary: None,
description: None,
location: None,
status: None,
categories: Vec::new(),
priority: None,
percent_complete: None,
rrule: None,
alarms: Vec::new(),
extra_properties: Vec::new(),
}
}
pub fn uid(mut self, uid: impl Into<String>) -> Self {
self.uid = Some(uid.into());
self
}
pub fn summary(mut self, summary: impl Into<String>) -> Self {
self.summary = Some(summary.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn location(mut self, location: impl Into<String>) -> Self {
self.location = Some(location.into());
self
}
pub fn starts(mut self, dt: &str) -> Self {
self.dtstart = Some(DateTimeValue::parse(dt).expect("invalid start date-time"));
self
}
pub fn starts_dt(mut self, dt: DateTimeValue) -> Self {
self.dtstart = Some(dt);
self
}
pub fn due_date(mut self, dt: &str) -> Self {
self.due = Some(DateTimeValue::parse(dt).expect("invalid due date-time"));
self
}
pub fn due_dt(mut self, dt: DateTimeValue) -> Self {
self.due = Some(dt);
self
}
pub fn completed_date(mut self, dt: &str) -> Self {
self.completed = Some(DateTimeValue::parse(dt).expect("invalid completed date-time"));
self
}
pub fn status(mut self, status: impl Into<String>) -> Self {
self.status = Some(status.into());
self
}
pub fn categories(mut self, categories: Vec<String>) -> Self {
self.categories = categories;
self
}
pub fn add_category(mut self, category: impl Into<String>) -> Self {
self.categories.push(category.into());
self
}
pub fn priority(mut self, priority: u8) -> Self {
self.priority = Some(priority);
self
}
pub fn percent_complete(mut self, pct: u8) -> Self {
self.percent_complete = Some(pct);
self
}
pub fn rrule(mut self, rrule: RecurrenceRule) -> Self {
self.rrule = Some(rrule);
self
}
pub fn alarm(mut self, alarm: Alarm) -> Self {
self.alarms.push(alarm);
self
}
pub fn property(mut self, prop: Property) -> Self {
self.extra_properties.push(prop);
self
}
pub fn get_uid(&self) -> Option<&str> {
self.uid.as_deref()
}
pub fn get_summary(&self) -> Option<&str> {
self.summary.as_deref()
}
pub fn get_description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn get_location(&self) -> Option<&str> {
self.location.as_deref()
}
pub fn get_starts(&self) -> Option<&DateTimeValue> {
self.dtstart.as_ref()
}
pub fn get_due(&self) -> Option<&DateTimeValue> {
self.due.as_ref()
}
pub fn get_completed(&self) -> Option<&DateTimeValue> {
self.completed.as_ref()
}
pub fn get_status(&self) -> Option<&str> {
self.status.as_deref()
}
pub fn get_categories(&self) -> &[String] {
&self.categories
}
pub fn get_priority(&self) -> Option<u8> {
self.priority
}
pub fn get_percent_complete(&self) -> Option<u8> {
self.percent_complete
}
pub fn get_rrule(&self) -> Option<&RecurrenceRule> {
self.rrule.as_ref()
}
pub fn get_alarms(&self) -> &[Alarm] {
&self.alarms
}
pub fn get_extra_properties(&self) -> &[Property] {
&self.extra_properties
}
pub(crate) fn to_properties(&self) -> Vec<Property> {
let mut props = Vec::new();
let uid = self
.uid
.clone()
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
props.push(Property::new("UID", uid));
if let Some(ref dtstamp) = self.dtstamp {
props.push(dtstamp.to_property("DTSTAMP"));
} else {
#[cfg(feature = "chrono")]
{
let now = chrono::Utc::now();
let dt = DateTimeValue::from_chrono_utc(now);
props.push(dt.to_property("DTSTAMP"));
}
#[cfg(not(feature = "chrono"))]
{
props.push(Property::new("DTSTAMP", "19700101T000000Z"));
}
}
if let Some(ref dt) = self.dtstart {
props.push(dt.to_property("DTSTART"));
}
if let Some(ref dt) = self.due {
props.push(dt.to_property("DUE"));
}
if let Some(ref dt) = self.completed {
props.push(dt.to_property("COMPLETED"));
}
if let Some(ref s) = self.summary {
props.push(Property::new("SUMMARY", escape_text(s)));
}
if let Some(ref s) = self.description {
props.push(Property::new("DESCRIPTION", escape_text(s)));
}
if let Some(ref s) = self.location {
props.push(Property::new("LOCATION", escape_text(s)));
}
if let Some(ref s) = self.status {
props.push(Property::new("STATUS", s));
}
if !self.categories.is_empty() {
props.push(Property::new(
"CATEGORIES",
self.categories
.iter()
.map(|c| escape_text(c))
.collect::<Vec<_>>()
.join(","),
));
}
if let Some(p) = self.priority {
props.push(Property::new("PRIORITY", p.to_string()));
}
if let Some(pct) = self.percent_complete {
props.push(Property::new("PERCENT-COMPLETE", pct.to_string()));
}
if let Some(ref rrule) = self.rrule {
props.push(Property::new("RRULE", rrule.to_string()));
}
props.extend(self.extra_properties.clone());
props
}
pub(crate) fn from_properties(props: Vec<Property>, alarms: Vec<Alarm>) -> Result<Self> {
let mut todo = Todo::new();
todo.alarms = alarms;
let mut extra = Vec::new();
for prop in props {
match prop.name.as_str() {
"UID" => todo.uid = Some(prop.value.clone()),
"DTSTAMP" => todo.dtstamp = Some(DateTimeValue::from_property(&prop)?),
"DTSTART" => todo.dtstart = Some(DateTimeValue::from_property(&prop)?),
"DUE" => todo.due = Some(DateTimeValue::from_property(&prop)?),
"COMPLETED" => todo.completed = Some(DateTimeValue::from_property(&prop)?),
"SUMMARY" => todo.summary = Some(unescape_text(&prop.value)),
"DESCRIPTION" => todo.description = Some(unescape_text(&prop.value)),
"LOCATION" => todo.location = Some(unescape_text(&prop.value)),
"STATUS" => todo.status = Some(prop.value.clone()),
"CATEGORIES" => {
todo.categories = prop
.value
.split(',')
.map(|s| unescape_text(s.trim()))
.collect();
}
"PRIORITY" => {
todo.priority = prop.value.parse().ok();
}
"PERCENT-COMPLETE" => {
todo.percent_complete = prop.value.parse().ok();
}
"RRULE" => todo.rrule = Some(RecurrenceRule::parse(&prop.value)?),
_ => extra.push(prop),
}
}
todo.extra_properties = extra;
Ok(todo)
}
}
impl Default for Todo {
fn default() -> Self {
Self::new()
}
}