diff --git a/pttodoer/src/main.rs b/pttodoer/src/main.rs index 89399b2..4b6b309 100644 --- a/pttodoer/src/main.rs +++ b/pttodoer/src/main.rs @@ -18,15 +18,63 @@ impl Tasks { } pub fn from_reader(r: impl std::io::Read) -> Result { + let result = Tasks::_from_reader(r)?; + if !result.is_legacy() { + return Ok(result); + } + let mut v2 = Tasks::new(); + for k in vec!["todo", "scheduled", "done"] { + v2.0.extend( + result.0[0] + .get_value(k.to_string()) + .or(Some(serde_yaml::Value::Null)) + .unwrap() + .as_sequence() + .or(Some(&vec![])) + .unwrap() + .iter() + .map(|v| { + Task::from_value(match k { + "done" => { + let mut t = Task::new(); + t.set_value( + "_done".to_string(), + v.clone(), + ); + serde_yaml::Value::from(t.0) + }, + _ => v.clone(), + }) + }) + ); + } + Ok(v2) + } + + fn _from_reader(mut r: impl std::io::Read) -> Result { + let mut buff = String::new(); + match r.read_to_string(&mut buff) { + Err(msg) => { + return Err(format!("failed to read body: {}", msg)); + }, + _ => {} + }; + let mut result = Tasks::new(); - match serde_yaml::from_reader::<_, Vec>(r) { + match serde_yaml::from_str::>(&buff) { Ok(v) => { result.0.extend(v.iter().map(|x| { Task::from_value(x.clone()) })); Ok(result) }, - Err(msg) => Err(format!("failed to read: {}", msg)), + Err(msg) => match Task::from_str(buff) { + Ok(t) => { + result.0.push(t); + Ok(result) + }, + Err(_) => Err(format!("failed to parse yaml: {}", msg)), + }, } } @@ -41,6 +89,13 @@ impl Tasks { .collect() ) } + + fn is_legacy(&self) -> bool { + self.len() == 1 + && self.0[0].get_value("done".to_string()).is_some() + && self.0[0].get_value("scheduled".to_string()).is_some() + && self.0[0].get_value("todo".to_string()).is_some() + } } #[cfg(test)] @@ -56,6 +111,20 @@ mod test_tasks { assert_eq!(2, tasks.due().len()); } + #[test] + fn from_reader_legacy() { + let tasks = Tasks::from_reader( + std::fs::File::open("./src/testdata/legacy.yaml").expect("failed to open file") + ).expect("failed to read file"); + assert_eq!(8, tasks.len()); + assert_eq!(5, tasks.due().len()); + assert_eq!("a".to_string(), tasks.due().0[0].get("is".to_string()).expect("missing 0th is")); + assert_eq!("b".to_string(), tasks.due().0[1].get("todo".to_string()).expect("missing 1st todo")); + assert_eq!("c".to_string(), tasks.due().0[2].get("is".to_string()).expect("missing 2nd is")); + assert_eq!("d".to_string(), tasks.due().0[3].get("todo".to_string()).expect("missing 3rd todo")); + assert_eq!("e".to_string(), tasks.due().0[4].get("todo".to_string()).expect("missing 4th todo")); + } + #[test] fn from_reader() { let tasks = Tasks::from_reader( @@ -79,8 +148,16 @@ impl Task { Task(serde_yaml::Mapping::new()) } - pub fn from_reader(r: impl std::io::Read) -> Result { - match serde_yaml::from_reader::<_, serde_yaml::Value>(r) { + pub fn from_reader(mut r: impl std::io::Read) -> Result { + let mut buff = String::new(); + match r.read_to_string(&mut buff) { + Err(msg) => Err(format!("failed to read body: {}", msg)), + _ => Task::from_str(buff), + } + } + + pub fn from_str(s: String) -> Result { + match serde_yaml::from_str::(&s) { Ok(v) => Ok(Task::from_value(v)), Err(msg) => Err(format!("failed to read value: {}", msg)), } @@ -169,6 +246,17 @@ impl Task { mod test_task { use super::*; + #[test] + fn from_str() { + assert!(Task::from_str("{ invalid".to_string()).is_err()); + assert!(Task::from_str("1".to_string()).is_ok()); + assert!(Task::from_str("'1'".to_string()).is_ok()); + assert!(Task::from_str("null".to_string()).is_ok()); + assert!(Task::from_str("true".to_string()).is_ok()); + assert!(Task::from_str("[]".to_string()).is_ok()); + assert!(Task::from_str("{}".to_string()).is_ok()); + } + #[test] fn from_reader() { let task = Task::from_reader( @@ -483,6 +571,7 @@ impl TS { } fn new(src: String) -> Result { + // %Y-%m-%dT%H:%MZ match DateTime::parse_from_str( &format!("{} +0000", src), "%Y-%m-%dT%H:%MZ %z", @@ -490,6 +579,8 @@ impl TS { Ok(v) => { return Ok(TS(v.timestamp() as u64)) }, _ => {}, }; + + // %Y-%m-%dT%H match NaiveDateTime::parse_from_str( &format!("{}:00", src), "%Y-%m-%dT%H:%M", @@ -497,6 +588,8 @@ impl TS { Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; + + // %Y-%m-%d match NaiveDateTime::parse_from_str( &format!("{}T00:00", src), "%Y-%m-%dT%H:%M", @@ -504,7 +597,31 @@ impl TS { Ok(v) => { return Ok(TS(v.and_utc().timestamp() as u64)) }, _ => {}, }; - Err(format!("cannot parse date format from {}", src)) + + // Sun Dec 3 23:29:27 EST 2023 + match DateTime::parse_from_str( + &format!("{}", src) + .replace("PDT", "-0800") + .replace("MDT", "-0700") + .replace("EDT", "-0400") + .replace("PST", "-0700") + .replace("MST", "-0600") + .replace("EST", "-0500") + .replace(" 1 ", " 01 ") + .replace(" 2 ", " 02 ") + .replace(" 3 ", " 03 ") + .replace(" 4 ", " 04 ") + .replace(" 5 ", " 05 ") + .replace(" 6 ", " 06 ") + .replace(" 7 ", " 07 ") + .replace(" 8 ", " 08 ") + .replace(" 9 ", " 09 ") + .replace(" ", " "), + "%a %b %d %H:%M:%S %z %Y", + ) { + Ok(v) => Ok(TS(v.timestamp() as u64)), + Err(msg) => Err(format!("failed to parse legacy golang time: {}", msg)), + } } fn unix(&self) -> u64 { @@ -525,6 +642,42 @@ mod test_ts { #[test] fn parse() { + match TS::new("Tue Nov 7 07:33:11 PST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-07T14:33Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Jun 4 05:24:32 PDT 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-06-04T13:24Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sat Nov 4 08:36:01 MDT 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-04T15:36Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Nov 5 22:27:17 MST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-11-06T04:27Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Thu Apr 18 16:17:41 EDT 2024".to_string()) { + Ok(ts) => { + assert_eq!("2024-04-18T20:17Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; + match TS::new("Sun Dec 3 23:29:27 EST 2023".to_string()) { + Ok(ts) => { + assert_eq!("2023-12-04T04:29Z", ts.to_string()); + }, + Err(err) => assert!(false, "failed to parse ts: {}", err), + }; match TS::new("2024-05-01T00:00Z".to_string()) { Ok(ts) => { assert_eq!(1714521600, ts.unix()); diff --git a/pttodoer/src/testdata/legacy.yaml b/pttodoer/src/testdata/legacy.yaml new file mode 100644 index 0000000..cd2c153 --- /dev/null +++ b/pttodoer/src/testdata/legacy.yaml @@ -0,0 +1,17 @@ +todo: +- a +- todo: b + schedule: 2000-01-01 +scheduled: +- c +- todo: d + schedule: 2000-02-01 +- todo: e + ts: Sun Dec 3 23:29:27 EST 2023 + schedule: '0 0 1 1 *' +- todo: f + schedule: 2099-02-02 +done: +- g +- todo: h + ts: Sun Dec 3 23:29:27 EST 2023