From 48cd4e6eb6ea9b1f1c03cadf4a46c6cacca49060 Mon Sep 17 00:00:00 2001 From: calcu1on Date: Sat, 5 Apr 2025 22:31:43 -0400 Subject: [PATCH] Switchin to icon enum, adding some basic tests. --- src/icons.rs | 34 ++++++++++++++++++++++++++++ src/main.rs | 28 ++++++----------------- src/nerdfont.rs | 10 --------- src/redsox.rs | 41 ++++++++++++++++++++++++++------- src/weather.rs | 60 +++++++++++++++---------------------------------- 5 files changed, 92 insertions(+), 81 deletions(-) create mode 100644 src/icons.rs delete mode 100644 src/nerdfont.rs diff --git a/src/icons.rs b/src/icons.rs new file mode 100644 index 0000000..c48737b --- /dev/null +++ b/src/icons.rs @@ -0,0 +1,34 @@ +#[allow(dead_code)] +pub enum Icons { + Fahrenheight, + Clock, + Baseball, + Sunny, + Mixed, + Rain, + Snow, + Clear, + Cloudy, +} + +impl Icons { + + pub fn get_icon_str(&self) -> String { + match self { + Icons::Fahrenheight => Self::get_icon("e341").unwrap().to_string(), + Icons::Clock => Self::get_icon("e641").unwrap().to_string(), + Icons::Baseball => Self::get_icon("f0852").unwrap().to_string(), + Icons::Sunny => Self::get_icon("f0599").unwrap().to_string(), + Icons::Mixed => Self::get_icon("f067f").unwrap().to_string(), + Icons::Rain => Self::get_icon("e239").unwrap().to_string(), + Icons::Snow => Self::get_icon("f0f36").unwrap().to_string(), + Icons::Clear => Self::get_icon("e30d").unwrap().to_string(), + Icons::Cloudy => Self::get_icon("e312").unwrap().to_string(), + } + } + + fn get_icon(code: &str) -> Option { + u32::from_str_radix(&code, 16).ok().and_then(char::from_u32) + } +} + diff --git a/src/main.rs b/src/main.rs index 0268293..568274f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod weather; mod redsox; -mod nerdfont; +mod icons; +use icons::Icons; use tabled::{Table, Tabled}; use serde::{Deserialize, Serialize}; use tabled::settings::{ @@ -18,22 +19,17 @@ struct TableRow { } fn main() { - // Get forecast. + // Get forecast & schedule. let entire_forecast: Vec = weather::WeatherOfficeLocation { x: 75, y: 59, code: "GYX".to_string(), }.get_full_forecast(); - // Get sox schedule. let sox_games: Vec = redsox::get_schedule(); // Build icons. - let baseball_icon = nerdfont::NerdFontIcon { - icon_code: "f0852".to_string(), - }.get_icon().unwrap(); - let clock_icon = nerdfont::NerdFontIcon { - icon_code: "e641".to_string(), - }.get_icon().unwrap(); - + let baseball_icon = Icons::Baseball.get_icon_str(); + let clock_icon = Icons::Clock.get_icon_str(); + let fahrenheight_icon = Icons::Fahrenheight.get_icon_str(); // Build the rows for the table. let mut table_rows: Vec = vec![]; for i in 0..entire_forecast.len() { @@ -43,20 +39,10 @@ fn main() { // Check if there is a sox game and print opp. for sox_game in &sox_games { if sox_game.date == yyyy_mm_dd { - sox_status = format!( - "{} {}\n{} {}", - &baseball_icon, - &sox_game.opponent, - &clock_icon, - &sox_game.start_time, - ); + sox_status = format!("{} {}\n{} {}", &baseball_icon, &sox_game.opponent, &clock_icon, &sox_game.start_time); break; } } - // Get fahrenheight icon; - let fahrenheight_icon = nerdfont::NerdFontIcon { - icon_code: "e341".to_string(), - }.get_icon().unwrap(); let row = TableRow { date: yyyy_mm_dd.to_string(), time_of_day: forecast_period.name.clone(), diff --git a/src/nerdfont.rs b/src/nerdfont.rs deleted file mode 100644 index 78e4770..0000000 --- a/src/nerdfont.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub struct NerdFontIcon { - pub icon_code: String, -} - -impl NerdFontIcon { - /// Get the single font by name. - pub fn get_icon(&self) -> Option { - u32::from_str_radix(&self.icon_code, 16).ok().and_then(char::from_u32) - } -} diff --git a/src/redsox.rs b/src/redsox.rs index 1331d8d..71c8ae0 100644 --- a/src/redsox.rs +++ b/src/redsox.rs @@ -27,8 +27,8 @@ pub struct Game { pub game_date: String, } -#[derive(Serialize, Deserialize, Debug, Clone)] #[serde_alias(CamelCase,SnakeCase)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Teams { away: Team, home: Team, @@ -55,6 +55,8 @@ pub struct TeamRecord { losses: i32, } +#[serde_alias(CamelCase,SnakeCase)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GameInfo { pub opponent: String, pub date: String, @@ -85,11 +87,9 @@ pub fn get_schedule() -> Vec { // Determine who the opponent is from the teams. pub fn extract_opponent(teams: &Teams) -> String { - if teams.home.team.name == "Boston Red Sox" { - teams.away.team.name.to_string() - } - else { - teams.home.team.name.to_string() + match teams.home.team.name == "Boston Red Sox" { + true => teams.away.team.name.to_string(), + false => teams.home.team.name.to_string(), } } @@ -100,17 +100,42 @@ fn build_api_url() -> String { format!("{}{}{}", url_first, TEAM_ID, url_second) } +// Get the start time of the game. fn get_start_time(iso_string: &String) -> String { let utc_dt: DateTime = iso_string.parse().expect("Invalid ISO8601 string"); let est_dt = utc_dt.with_timezone(&Eastern); est_dt.format("%I:%M").to_string() } + + #[cfg(test)] mod team_tests { use super::*; #[test] - fn check_schedule_retrieval() { - get_schedule(); + fn test_build_api_url() { + const EXPECTED_TEAM_ID: &str = "111"; + const TEAM_ID: &str = EXPECTED_TEAM_ID; + + fn build_api_url() -> String { + let url_first = "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=".to_string(); + let url_second = "&startDate=2025-04-01&endDate=2025-09-30".to_string(); + format!("{}{}{}", url_first, TEAM_ID, url_second) + } + + let expected_url = format!( + "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId={}&startDate=2025-04-01&endDate=2025-09-30", + TEAM_ID + ); + + assert_eq!(build_api_url(), expected_url); + } + #[test] + fn test_get_start_time() { + let iso_string = "2025-04-02T22:35:00Z".to_string(); // UTC time + let result = get_start_time(&iso_string); + + // EST is UTC-5 or UTC-4 depending on DST. April is typically daylight saving (EDT = UTC-4) + assert_eq!(result, "06:35"); // 22:35 UTC == 18:35 EDT == 06:35 PM in 12-hour format } } diff --git a/src/weather.rs b/src/weather.rs index 59af9d2..bcb47c2 100644 --- a/src/weather.rs +++ b/src/weather.rs @@ -1,7 +1,8 @@ use reqwest::header::USER_AGENT; use serde::{Deserialize, Serialize}; use serde_alias::serde_alias; -#[path = "nerdfont.rs"] mod nerdfont; +#[path = "icons.rs"] mod icons; +use icons::Icons; #[derive(Debug, Serialize, Deserialize)] struct ForecastWrapper { @@ -52,7 +53,11 @@ impl WeatherOfficeLocation { .send() .expect("Unable to get data") .text().unwrap().to_string(); - let ForecastWrapper { properties: Properties { mut periods } } = serde_json::from_str(&forecast).expect("JSON was not well-formatted"); + let ForecastWrapper { + properties: Properties { + mut periods + } + } = serde_json::from_str(&forecast).expect("JSON was not well-formatted"); for period in periods.iter_mut() { match detect_icon(&period.short_forecast) { None => println!("There was an issue detecting the correct icon!!!"), @@ -66,45 +71,16 @@ impl WeatherOfficeLocation { } // Detect which icon to display based on short forecast. -pub fn detect_icon(short_forecast: &String) -> Option { - if short_forecast.contains("Sunny") { - let icon = nerdfont::NerdFontIcon { - icon_code: "f0599".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else if short_forecast.contains("Rain") && short_forecast.contains("Snow") { - let icon = nerdfont::NerdFontIcon { - icon_code: "f067f".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else if short_forecast.contains("Snow") { - let icon = nerdfont::NerdFontIcon { - icon_code: "f0f36".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else if short_forecast.contains("Rain") { - let icon = nerdfont::NerdFontIcon { - icon_code: "e239".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else if short_forecast.contains("Cloudy") { - let icon = nerdfont::NerdFontIcon { - icon_code: "e312".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else if short_forecast.contains("Clear") { - let icon = nerdfont::NerdFontIcon { - icon_code: "e30d".to_string(), - }; - let icon_code = icon.get_icon(); - icon_code - } else { - None +pub fn detect_icon(short_forecast: &str) -> Option { + match true { + _ if short_forecast.contains("Sunny") => Some(Icons::Sunny.get_icon_str()), + _ if short_forecast.contains("Rain") && short_forecast.contains("Snow") => { + Some(Icons::Mixed.get_icon_str()) + } + _ if short_forecast.contains("Snow") => Some(Icons::Snow.get_icon_str()), + _ if short_forecast.contains("Rain") => Some(Icons::Rain.get_icon_str()), + _ if short_forecast.contains("Cloudy") => Some(Icons::Cloudy.get_icon_str()), + _ if short_forecast.contains("Clear") => Some(Icons::Clear.get_icon_str()), + _ => None, } } -