diff --git a/README.md b/README.md index 1c44d63..4346ca3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Console utility for accessing aviation weather information from the command line If you provide no flags to WXfetch at all, it will try and fetch weather info from your closest airfield according to your IP based position (geoip). -With `-a` you can provide the ICAO or IATA code for a reporting station. Alternatively, with `--lat` and `--lon` you can provide geographical coordinates. WXfetch will then try and find a reporting station close to that position. Please make sure to provide both parameters. +With `-a` or `--airfield` you can provide the ICAO or IATA code for a reporting station. Alternatively, with `--lat` and `--lon` you can provide geographical coordinates. WXfetch will then try and find a reporting station close to that position. Please make sure to provide both parameters. If there is any problem with the provided arguments WXfetch will print an error message and default to geoip. diff --git a/src/api.rs b/src/api.rs index 208863f..5b2a5ab 100644 --- a/src/api.rs +++ b/src/api.rs @@ -65,7 +65,7 @@ async fn get_nearest_station(config: &Config, secrets: &Secrets) -> Option bool { let uri = format!("https://avwx.rest/api/station/{icao}"); diff --git a/src/metar.rs b/src/metar.rs index 3764680..58c7918 100644 --- a/src/metar.rs +++ b/src/metar.rs @@ -22,7 +22,7 @@ pub struct Metar { /// ICAO code of the issuing station. icao_code: String, /// Contents of the report. - fields: Vec, + fields: Vec, /// True, if this METAR was issued by the exact station that was requested, false otherwise. exact_match: bool, // / Units. @@ -31,7 +31,7 @@ pub struct Metar { #[derive(PartialEq, Eq, Debug)] /// Elements of a METAR report. -pub enum MetarField { +pub enum WxField { /// Issue time. TimeStamp(DateTime), /// Prevailing winds. @@ -61,31 +61,29 @@ pub enum MetarField { Remarks(String), } -impl MetarField { +impl WxField { pub fn colourise(&self) -> ColoredString { match self { - MetarField::Visibility(vis) => colourise_visibility(*vis), - MetarField::TimeStamp(datetime) => colourize_timestamp(datetime), - MetarField::Wind { + WxField::Visibility(vis) => colourise_visibility(*vis), + WxField::TimeStamp(datetime) => colourize_timestamp(datetime), + WxField::Wind { direction, strength, gusts, unit, } => colourise_wind(*direction, *strength, *gusts, *unit), - MetarField::WindVariability { low_dir, hi_dir } => { - colourise_wind_var(*low_dir, *hi_dir) - } - MetarField::Temperature { + WxField::WindVariability { low_dir, hi_dir } => colourise_wind_var(*low_dir, *hi_dir), + WxField::Temperature { temp, dewpoint, unit, } => colourise_temperature(*temp, *dewpoint, *unit), - MetarField::Qnh(qnh, unit) => colourise_qnh(*qnh, *unit), - MetarField::WxCode(code, intensity, proximity, descriptor) => { + WxField::Qnh(qnh, unit) => colourise_qnh(*qnh, *unit), + WxField::WxCode(code, intensity, proximity, descriptor) => { colourise_wx_code(code, intensity, proximity, descriptor) } - MetarField::Remarks(str) => str.black().on_white(), - MetarField::Clouds(cloud, alt) => colourise_clouds(cloud, *alt), + WxField::Remarks(str) => str.black().on_white(), + WxField::Clouds(cloud, alt) => colourise_clouds(cloud, *alt), } } } @@ -123,7 +121,7 @@ fn colourise_wx_code( let intensitystr = format!("{intensity}").color(match intensity { WxCodeIntensity::Light => Color::BrightGreen, WxCodeIntensity::Heavy => Color::BrightRed, - _ => Color::White, + WxCodeIntensity::Moderate => Color::White, }); let descrstr = format!("{descriptor}").color(match descriptor { @@ -230,7 +228,7 @@ impl Metar { let units: Units = Units::from_json(json); - let mut fields: Vec = Vec::new(); + let mut fields: Vec = Vec::new(); if let Some(time) = get_timestamp(json) { fields.push(time); @@ -288,18 +286,19 @@ impl Metar { } } -fn get_remarks(json: &Value) -> Option { +fn get_remarks(json: &Value) -> Option { let rmks = json.get("remarks")?.as_str()?.to_string(); - Some(MetarField::Remarks(rmks)) + Some(WxField::Remarks(rmks)) } -fn get_timestamp(json: &Value) -> Option { +fn get_timestamp(json: &Value) -> Option { let datetime_str = json.get("time")?.get("dt")?.as_str()?; let datetime = DateTime::parse_from_rfc3339(datetime_str).ok()?; - Some(MetarField::TimeStamp(datetime)) + Some(WxField::TimeStamp(datetime)) } -fn get_qnh(json: &Value, units: Units) -> Option { +#[allow(clippy::cast_possible_truncation)] +fn get_qnh(json: &Value, units: Units) -> Option { let qnh_val: &Value = json.get("altimeter")?.get("value")?; let qnh: i64 = if qnh_val.is_f64() { qnh_val.as_f64()?.mul(100.).round() as i64 @@ -307,20 +306,20 @@ fn get_qnh(json: &Value, units: Units) -> Option { qnh_val.as_i64()? }; - Some(MetarField::Qnh(qnh, units.pressure)) + Some(WxField::Qnh(qnh, units.pressure)) } -fn get_temp(json: &Value, units: Units) -> Option { +fn get_temp(json: &Value, units: Units) -> Option { let temp = json.get("temperature")?.get("value")?.as_i64()?; let dewpoint = json.get("dewpoint")?.get("value")?.as_i64()?; - Some(MetarField::Temperature { + Some(WxField::Temperature { temp, dewpoint, unit: units.temperature, }) } -fn get_wind_var(json: &Value) -> Option { +fn get_wind_var(json: &Value) -> Option { let wind_dirs = json.get("wind_variable_direction")?.as_array()?; let mut dirs: Vec = Vec::new(); for dir in wind_dirs { @@ -329,13 +328,13 @@ fn get_wind_var(json: &Value) -> Option { dirs.sort_unstable(); let low_dir = dirs.first()?; let hi_dir = dirs.last()?; - Some(MetarField::WindVariability { + Some(WxField::WindVariability { low_dir: *low_dir, hi_dir: *hi_dir, }) } -fn get_winds(json: &Value, units: Units) -> Option { +fn get_winds(json: &Value, units: Units) -> Option { let direction = json.get("wind_direction")?.get("value")?.as_i64()?; let strength = json.get("wind_speed")?.get("value")?.as_i64()?; let gusts = json @@ -344,7 +343,7 @@ fn get_winds(json: &Value, units: Units) -> Option { .and_then(serde_json::Value::as_i64) .unwrap_or(0); - Some(MetarField::Wind { + Some(WxField::Wind { direction, strength, gusts, @@ -352,9 +351,9 @@ fn get_winds(json: &Value, units: Units) -> Option { }) } -fn get_visibility(json: &Value, _units: Units) -> Option { +fn get_visibility(json: &Value, _units: Units) -> Option { let vis = json.get("visibility")?.get("value")?.as_i64()?; - Some(MetarField::Visibility(vis)) + Some(WxField::Visibility(vis)) } fn is_exact_match(station: &str, config: &Config) -> bool { @@ -390,7 +389,7 @@ mod tests { }; let expected = DateTime::parse_from_rfc3339("2024-06-21T05:50:00Z").unwrap(); let metar = Metar::from_json(&json, &config); - assert!(metar.is_some_and(|m| m.fields.contains(&MetarField::TimeStamp(expected)))); + assert!(metar.is_some_and(|m| m.fields.contains(&WxField::TimeStamp(expected)))); } #[test] @@ -446,7 +445,7 @@ mod tests { #[test] fn test_get_winds() { let json: Value = Value::from_str("{\"wind_direction\": {\"value\":100}, \"wind_speed\":{\"value\":10}, \"wind_gust\":{\"value\":15}}").unwrap(); - let expected = MetarField::Wind { + let expected = WxField::Wind { direction: 100, strength: 10, gusts: 15, @@ -461,7 +460,7 @@ mod tests { let json: Value = Value::from_str("{\"wind_direction\": {\"value\":100}, \"wind_speed\":{\"value\":10}}") .unwrap(); - let expected = MetarField::Wind { + let expected = WxField::Wind { direction: 100, strength: 10, gusts: 0, @@ -474,7 +473,7 @@ mod tests { #[test] fn test_get_qnh() { let json: Value = Value::from_str("{\"altimeter\":{\"value\": 1013}}").unwrap(); - let expected = MetarField::Qnh(1013, PressureUnit::Hpa); + let expected = WxField::Qnh(1013, PressureUnit::Hpa); let actual = get_qnh(&json, Units::default()); assert!(actual.is_some_and(|q| q == expected)); } @@ -482,7 +481,7 @@ mod tests { #[test] fn test_get_qnh_inhg() { let json: Value = Value::from_str("{\"altimeter\":{\"value\": 29.92}}").unwrap(); - let expected = MetarField::Qnh(2992, PressureUnit::Inhg); + let expected = WxField::Qnh(2992, PressureUnit::Inhg); let units = Units { pressure: PressureUnit::Inhg, altitude: AltitudeUnit::Ft, @@ -500,7 +499,7 @@ mod tests { let json: Value = Value::from_str("{\"remarks\":\"RWY UNAVAILABLE\"}").unwrap(); let expected = "RWY UNAVAILABLE".to_string(); let actual = get_remarks(&json); - assert!(actual.is_some_and(|r| r == MetarField::Remarks(expected))); + assert!(actual.is_some_and(|r| r == WxField::Remarks(expected))); } #[test] @@ -508,7 +507,7 @@ mod tests { let json: Value = Value::from_str("{\"temperature\":{\"value\": 10}, \"dewpoint\":{\"value\": 9}}") .unwrap(); - let expected: MetarField = MetarField::Temperature { + let expected: WxField = WxField::Temperature { temp: 10, dewpoint: 9, unit: TemperatureUnit::C, @@ -522,7 +521,7 @@ mod tests { let json: Value = Value::from_str("{\"wind_variable_direction\":[{\"value\" : 80},{\"value\" : 150}]}") .unwrap(); - let expected: MetarField = MetarField::WindVariability { + let expected: WxField = WxField::WindVariability { low_dir: 80, hi_dir: 150, }; @@ -533,7 +532,7 @@ mod tests { #[test] fn test_get_visibility() { let json: Value = Value::from_str("{\"visibility\":{\"value\":9999}}").unwrap(); - let expected: MetarField = MetarField::Visibility(9999); + let expected: WxField = WxField::Visibility(9999); let actual = get_visibility(&json, Units::default()); assert!(actual.is_some_and(|v| v == expected)); } diff --git a/src/metar/clouds.rs b/src/metar/clouds.rs index 1f144da..54a5371 100644 --- a/src/metar/clouds.rs +++ b/src/metar/clouds.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use super::MetarField; +use super::WxField; use anyhow::anyhow; use regex::Regex; use serde_json::Value; @@ -23,8 +23,8 @@ pub enum Clouds { } /// Parses a METAR in JSON form and returns a `Vec` of `MetarField::Clouds` describing the cloud information contained. -pub fn get_clouds_from_json(json: &Value) -> Vec { - let mut result: Vec = Vec::new(); +pub fn get_clouds_from_json(json: &Value) -> Vec { + let mut result: Vec = Vec::new(); if let Some(wxcodes) = json.get("clouds").and_then(|x| x.as_array()) { for code in wxcodes { if let Some(repr) = code.get("repr").and_then(|x| x.as_str()) { @@ -38,14 +38,14 @@ pub fn get_clouds_from_json(json: &Value) -> Vec { } /// From a METAR compliant cloud code representation string (`&str`) parses a `MetarField::Cloud`. -fn clouds_from_str(repr: &str) -> Option { +fn clouds_from_str(repr: &str) -> Option { let regex = format!("(?{})(?\\d*)", Clouds::get_regex()); let regex = Regex::new(®ex) .expect("Creating RegEx pattern failed. This is likely a software bug, please report it."); let matches = regex.captures(repr)?; let obscuration: Clouds = matches["obscuration"].parse().ok()?; let level: i64 = matches["level"].parse().unwrap_or(0); - Some(MetarField::Clouds(obscuration, level)) + Some(WxField::Clouds(obscuration, level)) } impl Clouds { @@ -97,7 +97,7 @@ mod tests { use serde_json::Value; - use crate::metar::MetarField; + use crate::metar::WxField; use super::{clouds_from_str, get_clouds_from_json, Clouds}; @@ -110,14 +110,14 @@ mod tests { #[test] fn test_clouds_from_str() { - let expected = MetarField::Clouds(Clouds::Skc, 0); + let expected = WxField::Clouds(Clouds::Skc, 0); let actual = clouds_from_str("SKC"); assert_eq!(Some(expected), actual); } #[test] fn test_clouds_from_str_sct() { - let expected = MetarField::Clouds(Clouds::Sct, 50); + let expected = WxField::Clouds(Clouds::Sct, 50); let actual = clouds_from_str("SCT50"); assert_eq!(Some(expected), actual); } @@ -128,10 +128,10 @@ mod tests { "{\"clouds\":[{\"repr\": \"SCT050\"},{\"repr\": \"BRK100\"},{\"repr\": \"OVC200\"}]}", ) .unwrap(); - let expected: Vec = vec![ - MetarField::Clouds(Clouds::Sct, 50), - MetarField::Clouds(Clouds::Brk, 100), - MetarField::Clouds(Clouds::Ovc, 200), + let expected: Vec = vec![ + WxField::Clouds(Clouds::Sct, 50), + WxField::Clouds(Clouds::Brk, 100), + WxField::Clouds(Clouds::Ovc, 200), ]; let actual = get_clouds_from_json(&json); assert_eq!(expected, actual); diff --git a/src/metar/wxcodes.rs b/src/metar/wxcodes.rs index 213b0dc..4a5ece5 100644 --- a/src/metar/wxcodes.rs +++ b/src/metar/wxcodes.rs @@ -1,4 +1,4 @@ -use super::MetarField; +use super::WxField; use anyhow::{anyhow, Error}; use regex::Regex; use serde_json::Value; @@ -294,7 +294,7 @@ impl Display for WxCodeDescription { } } -pub(crate) fn wxcode_from_str(repr: &str) -> Option { +pub(crate) fn wxcode_from_str(repr: &str) -> Option { let regex_pattern = format!( r"(?({})?)(?({})?)(?{})(?({})?)", WxCodeIntensity::get_regex(), @@ -310,11 +310,11 @@ pub(crate) fn wxcode_from_str(repr: &str) -> Option { let descriptor: WxCodeDescription = matches["descr"].parse().ok()?; let proximity: WxCodeProximity = matches["location"].parse().ok()?; - Some(MetarField::WxCode(code, intensity, proximity, descriptor)) + Some(WxField::WxCode(code, intensity, proximity, descriptor)) } -pub fn get_wxcodes_from_json(json: &Value) -> Vec { - let mut result: Vec = Vec::new(); +pub fn get_wxcodes_from_json(json: &Value) -> Vec { + let mut result: Vec = Vec::new(); if let Some(wxcodes) = json.get("wx_codes").and_then(|x| x.as_array()) { for code in wxcodes { if let Some(repr) = code.get("repr").and_then(|x| x.as_str()) { @@ -333,7 +333,7 @@ mod tests { use std::str::FromStr; use super::{get_wxcodes_from_json, WxCode, WxCodeIntensity}; - use crate::metar::{MetarField, WxCodeProximity}; + use crate::metar::{WxCodeProximity, WxField}; #[test] fn test_get_regex() { @@ -358,7 +358,7 @@ mod tests { #[test] fn test_get_wxcodes_one() { - let expected: Vec = vec![MetarField::WxCode( + let expected: Vec = vec![WxField::WxCode( WxCode::Ra, WxCodeIntensity::Light, WxCodeProximity::OnStation,