Lots of changes and reorgnizing things.
This commit is contained in:
parent
8e1ae39164
commit
2d936f6272
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -89,6 +89,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -802,6 +811,7 @@ dependencies = [
|
|||||||
name = "pulse"
|
name = "pulse"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"colored",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_alias",
|
"serde_alias",
|
||||||
|
|||||||
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
colored = "3.0.0"
|
||||||
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"
|
||||||
|
|||||||
14
src/main.rs
14
src/main.rs
@ -1,12 +1,11 @@
|
|||||||
#![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 tabled::Table;
|
use tabled::{Table, Tabled};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tabled::settings::{
|
use tabled::settings::{
|
||||||
peaker::Priority, Width, Style, Alignment, object::Columns
|
peaker::Priority, Width, Style, Alignment, object::Columns
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tabled::Tabled;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Tabled)]
|
#[derive(Serialize, Deserialize, Debug, Tabled)]
|
||||||
#[tabled(rename_all = "UPPERCASE")]
|
#[tabled(rename_all = "UPPERCASE")]
|
||||||
@ -21,7 +20,6 @@ struct TableRow {
|
|||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
fn main() {
|
fn main() {
|
||||||
let baseball_diamond = '\u{f15ec}';
|
let baseball_diamond = '\u{f15ec}';
|
||||||
let sunny = '\u{f0599}';
|
|
||||||
// Set the weather location here.
|
// Set the weather location here.
|
||||||
let location = weather::WeatherOfficeLocation {
|
let location = weather::WeatherOfficeLocation {
|
||||||
x: 75,
|
x: 75,
|
||||||
@ -48,19 +46,17 @@ fn main() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut forecast_w_icon = String::new();
|
|
||||||
forecast_w_icon.push(sunny);
|
|
||||||
forecast_w_icon.push_str(" ");
|
|
||||||
forecast_w_icon.push_str(&item.detailed_forecast);
|
|
||||||
let row = TableRow {
|
let row = TableRow {
|
||||||
date: date.to_string(),
|
date: date.to_string(),
|
||||||
time_of_day: item.name.clone(),
|
time_of_day: item.name.clone(),
|
||||||
temp: item.temperature,
|
temp: item.temperature,
|
||||||
red_sox: sox_status,
|
red_sox: sox_status,
|
||||||
forecast: forecast_w_icon,
|
forecast: item.detailed_forecast.to_string(),
|
||||||
};
|
};
|
||||||
table_rows.push(row);
|
table_rows.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// here is where we actually render the table.
|
||||||
let mut table = Table::new(table_rows);
|
let mut table = Table::new(table_rows);
|
||||||
table.with(Style::modern());
|
table.with(Style::modern());
|
||||||
table.with((
|
table.with((
|
||||||
|
|||||||
10
src/nerdfont.rs
Normal file
10
src/nerdfont.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/weather.rs
Normal file
123
src/weather.rs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
use reqwest::header::USER_AGENT;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_alias::serde_alias;
|
||||||
|
#[path = "nerdfont.rs"] mod nerdfont;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct ForecastWrapper {
|
||||||
|
properties: Properties,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Properties {
|
||||||
|
periods: Vec<WeatherPeriod>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serde_alias(CamelCase,SnakeCase)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct WeatherPeriod {
|
||||||
|
pub name: String,
|
||||||
|
pub temperature: u64,
|
||||||
|
pub wind_direction: String,
|
||||||
|
pub wind_speed: String,
|
||||||
|
pub detailed_forecast: String,
|
||||||
|
pub short_forecast: String,
|
||||||
|
pub start_time: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct WeatherOfficeLocation {
|
||||||
|
pub x: i32,
|
||||||
|
pub y: i32,
|
||||||
|
pub code: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeatherOfficeLocation {
|
||||||
|
pub fn build_url(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"https://api.weather.gov/gridpoints/{}/{},{}/forecast",
|
||||||
|
self.code,
|
||||||
|
self.x,
|
||||||
|
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(periods: &Vec<WeatherPeriod>) -> Vec<WeatherPeriod> {
|
||||||
|
let mut rebuilt_periods: Vec<WeatherPeriod> = vec![];
|
||||||
|
for period in periods {
|
||||||
|
let icon = detect_icon(&period.short_forecast).unwrap();
|
||||||
|
let icon_forecast = format!("{} {}", icon, &period.detailed_forecast);
|
||||||
|
let rebuilt_period = WeatherPeriod {
|
||||||
|
name: period.name.to_string(),
|
||||||
|
temperature: period.temperature,
|
||||||
|
wind_direction: period.wind_direction.to_string(),
|
||||||
|
wind_speed: period.wind_speed.to_string(),
|
||||||
|
detailed_forecast: icon_forecast,
|
||||||
|
short_forecast: period.short_forecast.to_string(),
|
||||||
|
start_time: period.start_time.to_string(),
|
||||||
|
};
|
||||||
|
rebuilt_periods.push(rebuilt_period);
|
||||||
|
}
|
||||||
|
|
||||||
|
rebuilt_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
use reqwest::header::USER_AGENT;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_alias::serde_alias;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ForecastWrapper {
|
|
||||||
properties: Properties,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct Properties {
|
|
||||||
periods: Vec<WeatherPeriod>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_alias(CamelCase,SnakeCase)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WeatherPeriod {
|
|
||||||
pub name: String,
|
|
||||||
pub temperature: u64,
|
|
||||||
pub wind_direction: String,
|
|
||||||
pub wind_speed: String,
|
|
||||||
pub detailed_forecast: String,
|
|
||||||
pub start_time: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct WeatherOfficeLocation {
|
|
||||||
pub x: i32,
|
|
||||||
pub y: i32,
|
|
||||||
pub code: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WeatherOfficeLocation {
|
|
||||||
pub fn build_url(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"https://api.weather.gov/gridpoints/{}/{},{}/forecast",
|
|
||||||
self.code,
|
|
||||||
self.x,
|
|
||||||
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 periods: Vec<WeatherPeriod> = json.properties.periods.into_iter().collect();
|
|
||||||
|
|
||||||
periods
|
|
||||||
}
|
|
||||||
|
|
||||||
12
todo.md
Normal file
12
todo.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Todo
|
||||||
|
- [ ] weather: Parse each period and detect type of forecast
|
||||||
|
- [ ] weather: add colors to each icon for forecast
|
||||||
|
(sunny = yellow) :: (rainy = blue) :: (cloudy = gray)
|
||||||
|
- [ ] redsox: Only show one game per date
|
||||||
|
- [ ] main: clean things up in the function
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
### Detecting weather forecast type
|
||||||
|
Need to change the default return from get_full_forecast.
|
||||||
|
It should be a DATE => [forecast1, forecast2]
|
||||||
|
where each forecast has an icon associated with is
|
||||||
Loading…
x
Reference in New Issue
Block a user