Icon support, adding table render method, moving sox schedule to api instead of local file.
This commit is contained in:
parent
3c7e5e1d4c
commit
d3f6691ca9
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -569,6 +569,15 @@ version = "2.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iso8601"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c5c177cff824ab21a6f41079a4c401241c4e8be14f316c4c6b07d5fca351c98d"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@ -658,6 +667,15 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@ -812,6 +830,7 @@ name = "pulse"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colored",
|
"colored",
|
||||||
|
"iso8601",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_alias",
|
"serde_alias",
|
||||||
|
|||||||
@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
colored = "3.0.0"
|
colored = "3.0.0"
|
||||||
|
iso8601 = "0.6.2"
|
||||||
reqwest = { version = "0.12.15", features = ["blocking"] }
|
reqwest = { version = "0.12.15", features = ["blocking"] }
|
||||||
serde = {version = "1.0.219", features = ["derive"]}
|
serde = {version = "1.0.219", features = ["derive"]}
|
||||||
serde_alias = "0.0.2"
|
serde_alias = "0.0.2"
|
||||||
|
|||||||
12899
assets/mlb-response.json
Normal file
12899
assets/mlb-response.json
Normal file
File diff suppressed because it is too large
Load Diff
48
src/main.rs
48
src/main.rs
@ -1,7 +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;
|
||||||
use colored::Colorize;
|
// use colored::Colorize;
|
||||||
use tabled::{Table, Tabled};
|
use tabled::{Table, Tabled};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tabled::settings::{
|
use tabled::settings::{
|
||||||
@ -20,27 +20,24 @@ struct TableRow {
|
|||||||
|
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let baseball_diamond = '\u{f0852}';
|
|
||||||
// Set the weather location here.
|
// Set the weather location here.
|
||||||
let location = weather::WeatherOfficeLocation {
|
let location = weather::WeatherOfficeLocation {
|
||||||
x: 75,
|
x: 75,
|
||||||
y: 59,
|
y: 59,
|
||||||
code: "GYX".to_string(),
|
code: "GYX".to_string(),
|
||||||
};
|
};
|
||||||
// @todo - add a way to configure which teams to add
|
|
||||||
// @todo - add a way to get news articles?
|
|
||||||
let entire_forecast: Vec<weather::WeatherPeriod> = weather::get_full_forecast(location);
|
let entire_forecast: Vec<weather::WeatherPeriod> = weather::get_full_forecast(location);
|
||||||
let sox_games: Vec<redsox::Game> = redsox::get_upcoming_games();
|
let sox_games: Vec<redsox::GameInfo> = redsox::get_schedule();
|
||||||
|
let baseball_diamond = '\u{f0852}';
|
||||||
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 item = &entire_forecast[i];
|
let forecast_period = &entire_forecast[i];
|
||||||
let date = &item.start_time[0..10];
|
let yyyy_mm_dd = &forecast_period.start_time[0..10];
|
||||||
let mut sox_status = String::new();
|
let mut sox_status = String::new();
|
||||||
// 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 == date {
|
if sox_game.date == yyyy_mm_dd {
|
||||||
let mut opp_str = String::new();
|
let mut opp_str = String::from(baseball_diamond);
|
||||||
opp_str.push(baseball_diamond);
|
|
||||||
opp_str.push_str(" ");
|
opp_str.push_str(" ");
|
||||||
opp_str.push_str(&sox_game.opponent);
|
opp_str.push_str(&sox_game.opponent);
|
||||||
sox_status = opp_str;
|
sox_status = opp_str;
|
||||||
@ -48,23 +45,26 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let row = TableRow {
|
let row = TableRow {
|
||||||
date: date.to_string(),
|
date: yyyy_mm_dd.to_string(),
|
||||||
time_of_day: item.name.clone(),
|
time_of_day: forecast_period.name.clone(),
|
||||||
temp: item.temperature,
|
temp: forecast_period.temperature,
|
||||||
red_sox: sox_status,
|
red_sox: sox_status,
|
||||||
forecast: item.detailed_forecast.to_string(),
|
forecast: forecast_period.detailed_forecast.to_string(),
|
||||||
};
|
};
|
||||||
table_rows.push(row);
|
table_rows.push(row);
|
||||||
}
|
}
|
||||||
|
render_table(&table_rows);
|
||||||
// here is where we actually render the table.
|
}
|
||||||
let mut table = Table::new(table_rows);
|
|
||||||
table.with(Style::modern());
|
fn render_table(rows: &Vec<TableRow>) {
|
||||||
table.with((
|
// here is where we actually render the table.
|
||||||
Width::wrap(210).priority(Priority::max(true)),
|
let mut table = Table::new(rows);
|
||||||
Width::increase(50).priority(Priority::min(true)),
|
table.with(Style::modern());
|
||||||
));
|
table.with((
|
||||||
table.modify(Columns::first(), Alignment::right());
|
Width::wrap(210).priority(Priority::max(true)),
|
||||||
println!("{}", table);
|
Width::increase(50).priority(Priority::min(true)),
|
||||||
|
));
|
||||||
|
table.modify(Columns::first(), Alignment::right());
|
||||||
|
println!("{}", table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,34 @@
|
|||||||
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;
|
||||||
|
|
||||||
const SOX_ID: i32 = 111;
|
const SOX_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";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Schedule {
|
pub struct Schedule {
|
||||||
|
pub dates: Vec<Date>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Date {
|
||||||
|
pub date: String,
|
||||||
pub games: Vec<Game>,
|
pub games: Vec<Game>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
// pub teams: Teams,
|
pub season: String,
|
||||||
pub date: String,
|
pub teams: Teams,
|
||||||
|
// pub date: String,
|
||||||
|
pub official_date: String,
|
||||||
// pub start_time: String,
|
// pub start_time: String,
|
||||||
pub opponent: String,
|
// pub opponent: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@ -26,47 +38,71 @@ pub struct Teams {
|
|||||||
home: Team,
|
home: Team,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Team {
|
pub struct Team {
|
||||||
team: TeamInfo,
|
team: TeamInfo,
|
||||||
league_record: TeamRecord,
|
league_record: TeamRecord,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct TeamInfo {
|
pub struct TeamInfo {
|
||||||
id: i32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct TeamRecord {
|
pub struct TeamRecord {
|
||||||
wins: i32,
|
wins: i32,
|
||||||
losses: i32,
|
losses: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_upcoming_games() -> Vec<Game> {
|
pub struct GameInfo {
|
||||||
// @todo - change this to be a dynamic request from the API endpoint instead of a local file.
|
pub opponent: String,
|
||||||
let schedule_json: String = fs::read_to_string("/Users/danchadwick/Projects/rust/weather/assets/sox-schedule.json").expect("Unable to read file").to_owned();
|
pub date: String,
|
||||||
let json: Vec<Game> = serde_json::from_str(&schedule_json).expect("Something not good?");
|
// pub time: String,
|
||||||
let upcoming_games: &Vec<Game> = &json.into_iter().collect();
|
|
||||||
upcoming_games.to_owned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the full forecast from the response.
|
// Gets the full forecast from the response.
|
||||||
pub fn get_schedule() -> Schedule {
|
pub fn get_schedule() -> Vec<GameInfo> {
|
||||||
let url = "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=111&startDate=2025-04-01&endDate=2025-09-30".to_string();
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let schedule_string: String = client.get(&url).send().expect("Unable to get data").text().unwrap().to_string();
|
// let schedule_json: String = fs::read_to_string(SOX_SCHEDULE_LOCAL).expect("Unable to read file").to_owned();
|
||||||
let json: Schedule = serde_json::from_str(&schedule_string).expect("JSON was not well-formatted");
|
let schedule_json: String = client.get(SOX_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");
|
||||||
dbg!(json);
|
// Iterate over the schedule, extract datapoints to create new struct
|
||||||
let schedule = Schedule {
|
// Return a vec of the new structs.
|
||||||
games: vec![],
|
let mut full_schedule: Vec<GameInfo> = vec![];
|
||||||
};
|
let dates = schedule.dates;
|
||||||
|
for date in dates {
|
||||||
schedule
|
for game in date.games {
|
||||||
// json
|
let facing = extract_opponent(&game.teams);
|
||||||
|
let game_info = GameInfo {
|
||||||
|
opponent: facing,
|
||||||
|
date: game.official_date,
|
||||||
|
};
|
||||||
|
full_schedule.push(game_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
full_schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod sox_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_schedule_retrieval() {
|
||||||
|
get_schedule();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,13 +52,18 @@ pub fn get_full_forecast(location: WeatherOfficeLocation) -> Vec<WeatherPeriod>
|
|||||||
.send()
|
.send()
|
||||||
.expect("Unable to get data")
|
.expect("Unable to get data")
|
||||||
.text().unwrap().to_string();
|
.text().unwrap().to_string();
|
||||||
let json: ForecastWrapper = 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");
|
||||||
let mut weather_periods: Vec<WeatherPeriod> = json.properties.periods.into_iter().collect();
|
// let json: ForecastWrapper = serde_json::from_str(&forecast).expect("JSON was not well-formatted");
|
||||||
for period in weather_periods.iter_mut() {
|
// let mut weather_periods: Vec<WeatherPeriod> = json.properties.periods.into_iter().collect();
|
||||||
let icon = detect_icon(&period.short_forecast).unwrap();
|
for period in periods.iter_mut() {
|
||||||
period.detailed_forecast = format!("{} {}", icon, &period.detailed_forecast);
|
match detect_icon(&period.short_forecast) {
|
||||||
|
None => println!("There was an issue detecting the correct icon!!!"),
|
||||||
|
Some(icon) => {
|
||||||
|
period.detailed_forecast = format!("{} {}", icon, &period.detailed_forecast);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
weather_periods
|
periods
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect_icon(short_forecast: &String) -> Option<char> {
|
pub fn detect_icon(short_forecast: &String) -> Option<char> {
|
||||||
@ -97,6 +102,13 @@ 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("Clear") {
|
||||||
|
let icon = nerdfont::NerdFontIcon {
|
||||||
|
icon_code: "e30d".to_string(),
|
||||||
|
};
|
||||||
|
let icon_code = icon.get_icon();
|
||||||
|
icon_code
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user