summaryrefslogtreecommitdiffstats
path: root/src/actions/agenda.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/actions/agenda.rs')
-rw-r--r--src/actions/agenda.rs226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/actions/agenda.rs b/src/actions/agenda.rs
new file mode 100644
index 0000000..5b98fb1
--- /dev/null
+++ b/src/actions/agenda.rs
@@ -0,0 +1,226 @@
+use chrono::{Datelike, TimeZone, Local, Date};
+use itertools::Itertools;
+use yansi::{Style};
+
+use icalwrap::*;
+use utils::fileutil as utils;
+use config::{Config,CalendarConfig};
+
+pub fn show_events(config: &Config, lines: &mut Iterator<Item = String>) {
+ let cals = utils::read_calendars_from_files(lines).unwrap();
+
+ let mut not_over_yet: Vec<(usize, &IcalVCalendar, IcalVEvent, Option<&CalendarConfig>)> = Vec::new();
+ let mut cals_iter = cals.iter()
+ .enumerate()
+ .map(|(i, cal)| (i, cal, cal.get_principal_event(), config.get_config_for_calendar(&cal)))
+ .peekable();
+
+ let start_day = match cals_iter.peek() {
+ Some((_, _, event, _)) => {
+ event
+ .get_dtstart()
+ .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)| {
+ maybe_print_date_line(&config, cur_day, start_day, &mut last_printed_day);
+ print_event_line(*cal_config, *index, &event, cur_day);
+ event.continues_after(cur_day)
+ });
+
+ let relevant_events = cals_iter.peeking_take_while(|(_,_,event,_)| event.starts_on(cur_day));
+ for (i, cal, event, cal_config) in relevant_events {
+ maybe_print_date_line(&config, cur_day, start_day, &mut last_printed_day);
+ print_event_line(cal_config, i, &event, cur_day);
+ if event.continues_after(cur_day) {
+ not_over_yet.push((i, cal, 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() {
+ println!();
+ }
+}
+
+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::new().bold();
+ println!("{}, {}", style_heading.paint(date.format("%Y-%m-%d")), date.format("%A"));
+}
+
+fn print_event_line(config: Option<&CalendarConfig>, index: usize, event: &IcalVEvent, date: Date<Local>) {
+ match event_line(config, &event, date) {
+ Ok(line) => println!("{:4} {}", index, line),
+ Err(error) => warn!("{} in {}", error, event.get_uid())
+ }
+}
+
+pub fn event_line(config: Option<&CalendarConfig>, event: &IcalVEvent, cur_day: Date<Local>) -> Result<String, String> {
+ if !event.relevant_on(cur_day) {
+ return Err(format!("event is not relevant for {:?}", cur_day));
+ }
+
+ if event.is_allday() {
+ 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();
+ }
+ Ok(format!(" {}", summary))
+ } else {
+ let mut time_sep = " ";
+ let dtstart = event.get_dtstart().ok_or("Invalid DTSTART")?;
+ let start_string = if dtstart.date() != cur_day {
+ "".to_string()
+ } else {
+ time_sep = "-";
+ format!("{}", dtstart.format("%H:%M"))
+ };
+
+ let dtend = event.get_dtend().ok_or("Invalid DTEND")?;
+ let end_string = if dtend.date() != cur_day {
+ "".to_string()
+ } else {
+ time_sep = "-";
+ format!("{}", dtend.format("%H:%M"))
+ };
+
+ 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();
+ }
+
+ Ok(format!("{:5}{}{:5} {}", start_string, time_sep, end_string, summary))
+ }
+}
+
+impl IcalVEvent {
+ fn starts_on(&self, date: Date<Local>) -> bool {
+ self.get_dtstart().unwrap().date() == date
+ }
+
+ fn relevant_on(&self, date: Date<Local>) -> bool {
+ self.get_dtstart().map(|dtstart| dtstart.date() <= date).unwrap_or(false) &&
+ self.get_last_relevant_date().map(|enddate| enddate >= date).unwrap_or(false)
+ }
+
+ fn continues_after(&self, date: Date<Local>) -> bool {
+ self.get_last_relevant_date()
+ .map(|enddate| enddate > date)
+ .unwrap_or(false)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use testdata;
+ 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_event();
+
+ 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_event();
+ 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_event();
+ 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_event();
+ let date = Local.ymd(1998, 1, 1);
+ let event_line = event_line(None, &event, date);
+ 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_event();
+ let date = Local.ymd(1997, 3, 24);
+ let event_line = event_line(None, &event, date).unwrap();
+ assert_eq!("12:30-21: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_event();
+ 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).unwrap();
+ let event_line_middle = event_line(None, &event, middle).unwrap();
+ let event_line_end = event_line(None, &event, end).unwrap();
+ assert_eq!("13: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!(" -07: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_event();
+ let date = Local.ymd(2007, 6, 28);
+ let event_line = event_line(None, &event, date).unwrap();
+ assert_eq!(" Festival International de Jazz de Montreal".to_string(), event_line)
+ }
+}