use std::path::Path;
use nom::{
IResult, Parser,
branch::alt,
bytes::tag,
character::complete::multispace1,
combinator::map,
multi::separated_list0,
sequence::{preceded, terminated},
};
use rustc_hash::FxHashMap;
use crate::{
error::{HResult, HrdfError},
models::{Model, StopConnection},
parsing::{
error::{PResult, ParsingError},
helpers::{
i16_from_n_digits_parser, i32_from_n_digits_parser, read_lines, string_till_eol_parser,
},
},
storage::ResourceStorage,
utils::AutoIncrement,
};
enum StopConnectionLine {
Aline(String),
MetaStopLine {
stop_id_1: i32,
stop_id_2: i32,
duration: i16,
},
StopGroups {
#[allow(unused)]
group_id: i32,
#[allow(unused)]
stop_group: Vec<i32>,
},
}
fn a_line_combinator(input: &str) -> IResult<&str, StopConnectionLine> {
map(preceded(tag("*A"), string_till_eol_parser), |s| {
StopConnectionLine::Aline(s)
})
.parse(input)
}
fn meta_stop_line_combinator(input: &str) -> IResult<&str, StopConnectionLine> {
map(
(
i32_from_n_digits_parser(7),
preceded(multispace1, i32_from_n_digits_parser(7)),
preceded(multispace1, i16_from_n_digits_parser(3)),
),
|(stop_id_1, stop_id_2, duration)| StopConnectionLine::MetaStopLine {
stop_id_1,
stop_id_2,
duration,
},
)
.parse(input)
}
fn stop_groups_combinator(input: &str) -> IResult<&str, StopConnectionLine> {
map(
(
terminated(i32_from_n_digits_parser(7), tag(":")),
separated_list0(multispace1, i32_from_n_digits_parser(7)),
),
|(group_id, stop_group)| StopConnectionLine::StopGroups {
group_id,
stop_group,
},
)
.parse(input)
}
fn parse_line(
line: &str,
data: &mut FxHashMap<i32, StopConnection>,
attributes_pk_type_converter: &FxHashMap<String, i32>,
auto_increment: &AutoIncrement,
) -> PResult<()> {
let (_, stop_connection_line) = alt((
a_line_combinator,
stop_groups_combinator,
meta_stop_line_combinator,
))
.parse(line)?;
match stop_connection_line {
StopConnectionLine::Aline(s) => {
let attribute_id = *attributes_pk_type_converter
.get(&s)
.ok_or_else(|| ParsingError::UnknownId(format!("Legacy attribute ID: {s}")))?;
let current_instance = data.get_mut(&auto_increment.get()).ok_or_else(|| {
ParsingError::UnknownId(format!("Connection Id {}.", auto_increment.get()))
})?;
current_instance.set_attribute(attribute_id);
}
StopConnectionLine::MetaStopLine {
stop_id_1,
stop_id_2,
duration,
} => {
let stop_connection =
StopConnection::new(auto_increment.next(), stop_id_1, stop_id_2, duration);
data.insert(stop_connection.id(), stop_connection);
}
StopConnectionLine::StopGroups {
group_id: _,
stop_group: _,
} => {
}
}
Ok(())
}
pub fn parse(
path: &Path,
attributes_pk_type_converter: &FxHashMap<String, i32>,
) -> HResult<ResourceStorage<StopConnection>> {
log::info!("Parsing METABHF...");
let auto_increment = AutoIncrement::new();
let mut stations = FxHashMap::default();
let file = path.join("METABHF");
let station_lines = read_lines(&file, 0)?;
station_lines
.into_iter()
.enumerate()
.filter(|(_, line)| !line.trim().is_empty())
.try_for_each(|(line_number, line)| {
parse_line(
&line,
&mut stations,
attributes_pk_type_converter,
&auto_increment,
)
.map_err(|e| HrdfError::Parsing {
error: e,
file: String::from(file.to_string_lossy()),
line,
line_number,
})
})?;
Ok(ResourceStorage::new(stations))
}
#[cfg(test)]
mod tests {
use crate::parsing::tests::get_json_values;
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_a_line_combinator_basic() {
let input = "*A Y";
let result = a_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::Aline(s) => {
assert_eq!(s, "Y");
}
_ => panic!("Expected Aline variant"),
}
}
#[test]
fn test_a_line_combinator_with_spaces() {
let input = "*A Y ";
let result = a_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::Aline(s) => {
assert_eq!(s, "Y");
}
_ => panic!("Expected Aline variant"),
}
}
#[test]
fn test_a_line_combinator_multi_char() {
let input = "*A ABC";
let result = a_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::Aline(s) => {
assert_eq!(s, "ABC");
}
_ => panic!("Expected Aline variant"),
}
}
#[test]
fn test_meta_stop_line_combinator_basic() {
let input = "8500010 8500146 009";
let result = meta_stop_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::MetaStopLine {
stop_id_1,
stop_id_2,
duration,
} => {
assert_eq!(stop_id_1, 8500010);
assert_eq!(stop_id_2, 8500146);
assert_eq!(duration, 9);
}
_ => panic!("Expected MetaStopLine variant"),
}
}
#[test]
fn test_meta_stop_line_combinator_different_duration() {
let input = "8500010 8578143 006";
let result = meta_stop_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::MetaStopLine {
stop_id_1,
stop_id_2,
duration,
} => {
assert_eq!(stop_id_1, 8500010);
assert_eq!(stop_id_2, 8578143);
assert_eq!(duration, 6);
}
_ => panic!("Expected MetaStopLine variant"),
}
}
#[test]
fn test_meta_stop_line_combinator_with_extra_spaces() {
let input = "8500010 8500146 009";
let result = meta_stop_line_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::MetaStopLine {
stop_id_1,
stop_id_2,
duration,
} => {
assert_eq!(stop_id_1, 8500010);
assert_eq!(stop_id_2, 8500146);
assert_eq!(duration, 9);
}
_ => panic!("Expected MetaStopLine variant"),
}
}
#[test]
fn test_stop_groups_combinator_single_group() {
let input = "8389120: 8302430 8389120";
let result = stop_groups_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::StopGroups {
group_id,
stop_group,
} => {
assert_eq!(group_id, 8389120);
assert!(!stop_group.is_empty());
}
_ => panic!("Expected StopGroups variant"),
}
}
#[test]
fn test_stop_groups_combinator_multiple_stops() {
let input = "8500010: 8500010 8500146 8578143";
let result = stop_groups_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::StopGroups {
group_id,
stop_group,
} => {
assert_eq!(group_id, 8500010);
assert!(!stop_group.is_empty());
}
_ => panic!("Expected StopGroups variant"),
}
}
#[test]
fn test_stop_groups_combinator_two_stops() {
let input = "8500016: 8500016 8592322";
let result = stop_groups_combinator(input);
assert!(result.is_ok());
let (_, line) = result.unwrap();
match line {
StopConnectionLine::StopGroups {
group_id,
stop_group,
} => {
assert_eq!(group_id, 8500016);
assert!(!stop_group.is_empty());
}
_ => panic!("Expected StopGroups variant"),
}
}
#[test]
fn test_parse_line_meta_stop_creates_connection() {
let mut data = FxHashMap::default();
let attributes_pk_type_converter = FxHashMap::default();
let auto_increment = AutoIncrement::new();
parse_line(
"8500010 8500146 009",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
assert_eq!(data.len(), 1);
let connection = data.get(&1).unwrap();
assert_eq!(connection.stop_id_1(), 8500010);
assert_eq!(connection.stop_id_2(), 8500146);
assert_eq!(connection.duration(), 9);
}
#[test]
#[should_panic]
fn test_parse_line_a_line_requires_existing_connection() {
let mut data = FxHashMap::default();
let mut attributes_pk_type_converter = FxHashMap::default();
attributes_pk_type_converter.insert("Y".to_string(), 42);
let auto_increment = AutoIncrement::new();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
}
#[test]
#[should_panic]
fn test_parse_line_a_line_requires_valid_attribute() {
let mut data = FxHashMap::default();
let attributes_pk_type_converter = FxHashMap::default(); let auto_increment = AutoIncrement::new();
parse_line(
"8500010 8500146 009",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
}
#[test]
fn test_parse_line_complete_sequence() {
let mut data = FxHashMap::default();
let mut attributes_pk_type_converter = FxHashMap::default();
attributes_pk_type_converter.insert("Y".to_string(), 100);
let auto_increment = AutoIncrement::new();
parse_line(
"8500010 8500146 009",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
assert_eq!(data.len(), 1);
let connection = data.get(&1).unwrap();
assert_eq!(connection.stop_id_1(), 8500010);
assert_eq!(connection.stop_id_2(), 8500146);
assert_eq!(connection.duration(), 9);
}
#[test]
fn test_parse_line_multiple_connections() {
let mut data = FxHashMap::default();
let mut attributes_pk_type_converter = FxHashMap::default();
attributes_pk_type_converter.insert("Y".to_string(), 100);
let auto_increment = AutoIncrement::new();
parse_line(
"8500010 8500146 009",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"8500010 8578143 006",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
assert_eq!(data.len(), 2);
let conn1 = data.get(&1).unwrap();
assert_eq!(conn1.stop_id_1(), 8500010);
assert_eq!(conn1.stop_id_2(), 8500146);
assert_eq!(conn1.duration(), 9);
let conn2 = data.get(&2).unwrap();
assert_eq!(conn2.stop_id_1(), 8500010);
assert_eq!(conn2.stop_id_2(), 8578143);
assert_eq!(conn2.duration(), 6);
}
#[test]
fn test_parse_line_stop_groups_ignored() {
let mut data = FxHashMap::default();
let attributes_pk_type_converter = FxHashMap::default();
let auto_increment = AutoIncrement::new();
let result = parse_line(
"8500010: 8500010 8500146 8578143",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
);
assert!(result.is_ok());
assert_eq!(data.len(), 0);
}
#[test]
fn test_parse_line_realistic_scenario() {
let mut data = FxHashMap::default();
let mut attributes_pk_type_converter = FxHashMap::default();
attributes_pk_type_converter.insert("Y".to_string(), 50); let auto_increment = AutoIncrement::new();
assert!(
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.is_err()
);
parse_line(
"8500010 8500146 009",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"*A Y",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"8500010 8578143 006",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
parse_line(
"8500010: 8500010 8500146 8578143",
&mut data,
&attributes_pk_type_converter,
&auto_increment,
)
.unwrap();
assert_eq!(data.len(), 2);
let stop_connection = data.get(&1).unwrap();
let reference = r#"
{
"id":1,
"stop_id_1":8500010,
"stop_id_2":8500146,
"duration":9,
"attribute":50
}"#;
let (stop_connection, reference) = get_json_values(stop_connection, reference).unwrap();
assert_eq!(stop_connection, reference);
let stop_connection = data.get(&2).unwrap();
let reference = r#"
{
"id":2,
"stop_id_1":8500010,
"stop_id_2":8578143,
"duration":6,
"attribute":0
}"#;
let (stop_connection, reference) = get_json_values(stop_connection, reference).unwrap();
assert_eq!(stop_connection, reference);
}
}