use crate::{
parsers::{any_whitespace, some_whitespace, temperature},
traits::Parsable,
CloudLayer, CloudType, Clouds, ColourCode, CompassDirection, Data, Kind, MetarError, Pressure,
RunwayCondition, RunwayVisualRange, SeaCondition, Time, Trend, VerticalVisibility, Visibility,
Weather, WeatherCondition, Wind, WindDirection, WindSpeed, WindshearWarnings,
};
use chumsky::prelude::*;
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Metar {
pub station: String,
pub time: Time,
pub kind: Kind,
pub wind: Wind,
pub visibility: Data<Visibility>,
pub reduced_directional_visibility: Vec<(Option<CompassDirection>, Data<Visibility>)>,
pub rvr: Vec<RunwayVisualRange>,
pub clouds: Clouds,
pub cloud_layers: Vec<CloudLayer>,
pub vert_visibility: Option<VerticalVisibility>,
pub weather: Data<Vec<Weather>>,
pub temperature: Data<i32>,
pub dewpoint: Data<i32>,
pub pressure: Pressure,
pub colour_code: Option<ColourCode>,
pub recent_weather: Vec<Data<Vec<WeatherCondition>>>,
pub windshear_warnings: Option<WindshearWarnings>,
pub sea_condition: Option<SeaCondition>,
pub runway_conditions: Vec<RunwayCondition>,
pub trends: Vec<Trend>,
pub clouds_in_vicinity: Vec<(Vec<CompassDirection>, Data<CloudType>)>,
pub remarks: Option<String>,
}
impl Parsable for Metar {
#[allow(clippy::too_many_lines)]
fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<MetarError<'src>>> {
fn method<'src>() -> impl Parser<'src, &'src str, Kind, extra::Err<crate::MetarError<'src>>>
{
choice((
just("AUTO")
.map(|_| Kind::Automatic)
.then_ignore(some_whitespace()),
just("COR")
.map(|_| Kind::Correction)
.then_ignore(some_whitespace()),
just("CCA")
.map(|_| Kind::Correction)
.then_ignore(some_whitespace()),
empty().map(|()| Kind::Normal),
))
}
let station = regex("[A-Z0-9]{4}");
group((
just("METAR")
.then_ignore(some_whitespace())
.map(|_| ())
.or(empty()),
method(),
station.then_ignore(some_whitespace()),
Time::parser().then_ignore(some_whitespace()),
method(),
choice((
Wind::parser(),
empty().map(|()| Wind::Present {
dir: WindDirection::Heading(Data::Unknown),
speed: WindSpeed::Knots {
speed: Data::Unknown,
gusting: None,
},
varying: None,
}),
)),
choice((
Data::parser_inline(4, Visibility::parser()).then_ignore(some_whitespace()),
empty().map(|()| Data::Unknown),
)),
<(Option<CompassDirection>, Data<Visibility>) as Parsable>::parser()
.separated_by(some_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
RunwayVisualRange::parser()
.separated_by(some_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
choice((
just("SKC")
.map(|_| (Data::Known(vec![]), None, Clouds::NoCloudDetected, vec![]))
.then_ignore(some_whitespace()),
just("CLR")
.map(|_| (Data::Known(vec![]), None, Clouds::NoCloudDetected, vec![]))
.then_ignore(some_whitespace()),
group((
Data::parser_inline(
2,
Weather::parser()
.separated_by(some_whitespace())
.collect::<Vec<_>>(),
)
.then_ignore(some_whitespace())
.or(empty().map(|()| Data::Known(vec![]))),
VerticalVisibility::parser()
.map(Some)
.then_ignore(some_whitespace())
.or(empty().map(|()| None)),
Clouds::parser(),
CloudLayer::parser()
.separated_by(some_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
))
.map(|(wx, vvis, clouds, layers)| (wx, vvis, clouds, layers)),
empty().map(|()| (Data::Known(vec![]), None, Clouds::NoCloudDetected, vec![])),
)),
group((
Data::parser_inline(2, temperature()),
just("/"),
Data::parser_inline(2, temperature()).or(empty().map(|()| Data::Unknown)),
))
.map(|(temp, _, dewp)| (temp, dewp))
.then_ignore(some_whitespace())
.or(empty().map(|()| (Data::Unknown, Data::Unknown))),
Pressure::parser()
.then_ignore(some_whitespace())
.or(empty().map(|()| Pressure::Hectopascals(Data::Unknown))),
ColourCode::parser()
.map(Some)
.then_ignore(some_whitespace())
.or(empty().map(|()| None)),
choice((
just("RE")
.then(Data::parser_inline(
2,
WeatherCondition::parser()
.repeated()
.at_least(1)
.collect::<Vec<_>>(),
))
.map(|(_, wx)| wx)
.separated_by(some_whitespace())
.collect::<Vec<_>>()
.then_ignore(some_whitespace()),
empty().map(|()| vec![]),
)),
WindshearWarnings::parser()
.map(Some)
.then_ignore(some_whitespace())
.or(empty().map(|()| None)),
RunwayCondition::parser()
.separated_by(some_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
SeaCondition::parser()
.map(Some)
.then_ignore(some_whitespace())
.or(empty().map(|()| None)),
Trend::parser()
.separated_by(any_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
<(Vec<CompassDirection>, Data<CloudType>) as Parsable>::parser()
.separated_by(some_whitespace())
.allow_trailing()
.collect::<Vec<_>>(),
just("RMK")
.then(none_of("=").repeated().collect::<String>())
.map(|(_, s)| Some(s.trim().to_string()))
.or(empty().map(|()| None)),
any_whitespace(),
choice((just("=").map(|_| ()), empty().map(|()| ()))),
))
.map(
|(
(),
early_kind,
station,
time,
kind,
wind,
visibility,
reduced_directional_visibility,
rvr,
(weather, vert_visibility, clouds, cloud_layers),
(temperature, dewpoint),
pressure,
colour_code,
recent_weather,
windshear_warnings,
runway_conditions,
sea_condition,
trends,
clouds_in_vicinity,
remarks,
(),
(),
)| {
Metar {
station: station.to_string(),
time,
kind: if early_kind == Kind::Normal {
kind
} else {
early_kind
},
wind,
visibility,
reduced_directional_visibility,
rvr,
weather,
vert_visibility,
clouds,
cloud_layers,
temperature,
dewpoint,
pressure,
colour_code,
recent_weather,
windshear_warnings,
sea_condition,
runway_conditions,
trends,
clouds_in_vicinity,
remarks,
}
},
)
}
}
impl Metar {
pub fn parse(data: &str) -> Result<Self, Vec<MetarError<'_>>> {
<Metar as Parsable>::parse(data).map_err(|v| {
v.into_iter()
.map(|mut e| {
e.string = data;
e
})
.collect::<Vec<_>>()
})
}
}