tzhelp/src/cli.rs

197 lines
5.6 KiB
Rust
Raw Normal View History

2026-02-17 23:42:26 +01:00
use super::calendar::{create_timetable, get_todays_date};
use chrono::Local;
use chrono::NaiveDate;
use chrono::NaiveTime;
use chrono::TimeZone;
2026-02-17 23:42:26 +01:00
use chrono_tz::Tz;
2026-02-12 21:41:18 +01:00
use clap::Parser;
2026-02-17 23:42:43 +01:00
use colored::Colorize;
use std::error::Error;
use std::fmt;
use std::iter;
2026-02-12 21:41:18 +01:00
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Args {
#[arg(short, long)]
pub timezone: Vec<String>,
#[arg(short, long)]
pub date: Option<String>,
#[arg(short, long, default_value = "Local")]
pub local: String,
2026-02-12 21:41:18 +01:00
pub time: String,
}
#[derive(PartialEq, Debug)]
pub enum CliError {
CouldNotParseTime,
CouldNotParseDate,
2026-02-17 23:42:26 +01:00
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(),
2026-02-17 23:42:26 +01:00
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<NaiveTime>,
// }
pub fn print_timezones(
timezones: &Vec<String>,
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::<u32>() {
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,
)
2026-02-17 23:42:26 +01:00
} else {
let timezone = local_timezone
2026-02-17 23:42:26 +01:00
.parse::<Tz>()
.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<TzLocal: TimeZone>(
parsed_time: NaiveTime,
parsed_date: NaiveDate,
parsed_local_timezone: TzLocal,
local_timezone_name: &String,
timezones: &Vec<String>,
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;
2026-02-17 23:42:26 +01:00
let parsed_timezones: Vec<Tz> = timezones
.iter()
.map(|tz| {
tz.parse::<Tz>()
.map_err(|_| CliError::CouldNotParseTimezone(tz.to_string()))
})
.collect::<Result<Vec<_>, _>>()?;
let target_timetables = parsed_timezones
2026-02-17 23:42:26 +01:00
.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::<Result<Vec<_>, _>>()?;
let all_timetables: Vec<(String, Vec<NaiveTime>)> =
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)
2026-02-12 21:41:18 +01:00
}
Ok(())
2026-02-12 21:41:18 +01:00
}
2026-02-17 23:42:26 +01:00
fn print_timetable(name: &String, name_padding_len: u32, timetable: &Vec<NaiveTime>) {
2026-02-17 23:42:43 +01:00
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();
2026-02-17 23:42:26 +01:00
let formatted: Vec<String> = timetable
.iter()
.enumerate()
2026-02-17 23:42:43 +01:00
.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()
}
})
2026-02-17 23:42:26 +01:00
.collect();
let name_padding = (0..name_padding_len)
.map(|_| " ".to_string())
.collect::<Vec<String>>()
.join("");
println!("{}:{}\t{}", name, name_padding, formatted.join("\t"));
}