diff options
Diffstat (limited to 'src/actions')
-rw-r--r-- | src/actions/agenda.rs | 305 | ||||
-rw-r--r-- | src/actions/calendars.rs | 25 | ||||
-rw-r--r-- | src/actions/copy.rs | 45 | ||||
-rw-r--r-- | src/actions/cursor.rs | 156 | ||||
-rw-r--r-- | src/actions/delete.rs | 68 | ||||
-rw-r--r-- | src/actions/edit.rs | 50 | ||||
-rw-r--r-- | src/actions/gen_completions.rs | 35 | ||||
-rw-r--r-- | src/actions/get.rs | 49 | ||||
-rw-r--r-- | src/actions/index/action.rs | 182 | ||||
-rw-r--r-- | src/actions/index/bucketable.rs | 162 | ||||
-rw-r--r-- | src/actions/index/indextime.rs | 38 | ||||
-rw-r--r-- | src/actions/index/mod.rs | 18 | ||||
-rw-r--r-- | src/actions/list.rs | 32 | ||||
-rw-r--r-- | src/actions/mod.rs | 16 | ||||
-rw-r--r-- | src/actions/modify.rs | 140 | ||||
-rw-r--r-- | src/actions/new.rs | 358 | ||||
-rw-r--r-- | src/actions/select.rs | 86 | ||||
-rw-r--r-- | src/actions/seq.rs | 79 | ||||
-rw-r--r-- | src/actions/show.rs | 34 | ||||
-rw-r--r-- | src/actions/undo.rs | 131 | ||||
-rw-r--r-- | src/actions/unroll.rs | 37 |
21 files changed, 0 insertions, 2046 deletions
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<String>, -} - -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<Item = KhEvent>, - 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<Local>, start_date: Date<Local>, last_printed_date: Date<Local>) { - 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<Local>, start_date: Date<Local>, last_printed_date: &mut Date<Local>) { - 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<Local>, start_date: Date<Local>, last_printed_date: &mut Date<Local>) { - 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<Local>) { - 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<Local>, - 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<Local>, - is_cursor: bool -) -> Result<String, String> { - 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<Local> = 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<Local> = 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<Local>) -> bool { - let dtstart: Date<Local> = self.get_start().unwrap().into(); - dtstart == date - } - - fn relevant_on(&self, date: Date<Local>) -> bool { - let dtstart: Option<Date<Local>> = self.get_start().map(|date| date.into()); - let last_relevant_date: Option<Date<Local>> = 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<Local>) -> bool { - let last_relevant_date: Option<Date<Local>> = 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::<KhLine>().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<CursorDirection>, -} - -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::<KhLine>().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<String, Vec<String>>, 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<Item = PathBuf> { - 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<Item = PathBuf>) -> HashMap<String, Vec<String>> { - let mut buckets: HashMap<String, Vec<String>> = 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<String, Vec<String>>) { - 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 |