From 0e667b788c3eeb5003d83407ef87b030874a470d Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 8 Oct 2019 19:27:37 +0200 Subject: Extract libical-sys wrapper library --- src/actions/agenda.rs | 305 ------------------ src/actions/calendars.rs | 25 -- src/actions/copy.rs | 45 --- src/actions/cursor.rs | 156 ---------- src/actions/delete.rs | 68 ---- src/actions/edit.rs | 50 --- src/actions/gen_completions.rs | 35 --- src/actions/get.rs | 49 --- src/actions/index/action.rs | 182 ----------- src/actions/index/bucketable.rs | 162 ---------- src/actions/index/indextime.rs | 38 --- src/actions/index/mod.rs | 18 -- src/actions/list.rs | 32 -- src/actions/mod.rs | 16 - src/actions/modify.rs | 140 --------- src/actions/new.rs | 358 --------------------- src/actions/select.rs | 86 ------ src/actions/seq.rs | 79 ----- src/actions/show.rs | 34 -- src/actions/undo.rs | 131 -------- src/actions/unroll.rs | 37 --- src/backup.rs | 56 ---- src/bin/khaleesi.rs | 87 ------ src/calendars.rs | 33 -- src/cli.rs | 81 ----- src/component.rs | 130 ++++++++ src/config.rs | 131 -------- src/cursorfile.rs | 92 ------ src/defaults.rs | 96 ------ src/duration.rs | 177 +++++++++++ src/edit.rs | 46 --- src/icalwrap/icalcomponent.rs | 131 -------- src/icalwrap/icalduration.rs | 177 ----------- src/icalwrap/icalproperty.rs | 60 ---- src/icalwrap/icaltime.rs | 365 ---------------------- src/icalwrap/icaltimezone.rs | 153 --------- src/icalwrap/icalvcalendar.rs | 670 ---------------------------------------- src/icalwrap/icalvevent.rs | 394 ----------------------- src/icalwrap/mod.rs | 26 -- src/input.rs | 71 ----- src/khevent.rs | 279 ----------------- src/khline.rs | 280 ----------------- src/lib.rs | 66 ++-- src/macros.rs | 32 -- src/property.rs | 60 ++++ src/selectors/cal.rs | 76 ----- src/selectors/daterange.rs | 188 ----------- src/selectors/grep.rs | 98 ------ src/selectors/mod.rs | 142 --------- src/selectors/prop.rs | 81 ----- src/selectors/range.rs | 79 ----- src/selectors/test.rs | 30 -- src/seqfile.rs | 72 ----- src/testdata.rs | 27 -- src/testutils.rs | 15 - src/time.rs | 359 +++++++++++++++++++++ src/timezone.rs | 134 ++++++++ src/utils/dateutil.rs | 4 - src/utils/fileutil.rs | 40 +-- src/utils/mod.rs | 3 +- src/utils/stdioutils.rs | 124 -------- src/vcalendar.rs | 606 ++++++++++++++++++++++++++++++++++++ src/vevent.rs | 394 +++++++++++++++++++++++ 63 files changed, 1901 insertions(+), 6310 deletions(-) delete mode 100644 src/actions/agenda.rs delete mode 100644 src/actions/calendars.rs delete mode 100644 src/actions/copy.rs delete mode 100644 src/actions/cursor.rs delete mode 100644 src/actions/delete.rs delete mode 100644 src/actions/edit.rs delete mode 100644 src/actions/gen_completions.rs delete mode 100644 src/actions/get.rs delete mode 100644 src/actions/index/action.rs delete mode 100644 src/actions/index/bucketable.rs delete mode 100644 src/actions/index/indextime.rs delete mode 100644 src/actions/index/mod.rs delete mode 100644 src/actions/list.rs delete mode 100644 src/actions/mod.rs delete mode 100644 src/actions/modify.rs delete mode 100644 src/actions/new.rs delete mode 100644 src/actions/select.rs delete mode 100644 src/actions/seq.rs delete mode 100644 src/actions/show.rs delete mode 100644 src/actions/undo.rs delete mode 100644 src/actions/unroll.rs delete mode 100644 src/backup.rs delete mode 100644 src/bin/khaleesi.rs delete mode 100644 src/calendars.rs delete mode 100644 src/cli.rs create mode 100644 src/component.rs delete mode 100644 src/config.rs delete mode 100644 src/cursorfile.rs delete mode 100644 src/defaults.rs create mode 100644 src/duration.rs delete mode 100644 src/edit.rs delete mode 100644 src/icalwrap/icalcomponent.rs delete mode 100644 src/icalwrap/icalduration.rs delete mode 100644 src/icalwrap/icalproperty.rs delete mode 100644 src/icalwrap/icaltime.rs delete mode 100644 src/icalwrap/icaltimezone.rs delete mode 100644 src/icalwrap/icalvcalendar.rs delete mode 100644 src/icalwrap/icalvevent.rs delete mode 100644 src/icalwrap/mod.rs delete mode 100644 src/input.rs delete mode 100644 src/khevent.rs delete mode 100644 src/khline.rs delete mode 100644 src/macros.rs create mode 100644 src/property.rs delete mode 100644 src/selectors/cal.rs delete mode 100644 src/selectors/daterange.rs delete mode 100644 src/selectors/grep.rs delete mode 100644 src/selectors/mod.rs delete mode 100644 src/selectors/prop.rs delete mode 100644 src/selectors/range.rs delete mode 100644 src/selectors/test.rs delete mode 100644 src/seqfile.rs create mode 100644 src/time.rs create mode 100644 src/timezone.rs delete mode 100644 src/utils/stdioutils.rs create mode 100644 src/vcalendar.rs create mode 100644 src/vevent.rs (limited to 'src') diff --git a/src/actions/agenda.rs b/src/actions/agenda.rs deleted file mode 100644 index 9659017..0000000 --- a/src/actions/agenda.rs +++ /dev/null @@ -1,305 +0,0 @@ -use chrono::{DateTime, Datelike, TimeZone, Local, Date}; -use yansi::{Style}; -use itertools::Itertools; -use structopt::StructOpt; - -use crate::cursorfile; -use crate::input; -use crate::config::{Config,CalendarConfig}; -use crate::khevent::KhEvent; -use crate::khline::KhLine; -use crate::KhResult; - -#[derive(Debug, StructOpt)] -pub struct AgendaArgs { - /// Show agenda view - #[structopt(name = "args")] - pub args: Vec, -} - -pub fn show_events(config: &Config, args: &[&str]) -> KhResult<()> { - let mut events = input::selection(args)?; - - let cursor = cursorfile::read_cursorfile().ok(); - show_events_cursor(config, &mut events, cursor.as_ref()); - - Ok(()) -} - -pub fn show_events_cursor( - config: &Config, - events: &mut Iterator, - cursor: Option<&KhLine>, -) { - - let mut not_over_yet: Vec<(usize, KhEvent, Option<&CalendarConfig>)> = Vec::new(); - let mut cals_iter = events - .enumerate() - .map(|(i, event)| { - let config = event.get_calendar_name().and_then(|name| config.get_config_for_calendar(&name)); - (i, event, config) - }) - .peekable(); - - let start_day = match cals_iter.peek() { - Some((_, event, _)) => { - event - .get_start() - .map(|dtstart| dtstart.into()) - .unwrap_or_else(|| Local.timestamp(0, 0)) - .date() - } - None => return, - }; - - let mut cur_day = start_day.pred(); - let mut last_printed_day = start_day.pred(); - while cals_iter.peek().is_some() || !not_over_yet.is_empty() { - cur_day = cur_day.succ(); - - maybe_print_date_line_header(&config, cur_day, start_day, &mut last_printed_day); - - not_over_yet.retain( |(index, event, cal_config)| { - let is_cursor = cursor.map(|c| c.matches_khevent(&event)).unwrap_or(false); - maybe_print_date_line(&config, cur_day, start_day, &mut last_printed_day); - print_event_line(*cal_config, *index, &event, cur_day, is_cursor); - event.continues_after(cur_day) - }); - - let relevant_events = cals_iter.peeking_take_while(|(_,event,_)| event.starts_on(cur_day)); - for (i, event, cal_config) in relevant_events { - let is_cursor = cursor.map(|c| c.matches_khevent(&event)).unwrap_or(false); - maybe_print_date_line(&config, cur_day, start_day, &mut last_printed_day); - print_event_line(cal_config, i, &event, cur_day, is_cursor); - if event.continues_after(cur_day) { - not_over_yet.push((i, event, cal_config)); - } - } - } -} - -fn maybe_print_week_separator(config: &Config, date: Date, start_date: Date, last_printed_date: Date) { - if !config.agenda.print_week_separator { - return; - } - if date != start_date && last_printed_date.iso_week() < date.iso_week() { - khprintln!(); - } -} - -fn maybe_print_date_line_header(config: &Config, date: Date, start_date: Date, last_printed_date: &mut Date) { - if !config.agenda.print_empty_days { - return; - } - maybe_print_date_line(config, date, start_date, last_printed_date); -} - -fn maybe_print_date_line(config: &Config, date: Date, start_date: Date, last_printed_date: &mut Date) { - if date <= *last_printed_date { - return; - } - maybe_print_week_separator(config, date, start_date, *last_printed_date); - print_date_line(date); - *last_printed_date = date; -} - -fn print_date_line(date: Date) { - let style_heading = Style::default().bold(); - khprintln!("{}, {}", style_heading.paint(date.format("%Y-%m-%d")), date.format("%A")); -} - -fn print_event_line( - config: Option<&CalendarConfig>, - index: usize, - event: &KhEvent, - date: Date, - is_cursor: bool -) { - match event_line(config, &event, date, is_cursor) { - Ok(line) => khprintln!("{:4} {}", index, line), - Err(error) => warn!("{} in {}", error, event.get_uid()) - } -} - -pub fn event_line( - config: Option<&CalendarConfig>, - event: &KhEvent, - cur_day: Date, - is_cursor: bool -) -> Result { - if !event.relevant_on(cur_day) { - return Err(format!("event is not relevant for {:?}", cur_day)); - } - - let mut summary = event.get_summary().ok_or("Invalid SUMMARY")?; - if let Some(config) = config { - let calendar_style = config.get_style_for_calendar(); - summary = calendar_style.paint(summary).to_string(); - } - - let cursor_icon = if is_cursor { ">" } else { "" }; - - if event.is_allday() { - Ok(format!("{:3} {}", cursor_icon, summary)) - } else { - let mut time_sep = " "; - let dtstart: DateTime = event.get_start().ok_or("Invalid DTSTART")?.into(); - let start_string = if dtstart.date() != cur_day { - "".to_string() - } else { - time_sep = "-"; - format!("{}", dtstart.format("%H:%M")) - }; - - let dtend: DateTime = event.get_end().ok_or("Invalid DTEND")?.into(); - let end_string = if dtend.date() != cur_day { - "".to_string() - } else { - time_sep = "-"; - format!("{}", dtend.format("%H:%M")) - }; - - Ok(format!("{:3}{:5}{}{:5} {}", cursor_icon, start_string, time_sep, end_string, summary)) - } -} - -impl KhEvent { - fn starts_on(&self, date: Date) -> bool { - let dtstart: Date = self.get_start().unwrap().into(); - dtstart == date - } - - fn relevant_on(&self, date: Date) -> bool { - let dtstart: Option> = self.get_start().map(|date| date.into()); - let last_relevant_date: Option> = self.get_last_relevant_date().map(|date| date.into()); - - dtstart.map(|dtstart| dtstart <= date).unwrap_or(false) && - last_relevant_date.map(|enddate| enddate >= date).unwrap_or(false) - } - - fn continues_after(&self, date: Date) -> bool { - let last_relevant_date: Option> = self.get_last_relevant_date().map(|date| date.into()); - last_relevant_date - .map(|enddate| enddate > date) - .unwrap_or(false) - } -} - -#[cfg(test)] -mod integration { - use super::*; - use crate::testdata; - use crate::testutils::*; - use crate::utils::stdioutils; - use crate::config::Config; - use crate::icalwrap::IcalVCalendar; - - use chrono::{Local, TimeZone}; - - #[test] - fn test_starts_on() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - let event = cal.get_principal_khevent(); - - let first_day = Local.ymd(2007, 6, 28); - assert!(event.starts_on(first_day)); - - let last_day = Local.ymd(2007, 7, 7); - assert!(!event.starts_on(last_day)); - } - - #[test] - fn test_continues_after_allday() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_ALLDAY, None).unwrap(); - let event = cal.get_principal_khevent(); - let first_day = Local.ymd(2007, 6, 28); - assert!(event.continues_after(first_day)); - let last_day = Local.ymd(2007, 7, 8); - assert!(!event.continues_after(last_day)); - } - - #[test] - fn test_continues_after_simple() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, None).unwrap(); - let event = cal.get_principal_khevent(); - let date = Local.ymd(1997, 3, 24); - assert!(!event.continues_after(date)); - } - - #[test] - fn test_event_line_negative() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, None).unwrap(); - let event = cal.get_principal_khevent(); - let date = Local.ymd(1998, 1, 1); - let event_line = event_line(None, &event, date, false); - assert!(event_line.is_err()) - } - - #[test] - fn test_event_line_simple() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, None).unwrap(); - let event = cal.get_principal_khevent(); - let date = Local.ymd(1997, 3, 24); - let event_line = event_line(None, &event, date, false).unwrap(); - assert_eq!(" 13:30-22:00 Calendaring Interoperability Planning Meeting".to_string(), event_line) - } - - #[test] - fn test_event_line_cursor() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, None).unwrap(); - let event = cal.get_principal_khevent(); - let date = Local.ymd(1997, 3, 24); - let event_line = event_line(None, &event, date, true).unwrap(); - assert_eq!("> 13:30-22:00 Calendaring Interoperability Planning Meeting".to_string(), event_line) - } - - #[test] - fn test_event_line_multiday() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - let event = cal.get_principal_khevent(); - let begin = Local.ymd(2007, 6, 28); - let middle = Local.ymd(2007, 6, 30); - let end = Local.ymd(2007, 7, 9); - let event_line_begin = event_line(None, &event, begin, false).unwrap(); - let event_line_middle = event_line(None, &event, middle, false).unwrap(); - let event_line_end = event_line(None, &event, end, false).unwrap(); - assert_eq!(" 15:29- Festival International de Jazz de Montreal".to_string(), event_line_begin); - assert_eq!(" Festival International de Jazz de Montreal".to_string(), event_line_middle); - assert_eq!(" -09:29 Festival International de Jazz de Montreal".to_string(), event_line_end); - } - - #[test] - fn test_event_line_multiday_allday() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_ALLDAY, None).unwrap(); - let event = cal.get_principal_khevent(); - let date = Local.ymd(2007, 6, 28); - let event_line = event_line(None, &event, date, false).unwrap(); - assert_eq!(" Festival International de Jazz de Montreal".to_string(), event_line) - } - - #[test] - fn test_stdout_simple() { - testdata::setup(); - let _testdir = prepare_testdir("testdir_with_seq"); - - show_events(&Config::read_config(), &[]).unwrap(); - - let stdout = stdioutils::test_stdout_clear(); - let expected = indoc!(" - 2018-12-13, Thursday - 0 23:30- shows up on two days - 2018-12-14, Friday - 0 shows up on two days - 2018-12-15, Saturday - 0 shows up on two days - 2018-12-16, Sunday - 0 shows up on two days - 2018-12-17, Monday - 0 -19:30 shows up on two days - "); - assert_eq!(expected, stdout); - } -} diff --git a/src/actions/calendars.rs b/src/actions/calendars.rs deleted file mode 100644 index 72a8a7d..0000000 --- a/src/actions/calendars.rs +++ /dev/null @@ -1,25 +0,0 @@ -use calendars; - -pub fn action_list_calendars(_args: &[String]) -> Result<(), String> { - for calendar in calendars::calendar_list() { - khprintln!("{}", calendar); - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use testutils; - - #[test] - fn test() { - let _testdir = testutils::prepare_testdir("testdir_two_cals"); - - action_list_calendars(&[]).unwrap(); - - assert_eq!("second\nsecond/second_sub\nfirst\n", testutils::test_stdout_clear()); - } -} diff --git a/src/actions/copy.rs b/src/actions/copy.rs deleted file mode 100644 index 811bcc5..0000000 --- a/src/actions/copy.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::input; -use crate::utils::fileutil; -use crate::utils::misc; - -use crate::KhResult; - -pub fn do_copy() -> KhResult<()> { - let khline = input::default_input_khline()?; - - let uid = &misc::make_new_uid(); - let cal = khline.to_cal()?; - let new_cal = cal.with_uid(uid)?.with_dtstamp_now(); - - fileutil::write_cal(&new_cal)?; - - info!("Successfully wrote file: {}", new_cal.get_path().unwrap().display()); - - Ok(()) -} - - -#[cfg(test)] -mod integration { - use super::*; - - use assert_fs::prelude::*; - use crate::khline::KhLine; - use crate::testutils::prepare_testdir; - use crate::utils::stdioutils; - use predicates::prelude::*; - - #[test] - fn copy_test() { - let testdir = prepare_testdir("testdir"); - stdioutils::test_stdin_write("twodaysacrossbuckets.ics"); - - do_copy().unwrap(); - - let child = testdir.child(".khaleesi/cal/11111111-2222-3333-4444-444444444444@khaleesi.ics"); - child.assert(predicate::path::exists()); - - let khline = "11111111-2222-3333-4444-444444444444@khaleesi.ics".parse::().unwrap(); - assert_eq!("11111111-2222-3333-4444-444444444444@khaleesi", khline.to_event().unwrap().get_uid()); - } -} diff --git a/src/actions/cursor.rs b/src/actions/cursor.rs deleted file mode 100644 index b15ffed..0000000 --- a/src/actions/cursor.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::cursorfile; -use crate::utils::stdioutils; -use crate::KhResult; -use crate::seqfile; - -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct CursorArgs { - /// Move the cursor on the selection. - #[structopt(name = "direction", raw(possible_values = "&CursorDirection::variants()"))] - pub direction: Option, -} - -arg_enum! { -#[derive(Debug)] - pub enum CursorDirection { - Next, - Prev, - } -} - -enum Direction { - Up, - Down, -} - -pub fn do_cursor(args: &CursorArgs) -> KhResult<()> { - if !stdioutils::is_stdin_tty() { - write_stdin_to_cursorfile()?; - } else { - //println!("stdin is tty") - if let Some(direction) = &args.direction { - match direction { - CursorDirection::Prev => return cursor_sequence_move(&Direction::Up), - CursorDirection::Next => return cursor_sequence_move(&Direction::Down), - } - }; - } - - if !stdioutils::is_stdout_tty() || stdioutils::is_stdin_tty() { - write_cursorfile_to_stdout(); - } - - Ok(()) -} - -fn write_stdin_to_cursorfile() -> KhResult<()> { - let lines = stdioutils::read_lines_from_stdin()?; - - if lines.len() > 1 { - Err("Too many lines on stdin")?; - }; - - cursorfile::write_cursorfile(&lines[0])?; - - Ok(()) -} - -fn write_cursorfile_to_stdout() { - if let Ok(cursor) = cursorfile::read_cursorfile() { - khprintln!("{}", cursor); - } -} - -fn cursor_sequence_move(direction: &Direction) -> KhResult<()> { - let cursor_event = cursorfile::read_cursorfile()?; - let mut seq = seqfile::read_seqfile_khlines()?; - let next_elem = match direction { - Direction::Up => { - let mut seq_rev = seq.rev(); - seq_rev.find(|line| line == &cursor_event); - seq_rev.next() - }, - Direction::Down => { - seq.find(|line| line == &cursor_event); - seq.next() - } - }; - - match next_elem { - Some(next_elem) => cursorfile::write_cursorfile(&next_elem.to_string()), - None => { - warn!("Already at end of sequence"); - Ok(()) - } - } -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils; - use assert_fs::prelude::*; - use predicates::prelude::*; - - #[test] - fn test_with_stdin() { - let testdir = testutils::prepare_testdir_empty(); - let expected_str = "hi there"; - stdioutils::test_stdin_write(expected_str); - - let args = CursorArgs{direction: None}; - do_cursor(&args).unwrap(); - - testdir.child(".khaleesi/cursor").assert(expected_str); - } - - #[test] - fn test_cursor_sequence_move_next() { - let testdir = testutils::prepare_testdir("testdir_with_seq_and_cursor"); - let args = CursorArgs{direction: Some(CursorDirection::Next)}; - do_cursor(&args).unwrap(); - - let out = "1182988800 rfc_multi_day_allday.ics"; - let predicate = predicate::str::similar(out); - testdir.child(".khaleesi/cursor").assert(predicate); - } - - #[test] - fn test_cursor_sequence_move_prev_at_end() { - let testdir = testutils::prepare_testdir("testdir_with_seq_and_cursor"); - let args = CursorArgs{direction: Some(CursorDirection::Prev)}; - do_cursor(&args).unwrap(); - - let out = "1544740200 twodaysacrossbuckets.ics\n"; - let predicate = predicate::str::similar(out); - testdir.child(".khaleesi/cursor").assert(predicate); - } - - #[test] - fn test_with_stdin_linebreak() { - let _testdir = testutils::prepare_testdir_empty(); - let expected_str = "hi\nthere"; - stdioutils::test_stdin_write(expected_str); - - let args = CursorArgs {direction: None}; - let result = do_cursor(&args); - - assert!(result.is_err()); - //testdir.child(".khaleesi/cursor").assert(expected_str); - } - - #[test] - fn test_no_stdin() { - let testdir = testutils::prepare_testdir("testdir_with_cursor"); - - let args = CursorArgs {direction: None}; - do_cursor(&args).unwrap(); - let out = stdioutils::test_stdout_clear(); - - let predicate = predicate::str::similar(out); - testdir.child(".khaleesi/cursor").assert(predicate); - } -} diff --git a/src/actions/delete.rs b/src/actions/delete.rs deleted file mode 100644 index ac2065a..0000000 --- a/src/actions/delete.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::input; -use crate::backup::backup; -use crate::KhResult; -use crate::khline::KhLine; -use crate::utils::stdioutils; - -use std::path::PathBuf; -use std::fs::remove_file; - -pub fn do_delete() -> KhResult<()> { - info!("do_delete"); - - let cursor_khline = input::default_input_khline()?; - - delete_file(cursor_khline) -} - -fn delete_file(khline: KhLine) -> KhResult<()> { - - if ask_really_delete(&khline.path) { - let backup_path = backup(&khline).unwrap(); - info!("Backup written to {}", backup_path.display()); - - remove_file(khline.path.clone())?; - info!("deleted {:#?}", khline.get_normalized_path()); - } - - Ok(()) -} - -fn ask_really_delete(path: &PathBuf) -> bool { - if cfg!(test) { return true }; - - println!("Really delete {:#?}? y/n:", path); - - match stdioutils::read_single_char_from_stdin().unwrap() { - 'y' => true, - _ => false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::testutils::*; - use assert_fs::prelude::*; - use predicates::prelude::*; - - #[test] - fn test_do_delete_cursor() { - let testdir = prepare_testdir("testdir_with_cursor"); - - do_delete().unwrap(); - - let predicate = predicate::path::missing(); - testdir.child(".khaleesi/cal/twodaysacrossbuckets").assert(predicate); - - } - - #[test] - #[should_panic] - fn test_do_delete_no_cursor() { - let _testdir = prepare_testdir("testdir"); - - do_delete().unwrap(); - } -} diff --git a/src/actions/edit.rs b/src/actions/edit.rs deleted file mode 100644 index 9d1abf8..0000000 --- a/src/actions/edit.rs +++ /dev/null @@ -1,50 +0,0 @@ -use tempfile::NamedTempFile; - -use crate::backup::backup; -use crate::edit; -use crate::input; -use crate::khline::KhLine; -use crate::utils::fileutil; -use crate::KhResult; - -pub fn do_edit() -> KhResult<()> { - let khline = input::default_input_khline()?; - edit(&khline) -} - -fn edit(khline: &KhLine) -> KhResult<()> { - let tempfile = NamedTempFile::new()?; - let calendar = khline.to_cal()?; - - fileutil::write_file(tempfile.path(), &calendar.to_string())?; - edit::edit_loop(&tempfile.path())?; - - let backup_path = backup(&khline).unwrap(); - info!("Backup written to {}", backup_path.display()); - - let edited_cal = KhLine::new(tempfile.path(), None).to_cal()?.with_dtstamp_now().with_last_modified_now(); - fileutil::write_file(&khline.path, &edited_cal.to_string())?; - info!("Successfully edited file {}", khline.path.display()); - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::prepare_testdir; - - #[test] - fn edit_test() { - let _testdir = prepare_testdir("testdir"); - - let khline = "twodaysacrossbuckets.ics".parse::().unwrap(); - - assert!(edit(&khline).is_ok()); - let event = khline.to_event().unwrap(); - - assert_eq!("20130101T010203Z", event.get_dtstamp().unwrap()); - assert_eq!("20130101T010203Z", event.get_last_modified().unwrap()); - } -} diff --git a/src/actions/gen_completions.rs b/src/actions/gen_completions.rs deleted file mode 100644 index e8e11ca..0000000 --- a/src/actions/gen_completions.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::io; -use structopt::clap::Shell; -use structopt::StructOpt; - -use crate::cli::CommandLine; -use crate::KhResult; - -#[derive(Debug, StructOpt)] -pub struct GenCompletionsArgs { - /// the shell - #[structopt(name = "shell", raw(possible_values = "&ShellArg::variants()"))] - pub shell: ShellArg, -} - -arg_enum! { -#[derive(Debug)] - pub enum ShellArg{ - Bash, - Zsh, - Fish, - Elvish - } -} - -pub fn gen_completions(args: &GenCompletionsArgs) -> KhResult<()> { - let mut app = CommandLine::clap(); - let binary_name = "khaleesi"; - match args.shell { - ShellArg::Bash => app.gen_completions_to(binary_name, Shell::Bash, &mut io::stdout()), - ShellArg::Zsh => app.gen_completions_to(binary_name, Shell::Zsh, &mut io::stdout()), - ShellArg::Fish => app.gen_completions_to(binary_name, Shell::Fish, &mut io::stdout()), - ShellArg::Elvish => app.gen_completions_to(binary_name, Shell::Elvish, &mut io::stdout()), - } - Ok(()) -} diff --git a/src/actions/get.rs b/src/actions/get.rs deleted file mode 100644 index 513c0b3..0000000 --- a/src/actions/get.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::calendars; -use crate::KhResult; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct GetArgs { - /// Show information about this - #[structopt(name = "query", raw(possible_values = "&GetQueryArgs::variants()"))] - pub query: GetQueryArgs, -} - -arg_enum! { -#[derive(Debug)] - pub enum GetQueryArgs{ - Calendars, - } -} - -pub fn action_get(args: &GetArgs) -> KhResult<()> { - match args.query { - GetQueryArgs::Calendars => action_get_calendars(), - } -} - -pub fn action_get_calendars() -> KhResult<()> { - for calendar in calendars::calendar_list() { - khprintln!("{}", calendar); - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils; - use crate::utils::stdioutils; - - #[test] - fn test_get_calendars() { - let _testdir = testutils::prepare_testdir("testdir_two_cals"); - - let args = GetArgs { query: GetQueryArgs::Calendars }; - action_get(&args).unwrap(); - - assert_eq!("first\nsecond\nsecond/second_sub\n", stdioutils::test_stdout_clear()); - } -} diff --git a/src/actions/index/action.rs b/src/actions/index/action.rs deleted file mode 100644 index b32a248..0000000 --- a/src/actions/index/action.rs +++ /dev/null @@ -1,182 +0,0 @@ -use chrono::prelude::*; -use crate::icalwrap::*; -use std::collections::HashMap; -use std::fs; -use std::path::{Path,PathBuf}; -use std::time::SystemTime; -use walkdir::DirEntry; - -use crate::defaults::*; -use super::{IndexArgs, indextime}; -use crate::utils::fileutil; -use crate::utils::lock; -use crate::utils::misc; -use crate::KhResult; - -pub fn action_index(args: &IndexArgs) -> KhResult<()> { - let reindex = args.reindex; - let indexpath = match &args.path { - Some(path) => path.clone(), - None => get_caldir(), - }; - - index_dir(&indexpath, reindex) -} - -fn add_buckets_for_calendar(buckets: &mut HashMap>, cal: &IcalVCalendar) { - use super::bucketable::Bucketable; - use super::bucketable::Merge; - - match cal.get_buckets() { - Ok(cal_buckets) => buckets.merge(cal_buckets), - Err(error) => { - warn!("{}", error) - } - } -} - -fn index_dir(dir: &Path, reindex: bool) -> KhResult<()> { - use std::time::Instant; - - let _lock = lock::lock_file_exclusive(&get_indexlockfile())?; - - info!("Recursively indexing '.ics' files in directory: {}", dir.to_string_lossy()); - if !dir.exists() { - Err(format!("Directory doesn't exist: {}", dir.to_string_lossy()))?; - } - - let now = Instant::now(); - let start_time = Utc::now(); - - let last_index_time = if reindex { - debug!("Forced reindex, indexing all files"); - None - } else { - let last_index_time = indextime::get_index_time(); - match last_index_time { - Some(time) => debug!("Previously indexed {}, indexing newer files only", time.with_timezone(&Local)), - None => debug!("No previous index time, indexing all files"), - } - last_index_time - }; - - let modified_since = last_index_time.map(|time| time.timestamp()).unwrap_or(0); - let ics_files = get_ics_files(dir, modified_since); - - let buckets = read_buckets(ics_files); - - let indexdir = get_indexdir(); - let clear_index_dir = last_index_time.is_none(); - prepare_index_dir(&indexdir, clear_index_dir)?; - - write_index(&indexdir, &buckets); - info!("Index written in {}ms", misc::format_duration(&now.elapsed())); - - indextime::write_index_time(&start_time); - - Ok(()) -} - -pub fn get_ics_files(dir: &Path, modified_since: i64) -> impl Iterator { - use walkdir::WalkDir; - - WalkDir::new(dir) - .follow_links(true) - .into_iter() - .filter_entry(move |entry| accept_entry(entry, modified_since)) - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_file()) - .filter(|e| e.path().extension().map_or(false, |extension| extension == "ics")) - .map(|entry| entry.into_path()) -} - -fn accept_entry(dir_entry: &DirEntry, modified_since: i64) -> bool { - if dir_entry.path().is_dir() { - return true; - } - dir_entry.metadata() - .map_err(|err| err.into()) // transform to io::Error - .and_then(|metadata| metadata.modified()) - .map(|modified| modified.duration_since(SystemTime::UNIX_EPOCH).unwrap()) - .map(|modified| modified.as_secs() as i64) - .map(|modified| modified > modified_since) - .unwrap_or(false) -} - -fn read_buckets(ics_files: impl Iterator) -> HashMap> { - let mut buckets: HashMap> = HashMap::new(); - - let mut total_files = 0; - for file in ics_files { - debug!("Indexing file: {:?}", file); - match fileutil::read_file_to_string(&file) { - Ok(content) => { - total_files += 1; - match IcalVCalendar::from_str(&content, Some(&file)) { - Ok(cal) => add_buckets_for_calendar(&mut buckets, &cal), - Err(error) => error!("{:?}: {}", file, error) - } - } - Err(error) => error!("{}", error), - } - } - - info!("Loaded {} files into {} buckets", total_files, buckets.len()); - buckets -} - -fn write_index(index_dir: &Path, buckets: &HashMap>) { - for (key, val) in buckets.iter() { - let bucketfile = bucket_file(index_dir, key); - trace!("Writing bucket: {}", key); - let content = &[&val.join("\n"), "\n"].concat(); - if let Err(error) = fileutil::append_file(&bucketfile, content) { - error!("{}", error); - return; - } - } -} - -fn bucket_file(index_dir: &Path, key: &str) -> PathBuf { - let mut result = PathBuf::from(index_dir); - result.push(key); - result -} - -fn prepare_index_dir(indexdir: &Path, clear_index_dir: bool) -> Result<(), std::io::Error> { - if indexdir.exists() && clear_index_dir { - info!("Clearing index directory: {}", indexdir.to_string_lossy()); - fs::remove_dir_all(&indexdir)? - } - - if !indexdir.exists() { - info!("Creating index directory: {}", indexdir.to_string_lossy()); - fs::create_dir(&indexdir)?; - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::prepare_testdir; - use assert_fs::prelude::*; - use crate::cli::CommandLine; - use crate::cli::Command::Index; - use structopt::StructOpt; - - #[test] - fn test_index() { - let testdir = prepare_testdir("testdir"); - - let args = CommandLine::from_iter(&["khaleesi", "index"]); - if let Index(x) = args.cmd { - action_index(&x).unwrap(); - } - - testdir.child(".khaleesi/index/2018-W50").assert("1544740200 twodaysacrossbuckets.ics\n"); - testdir.child(".khaleesi/index/2018-W51").assert("1544740200 twodaysacrossbuckets.ics\n"); - } -} diff --git a/src/actions/index/bucketable.rs b/src/actions/index/bucketable.rs deleted file mode 100644 index 8644c1e..0000000 --- a/src/actions/index/bucketable.rs +++ /dev/null @@ -1,162 +0,0 @@ -use chrono::{Local, Date, Datelike, Duration}; -use std::collections::HashMap; -use std::{hash, cmp}; - -use crate::icalwrap::IcalVCalendar; -//use crate::icalwrap::IcalVEvent; -use crate::utils::misc; -use crate::khline::KhLine; -use crate::khevent::KhEvent; - -pub trait Bucketable { - fn get_buckets(&self) -> Result>, String>; - - fn buckets_for_interval(mut start: Date, end: Date) -> Vec { - let mut buckets = Vec::new(); - - while start.iso_week() <= end.iso_week() { - let bucket = misc::get_bucket_for_date(start); - buckets.push(bucket); - start = start.checked_add_signed(Duration::days(7)).unwrap(); - } - buckets - } -} - -impl Bucketable for KhEvent { - fn get_buckets(&self) -> Result>, String> { - let mut result: HashMap> = HashMap::new(); - - let start_date: Date = self.get_start().ok_or_else(|| format!("Invalid DTSTART in {}", self.get_uid()))?.into(); - //TODO - //let mut end_date: Date = self.get_end().map(|date| date.into()).unwrap_or(start_date); - - let end_date = self.get_last_relevant_date().map(|date| date.into()).unwrap_or(start_date); - // end-dtimes are non-inclusive - // so in case of date-only events, the last day of the event is dtend-1 - //if self.is_allday() { - //end_date = end_date.pred(); - //} - - let buckets = Self::buckets_for_interval(start_date, end_date); - let khline = KhLine::from(self); - for bucketid in buckets { - result - .entry(bucketid) - .and_modify(|items| items.push(khline.to_string())) - .or_insert_with(|| vec!(khline.to_string())); - } - - if self.is_recur_master() { - for instance in self.get_recur_instances() { - let recur_buckets = instance.get_buckets()?; - result.merge(recur_buckets) - } - } - - for vec in result.values_mut() { - vec.dedup() - } - Ok(result) - } -} - -impl Bucketable for IcalVCalendar { - fn get_buckets(&self) -> Result>, String> { - let mut result: HashMap> = HashMap::new(); - for event in self.events_iter() { - let event = KhEvent::from_event(event); - //let recur_buckets = event.get_buckets()?; - let recur_buckets = event.get_buckets()?; - result.merge(recur_buckets); - } - Ok(result) - } -} - -pub trait Merge -where K: cmp::Eq + hash::Hash -{ - fn merge(&mut self, other: HashMap>); -} - -impl Merge for HashMap, S> -where K: cmp::Eq + hash::Hash, - S: std::hash::BuildHasher -{ - fn merge(&mut self, other: HashMap>) { - for (key, mut lines) in other.into_iter() { - self - .entry(key) - .and_modify(|items| items.append(&mut lines)) - .or_insert(lines); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn merge_test() { - let mut map_a: HashMap<&str, Vec> = HashMap::new(); - let mut map_b: HashMap<&str, Vec> = HashMap::new(); - - let key = "key"; - map_a.insert(&key, vec!["a".to_string(), "b".to_string()]); - map_b.insert(&key, vec!["c".to_string(), "d".to_string()]); - - map_a.merge(map_b); - assert_eq!(map_a.get(&key).unwrap(), &vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()]); - } - - #[test] - fn buckets_multi_day_allday() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_ALLDAY, Some(&path)).unwrap(); - - let event_buckets = cal.get_principal_khevent().get_buckets().unwrap(); - - assert_eq!(2, event_buckets.len()); - - let mut bucket_names = event_buckets.keys().collect::>(); - bucket_names.sort_unstable(); - assert_eq!(vec!("2007-W26", "2007-W27"), bucket_names); - - let cal_buckets = cal.get_buckets().unwrap(); - assert_eq!(event_buckets, cal_buckets); - } - - #[test] - fn buckets_single_event() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, Some(&path)).unwrap(); - - let comp_buckets = cal.get_buckets().unwrap(); - assert_eq!(vec!("1997-W13"), comp_buckets.keys().collect::>()); - } - - #[test] - fn buckets_simple_recurring_event() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_RECUR, Some(&path)).unwrap(); - - let event = cal.get_principal_khevent(); - let event_buckets = event.get_buckets().unwrap(); - let cal_buckets = cal.get_buckets().unwrap(); - assert_eq!(event_buckets, cal_buckets); - let mut cal_bucket_names = cal_buckets.keys().collect::>(); - cal_bucket_names.sort_unstable(); - assert_eq!(vec!("2018-W41", "2018-W42", "2018-W43", "2018-W44", "2018-W45", "2018-W46", "2018-W47", "2018-W48", "2018-W49", "2018-W50"), cal_bucket_names); - } -} diff --git a/src/actions/index/indextime.rs b/src/actions/index/indextime.rs deleted file mode 100644 index 6d56688..0000000 --- a/src/actions/index/indextime.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fs; -use std::io::{Read,Write}; -use chrono::prelude::*; - -use crate::defaults::*; - -pub fn write_index_time(index_time: &DateTime) { - let mut timefile = fs::File::create(get_indextimefile()).unwrap(); - timefile.write_all(format!("{}\n", index_time.timestamp()).as_bytes()).unwrap(); -} - -pub fn get_index_time() -> Option> { - let mut timefile = fs::File::open(get_indextimefile()).ok()?; - let mut timestamp_str = String::new(); - timefile.read_to_string(&mut timestamp_str).ok()?; - let timestamp = timestamp_str.trim().parse::().ok()?; - Some(Utc.timestamp(timestamp, 0)) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::testutils; - use assert_fs::prelude::*; - - #[test] - fn test_write_read() { - let testdir = testutils::prepare_testdir("testdir"); - - let timestamp = Utc.ymd(1990,01,01).and_hms(1, 1, 0); - write_index_time(×tamp); - testdir.child(".khaleesi/index-time").assert("631155660\n"); - - let indextime = get_index_time(); - assert_eq!(Some(timestamp), indextime); - } -} diff --git a/src/actions/index/mod.rs b/src/actions/index/mod.rs deleted file mode 100644 index cc4c0ae..0000000 --- a/src/actions/index/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub mod action; -mod indextime; -mod bucketable; - -pub use self::action::action_index; - -use structopt::StructOpt; -use std::path::PathBuf; - -#[derive(Debug, StructOpt)] -pub struct IndexArgs { - /// Rebuild index - #[structopt(short = "r", long = "reindex")] - pub reindex: bool, - /// index path - #[structopt(name = "path", parse(from_os_str))] - pub path: Option, -} diff --git a/src/actions/list.rs b/src/actions/list.rs deleted file mode 100644 index 4c29188..0000000 --- a/src/actions/list.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::selectors::SelectFilters; -use crate::input; -use crate::KhResult; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct ListArgs { - /// the arguments for the selection - #[structopt(name = "args")] - pub args: Vec, -} - -pub fn list_by_args(args: &[&str]) -> KhResult<()> { - let lines = input::default_input_khlines()?; - let filters = SelectFilters::parse_from_args_with_range(args)?; - - let events = lines - .enumerate() - .filter(|(index, khline)| { - match khline.to_event() { - Ok(event) => filters.is_selected_index(*index, &event), - Err(cause) => { warn!("{}", cause); false }, - } - }); - - for (_, khline) in events { - khprintln!("{}", khline); - } - - Ok(()) -} - diff --git a/src/actions/mod.rs b/src/actions/mod.rs deleted file mode 100644 index 8b48fa2..0000000 --- a/src/actions/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod agenda; -pub mod get; -pub mod copy; -pub mod cursor; -pub mod edit; -pub mod index; -pub mod list; -pub mod modify; -pub mod delete; -pub mod new; -pub mod select; -pub mod seq; -pub mod show; -pub mod undo; -pub mod unroll; -pub mod gen_completions; diff --git a/src/actions/modify.rs b/src/actions/modify.rs deleted file mode 100644 index 1b58b2d..0000000 --- a/src/actions/modify.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::backup::backup; -use crate::input; -use crate::utils::fileutil::write_cal; -use crate::KhResult; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct ModifyArgs { - /// Rebuild index - #[structopt(short = "n", long = "dry-run")] - pub dry_run: bool, - /// index path - #[structopt(subcommand)] - pub modify_cmd: ModifyCommand, -} - -#[derive(Debug, StructOpt)] -pub enum ModifyCommand { - /// Show agenda view - #[structopt(name = "remove-xlicerror", author = "")] - RemoveXlicerror, -} - -pub fn do_modify(args: &ModifyArgs) -> KhResult<()> { - info!("do_modify"); - - match &args.modify_cmd { - ModifyCommand::RemoveXlicerror => { - let dry_run = args.dry_run; - - let khlines = input::default_input_khlines()?; - for khline in khlines { - let (cal, count_removed) = khline.to_cal()?.with_remove_property("X-LIC-ERROR"); - if count_removed > 0 { - if !dry_run { - info!("Modifying {}", cal.get_path_as_string().unwrap()); - - let backup_path = backup(&khline).unwrap(); - info!("Backup written to {}", backup_path.display()); - write_cal(&cal)? - } else { - info!("Would modify {}", cal.get_path_as_string().unwrap()); - }; - } - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::*; - use assert_fs::prelude::*; - use predicates::prelude::*; - - use crate::cli::CommandLine; - use crate::cli::Command::Modify; - use structopt::StructOpt; - - #[test] - fn test_do_modify() { - let testdir = prepare_testdir("testdir_with_xlicerror"); - - let args = CommandLine::from_iter(&["khaleesi", "modify", "remove-xlicerror"]); - if let Modify(x) = args.cmd { - do_modify(&x).unwrap(); - } - - let expected = indoc!( - " - BEGIN:VCALENDAR - PRODID:CommuniGate Pro 6.2.5 - VERSION:2.0 - BEGIN:VEVENT - DTSTAMP:20180813T160004Z - UID:1c441c1b-8ca7-4898-b670-49ce30a7137b - SEQUENCE:2 - SUMMARY:some summary - DTSTART:20161007T073000Z - DTEND:20161007T160000Z - LAST-MODIFIED:20161018T095049Z - CREATED:20161018T094913Z - PRIORITY:5 - END:VEVENT - END:VCALENDAR - " - ) - .replace("\n", "\r\n"); - let predicate = predicate::str::similar(expected); - testdir - .child(".khaleesi/cal/xlicerror.ics") - .assert(predicate); - } - - #[test] - fn test_do_modify_dry_run() { - let testdir = prepare_testdir("testdir_with_xlicerror"); - - let args = CommandLine::from_iter(&["khaleesi", "modify", "--dry-run", "remove-xlicerror"]); - if let Modify(x) = args.cmd { - do_modify(&x).unwrap(); - } - - let expected = indoc!(" - BEGIN:VCALENDAR - PRODID:CommuniGate Pro 6.2.5 - VERSION:2.0 - BEGIN:VEVENT - DTSTAMP:20180813T160004Z - UID:1c441c1b-8ca7-4898-b670-49ce30a7137b - SEQUENCE:2 - SUMMARY:some summary - DTSTART:20161007T073000Z - DTEND:20161007T160000Z - LAST-MODIFIED:20161018T095049Z - CREATED:20161018T094913Z - PRIORITY:5 - X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:No value for SUMMARY property. Removing entire property: - END:VEVENT - END:VCALENDAR - "); - let predicate = predicate::str::similar(expected); - testdir - .child(".khaleesi/cal/xlicerror.ics") - .assert(predicate); - } - -// #[test] -// fn test_do_modify_negative() { -// -// let args = CommandLine::from_iter(&["khaleesi", "modify", "nonsense"]); -// if let Modify(x) = args.cmd { -// assert!(do_modify(&x).is_ok()); -// } -// } -} diff --git a/src/actions/new.rs b/src/actions/new.rs deleted file mode 100644 index e75dd34..0000000 --- a/src/actions/new.rs +++ /dev/null @@ -1,358 +0,0 @@ -use crate::calendars; -use crate::cursorfile; -use crate::defaults; -use crate::icalwrap::{IcalTime, IcalVCalendar}; -use crate::khline::KhLine; -use crate::utils::{fileutil, misc}; -use crate::KhResult; -use std::path::PathBuf; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct NewArgs { - /// the calendar - #[structopt(name = "calendar")] - pub calendar: String, - /// from - #[structopt(name = "from")] - pub from: String, - /// to - #[structopt(name = "to")] - pub to: String, - /// summary - #[structopt(name = "summary")] - pub summary: String, - /// location - #[structopt(name = "location")] - pub location: String, -} - -struct EventProperties { - calendar: String, - from: IcalTime, - to: IcalTime, - summary: String, - location: String, -} - -impl EventProperties { - fn parse_from_args(args: &NewArgs) -> KhResult { - let calendar = EventProperties::parse_calendar(&args.calendar)?; - let from = EventProperties::parse_from(&args.from)?; - let to = EventProperties::parse_to(&args.to)?; - let summary = EventProperties::parse_summary(&args.summary)?; - let location = EventProperties::parse_location(&args.location)?; - Ok(EventProperties { - calendar, - from, - to, - summary, - location, - }) - } - - fn parse_from(arg: &str) -> KhResult { - if arg.is_empty() { - Err("no start date/time given")? - }; - let time = arg.parse::()?; - Ok(time) - } - - fn parse_to(arg: &str) -> KhResult { - if arg.is_empty() { - Err("no end date/time given")? - }; - let time = arg.parse::()?; - Ok(time) - } - - fn parse_location(arg: &str) -> KhResult { - if arg.is_empty() { - Err("no location given")? - }; - Ok(arg.to_string()) - } - - fn parse_summary(arg: &str) -> KhResult { - if arg.is_empty() { - Err("no summary given")? - }; - Ok(arg.to_string()) - } - - fn parse_calendar(arg: &str) -> KhResult { - if arg.is_empty() { - Err("no calendar given")? - }; - let cal = arg.to_string(); - if !calendars::calendar_list().contains(&cal) { - Err("calendar does not exist")? - } - Ok(cal) - } -} - -pub fn do_new(args: &NewArgs) -> KhResult<()> { - let uid = misc::make_new_uid(); - let ep = EventProperties::parse_from_args(args)?; - - let path = assemble_file_path(&ep.calendar, &uid); - - let new_cal = IcalVCalendar::from_str(TEMPLATE_EVENT, Some(&path))? - .with_uid(&uid)? - .with_dtstamp_now() - .with_last_modified_now() - .with_eventprops(&ep); - - let khline = KhLine::from(&new_cal); - - fileutil::write_cal(&new_cal)?; - - cursorfile::write_cursorfile(&khline.to_string())?; - khprintln!("{}", khline); - - Ok(()) -} - -fn assemble_file_path(cal_name: &String, uid: &String ) -> PathBuf { - let mut path = defaults::get_caldir(); - path.push(cal_name); - path.push(uid); - path.set_extension("ics"); - path -} - -impl IcalVCalendar { - fn with_eventprops(self, ep: &EventProperties) -> Self { - self - .with_dtstart(&ep.from) - .with_dtend(&ep.to) - .with_summary(&ep.summary) - .with_location(&ep.location) - } -} - -static TEMPLATE_EVENT: &str = indoc!( - " - BEGIN:VCALENDAR - VERSION:2.0 - PRODID:-//khaleesi //EN - BEGIN:VTIMEZONE - TZID:Europe/Berlin - BEGIN:STANDARD - DTSTART:19711025T030000 - TZOFFSETFROM:+0200 - TZOFFSETTO:+0100 - RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 - END:STANDARD - BEGIN:DAYLIGHT - DTSTART:19710329T020000 - TZOFFSETFROM:+0100 - TZOFFSETTO:+0200 - RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 - END:DAYLIGHT - END:VTIMEZONE - BEGIN:VEVENT - SUMMARY:<> - LOCATION:<> - DTSTART;TZID=Europe/Berlin;VALUE=DATE-TIME:20181026T133000 - DTEND;TZID=Europe/Berlin;VALUE=DATE-TIME:20181026T160000 - DTSTAMP;VALUE=DATE-TIME:20181022T145405Z - UID:foo - END:VEVENT - END:VCALENDAR -" -); - -#[cfg(test)] -mod integration { - use assert_fs::prelude::*; - use predicates::prelude::*; - - use super::*; - use crate::testdata; - use crate::testutils; - - #[test] - fn test_parse_calendar() { - let _testdir = testutils::prepare_testdir("testdir_two_cals"); - let calendar = EventProperties::parse_calendar("first").unwrap(); - assert_eq!("first", calendar); - } - - #[test] - fn test_parse_calendar_neg() { - let _testdir = testutils::prepare_testdir("testdir_two_cals"); - let calendar = EventProperties::parse_calendar(""); - assert!(calendar.is_err()); - let calendar = EventProperties::parse_calendar("foo"); - assert!(calendar.is_err()); - } - - #[test] - fn test_parse_location() { - let location = EventProperties::parse_location("room 101").unwrap(); - assert_eq!("room 101", location); - } - - #[test] - fn test_parse_location_neg() { - let location = EventProperties::parse_location(""); - assert!(location.is_err()); - } - - #[test] - fn test_parse_summary() { - let summary = EventProperties::parse_summary("first").unwrap(); - assert_eq!("first", summary); - } - - #[test] - fn test_parse_summary_neg() { - let summary = EventProperties::parse_summary(""); - assert!(summary.is_err()); - } - - #[test] - fn test_parse_from() { - testdata::setup(); - let from = EventProperties::parse_from("2017-07-14T17:45:00").unwrap(); - let expected = IcalTime::floating_ymd(2017, 7, 14).and_hms(17, 45, 0); - assert_eq!(expected, from); - } - - #[test] - fn test_parse_from_neg() { - let from = EventProperties::parse_from("blödsinn"); - assert!(from.is_err()); - let from = EventProperties::parse_from(""); - assert!(from.is_err()); - } - - #[test] - fn test_parse_to() { - testdata::setup(); - let to = EventProperties::parse_to("2017-07-14T17:45:00").unwrap(); - let expected = IcalTime::floating_ymd(2017, 7, 14).and_hms(17, 45, 0); - assert_eq!(expected, to); - } - - #[test] - fn test_parse_to_neg() { - let to = EventProperties::parse_to("quatsch"); - assert!(to.is_err()); - let to = EventProperties::parse_to(""); - assert!(to.is_err()); - } - - #[test] - fn test_parse_from_args() { - let _testdir = testutils::prepare_testdir("testdir_two_cals"); - let args = NewArgs { - calendar: "second".to_string(), - from: "2017-11-03T12:30:00".to_string(), - to: "2017-11-07T11:11:00".to_string(), - summary: "summary text".to_string(), - location: "location text".to_string(), - }; - let ep = EventProperties::parse_from_args(&args).unwrap(); - assert_eq!("second".to_string(), ep.calendar); - assert_eq!("summary text".to_string(), ep.summary); - assert_eq!("location text".to_string(), ep.location); - } - - //#[test] - //fn test_parse_from_args_neg() { - // let args = &["1", "2", "3", "4"]; - // let ep = EventProperties::parse_from_args(args); - // assert!(ep.is_err()); - //} - - #[test] - fn test_with_eventprops() { - testdata::setup(); - - let calendar = "foo".to_string(); - let from = IcalTime::floating_ymd(2015, 04, 17).and_hms(8, 17, 3); - let to = IcalTime::floating_ymd(2015, 05, 17).and_hms(8, 17, 3); - let summary = "summary"; - let location = "home"; - let ep = EventProperties { - calendar, - from: from.clone(), - to: to.clone(), - summary: summary.to_string(), - location: location.to_string(), - }; - - let _testdir = testutils::prepare_testdir("testdir"); - let khline = "twodaysacrossbuckets.ics".parse::().unwrap(); - - let cal = khline.to_cal().unwrap().with_eventprops(&ep); - - let event = cal.get_principal_khevent(); - assert_eq!(Some(from), event.get_start()); - assert_eq!(Some(to), event.get_end()); - assert_eq!(summary, event.get_summary().unwrap()); - assert_eq!(location, event.get_location().unwrap()); - } - - #[test] - fn test_do_new() { - testdata::setup(); - let testdir = testutils::prepare_testdir("testdir_two_cals"); - - let args = NewArgs { - calendar: "second".to_string(), - from: "2017-11-03T12:30:00".to_string(), - to: "2017-11-07T11:11:00".to_string(), - summary: "summary text".to_string(), - location: "location text".to_string(), - }; - - let result = do_new(&args); - assert!(result.is_ok()); - - let expected = indoc!( - " - BEGIN:VCALENDAR - VERSION:2.0 - PRODID:-//khaleesi //EN - BEGIN:VTIMEZONE - TZID:Europe/Berlin - BEGIN:STANDARD - DTSTART:19711025T030000 - TZOFFSETFROM:+0200 - TZOFFSETTO:+0100 - RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 - END:STANDARD - BEGIN:DAYLIGHT - DTSTART:19710329T020000 - TZOFFSETFROM:+0100 - TZOFFSETTO:+0200 - RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 - END:DAYLIGHT - END:VTIMEZONE - BEGIN:VEVENT - SUMMARY:summary text - LOCATION:location text - DTSTART;TZID=Europe/Berlin:20171103T123000 - DTEND;TZID=Europe/Berlin:20171107T111100 - DTSTAMP:20130101T010203Z - UID:11111111-2222-3333-4444-444444444444@khaleesi - LAST-MODIFIED:20130101T010203Z - END:VEVENT - END:VCALENDAR - " - ) - .replace("\n", "\r\n"); - let predicate = predicate::str::similar(expected); - testdir - .child(".khaleesi/cal/second/11111111-2222-3333-4444-444444444444@khaleesi.ics") - .assert(predicate); - - let cursor_expected = "1509708600 second/11111111-2222-3333-4444-444444444444@khaleesi.ics"; - testdir.child(".khaleesi/cursor").assert(cursor_expected); - } -} diff --git a/src/actions/select.rs b/src/actions/select.rs deleted file mode 100644 index 05161e2..0000000 --- a/src/actions/select.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::path::PathBuf; - -use crate::defaults; -use crate::selectors::{SelectFilters,daterange::SelectFilterFrom,daterange::SelectFilterTo}; -use crate::utils::fileutil as utils; -use crate::khline::KhLine; -use crate::KhResult; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -pub struct SelectArgs { - /// the arguments for the selection - #[structopt(name = "args")] - pub args: Vec, -} - -impl SelectFilters { - fn predicate_path_skip_while(&self) -> impl Fn(&PathBuf) -> bool + '_ { - move |path| { - let bucketname = match path.file_name() { - Some(path_os_str) => path_os_str.to_string_lossy(), - None => panic!("{:?} not a file", path), - }; - self.from.is_bucket_before(&bucketname) - } - } - - fn predicate_path_take_while<'a>(&'a self) -> impl Fn(&PathBuf) -> bool + 'a { - move |path| { - let bucketname = match path.file_name() { - Some(path_os_str) => path_os_str.to_string_lossy(), - None => panic!("{:?} not a file", path), - }; - self.to.is_bucket_while(&bucketname) - } - } -} - -impl SelectFilterFrom { - fn is_bucket_before(&self, bucketname: &str) -> bool { - self.bucket.as_ref().map_or(false, |bucket| bucketname < bucket.as_str()) - } -} - -impl SelectFilterTo { - fn is_bucket_while(&self, bucketname: &str) -> bool { - self.bucket.as_ref().map_or(true, |bucket| bucketname <= bucket.as_str()) - } -} - -pub fn select_by_args(args: &[&str]) -> KhResult<()> { - let filters = SelectFilters::parse_from_args(args)?; - - let indexdir = defaults::get_indexdir(); - - let mut buckets: Vec = utils::file_iter(&indexdir) - .collect(); - buckets.sort_unstable(); - let buckets = buckets.into_iter() - .skip_while(filters.predicate_path_skip_while()) - .take_while(filters.predicate_path_take_while()); - - let cals = buckets.map(|bucket| utils::read_lines_from_file(&bucket)) - .filter_map(|lines| lines.ok()) - .flatten() - .map(|line| line.parse::()) - .filter_map(|cal| cal.ok()) - .map(|khline| khline.to_event()) - .flatten() - ; - - let mut lines: Vec = cals - .filter(|event| filters.is_selected(event)) - .map(|event| KhLine::from(&event)) - .map(|khline| khline.to_string()) - .collect(); - - lines.sort_unstable(); - lines.dedup(); - - for line in lines { - println!("{}", line); - } - - Ok(()) -} diff --git a/src/actions/seq.rs b/src/actions/seq.rs deleted file mode 100644 index 7cdfe90..0000000 --- a/src/actions/seq.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::seqfile; -use crate::utils::stdioutils; -use crate::KhResult; - -pub fn action_seq() -> KhResult<()> { - if !stdioutils::is_stdin_tty() { - write_stdin_to_seqfile()?; - } else { - //println!("stdin is tty") - } - - if !stdioutils::is_stdout_tty() || stdioutils::is_stdin_tty() { - write_seqfile_to_stdout(); - } - - Ok(()) -} - -fn write_stdin_to_seqfile() -> KhResult<()> { - let mut lines = stdioutils::read_lines_from_stdin()?.join("\n"); - lines.push_str("\n"); - - seqfile::write_to_seqfile(&lines)?; - - Ok(()) -} - -fn write_seqfile_to_stdout() { - if let Ok(sequence) = seqfile::read_seqfile() { - for line in sequence { - khprintln!("{}", line); - } - } -} - -#[cfg(test)] -mod integration { - use super::*; - - use assert_fs::prelude::*; - use predicates::prelude::*; - use crate::testutils; - use crate::utils::stdioutils; - - #[test] - fn test_with_stdin() { - let testdir = testutils::prepare_testdir_empty(); - stdioutils::test_stdin_write("hi\nthere"); - - action_seq().unwrap(); - - testdir.child(".khaleesi/seq").assert("hi\nthere\n"); - } - - #[test] - fn test_no_stdin() { - let testdir = testutils::prepare_testdir("testdir_with_seq"); - - action_seq().unwrap(); - let out = stdioutils::test_stdout_clear(); - - let predicate = predicate::str::similar(out); - testdir.child(".khaleesi/seq").assert(predicate); - } - - #[test] - fn test_with_stdin_stdout() { - let testdir = testutils::prepare_testdir_empty(); - stdioutils::test_stdin_write("hi\nthere"); - stdioutils::test_stdout_set_tty(false); - - action_seq().unwrap(); - let out = stdioutils::test_stdout_clear(); - - testdir.child(".khaleesi/seq").assert("hi\nthere\n"); - assert_eq!("hi\nthere\n", out); - } -} - diff --git a/src/actions/show.rs b/src/actions/show.rs deleted file mode 100644 index db9da15..0000000 --- a/src/actions/show.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::input; -use crate::utils::fileutil; -use crate::KhResult; - -pub fn do_show() -> KhResult<()> { - info!("do_show"); - let lines = input::default_input_khlines()?; - - for line in lines { - let output = fileutil::read_file_to_string(line.get_path()).unwrap(); - khprintln!("{}", output); - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::*; - use crate::utils::stdioutils::*; - - #[test] - fn test_() { - let _testdir = prepare_testdir("testdir_with_seq"); - - do_show().unwrap(); - - let stdout = test_stdout_clear(); - assert_eq!(784, stdout.len()); - assert_eq!(32, stdout.lines().count()); - } -} diff --git a/src/actions/undo.rs b/src/actions/undo.rs deleted file mode 100644 index 3e003a6..0000000 --- a/src/actions/undo.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::defaults; -use crate::KhResult; -use crate::utils::stdioutils; - -use std::fs; -use std::path::{Path, PathBuf}; -use walkdir::WalkDir; - -pub fn do_undo() -> KhResult<()> { - let backupdir = defaults::get_backupdir(); - - let source_dir = get_most_recent_backup()?; - - let backup_id = source_dir.strip_prefix(backupdir)?; - - info!("Restoring {:?}", backup_id); - - let files = WalkDir::new(source_dir.clone()) - .into_iter() - .flatten() - .filter(|dir_entry| dir_entry.path().is_file()); - - for file in files { - restore_file_from_backup(&source_dir, &file.path())?; - }; - - Ok(()) -} - -fn restore_file_from_backup(source_prefix: &Path, file_path: &Path) -> KhResult<()> { - let caldir = defaults::get_caldir(); - let path_in_cal = file_path.strip_prefix(source_prefix)?; - - let mut target_path = caldir.clone(); - target_path.push(path_in_cal); - - if target_path.exists() && !ask_overwrite(&target_path) { - info!("ignoring {}", target_path.display()); - return Ok(()); - } - fs::create_dir_all(&target_path.parent().ok_or_else(|| "error creating calendar directory")?)?; - fs::copy(file_path, &target_path)?; - - info!("Restore {} to {}", file_path.display(), target_path.display()); - - Ok(()) -} - -fn get_most_recent_backup() -> KhResult { - let backupdir = defaults::get_backupdir(); - backupdir - .read_dir()? - .filter_map(|result| result.ok()) - .map(|dir_entry| dir_entry.path()) - .max() - .ok_or_else(|| "there are no backups, nothing to undo!".into()) -} - -fn ask_overwrite(path: &Path) -> bool { - if cfg!(test) { return true}; - println!("File exists:\n{}", path.display()); - println!("Overwrite? y/n:"); - - match stdioutils::read_single_char_from_stdin().unwrap() { - 'y' => true, - _ => false - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::testutils::prepare_testdir; - use assert_fs::prelude::*; - use predicates::prelude::*; - - #[test] - fn test_get_most_recent_backup() { - let _testdir = prepare_testdir("testdir_with_backup"); - let result = get_most_recent_backup().unwrap(); - assert_eq!("backup_id", result.file_name().unwrap().to_str().unwrap()); - } - - #[test] - #[should_panic] - fn test_get_most_recent_backup_negative() { - let _testdir = prepare_testdir("testdir"); - get_most_recent_backup().unwrap(); - } - - #[test] - fn test_restore_file_from_backup() { - let testdir = prepare_testdir("testdir_with_backup"); - let source_file = testdir.child(".khaleesi/backup/backup_id/my_calendar/twodaysacrossbuckets.ics"); - let source_folder = testdir.child(".khaleesi/backup/backup_id"); - let target_file = testdir.child(".khaleesi/cal/my_calendar/twodaysacrossbuckets.ics"); - - restore_file_from_backup(source_folder.path(), source_file.path()).unwrap(); - target_file.assert(predicate::path::exists()); - } - - #[test] - fn test_restore_file_from_backup_overwrite() { - let testdir = prepare_testdir("testdir_with_backup"); - let source_file = testdir.child(".khaleesi/backup/backup_id/my_calendar/twodaysacrossbuckets.ics"); - let source_folder = testdir.child(".khaleesi/backup/backup_id"); - let target_file = testdir.child(".khaleesi/cal/my_calendar/twodaysacrossbuckets.ics"); - target_file.touch().unwrap(); - - restore_file_from_backup(source_folder.path(), source_file.path()).unwrap(); - target_file.assert(predicate::path::exists()); - } -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::prepare_testdir; - use assert_fs::prelude::*; - use predicates::prelude::*; - - #[test] - fn test_do_undo() { - let testdir = prepare_testdir("testdir_with_backup"); - do_undo().unwrap(); - let target_folder = testdir.child(".khaleesi/cal/my_calendar/twodaysacrossbuckets.ics"); - target_folder.assert(predicate::path::e