Compare commits
10 Commits
aa1893fbf6
...
a1270866a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1270866a5 | ||
|
|
c3b36f983e | ||
|
|
84dda34df6 | ||
|
|
48cd4e6eb6 | ||
|
|
bc974f44f4 | ||
|
|
b07c4abd88 | ||
|
|
3bbf63132e | ||
|
|
5da8701978 | ||
|
|
d3f6691ca9 | ||
|
|
3c7e5e1d4c |
238
Cargo.lock
generated
238
Cargo.lock
generated
@ -17,6 +17,30 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@ -89,6 +113,42 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
@ -414,6 +474,30 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
@ -658,6 +742,15 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@ -728,12 +821,59 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@ -811,6 +951,8 @@ dependencies = [
|
||||
name = "pulse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"colored",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@ -834,6 +976,50 @@ version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.15"
|
||||
@ -1058,6 +1244,12 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@ -1462,6 +1654,41 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
@ -1475,7 +1702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
@ -1497,6 +1724,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@ -10,3 +10,5 @@ serde = {version = "1.0.219", features = ["derive"]}
|
||||
serde_alias = "0.0.2"
|
||||
serde_json = "1.0.140"
|
||||
tabled = "0.18.0"
|
||||
chrono = "0.4"
|
||||
chrono-tz = "0.8"
|
||||
|
||||
2
README.md
Normal file
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Currently looks like this
|
||||

|
||||
12899
assets/mlb-response.json
Normal file
12899
assets/mlb-response.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
assets/preview.png
Normal file
BIN
assets/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 628 KiB |
34
src/icons.rs
Normal file
34
src/icons.rs
Normal file
@ -0,0 +1,34 @@
|
||||
#[allow(dead_code)]
|
||||
pub enum Icons {
|
||||
Fahrenheit,
|
||||
Clock,
|
||||
Baseball,
|
||||
Sunny,
|
||||
Mixed,
|
||||
Rain,
|
||||
Snow,
|
||||
Clear,
|
||||
Cloudy,
|
||||
}
|
||||
|
||||
impl Icons {
|
||||
|
||||
pub fn get_icon_str(&self) -> String {
|
||||
match self {
|
||||
Icons::Fahrenheit => 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)
|
||||
}
|
||||
}
|
||||
|
||||
58
src/main.rs
58
src/main.rs
@ -1,7 +1,7 @@
|
||||
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
|
||||
mod weather;
|
||||
mod redsox;
|
||||
use colored::Colorize;
|
||||
mod icons;
|
||||
use icons::Icons;
|
||||
use tabled::{Table, Tabled};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tabled::settings::{
|
||||
@ -13,58 +13,58 @@ use tabled::settings::{
|
||||
struct TableRow {
|
||||
date: String,
|
||||
time_of_day: String,
|
||||
temp: i32,
|
||||
temp: String,
|
||||
red_sox: String,
|
||||
forecast: String,
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
fn main() {
|
||||
let baseball_diamond = '\u{f0852}';
|
||||
// Set the weather location here.
|
||||
let location = weather::WeatherOfficeLocation {
|
||||
// Get forecast & schedule.
|
||||
let entire_forecast: Vec<weather::WeatherPeriod> = weather::WeatherOfficeLocation {
|
||||
x: 75,
|
||||
y: 59,
|
||||
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 sox_games: Vec<redsox::Game> = redsox::get_upcoming_games();
|
||||
}.get_full_forecast();
|
||||
let sox_games: Vec<redsox::GameInfo> = redsox::get_schedule();
|
||||
// Build icons.
|
||||
let baseball_icon = Icons::Baseball.get_icon_str();
|
||||
let clock_icon = Icons::Clock.get_icon_str();
|
||||
let fahrenheit_icon = Icons::Fahrenheit.get_icon_str();
|
||||
// Build the rows for the table.
|
||||
let mut table_rows: Vec<TableRow> = vec![];
|
||||
for i in 0..entire_forecast.len() {
|
||||
let item = &entire_forecast[i];
|
||||
let date = &item.start_time[0..10];
|
||||
let forecast_period = &entire_forecast[i];
|
||||
let yyyy_mm_dd = &forecast_period.start_time[0..10];
|
||||
let mut sox_status = String::new();
|
||||
// Check if there is a sox game and print opp.
|
||||
for sox_game in &sox_games {
|
||||
if sox_game.date == date {
|
||||
let mut opp_str = String::new();
|
||||
opp_str.push(baseball_diamond);
|
||||
opp_str.push_str(" ");
|
||||
opp_str.push_str(&sox_game.opponent);
|
||||
sox_status = opp_str;
|
||||
if sox_game.date == yyyy_mm_dd {
|
||||
sox_status = format!("{} {}\n{} {}", &baseball_icon, &sox_game.opponent, &clock_icon, &sox_game.start_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let row = TableRow {
|
||||
date: date.to_string(),
|
||||
time_of_day: item.name.clone(),
|
||||
temp: item.temperature,
|
||||
date: yyyy_mm_dd.to_string(),
|
||||
time_of_day: forecast_period.name.clone(),
|
||||
temp: format!("{}{}", forecast_period.temperature, fahrenheit_icon),
|
||||
red_sox: sox_status,
|
||||
forecast: item.detailed_forecast.to_string(),
|
||||
forecast: forecast_period.detailed_forecast.to_string(),
|
||||
};
|
||||
table_rows.push(row);
|
||||
}
|
||||
render_table(&table_rows);
|
||||
}
|
||||
|
||||
fn render_table(rows: &Vec<TableRow>) {
|
||||
// here is where we actually render the table.
|
||||
let mut table = Table::new(table_rows);
|
||||
table.with(Style::modern());
|
||||
let mut table = Table::new(rows);
|
||||
table.with((Style::modern(), Alignment::center()));
|
||||
table.modify(Columns::last(), Alignment::left());
|
||||
table.with((
|
||||
Width::wrap(210).priority(Priority::max(true)),
|
||||
Width::increase(50).priority(Priority::min(true)),
|
||||
Width::wrap(200).priority(Priority::max(true)),
|
||||
Width::increase(60).priority(Priority::min(true)),
|
||||
));
|
||||
table.modify(Columns::first(), Alignment::right());
|
||||
table.modify(Columns::single(3), Alignment::left());
|
||||
println!("{}", table);
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
129
src/redsox.rs
129
src/redsox.rs
@ -1,72 +1,141 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_alias::serde_alias;
|
||||
use std::fs;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono_tz::US::Eastern;
|
||||
|
||||
const SOX_ID: i32 = 111;
|
||||
const TEAM_ID: i32 = 111;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Game {
|
||||
// pub teams: Teams,
|
||||
pub date: String,
|
||||
// pub start_time: String,
|
||||
pub opponent: String,
|
||||
pub season: String,
|
||||
pub teams: Teams,
|
||||
pub official_date: String,
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Team {
|
||||
team: TeamInfo,
|
||||
league_record: TeamRecord,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TeamInfo {
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TeamRecord {
|
||||
wins: i32,
|
||||
losses: i32,
|
||||
}
|
||||
|
||||
pub fn get_upcoming_games() -> Vec<Game> {
|
||||
// @todo - change this to be a dynamic request from the API endpoint instead of a local file.
|
||||
let schedule_json: String = fs::read_to_string("/Users/danchadwick/Projects/rust/weather/assets/sox-schedule.json").expect("Unable to read file").to_owned();
|
||||
let json: Vec<Game> = serde_json::from_str(&schedule_json).expect("Something not good?");
|
||||
let upcoming_games: &Vec<Game> = &json.into_iter().collect();
|
||||
upcoming_games.to_owned()
|
||||
#[serde_alias(CamelCase,SnakeCase)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GameInfo {
|
||||
pub opponent: String,
|
||||
pub date: String,
|
||||
pub start_time: String,
|
||||
}
|
||||
|
||||
// Gets the full forecast from the response.
|
||||
pub fn get_schedule() -> Schedule {
|
||||
let url = "https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=111&startDate=2025-04-01&endDate=2025-09-30".to_string();
|
||||
pub fn get_schedule() -> Vec<GameInfo> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let schedule_string: String = client.get(&url).send().expect("Unable to get data").text().unwrap().to_string();
|
||||
let json: Schedule = serde_json::from_str(&schedule_string).expect("JSON was not well-formatted");
|
||||
|
||||
dbg!(json);
|
||||
let schedule = Schedule {
|
||||
games: vec![],
|
||||
};
|
||||
|
||||
schedule
|
||||
// json
|
||||
let schedule_url = build_api_url();
|
||||
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 mut full_schedule: Vec<GameInfo> = vec![];
|
||||
let dates = schedule.dates;
|
||||
for date in dates {
|
||||
for game in date.games {
|
||||
let facing = extract_opponent(&game.teams);
|
||||
let game_info = GameInfo {
|
||||
opponent: facing,
|
||||
date: game.official_date,
|
||||
start_time: get_start_time(&game.game_date),
|
||||
};
|
||||
full_schedule.push(game_info);
|
||||
}
|
||||
}
|
||||
full_schedule
|
||||
}
|
||||
|
||||
// Determine who the opponent is from the teams.
|
||||
pub fn extract_opponent(teams: &Teams) -> String {
|
||||
match teams.home.team.name == "Boston Red Sox" {
|
||||
true => teams.away.team.name.to_string(),
|
||||
false => teams.home.team.name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// Build the url for the api request.
|
||||
fn build_api_url() -> String {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
|
||||
105
src/weather.rs
105
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 {
|
||||
@ -33,6 +34,7 @@ pub struct WeatherOfficeLocation {
|
||||
}
|
||||
|
||||
impl WeatherOfficeLocation {
|
||||
// Build api request URL.
|
||||
pub fn build_url(&self) -> String {
|
||||
format!(
|
||||
"https://api.weather.gov/gridpoints/{}/{},{}/forecast",
|
||||
@ -41,71 +43,44 @@ impl WeatherOfficeLocation {
|
||||
self.y,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the full forecast from the response.
|
||||
pub fn get_full_forecast(location: WeatherOfficeLocation) -> Vec<WeatherPeriod> {
|
||||
let url = WeatherOfficeLocation::build_url(&location);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let forecast = client.get(&url)
|
||||
.header(USER_AGENT, "My SuperAwesome Weather App")
|
||||
.send()
|
||||
.expect("Unable to get data")
|
||||
.text().unwrap().to_string();
|
||||
let json: ForecastWrapper = serde_json::from_str(&forecast).expect("JSON was not well-formatted");
|
||||
let weather_periods: Vec<WeatherPeriod> = json.properties.periods.into_iter().collect();
|
||||
let icon_forecasts = enhance_forecasts(weather_periods);
|
||||
icon_forecasts
|
||||
}
|
||||
|
||||
pub fn enhance_forecasts(mut periods: Vec<WeatherPeriod>) -> Vec<WeatherPeriod> {
|
||||
for period in periods.iter_mut() {
|
||||
let icon = detect_icon(&period.short_forecast).unwrap();
|
||||
let icon_forecast = format!("{} {}", icon, &period.detailed_forecast);
|
||||
period.detailed_forecast = icon_forecast;
|
||||
}
|
||||
periods
|
||||
}
|
||||
|
||||
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 {
|
||||
None
|
||||
// Get the full forecast for the location.
|
||||
pub fn get_full_forecast(&self) -> Vec<WeatherPeriod> {
|
||||
let url = WeatherOfficeLocation::build_url(&self);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let forecast = client.get(&url)
|
||||
.header(USER_AGENT, "My SuperAwesome Weather App")
|
||||
.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");
|
||||
for period in periods.iter_mut() {
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
periods
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Detect which icon to display based on short forecast.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user