Switchin to icon enum, adding some basic tests.

This commit is contained in:
calcu1on 2025-04-05 22:31:43 -04:00
parent bc974f44f4
commit 48cd4e6eb6
5 changed files with 92 additions and 81 deletions

34
src/icons.rs Normal file
View File

@ -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<char> {
u32::from_str_radix(&code, 16).ok().and_then(char::from_u32)
}
}

View File

@ -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::WeatherPeriod> = weather::WeatherOfficeLocation {
x: 75,
y: 59,
code: "GYX".to_string(),
}.get_full_forecast();
// Get sox schedule.
let sox_games: Vec<redsox::GameInfo> = 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<TableRow> = 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(),

View File

@ -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<char> {
u32::from_str_radix(&self.icon_code, 16).ok().and_then(char::from_u32)
}
}

View File

@ -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<GameInfo> {
// 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<Utc> = 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
}
}

View File

@ -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<char> {
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<String> {
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,
}
}