Better handling for icons, moving things to impl for weather.

This commit is contained in:
calcu1on 2025-04-05 20:19:42 -04:00
parent d3f6691ca9
commit 5da8701978
3 changed files with 69 additions and 61 deletions

View File

@ -1,6 +1,7 @@
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] #![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
mod weather; mod weather;
mod redsox; mod redsox;
mod nerdfont;
// use colored::Colorize; // use colored::Colorize;
use tabled::{Table, Tabled}; use tabled::{Table, Tabled};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -13,22 +14,30 @@ use tabled::settings::{
struct TableRow { struct TableRow {
date: String, date: String,
time_of_day: String, time_of_day: String,
temp: i32, temp: String,
red_sox: String, red_sox: String,
forecast: String, forecast: String,
} }
#[allow(unreachable_code)] #[allow(unreachable_code)]
fn main() { fn main() {
// Set the weather location here. // Get forecast.
let location = weather::WeatherOfficeLocation { let entire_forecast: Vec<weather::WeatherPeriod> = weather::WeatherOfficeLocation {
x: 75, x: 75,
y: 59, y: 59,
code: "GYX".to_string(), code: "GYX".to_string(),
}; }.get_full_forecast();
let entire_forecast: Vec<weather::WeatherPeriod> = weather::get_full_forecast(location); // Get sox schedule.
let sox_games: Vec<redsox::GameInfo> = redsox::get_schedule(); let sox_games: Vec<redsox::GameInfo> = redsox::get_schedule();
let baseball_diamond = '\u{f0852}'; // 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();
// Build the rows for the table.
let mut table_rows: Vec<TableRow> = vec![]; let mut table_rows: Vec<TableRow> = vec![];
for i in 0..entire_forecast.len() { for i in 0..entire_forecast.len() {
let forecast_period = &entire_forecast[i]; let forecast_period = &entire_forecast[i];
@ -37,17 +46,19 @@ fn main() {
// Check if there is a sox game and print opp. // Check if there is a sox game and print opp.
for sox_game in &sox_games { for sox_game in &sox_games {
if sox_game.date == yyyy_mm_dd { if sox_game.date == yyyy_mm_dd {
let mut opp_str = String::from(baseball_diamond); // @todo - currently hardcoding time - figure out how to get it.
opp_str.push_str(" "); sox_status = format!("{} {}\n{} {}", baseball_icon, &sox_game.opponent, clock_icon, "8:00".to_string());
opp_str.push_str(&sox_game.opponent);
sox_status = opp_str;
break; break;
} }
} }
// Get fahrenheight icon;
let fahrenheight_icon = nerdfont::NerdFontIcon {
icon_code: "e341".to_string(),
}.get_icon().unwrap();
let row = TableRow { let row = TableRow {
date: yyyy_mm_dd.to_string(), date: yyyy_mm_dd.to_string(),
time_of_day: forecast_period.name.clone(), time_of_day: forecast_period.name.clone(),
temp: forecast_period.temperature, temp: format!("{}{}", forecast_period.temperature, fahrenheight_icon),
red_sox: sox_status, red_sox: sox_status,
forecast: forecast_period.detailed_forecast.to_string(), forecast: forecast_period.detailed_forecast.to_string(),
}; };
@ -59,12 +70,13 @@ fn main() {
fn render_table(rows: &Vec<TableRow>) { fn render_table(rows: &Vec<TableRow>) {
// here is where we actually render the table. // here is where we actually render the table.
let mut table = Table::new(rows); let mut table = Table::new(rows);
table.with(Style::modern()); table.with((Style::modern(), Alignment::center()));
table.modify(Columns::last(), Alignment::left());
table.with(( table.with((
Width::wrap(210).priority(Priority::max(true)), Width::wrap(195).priority(Priority::max(true)),
Width::increase(50).priority(Priority::min(true)), Width::increase(60).priority(Priority::min(true)),
)); ));
table.modify(Columns::first(), Alignment::right()); table.modify(Columns::single(3), Alignment::left());
println!("{}", table); println!("{}", table);
} }

View File

@ -1,11 +1,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_alias::serde_alias; use serde_alias::serde_alias;
use std::fs; use std::fs;
use iso8601; // use iso8601;
const SOX_ID: i32 = 111; const TEAM_ID: i32 = 111;
const SOX_SCHEDULE_URL: &str = "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=111&startDate=2025-04-01&endDate=2025-09-30";
const SOX_SCHEDULE_LOCAL: &str = "/Users/danchadwick/Projects/rust/pulse/assets/mlb-response.json";
#[serde_alias(CamelCase,SnakeCase)] #[serde_alias(CamelCase,SnakeCase)]
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -68,11 +66,9 @@ pub struct GameInfo {
// Gets the full forecast from the response. // Gets the full forecast from the response.
pub fn get_schedule() -> Vec<GameInfo> { pub fn get_schedule() -> Vec<GameInfo> {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
// let schedule_json: String = fs::read_to_string(SOX_SCHEDULE_LOCAL).expect("Unable to read file").to_owned(); let schedule_url = build_api_url();
let schedule_json: String = client.get(SOX_SCHEDULE_URL).send().expect("Unable to get data").text().unwrap().to_string(); let schedule_json: String = client.get(&schedule_url).send().expect("Unable to get data").text().unwrap().to_string();
let schedule: Schedule = serde_json::from_str(&schedule_json).expect("JSON was not well-formatted"); let schedule: Schedule = serde_json::from_str(&schedule_json).expect("JSON was not well-formatted");
// Iterate over the schedule, extract datapoints to create new struct
// Return a vec of the new structs.
let mut full_schedule: Vec<GameInfo> = vec![]; let mut full_schedule: Vec<GameInfo> = vec![];
let dates = schedule.dates; let dates = schedule.dates;
for date in dates { for date in dates {
@ -88,6 +84,7 @@ pub fn get_schedule() -> Vec<GameInfo> {
full_schedule full_schedule
} }
// Determine who the opponent is from the teams.
pub fn extract_opponent(teams: &Teams) -> String { pub fn extract_opponent(teams: &Teams) -> String {
if teams.home.team.name == "Boston Red Sox" { if teams.home.team.name == "Boston Red Sox" {
teams.away.team.name.to_string() teams.away.team.name.to_string()
@ -97,10 +94,16 @@ pub fn extract_opponent(teams: &Teams) -> String {
} }
} }
#[cfg(test)] // Build the url for the api request.
mod sox_tests { fn build_api_url() -> String {
use super::*; let url_first: String = "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=".to_string();
let url_second: String= "&startDate=2025-04-01&endDate=2025-09-30".to_string();
format!("{}{}{}", url_first, TEAM_ID, url_second)
}
#[cfg(test)]
mod team_tests {
use super::*;
#[test] #[test]
fn check_schedule_retrieval() { fn check_schedule_retrieval() {
get_schedule(); get_schedule();

View File

@ -33,6 +33,7 @@ pub struct WeatherOfficeLocation {
} }
impl WeatherOfficeLocation { impl WeatherOfficeLocation {
// Build api request URL.
pub fn build_url(&self) -> String { pub fn build_url(&self) -> String {
format!( format!(
"https://api.weather.gov/gridpoints/{}/{},{}/forecast", "https://api.weather.gov/gridpoints/{}/{},{}/forecast",
@ -41,31 +42,30 @@ impl WeatherOfficeLocation {
self.y, self.y,
) )
} }
}
// Get the full forecast for the location.
// Gets the full forecast from the response. pub fn get_full_forecast(&self) -> Vec<WeatherPeriod> {
pub fn get_full_forecast(location: WeatherOfficeLocation) -> Vec<WeatherPeriod> { let url = WeatherOfficeLocation::build_url(&self);
let url = WeatherOfficeLocation::build_url(&location); let client = reqwest::blocking::Client::new();
let client = reqwest::blocking::Client::new(); let forecast = client.get(&url)
let forecast = client.get(&url) .header(USER_AGENT, "My SuperAwesome Weather App")
.header(USER_AGENT, "My SuperAwesome Weather App") .send()
.send() .expect("Unable to get data")
.expect("Unable to get data") .text().unwrap().to_string();
.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() {
// let json: ForecastWrapper = serde_json::from_str(&forecast).expect("JSON was not well-formatted"); match detect_icon(&period.short_forecast) {
// let mut weather_periods: Vec<WeatherPeriod> = json.properties.periods.into_iter().collect(); None => println!("There was an issue detecting the correct icon!!!"),
for period in periods.iter_mut() { Some(icon) => {
match detect_icon(&period.short_forecast) { period.detailed_forecast = format!("{} {}", icon, &period.detailed_forecast);
None => println!("There was an issue detecting the correct icon!!!"), }
Some(icon) => { };
period.detailed_forecast = format!("{} {}", icon, &period.detailed_forecast); }
} periods
};
} }
periods
} }
// Detect which icon to display based on short forecast.
pub fn detect_icon(short_forecast: &String) -> Option<char> { pub fn detect_icon(short_forecast: &String) -> Option<char> {
if short_forecast.contains("Sunny") { if short_forecast.contains("Sunny") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
@ -73,45 +73,38 @@ pub fn detect_icon(short_forecast: &String) -> Option<char> {
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else if short_forecast.contains("Rain") && short_forecast.contains("Snow") {
else if short_forecast.contains("Rain") && short_forecast.contains("Snow") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
icon_code: "f067f".to_string(), icon_code: "f067f".to_string(),
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else if short_forecast.contains("Snow") {
else if short_forecast.contains("Snow") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
icon_code: "f0f36".to_string(), icon_code: "f0f36".to_string(),
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else if short_forecast.contains("Rain") {
else if short_forecast.contains("Rain") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
icon_code: "e239".to_string(), icon_code: "e239".to_string(),
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else if short_forecast.contains("Cloudy") {
else if short_forecast.contains("Cloudy") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
icon_code: "e312".to_string(), icon_code: "e312".to_string(),
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else if short_forecast.contains("Clear") {
else if short_forecast.contains("Clear") {
let icon = nerdfont::NerdFontIcon { let icon = nerdfont::NerdFontIcon {
icon_code: "e30d".to_string(), icon_code: "e30d".to_string(),
}; };
let icon_code = icon.get_icon(); let icon_code = icon.get_icon();
icon_code icon_code
} } else {
else {
None None
} }
} }