Promote engine type and imb parser fields
This commit is contained in:
parent
1bd4158c0c
commit
1aeb5cf663
2 changed files with 199 additions and 0 deletions
|
|
@ -144,6 +144,12 @@ pub struct EngineTypesInspectionReport {
|
|||
pub unmatched_lco_file_count: usize,
|
||||
pub unmatched_cgo_file_count: usize,
|
||||
pub unmatched_cct_file_count: usize,
|
||||
pub car_side_view_resource_counts: BTreeMap<String, usize>,
|
||||
pub car_auxiliary_stem_counts: BTreeMap<String, usize>,
|
||||
pub lco_companion_stem_counts: BTreeMap<String, usize>,
|
||||
pub lco_body_type_label_counts: BTreeMap<String, usize>,
|
||||
pub cgo_scalar_value_counts: BTreeMap<String, usize>,
|
||||
pub cgo_scalar_values_by_content_stem: BTreeMap<String, Vec<String>>,
|
||||
pub locomotive_display_census: EngineTypeLocomotiveDisplayCensusReport,
|
||||
pub families: Vec<EngineTypeFamilyEntry>,
|
||||
}
|
||||
|
|
@ -338,6 +344,33 @@ pub fn inspect_engine_types_dir(
|
|||
.iter()
|
||||
.filter(|family| family.has_matched_locomotive_pair)
|
||||
.count();
|
||||
let car_side_view_resource_counts = count_named_values(
|
||||
family_entries
|
||||
.iter()
|
||||
.filter_map(|family| family.side_view_resource.as_deref()),
|
||||
);
|
||||
let car_auxiliary_stem_counts = count_named_values(
|
||||
family_entries
|
||||
.iter()
|
||||
.filter_map(|family| family.auxiliary_stem.as_deref()),
|
||||
);
|
||||
let lco_companion_stem_counts = count_named_values(
|
||||
family_entries
|
||||
.iter()
|
||||
.filter_map(|family| family.companion_stem.as_deref()),
|
||||
);
|
||||
let lco_body_type_label_counts = count_named_values(
|
||||
family_entries
|
||||
.iter()
|
||||
.filter_map(|family| family.body_type_label.as_deref()),
|
||||
);
|
||||
let cgo_scalar_value_counts = count_owned_values(
|
||||
cgo_reports
|
||||
.values()
|
||||
.filter_map(|report| report.leading_f32.map(|value| format!("{value:.6}"))),
|
||||
);
|
||||
let cgo_scalar_values_by_content_stem =
|
||||
build_cgo_scalar_values_by_content_stem(cgo_reports.values());
|
||||
let locomotive_display_census =
|
||||
build_locomotive_display_census(path, &family_entries, &car_reports)?;
|
||||
|
||||
|
|
@ -381,6 +414,12 @@ pub fn inspect_engine_types_dir(
|
|||
entry.cct_file.is_some() && !(entry.car_file.is_some() || entry.lco_file.is_some())
|
||||
})
|
||||
.count(),
|
||||
car_side_view_resource_counts,
|
||||
car_auxiliary_stem_counts,
|
||||
lco_companion_stem_counts,
|
||||
lco_body_type_label_counts,
|
||||
cgo_scalar_value_counts,
|
||||
cgo_scalar_values_by_content_stem,
|
||||
locomotive_display_census,
|
||||
families: family_entries,
|
||||
})
|
||||
|
|
@ -604,6 +643,45 @@ fn decode_windows_1252_byte(byte: u8) -> char {
|
|||
}
|
||||
}
|
||||
|
||||
fn count_named_values<'a>(values: impl Iterator<Item = &'a str>) -> BTreeMap<String, usize> {
|
||||
let mut counts = BTreeMap::new();
|
||||
for value in values {
|
||||
*counts.entry(value.to_string()).or_insert(0) += 1;
|
||||
}
|
||||
counts
|
||||
}
|
||||
|
||||
fn count_owned_values(values: impl Iterator<Item = String>) -> BTreeMap<String, usize> {
|
||||
let mut counts = BTreeMap::new();
|
||||
for value in values {
|
||||
*counts.entry(value).or_insert(0) += 1;
|
||||
}
|
||||
counts
|
||||
}
|
||||
|
||||
fn build_cgo_scalar_values_by_content_stem<'a>(
|
||||
reports: impl Iterator<Item = &'a EngineTypeCgoInspectionReport>,
|
||||
) -> BTreeMap<String, Vec<String>> {
|
||||
let mut grouped = BTreeMap::<String, Vec<String>>::new();
|
||||
for report in reports {
|
||||
let Some(content_stem) = report.content_stem.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let Some(leading_f32) = report.leading_f32 else {
|
||||
continue;
|
||||
};
|
||||
grouped
|
||||
.entry(content_stem.clone())
|
||||
.or_default()
|
||||
.push(format!("{leading_f32:.6}"));
|
||||
}
|
||||
for values in grouped.values_mut() {
|
||||
values.sort();
|
||||
values.dedup();
|
||||
}
|
||||
grouped
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -729,6 +807,52 @@ mod tests {
|
|||
assert_eq!(entry.cct_identifier.as_deref(), Some("GP7"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counts_directory_level_slot_values() {
|
||||
let counts = count_named_values(
|
||||
["CarSideView_1.imb", "CarSideView_1.imb", "VL80T"].into_iter(),
|
||||
);
|
||||
assert_eq!(counts.get("CarSideView_1.imb"), Some(&2));
|
||||
assert_eq!(counts.get("VL80T"), Some(&1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groups_cgo_scalar_values_by_content_stem() {
|
||||
let grouped = build_cgo_scalar_values_by_content_stem(
|
||||
[
|
||||
EngineTypeCgoInspectionReport {
|
||||
file_size: 25,
|
||||
leading_u32: Some(0),
|
||||
leading_u32_hex: Some("0x00000000".to_string()),
|
||||
leading_f32: Some(10.0),
|
||||
content_stem: Some("Box".to_string()),
|
||||
notes: Vec::new(),
|
||||
},
|
||||
EngineTypeCgoInspectionReport {
|
||||
file_size: 25,
|
||||
leading_u32: Some(0),
|
||||
leading_u32_hex: Some("0x00000000".to_string()),
|
||||
leading_f32: Some(20.0),
|
||||
content_stem: Some("Box".to_string()),
|
||||
notes: Vec::new(),
|
||||
},
|
||||
EngineTypeCgoInspectionReport {
|
||||
file_size: 25,
|
||||
leading_u32: Some(0),
|
||||
leading_u32_hex: Some("0x00000000".to_string()),
|
||||
leading_f32: Some(20.0),
|
||||
content_stem: Some("Box".to_string()),
|
||||
notes: Vec::new(),
|
||||
},
|
||||
]
|
||||
.iter(),
|
||||
);
|
||||
assert_eq!(
|
||||
grouped.get("Box"),
|
||||
Some(&vec!["10.000000".to_string(), "20.000000".to_string()])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_locomotive_display_census() {
|
||||
let mut car_reports = BTreeMap::new();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ pub struct ImbInspectionReport {
|
|||
pub entry_count: usize,
|
||||
pub blank_line_count: usize,
|
||||
pub malformed_line_count: usize,
|
||||
pub tga_name: Option<String>,
|
||||
pub texture_width: Option<i64>,
|
||||
pub texture_height: Option<i64>,
|
||||
pub target_screen_width: Option<i64>,
|
||||
pub target_screen_height: Option<i64>,
|
||||
pub scaleable: Option<bool>,
|
||||
pub max_percent_of_interface_vram: Option<f64>,
|
||||
pub image_rect: Option<[i64; 4]>,
|
||||
pub notes: Vec<String>,
|
||||
pub entries: Vec<ImbInspectionEntry>,
|
||||
pub malformed_lines: Vec<String>,
|
||||
|
|
@ -64,14 +72,33 @@ pub fn inspect_imb_bytes(bytes: &[u8]) -> Result<ImbInspectionReport, Box<dyn st
|
|||
});
|
||||
}
|
||||
|
||||
let tga_name = find_scalar_string(&entries, "TGAName");
|
||||
let texture_width = find_scalar_i64(&entries, "TGAWidth");
|
||||
let texture_height = find_scalar_i64(&entries, "TGAHeight");
|
||||
let target_screen_width = find_scalar_i64(&entries, "TGATargetScreenWidth");
|
||||
let target_screen_height = find_scalar_i64(&entries, "TGATargetScreenHeight");
|
||||
let scaleable = find_scalar_i64(&entries, "Scaleable").map(|value| value != 0);
|
||||
let max_percent_of_interface_vram =
|
||||
find_scalar_f64(&entries, "MaxPercentOfInterfaceVRAM");
|
||||
let image_rect = find_i64_quad(&entries, "ImageWH");
|
||||
|
||||
Ok(ImbInspectionReport {
|
||||
line_count: text.lines().count(),
|
||||
entry_count: entries.len(),
|
||||
blank_line_count,
|
||||
malformed_line_count: malformed_lines.len(),
|
||||
tga_name,
|
||||
texture_width,
|
||||
texture_height,
|
||||
target_screen_width,
|
||||
target_screen_height,
|
||||
scaleable,
|
||||
max_percent_of_interface_vram,
|
||||
image_rect,
|
||||
notes: vec![
|
||||
"The current .imb parser preserves one whitespace-delimited key plus the remaining token list per line.".to_string(),
|
||||
"Integer and float projections are only populated when every token in the value lane parses cleanly.".to_string(),
|
||||
"Known profile-style keys such as `TGAName`, `TGAWidth`, `TGAHeight`, `TGATargetScreenWidth`, `TGATargetScreenHeight`, `Scaleable`, `MaxPercentOfInterfaceVRAM`, and `ImageWH` are promoted into typed report fields when present.".to_string(),
|
||||
],
|
||||
entries,
|
||||
malformed_lines,
|
||||
|
|
@ -92,6 +119,37 @@ fn parse_f64_tokens(tokens: &[String]) -> Option<Vec<f64>> {
|
|||
.collect::<Option<Vec<_>>>()
|
||||
}
|
||||
|
||||
fn find_scalar_string(entries: &[ImbInspectionEntry], key: &str) -> Option<String> {
|
||||
entries
|
||||
.iter()
|
||||
.find(|entry| entry.key == key && entry.tokens.len() == 1)
|
||||
.map(|entry| entry.tokens[0].clone())
|
||||
}
|
||||
|
||||
fn find_scalar_i64(entries: &[ImbInspectionEntry], key: &str) -> Option<i64> {
|
||||
entries
|
||||
.iter()
|
||||
.find(|entry| entry.key == key)
|
||||
.and_then(|entry| entry.integer_values.as_ref())
|
||||
.and_then(|values| (values.len() == 1).then_some(values[0]))
|
||||
}
|
||||
|
||||
fn find_scalar_f64(entries: &[ImbInspectionEntry], key: &str) -> Option<f64> {
|
||||
entries
|
||||
.iter()
|
||||
.find(|entry| entry.key == key)
|
||||
.and_then(|entry| entry.float_values.as_ref())
|
||||
.and_then(|values| (values.len() == 1).then_some(values[0]))
|
||||
}
|
||||
|
||||
fn find_i64_quad(entries: &[ImbInspectionEntry], key: &str) -> Option<[i64; 4]> {
|
||||
let values = entries
|
||||
.iter()
|
||||
.find(|entry| entry.key == key)
|
||||
.and_then(|entry| entry.integer_values.as_ref())?;
|
||||
(values.len() == 4).then_some([values[0], values[1], values[2], values[3]])
|
||||
}
|
||||
|
||||
fn decode_windows_1252(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
|
|
@ -145,5 +203,22 @@ mod tests {
|
|||
assert_eq!(report.entries[0].key, "TGAName");
|
||||
assert_eq!(report.entries[1].integer_values, Some(vec![256]));
|
||||
assert_eq!(report.entries[2].integer_values, Some(vec![0, 0, 138, 32]));
|
||||
assert_eq!(report.tga_name.as_deref(), Some("ICE_Profile"));
|
||||
assert_eq!(report.texture_width, Some(256));
|
||||
assert_eq!(report.image_rect, Some([0, 0, 138, 32]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn promotes_known_profile_keys_into_typed_fields() {
|
||||
let report = inspect_imb_bytes(
|
||||
b"TGAName ICE_Profile\nTGAWidth 256\nTGAHeight 32\nTGATargetScreenWidth 1600\nTGATargetScreenHeight 1200\nScaleable 1\nMaxPercentOfInterfaceVRAM 0.06\nImageWH 0 0 138 32\n",
|
||||
)
|
||||
.expect("imb should parse");
|
||||
|
||||
assert_eq!(report.texture_height, Some(32));
|
||||
assert_eq!(report.target_screen_width, Some(1600));
|
||||
assert_eq!(report.target_screen_height, Some(1200));
|
||||
assert_eq!(report.scaleable, Some(true));
|
||||
assert_eq!(report.max_percent_of_interface_vram, Some(0.06));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue