use chrono::{DateTime, Utc}; use configparser::ini::Ini; use serde::{de, Deserialize, Deserializer, Serialize}; use serde_json; use std::env; use std::io::Error; use std::path::Path; use std::process::Command; use std::str; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum Recurrence { Daily, Weekly, Monthly, Yearly, } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum Status { Pending, Completed, Recurring, Deleted, } #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct ContextSwitchMetadata { pub test: u32, } #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Task { pub uuid: Uuid, pub id: u32, #[serde(with = "tw_date_format")] pub entry: DateTime, #[serde(with = "tw_date_format")] pub modified: DateTime, pub status: Status, pub description: String, pub urgency: f64, #[serde( default, skip_serializing_if = "Option::is_none", with = "opt_tw_date_format" )] pub due: Option>, #[serde( default, skip_serializing_if = "Option::is_none", with = "opt_tw_date_format" )] pub end: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub parent: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub project: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub recur: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub tags: Option>, #[serde( default, skip_serializing_if = "Option::is_none", deserialize_with = "deserialize_from_json" )] pub contextswitch: Option, } fn deserialize_from_json<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; serde_json::from_str(&s).map_err(de::Error::custom) } pub mod tw_date_format { use chrono::{DateTime, TimeZone, Utc}; use serde::{self, Deserialize, Deserializer, Serializer}; const FORMAT: &'static str = "%Y%m%dT%H%M%SZ"; pub fn serialize(date: &DateTime, serializer: S) -> Result where S: Serializer, { let s = format!("{}", date.format(FORMAT)); serializer.serialize_str(&s) } pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Utc.datetime_from_str(&s, FORMAT) .map_err(serde::de::Error::custom) } } pub mod opt_tw_date_format { use chrono::{DateTime, TimeZone, Utc}; use serde::{self, Deserialize, Deserializer, Serializer}; const FORMAT: &'static str = "%Y%m%dT%H%M%SZ"; pub fn serialize(date: &Option>, serializer: S) -> Result where S: Serializer, { if let Some(ref d) = *date { return serializer.serialize_str(&d.format(FORMAT).to_string()); } serializer.serialize_none() } pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; Utc.datetime_from_str(&s, FORMAT) .map(Some) .map_err(serde::de::Error::custom) } } pub fn load_config(task_data_location: Option<&str>) -> String { if let Ok(taskrc_location) = env::var("TASKRC") { let mut taskrc = Ini::new(); taskrc .load(&taskrc_location) .expect(&format!("Cannot load taskrc file {}", taskrc_location)); return taskrc.get("default", "data.location").expect(&format!( "'data.location' must be set in taskrc file {}", taskrc_location )); } let data_location = task_data_location .map(|s| s.to_string()) .unwrap_or_else(|| { env::var("TASK_DATA_LOCATION") .expect("Expecting TASKRC or TASK_DATA_LOCATION environment variable value") }); let mut taskrc = Ini::new(); taskrc.setstr("default", "uda.contextswitch.type", Some("string")); taskrc.setstr( "default", "uda.contextswitch.label", Some("Context Switch metadata"), ); taskrc.setstr("default", "uda.contextswitch.default", Some("{}")); taskrc.setstr("default", "data.location", Some(&data_location)); taskrc.setstr("default", "uda.contextswitch.type", Some("string")); taskrc.setstr( "default", "uda.contextswitch.label", Some("Context Switch metadata"), ); taskrc.setstr("default", "uda.contextswitch.default", Some("{}")); let taskrc_path = Path::new(&data_location).join(".taskrc"); let taskrc_location = taskrc_path.to_str().unwrap(); taskrc.write(taskrc_location).unwrap(); env::set_var("TASKRC", taskrc_location); return data_location; } pub fn export(filters: Vec<&str>) -> Result, Error> { let mut args = vec!["export"]; args.extend(filters); let export_output = Command::new("task").args(args).output()?; let tasks: Vec = serde_json::from_slice(&export_output.stdout)?; return Ok(tasks); } pub fn add(add_args: Vec<&str>) -> Result<(), Error> { let mut args = vec!["add"]; args.extend(add_args); Command::new("task").args(args).output()?; return Ok(()); }