#![deny(missing_docs)]
mod types;
mod parsers;
use std::fmt;
pub use types::*;
pub use parsers::errors::*;
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Metar<'a> {
pub station: &'a str,
pub time: Time,
pub wind: Wind,
pub visibility: Visibility,
pub clouds: Clouds,
pub cloud_layers: Vec<CloudLayer>,
pub vert_visibility: Option<VertVisibility>,
pub weather: Vec<Weather>,
pub temperature: i32,
pub dewpoint: i32,
pub pressure: Pressure,
pub remarks: Option<&'a str>,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct MetarError<'a> {
pub string: &'a str,
pub start: usize,
pub length: usize,
pub parser_state: ParseState,
pub error: ParserError,
}
impl<'a> MetarError<'a> {
fn new(string: &'a str, start: usize, length: usize,
parser_state: ParseState, error: ParserError) -> Self {
Self {
string, start, length, parser_state, error,
}
}
}
impl<'a> fmt::Display for MetarError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut caret = String::new();
for _ in 0..self.start { caret.push(' '); }
caret.push('^');
for _ in 1..self.length { caret.push('~'); }
write!(f, "{}\n{}\n{}", self.string, caret, self.error)
}
}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Station(e) => write!(f, "{}", e),
Self::ObservationTime(e) => write!(f, "{}", e),
Self::Wind(e) => write!(f, "{}", e),
Self::WindVarying(e) => write!(f, "{}", e),
Self::CloudVisibility(e) => write!(f, "{}", e),
Self::Temperature(e) => write!(f, "{}", e),
Self::Pressure(e) => write!(f, "{}", e),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ParserError {
Station(StationError),
ObservationTime(ObservationTimeError),
Wind(WindError),
WindVarying(WindVaryingError),
CloudVisibility(CloudVisibilityError),
Temperature(TemperatureError),
Pressure(PressureError),
}
fn find_words<'a>(s: &'a str) -> Vec<(usize, usize)> {
let mut words = Vec::new();
let chs: Vec<_> = s.chars().collect();
let mut start_idx = 0;
let mut last_read_ws = false;
let len = chs.len();
for i in 0..len {
if chs[i].is_whitespace() && !last_read_ws {
last_read_ws = true;
words.push((start_idx, i - start_idx));
}
if !chs[i].is_whitespace() {
if last_read_ws {
start_idx = i;
}
last_read_ws = false;
}
}
if !last_read_ws {
words.push((start_idx, len - start_idx));
}
words
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ParseState {
Station,
ObservationTime,
MethodOrWind,
WindVaryingOrCloudsVis,
CloudVisOrTemps,
Pressure,
RemarksOrEnd,
}
impl<'a> Metar<'a> {
pub fn parse(data: &'a str) -> Result<Self, MetarError> {
let mut metar = Metar {
station: "XXXX",
time: Time {
date: 0,
hour: 0,
minute: 0,
},
wind: Wind {
dir: WindDirection::Heading(0),
speed: WindSpeed::Knot(0),
varying: None,
gusting: None,
},
visibility: Visibility::Metres(10000),
clouds: Clouds::SkyClear,
cloud_layers: Vec::new(),
vert_visibility: None,
weather: Vec::new(),
temperature: 0,
dewpoint: 0,
pressure: Pressure::Hectopascals(0),
remarks: None,
};
let mut state = ParseState::Station;
let words = find_words(data);
for word_idx in words {
let word = &data[word_idx.0..word_idx.0 + word_idx.1];
match state {
ParseState::Station => {
let r = parsers::parse_station(word);
if let Ok(data) = r {
metar.station = data;
state = ParseState::ObservationTime;
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::Station(e.2)));
}
},
ParseState::ObservationTime => {
let r = parsers::parse_obs_time(word);
if let Ok(data) = r {
metar.time = data;
state = ParseState::MethodOrWind;
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::ObservationTime(e.2)));
}
},
ParseState::MethodOrWind => {
if word == "AUTO" {
continue;
}
let r = parsers::parse_wind(word);
if let Ok(data) = r {
metar.wind = data;
state = ParseState::WindVaryingOrCloudsVis;
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::Wind(e.2)));
}
},
ParseState::WindVaryingOrCloudsVis => {
let r = parsers::parse_wind_varying(word);
if let Ok(data) = r {
metar.wind.varying = Some(data);
state = ParseState::CloudVisOrTemps;
} else if let Err(e) = r {
if let (_s, _l, WindVaryingError::NotWindVarying) = e {
let r = parsers::parse_cloud_visibility_info(word);
if let Ok(data) = r {
match data {
parsers::CloudVisibilityInfo::CloudLayer(layer) => {
metar.clouds = Clouds::CloudLayers;
metar.cloud_layers.push(layer);
},
parsers::CloudVisibilityInfo::Clouds(clouds) => {
metar.clouds = clouds;
},
parsers::CloudVisibilityInfo::RVR(..) => {
},
parsers::CloudVisibilityInfo::VerticalVisibility(vv) => {
metar.vert_visibility = Some(vv);
metar.clouds = Clouds::Undetermined;
},
parsers::CloudVisibilityInfo::Visibility(visibility) => {
metar.visibility = visibility;
},
parsers::CloudVisibilityInfo::Weather(wx) => {
metar.weather.push(wx);
},
};
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::CloudVisibility(e.2)));
}
state = ParseState::CloudVisOrTemps;
continue;
} else {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::WindVarying(e.2)));
}
}
},
ParseState::CloudVisOrTemps => {
let r = parsers::parse_temperatures(word);
if let Ok(data) = r {
metar.temperature = data.0;
metar.dewpoint = data.1;
state = ParseState::Pressure;
} else if let Err(e) = r {
if let (_s, _l, TemperatureError::NotTemperatureDewpointPair) = e {
let r = parsers::parse_cloud_visibility_info(word);
if let Ok(data) = r {
match data {
parsers::CloudVisibilityInfo::CloudLayer(layer) => {
metar.clouds = Clouds::CloudLayers;
metar.cloud_layers.push(layer);
},
parsers::CloudVisibilityInfo::Clouds(clouds) => {
metar.clouds = clouds;
},
parsers::CloudVisibilityInfo::RVR(..) => {
},
parsers::CloudVisibilityInfo::VerticalVisibility(vv) => {
metar.vert_visibility = Some(vv);
metar.clouds = Clouds::Undetermined;
},
parsers::CloudVisibilityInfo::Visibility(visibility) => {
metar.visibility = visibility;
},
parsers::CloudVisibilityInfo::Weather(wx) => {
metar.weather.push(wx);
},
};
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::CloudVisibility(e.2)));
}
state = ParseState::CloudVisOrTemps;
continue;
} else {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::Temperature(e.2)));
}
}
},
ParseState::Pressure => {
let r = parsers::parse_pressure(word);
if let Ok(data) = r {
metar.pressure = data;
state = ParseState::RemarksOrEnd;
} else if let Err(e) = r {
return Err(MetarError::new(data, word_idx.0 + e.0, e.1,
state, ParserError::Pressure(e.2)));
}
},
ParseState::RemarksOrEnd => {
metar.remarks = Some(&data[word_idx.0..]);
break;
},
}
}
Ok(metar)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_words() {
let r = find_words("The quick brown fox.");
assert_eq!(r, [(0, 3), (4, 5), (10, 5), (16, 4)]);
}
}