use super::calendar::{create_timetable, get_todays_date}; use chrono::Local; use chrono::NaiveDate; use chrono::NaiveTime; use chrono::TimeZone; use chrono_tz::Tz; use clap::Parser; use colored::Colorize; use std::error::Error; use std::fmt; use std::iter; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct Args { #[arg(short, long)] pub timezone: Vec, #[arg(short, long)] pub date: Option, /// number of preceding and succeeding time records in the timetable #[arg(short, long, default_value_t = 5)] pub padding: i8, #[arg(short, long, default_value = "Local")] pub local: String, pub time: String, } #[derive(PartialEq, Debug)] pub enum CliError { CouldNotParseTime, CouldNotParseDate, CouldNotParseTimezone(String), CouldNotGenerateTimetable(String), } impl fmt::Display for CliError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let message = match *self { Self::CouldNotParseTime => "Couldn't parse time".to_string(), Self::CouldNotParseDate => "Couldn't parse date".to_string(), Self::CouldNotParseTimezone(ref tz) => { format!("Couldn't parse timezone {}", tz).to_string() } Self::CouldNotGenerateTimetable(ref er) => { format!("Could not generate timetable: {}", er) } }; f.write_str(&message) } } impl Error for CliError {} // struct Timetable { // pub name: String, // pub values: Vec, // } pub fn print_timezones( timezones: &Vec, time: &String, date: Option<&String>, local_timezone: &String, padding_hours: i8, ) -> Result<(), CliError> { let todays_date = &get_todays_date(); let date = date.unwrap_or(todays_date); let parsed_time = if let Ok(hour) = time.parse::() { NaiveTime::from_hms_opt(hour, 0, 0).ok_or(CliError::CouldNotParseTime)? } else { NaiveTime::parse_from_str(time, "%H:%M").map_err(|_| CliError::CouldNotParseTime)? }; let parsed_date = NaiveDate::parse_from_str(date, "%d-%m-%Y") .or_else(|_| NaiveDate::parse_from_str(date, "%d-%m")) .map_err(|_| CliError::CouldNotParseDate)?; if local_timezone == "Local" { print_calendar( parsed_time, parsed_date, Local, &"Local".to_string(), &timezones, padding_hours, ) } else { let timezone = local_timezone .parse::() .map_err(|_| CliError::CouldNotGenerateTimetable(local_timezone.to_string()))?; print_calendar( parsed_time, parsed_date, timezone, &timezone.name().to_string(), &timezones, padding_hours, ) } } fn print_calendar( parsed_time: NaiveTime, parsed_date: NaiveDate, parsed_local_timezone: TzLocal, local_timezone_name: &String, timezones: &Vec, padding_hours: i8, ) -> Result<(), CliError> { let local_timetable = create_timetable( &parsed_time, &parsed_date, &parsed_local_timezone, &parsed_local_timezone, padding_hours, ) .map_err(|_| CliError::CouldNotParseTimezone(local_timezone_name.to_string()))?; let longest_name_length = iter::once(local_timezone_name.to_string()) .chain(timezones.iter().map(|tz| tz.to_string())) .map(|t| t.len()) .max() .unwrap_or(0) as u32; let parsed_timezones: Vec = timezones .iter() .map(|tz| { tz.parse::() .map_err(|_| CliError::CouldNotParseTimezone(tz.to_string())) }) .collect::, _>>()?; let target_timetables = parsed_timezones .iter() .map(|tz| { create_timetable( &parsed_time, &parsed_date, &parsed_local_timezone, tz, padding_hours, ) .map(|tt| (tz.name().to_string(), tt)) .map_err(|_| CliError::CouldNotGenerateTimetable(tz.name().to_string())) }) .collect::, _>>()?; let all_timetables: Vec<(String, Vec)> = iter::once((local_timezone_name.to_string(), local_timetable)) .chain(target_timetables) .collect(); for timezone in all_timetables { let name: String = timezone.0; let timetable = timezone.1; let name_padding_len = longest_name_length - name.len() as u32; print_timetable(&name, name_padding_len, &timetable) } Ok(()) } fn print_timetable(name: &String, name_padding_len: u32, timetable: &Vec) { let len = timetable.len(); let mid = (len - 1) / 2; let acceptable_time_from = NaiveTime::from_hms_opt(8, 0, 0).unwrap(); let acceptable_time_to = NaiveTime::from_hms_opt(20, 0, 0).unwrap(); let formatted: Vec = timetable .iter() .enumerate() .map(|(i, t)| { let stringified = t.format("%H:%M").to_string(); let acceptable_hours_formatted = if t < &acceptable_time_from || t > &acceptable_time_to { stringified.color("#fea201") } else { stringified.green() }; if i == mid { acceptable_hours_formatted.reversed().to_string() } else { acceptable_hours_formatted.to_string() } }) .collect(); let name_padding = (0..name_padding_len) .map(|_| " ".to_string()) .collect::>() .join(""); println!("{}:{}\t{}", name, name_padding, formatted.join("\t")); }