This commit is contained in:
Tsuki 2025-12-24 02:07:44 +08:00
parent 3f2a2bcefc
commit b8b31c4e69
65 changed files with 601 additions and 611 deletions

View File

@ -1,7 +1,11 @@
use crate::{FXY, TableEntry, TableEntryLoader};
use rkyv::Archive;
use super::EntryLoader;
use crate::{
FXY,
tables::{BTable, BTableEntry},
};
pub struct BTableCsvLoader;
#[derive(Default)]
pub struct BTableLoader;
#[derive(Debug)]
pub struct RawBTableEntry {
@ -10,145 +14,62 @@ pub struct RawBTableEntry {
pub y: u16,
}
// Helper function to deserialize empty strings as None
fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = serde::Deserialize::deserialize(deserializer)?;
if s.is_empty() { Ok(None) } else { Ok(Some(s)) }
}
impl EntryLoader for BTableLoader {
type Output = BTableEntry;
type TableType = BTable;
// Helper function to deserialize empty strings as None for u32
fn deserialize_optional_u32<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = serde::Deserialize::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
s.parse::<u32>().map(Some).map_err(serde::de::Error::custom)
}
}
fn process_entry(&mut self, raw: csv::StringRecord) -> anyhow::Result<Option<Self::Output>> {
let f = raw
.get(0)
.ok_or_else(|| anyhow::anyhow!("Missing F field"))?
.parse()?;
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct BTableEntry {
fxy: FXY,
class_name_en: String,
element_name_en: String,
bufr_unit: String,
bufr_scale: i32,
bufr_reference_value: i32,
bufr_datawidth_bits: u32,
note_en: Option<String>,
note_ids: Option<String>,
status: String,
}
let x = raw
.get(1)
.ok_or_else(|| anyhow::anyhow!("Missing X field"))?
.parse()?;
impl BTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
let y = raw
.get(2)
.ok_or_else(|| anyhow::anyhow!("Missing Y field"))?
.parse()?;
pub fn class_name_en(&self) -> &str {
&self.class_name_en
}
let fxy = FXY::new(f, x, y);
pub fn element_name_en(&self) -> &str {
&self.element_name_en
}
pub fn bufr_unit(&self) -> &str {
&self.bufr_unit
}
pub fn bufr_scale(&self) -> i32 {
self.bufr_scale
}
pub fn bufr_reference_value(&self) -> i32 {
self.bufr_reference_value
}
pub fn bufr_datawidth_bits(&self) -> u32 {
self.bufr_datawidth_bits
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> Option<&str> {
self.note_ids.as_deref()
}
pub fn status(&self) -> &str {
&self.status
}
}
impl std::fmt::Display for BTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let element_name = if self.element_name_en.len() > 40 {
format!("{}...", &self.element_name_en[..37])
} else {
self.element_name_en.clone()
};
let unit = if self.bufr_unit.len() > 15 {
format!("{}...", &self.bufr_unit[..12])
} else {
self.bufr_unit.clone()
};
write!(
f,
"{:02}{:02}{:03} | {:<40} | {:<15} | {:>5} | {:>8} | {:>8} | {}",
self.fxy.f,
self.fxy.x,
self.fxy.y,
element_name,
unit,
self.bufr_scale,
self.bufr_reference_value,
self.bufr_datawidth_bits,
self.status
)
}
}
impl TableEntryLoader for BTableCsvLoader {
type RawEntry = RawBTableEntry;
type TableEntry = BTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::B;
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::TableEntry>> {
// Parse FXY string (e.g., "001001") to u32
let fxy = FXY::from_str(&raw.fxy)?;
let class_name_en = raw
.get(3)
.ok_or_else(|| anyhow::anyhow!("Missing Class Name EN"))?
.to_string();
let bufr_unit = raw
.get(4)
.ok_or_else(|| anyhow::anyhow!("Missing BUFR Unit"))?
.to_string();
let bufr_scale = raw
.get(5)
.ok_or_else(|| anyhow::anyhow!("Missing Scaling Field"))?
.parse()?;
let bufr_reference_value = raw
.get(6)
.ok_or_else(|| anyhow::anyhow!("Missing Reference Value Field"))?
.parse()?;
let bufr_datawidth_bits = raw
.get(7)
.ok_or_else(|| anyhow::anyhow!("Missing Datawidth Bits Field"))?
.parse()?;
let entry = BTableEntry {
fxy,
class_name_en: raw.class_name_en,
element_name_en: raw.element_name_en,
bufr_unit: raw.bufr_unit,
bufr_scale: raw.bufr_scale,
bufr_reference_value: raw.bufr_reference_value,
bufr_datawidth_bits: raw.bufr_datawidth_bits,
note_en: raw.note_en,
note_ids: raw.note_ids,
status: raw.status,
class_name_en: class_name_en.clone(),
element_name_en: class_name_en,
bufr_unit,
bufr_scale,
bufr_reference_value,
bufr_datawidth_bits,
note_en: None,
note_ids: None,
status: None,
};
Ok(Some(entry))
}
}
impl TableEntry for BTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}

View File

@ -1,34 +1,67 @@
use super::TableEntryLoader;
use super::EntryLoader;
use crate::{
FXY,
tables::{DTable, DTableEntry},
};
use csv::StringRecord;
use rkyv::Archive;
#[derive(Debug, Clone, Default)]
pub struct DTableCsvLoader {
#[derive(Default)]
pub struct FRDTableLoader {
current_chain: Option<DTableEntry>,
}
impl TableEntryLoader for DTableCsvLoader {
type TableEntry = DTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::D;
impl EntryLoader for FRDTableLoader {
type Output = DTableEntry;
type TableType = DTable;
fn process_entry(&mut self, raw: &StringRecord) -> anyhow::Result<Option<Self::TableEntry>> {
fn process_entry(&mut self, raw: StringRecord) -> anyhow::Result<Option<Self::Output>> {
let f = raw
.get(0)
.ok_or_else(|| anyhow::anyhow!("Missing F field"))?
.parse::<u16>()?;
let x = raw
.get(1)
.ok_or_else(|| anyhow::anyhow!("Missing X field"))?
.parse::<u16>()?;
let y = raw
.get(2)
.ok_or_else(|| anyhow::anyhow!("Missing Y field"))?
.parse::<u16>()?;
let f1 = raw
.get(3)
.ok_or_else(|| anyhow::anyhow!("Missing F1 field"))?;
let x1 = raw
.get(4)
.ok_or_else(|| anyhow::anyhow!("Missing X1 field"))?;
let y1 = raw
.get(5)
.ok_or_else(|| anyhow::anyhow!("Missing Y1 field"))?;
let fxy1 = FXY::new(f1.parse()?, x1.parse()?, y1.parse()?);
let fxy = FXY::new(f, x, y);
// Process the raw entry as needed
if self.current_chain.is_none() {
let entry = DTableEntry {
fxy: FXY::from_str(&raw.fxy1)?,
fxy_chain: vec![FXY::from_str(&raw.fxy2)?],
category: raw.category,
category_of_sequences_en: raw.category_of_sequences_en,
title_en: raw.title_en,
subtitle_en: raw.subtitle_en,
note_en: raw.note_en,
note_ids: raw.note_ids,
status: raw.status,
fxy,
fxy_chain: vec![fxy1],
category: None,
category_of_sequences_en: None,
title_en: None,
subtitle_en: None,
note_en: None,
note_ids: None,
status: None,
};
self.current_chain = Some(entry);
return Ok(None);
} else {
let fxy = FXY::from_str(&raw.fxy1)?;
let fxy = FXY::new(f, x, y);
if self.current_chain.as_ref().unwrap().fxy != fxy {
// First take out the old completed chain
let finished = self.current_chain.take();
@ -36,116 +69,26 @@ impl TableEntryLoader for DTableCsvLoader {
// Then create and save the new chain
let entry = DTableEntry {
fxy,
fxy_chain: vec![FXY::from_str(&raw.fxy2)?],
category: raw.category,
category_of_sequences_en: raw.category_of_sequences_en,
title_en: raw.title_en,
subtitle_en: raw.subtitle_en,
note_en: raw.note_en,
note_ids: raw.note_ids,
status: raw.status,
fxy_chain: vec![fxy1],
category: None,
category_of_sequences_en: None,
title_en: None,
subtitle_en: None,
note_en: None,
note_ids: None,
status: None,
};
self.current_chain = Some(entry);
// Return the old completed chain
return Ok(finished);
} else {
self.current_chain
.as_mut()
.unwrap()
.fxy_chain
.push(FXY::from_str(&raw.fxy2)?);
self.current_chain.as_mut().unwrap().fxy_chain.push(fxy1);
return Ok(None);
}
}
}
fn finish(&mut self) -> anyhow::Result<Option<Self::TableEntry>> {
fn finish(&mut self) -> anyhow::Result<Option<Self::Output>> {
Ok(self.current_chain.take())
}
}
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct DTableEntry {
fxy: FXY,
fxy_chain: Vec<FXY>,
category: String,
category_of_sequences_en: String,
title_en: Option<String>,
subtitle_en: Option<String>,
note_en: Option<String>,
note_ids: String,
status: String,
}
impl DTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
pub fn fxy_chain(&self) -> &[FXY] {
&self.fxy_chain
}
pub fn category(&self) -> &str {
&self.category
}
pub fn category_of_sequences_en(&self) -> &str {
&self.category_of_sequences_en
}
pub fn title_en(&self) -> Option<&str> {
self.title_en.as_deref()
}
pub fn subtitle_en(&self) -> Option<&str> {
self.subtitle_en.as_deref()
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> &str {
&self.note_ids
}
pub fn status(&self) -> &str {
&self.status
}
}
impl std::fmt::Display for DTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fxy_chain_str: String = self
.fxy_chain
.iter()
.map(|fxy| format!("{:02}{:02}{:03}", fxy.f, fxy.x, fxy.y))
.collect::<Vec<_>>()
.join(", ");
let title = self.title_en.as_deref().unwrap_or("N/A");
let truncated_title = if title.len() > 50 {
format!("{}...", &title[..47])
} else {
title.to_string()
};
write!(
f,
"{:02}{:02}{:03} | {:<50} | {:<12} | [{}]",
self.fxy.f, self.fxy.x, self.fxy.y, truncated_title, self.status, fxy_chain_str
)
}
}
impl TableEntry for DTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}

View File

@ -1,24 +1,34 @@
use crate::{
TableConverter,
tables::{TableEntry, TableEntryFull, TableTypeTrait},
};
use csv::{ReaderBuilder, StringRecord};
use std::path::Path;
pub mod btable;
pub mod dtable;
pub struct TableLoader;
pub type FRDTableLoader = TableLoader<dtable::FRDTableLoader>;
pub type FRBTableLoader = TableLoader<btable::BTableLoader>;
impl TableLoader {
pub fn load_table<P: AsRef<Path>, T: TableEntryLoader>(
#[derive(Default)]
pub struct TableLoader<C: EntryLoader> {
_marker: std::marker::PhantomData<C>,
}
impl<C: EntryLoader> TableLoader<C> {
pub fn load_table<P: AsRef<std::path::Path>>(
&self,
path: P,
loader: &mut T,
) -> anyhow::Result<Vec<T::TableEntry>> {
loader: &mut C,
) -> anyhow::Result<Vec<C::Output>> {
let path = path.as_ref();
let mut entries = vec![];
let mut rdr = ReaderBuilder::new()
.has_headers(false)
.delimiter(b';')
.flexible(false) // Allow variable number of fields
.from_path(path.as_ref())?;
.flexible(false)
.from_path(path)?;
let mut line_num = 1; // Start at 1 for header
let mut line_num = 1;
for result in rdr.records() {
line_num += 1;
match result {
@ -28,11 +38,10 @@ impl TableLoader {
}
}
Err(e) => {
// Log the error but continue processing
eprintln!(
"Warning: Skipping line {} in {}: {}",
line_num,
path.as_ref().display(),
path.display(),
e
);
}
@ -47,15 +56,25 @@ impl TableLoader {
}
}
pub trait TableEntryLoader: Sized {
type TableEntry: TableEntry;
pub trait EntryLoader: Default {
type Output: TableEntryFull;
type TableType: TableTypeTrait;
const TABLE_TYPE: TableType;
/// Process a single entry from the CSV file
fn process_entry(&mut self, raw: &StringRecord) -> anyhow::Result<Option<Self::TableEntry>>;
fn finish(&mut self) -> anyhow::Result<Option<Self::TableEntry>> {
fn process_entry(&mut self, raw: StringRecord) -> anyhow::Result<Option<Self::Output>>;
fn finish(&mut self) -> anyhow::Result<Option<Self::Output>> {
Ok(None)
}
}
impl<T: EntryLoader> TableConverter for TableLoader<T> {
type OutputEntry = T::Output;
type TableType = T::TableType;
fn convert<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> anyhow::Result<Vec<Self::OutputEntry>> {
let mut loader = T::default();
self.load_table(path, &mut loader)
}
}

View File

@ -1,117 +1,44 @@
// pub mod fr;
pub mod fr;
pub mod prelude;
pub mod tables;
mod utils;
pub mod wmo;
use anyhow::Context;
use memmap2::Mmap;
use ph::fmph::GOFunction;
use rkyv::Archive;
use rkyv::api::high::{HighDeserializer, HighSerializer};
use rkyv::rancor::Error;
use serde::de::DeserializeOwned;
use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
use std::fmt::Debug;
use std::io::Write;
use std::path::Path;
use csv::ReaderBuilder;
use crate::tables::{TableEntryFull, TableTypeTrait};
pub struct TableLoader;
pub trait TableConverter {
type OutputEntry: TableEntryFull;
type TableType: TableTypeTrait;
fn convert<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<Vec<Self::OutputEntry>>;
impl TableLoader {
pub fn load_table<P: AsRef<Path>, T: TableEntryLoader>(
&self,
path: P,
loader: &mut T,
) -> anyhow::Result<Vec<T::TableEntry>> {
let mut entries = vec![];
let mut rdr = ReaderBuilder::new()
.has_headers(true)
.delimiter(b',')
.flexible(true) // Allow variable number of fields
.from_path(path.as_ref())?;
let mut line_num = 1; // Start at 1 for header
for result in rdr.deserialize() {
line_num += 1;
match result {
Ok(record) => {
let record: T::RawEntry = record;
if let Some(processed_entry) = loader.process_entry(record)? {
entries.push(processed_entry);
}
}
Err(e) => {
// Log the error but continue processing
eprintln!(
"Warning: Skipping line {} in {}: {}",
line_num,
path.as_ref().display(),
e
);
}
}
}
if let Some(processed_entry) = loader.finish()? {
entries.push(processed_entry);
}
Ok(entries)
fn table_type(&self) -> crate::TableType {
Self::TableType::TABLE_TYPE
}
}
pub trait TableEntryLoader: Sized
where
Self::TableEntry: for<'a> rkyv::Serialize<
HighSerializer<rkyv::util::AlignedVec, rkyv::ser::allocator::ArenaHandle<'a>, Error>,
>,
<Self::TableEntry as Archive>::Archived:
rkyv::Deserialize<Self::TableEntry, HighDeserializer<Error>>,
{
/// The raw CSV entry type that will be deserialized
type RawEntry: for<'de> serde::Deserialize<'de> + Debug;
type TableEntry: TableEntry;
const TABLE_TYPE: TableType;
/// Process a single entry from the CSV file
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::TableEntry>>;
fn finish(&mut self) -> anyhow::Result<Option<Self::TableEntry>> {
Ok(None)
}
}
pub trait TableEntry: SerdeSerialize + DeserializeOwned + Debug + Clone + Archive {
fn fxy(&self) -> FXY;
}
struct BufrTableMph<T: TableEntryLoader> {
struct BufrTableMph<T: TableEntryFull> {
mphf: GOFunction,
offsets: Vec<u64>,
mmap: Mmap,
_marker: std::marker::PhantomData<T>,
}
struct BUFRTableM {
mphf: GOFunction,
}
impl<T: TableEntryLoader> BufrTableMph<T>
where
for<'a> T::TableEntry: rkyv::Serialize<
HighSerializer<rkyv::util::AlignedVec, rkyv::ser::allocator::ArenaHandle<'a>, Error>,
>,
<T::TableEntry as Archive>::Archived: rkyv::Deserialize<T::TableEntry, HighDeserializer<Error>>,
{
fn build(entries: Vec<T::TableEntry>, output_path: &str) -> std::io::Result<Self> {
impl<T: TableEntryFull> BufrTableMph<T> {
fn build(entries: Vec<T>, output_path: &str) -> std::io::Result<Self> {
println!("Building MPH table with {} entries...", entries.len());
let keys: Vec<FXY> = entries.iter().map(|e| e.fxy()).collect();
let mphf = GOFunction::from_slice(&keys);
let mut sorted_entries: Vec<(usize, T::TableEntry)> = entries
let mut sorted_entries: Vec<(usize, T)> = entries
.into_iter()
.map(|e| (mphf.get(&(e.fxy())).unwrap() as usize, e))
.collect();
@ -212,7 +139,7 @@ where
}
/// 获取拥有的版本
fn get(&self, fxy: FXY) -> Option<T::TableEntry> {
fn get(&self, fxy: FXY) -> Option<T> {
let hash = self.mphf.get(&fxy)? as usize;
let offset = *self.offsets.get(hash)? as usize;
@ -221,13 +148,12 @@ where
let data = self.mmap.get(offset + 4..offset + 4 + len)?;
let archived =
unsafe { rkyv::access_unchecked::<<T::TableEntry as Archive>::Archived>(data) };
rkyv::deserialize::<T::TableEntry, Error>(archived).ok()
let archived = unsafe { rkyv::access_unchecked::<<T as TableEntryFull>::Archived>(data) };
rkyv::deserialize::<T, Error>(archived).ok()
}
/// 获取所有条目
fn get_all(&self) -> Vec<T::TableEntry> {
fn get_all(&self) -> Vec<T> {
let mut entries = Vec::new();
for offset in &self.offsets {
let offset = *offset as usize;
@ -236,9 +162,9 @@ where
let len = u32::from_le_bytes(len_bytes_array) as usize;
if let Some(data) = self.mmap.get(offset + 4..offset + 4 + len) {
let archived = unsafe {
rkyv::access_unchecked::<<T::TableEntry as Archive>::Archived>(data)
rkyv::access_unchecked::<<T as TableEntryFull>::Archived>(data)
};
if let Ok(entry) = rkyv::deserialize::<T::TableEntry, Error>(archived) {
if let Ok(entry) = rkyv::deserialize::<T, Error>(archived) {
entries.push(entry);
}
}
@ -269,15 +195,6 @@ pub struct FXY {
pub y: u16,
}
// // Custom Hash implementation to work around boomphf's overflow bug
// // We implement Hash by converting to u32 first
// impl std::hash::Hash for FXY {
// fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// // Convert FXY to a simple u32 value to avoid complex hashing
// self.to_u32().hash(state);
// }
// }
impl FXY {
pub fn new(f: u16, x: u16, y: u16) -> Self {
FXY { f, x, y }
@ -319,32 +236,37 @@ impl FXY {
}
}
pub struct BUFRTableMPH<T: TableEntryLoader> {
inner: BufrTableMph<T>,
pub struct BUFRTableMPH<T: TableTypeTrait> {
inner: BufrTableMph<T::EntryType>,
}
impl<T: TableEntryLoader> BUFRTableMPH<T> {
pub fn build_from_csv<P: AsRef<Path>>(
mut loader: T,
csv_path: P,
impl<T: TableTypeTrait> BUFRTableMPH<T> {
pub fn build_from_csv<P: AsRef<Path>, L: TableConverter>(
loader: L,
path: P,
output_path: P,
) -> anyhow::Result<Self> {
let entries = TableLoader.load_table(csv_path, &mut loader)?;
let bhm = BufrTableMph::<T>::build(entries, output_path.as_ref().to_str().unwrap())?;
) -> anyhow::Result<Self>
where
L: TableConverter<OutputEntry = T::EntryType>,
L: TableConverter<TableType = T>,
{
let entries = loader.convert(path)?;
let bhm =
BufrTableMph::<T::EntryType>::build(entries, output_path.as_ref().to_str().unwrap())?;
Ok(BUFRTableMPH { inner: bhm })
}
pub fn load_from_disk<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let bhm: BufrTableMph<T> = BufrTableMph::load(path)?;
let bhm: BufrTableMph<T::EntryType> = BufrTableMph::load(path)?;
Ok(BUFRTableMPH { inner: bhm })
}
pub fn lookup(&self, fxy: FXY) -> anyhow::Result<Option<T::TableEntry>> {
pub fn lookup(&self, fxy: FXY) -> anyhow::Result<Option<T::EntryType>> {
Ok(self.inner.get(fxy))
}
pub fn get_all_entries(&self) -> Vec<T::TableEntry> {
pub fn get_all_entries(&self) -> Vec<T::EntryType> {
self.inner.get_all()
}
}
@ -358,16 +280,7 @@ pub enum TableType {
#[cfg(test)]
mod test {
use crate::{BUFRTableMPH, dtable::DTableCsvLoader};
#[test]
fn test() {
let d_loader = DTableCsvLoader::default();
BUFRTableMPH::build_from_csv(
d_loader,
"/Users/xiang.li1/projects/rbufr/BUFR4/BUFR_TableD_en_00.csv",
"test_table_d",
)
.unwrap();
}
fn test() {}
}

View File

@ -1,6 +1,9 @@
use anyhow::{Context, Result};
use anyhow::{Context, Result, anyhow};
use clap::{Parser, Subcommand};
use genlib::{BUFRTableMPH, btable::BTableCsvLoader, dtable::DTableCsvLoader};
use genlib::{
TableType,
prelude::{BUFRTableB, BUFRTableD},
};
use std::path::{Path, PathBuf};
#[derive(Parser)]
@ -196,16 +199,63 @@ fn convert_single_file(input_path: &Path, output_path: &Path, table_type: &str)
Ok(())
}
type BuildFn = fn(&Path, &Path) -> Result<()>;
fn run_with_fallbacks(
kind: TableType,
input_path: &Path,
output_path: &Path,
attempts: &[(&str, BuildFn)],
) -> Result<()> {
let mut errors = Vec::new();
for (label, build_fn) in attempts {
match build_fn(input_path, output_path) {
Ok(()) => return Ok(()),
Err(err) => errors.push(format!("{label} failed: {err:#}")),
}
}
Err(anyhow!(
"all {:?} loaders failed:\n{}",
kind,
errors.join("\n---\n")
))
}
fn build_wmo_d(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = genlib::wmo::TableLoader::<genlib::wmo::WMODTableLoader>::default();
BUFRTableD::build_from_csv(loader, input_path, output_path).map(|_| ())
}
fn build_fr_d(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = genlib::fr::FRDTableLoader::default();
BUFRTableD::build_from_csv(loader, input_path, output_path).map(|_| ())
}
fn convert_table_d(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = DTableCsvLoader::default();
BUFRTableMPH::build_from_csv(loader, input_path, output_path)?;
Ok(())
const ATTEMPTS: &[(&str, BuildFn)] = &[
("WMO Table D loader", build_wmo_d),
("FR Table D loader", build_fr_d),
];
run_with_fallbacks(TableType::D, input_path, output_path, ATTEMPTS)
}
fn build_wmo_b(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = genlib::wmo::TableLoader::<genlib::wmo::WMOBTableLoader>::default();
BUFRTableB::build_from_csv(loader, input_path, output_path).map(|_| ())
}
fn build_fr_b(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = genlib::fr::FRBTableLoader::default();
BUFRTableB::build_from_csv(loader, input_path, output_path).map(|_| ())
}
fn convert_table_b(input_path: &Path, output_path: &Path) -> Result<()> {
let loader = BTableCsvLoader;
BUFRTableMPH::build_from_csv(loader, input_path, output_path)?;
Ok(())
const ATTEMPTS: &[(&str, BuildFn)] = &[
("WMO Table B loader", build_wmo_b),
("FR Table B loader", build_fr_b),
];
run_with_fallbacks(TableType::B, input_path, output_path, ATTEMPTS)
}
fn print_table(input_path: &Path, table_type: &str, limit: Option<usize>) -> Result<()> {
@ -219,11 +269,9 @@ fn print_table(input_path: &Path, table_type: &str, limit: Option<usize>) -> Res
}
fn print_table_d(input_path: &Path, limit: Option<usize>) -> Result<()> {
use genlib::dtable::DTableEntry;
println!("Loading Table D from: {}", input_path.display());
let table: BUFRTableMPH<DTableCsvLoader> = BUFRTableMPH::load_from_disk(input_path)?;
let table: BUFRTableD = BUFRTableD::load_from_disk(input_path)?;
let entries = table.get_all_entries();
println!("\nTable D Entries (Total: {})", entries.len());
@ -254,11 +302,9 @@ fn print_table_d(input_path: &Path, limit: Option<usize>) -> Result<()> {
}
fn print_table_b(input_path: &Path, limit: Option<usize>) -> Result<()> {
use genlib::btable::BTableEntry;
println!("Loading Table B from: {}", input_path.display());
let table: BUFRTableMPH<BTableCsvLoader> = BUFRTableMPH::load_from_disk(input_path)?;
let table: BUFRTableB = BUFRTableB::load_from_disk(input_path)?;
let entries = table.get_all_entries();
println!("\nTable B Entries (Total: {})", entries.len());

View File

@ -1,6 +1,8 @@
use crate::tables::BTable;
use crate::tables::DTable;
pub use crate::wmo;
// pub type BUFRTableD = crate::BUFRTableMPH<DTableCsvLoader>;
// pub type BUFRTableB = crate::BUFRTableMPH<crate::btable::BTableCsvLoader>;
pub type BUFRTableD = crate::BUFRTableMPH<DTable>;
pub type BUFRTableB = crate::BUFRTableMPH<BTable>;
pub use crate::BUFRTableMPH;
pub use crate::FXY;
pub use crate::TableType;

238
gen/src/tables.rs Normal file
View File

@ -0,0 +1,238 @@
use crate::FXY;
use rkyv::Archive;
use rkyv::api::high::{HighDeserializer, HighSerializer};
use rkyv::de::Pool;
use rkyv::rancor::{Error, Strategy};
use serde::Serialize as SerdeSerialize;
use serde::de::DeserializeOwned;
use std::fmt::Debug;
pub struct BTable;
pub struct DTable;
pub trait TableTypeTrait {
type EntryType: TableEntryFull;
const TABLE_TYPE: crate::TableType;
}
impl TableTypeTrait for BTable {
type EntryType = crate::tables::BTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::B;
}
impl TableTypeTrait for DTable {
type EntryType = crate::tables::DTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::D;
}
pub trait TableEntry:
SerdeSerialize
+ DeserializeOwned
+ Debug
+ Clone
+ Sized
+ Archive
+ for<'a> rkyv::Serialize<
HighSerializer<rkyv::util::AlignedVec, rkyv::ser::allocator::ArenaHandle<'a>, Error>,
>
{
fn fxy(&self) -> FXY;
}
pub trait TableEntryFull: TableEntry {
type Archived: for<'a> rkyv::Deserialize<Self, HighDeserializer<Error>>
+ rkyv::Deserialize<Self, Strategy<Pool, rkyv::rancor::Error>>
+ rkyv::Portable;
}
impl<T> TableEntryFull for T
where
T: TableEntry,
<T as Archive>::Archived: for<'a> rkyv::Deserialize<T, HighDeserializer<Error>>
+ rkyv::Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
{
type Archived = <T as Archive>::Archived;
}
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct BTableEntry {
pub fxy: FXY,
pub class_name_en: String,
pub element_name_en: String,
pub bufr_unit: String,
pub bufr_scale: i32,
pub bufr_reference_value: i32,
pub bufr_datawidth_bits: u32,
pub note_en: Option<String>,
pub note_ids: Option<String>,
pub status: Option<String>,
}
impl BTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
pub fn class_name_en(&self) -> &str {
&self.class_name_en
}
pub fn element_name_en(&self) -> &str {
&self.element_name_en
}
pub fn bufr_unit(&self) -> &str {
&self.bufr_unit
}
pub fn bufr_scale(&self) -> i32 {
self.bufr_scale
}
pub fn bufr_reference_value(&self) -> i32 {
self.bufr_reference_value
}
pub fn bufr_datawidth_bits(&self) -> u32 {
self.bufr_datawidth_bits
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> Option<&str> {
self.note_ids.as_deref()
}
pub fn status(&self) -> Option<&str> {
self.status.as_deref()
}
}
impl std::fmt::Display for BTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let element_name = if self.element_name_en.len() > 40 {
format!("{}...", &self.element_name_en[..37])
} else {
self.element_name_en.clone()
};
let unit = if self.bufr_unit.len() > 15 {
format!("{}...", &self.bufr_unit[..12])
} else {
self.bufr_unit.clone()
};
write!(
f,
"{:02}{:02}{:03} | {:<40} | {:<15} | {:>5} | {:>8} | {:>8} | {}",
self.fxy.f,
self.fxy.x,
self.fxy.y,
element_name,
unit,
self.bufr_scale,
self.bufr_reference_value,
self.bufr_datawidth_bits,
self.status().unwrap_or("N/A")
)
}
}
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct DTableEntry {
pub fxy: FXY,
pub fxy_chain: Vec<FXY>,
pub category: Option<String>,
pub category_of_sequences_en: Option<String>,
pub title_en: Option<String>,
pub subtitle_en: Option<String>,
pub note_en: Option<String>,
pub note_ids: Option<String>,
pub status: Option<String>,
}
impl DTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
pub fn fxy_chain(&self) -> &[FXY] {
&self.fxy_chain
}
pub fn category(&self) -> Option<&str> {
self.category.as_deref()
}
pub fn category_of_sequences_en(&self) -> Option<&str> {
self.category_of_sequences_en.as_deref()
}
pub fn title_en(&self) -> Option<&str> {
self.title_en.as_deref()
}
pub fn subtitle_en(&self) -> Option<&str> {
self.subtitle_en.as_deref()
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> Option<&str> {
self.note_ids.as_deref()
}
pub fn status(&self) -> Option<&str> {
self.status.as_deref()
}
}
impl std::fmt::Display for DTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fxy_chain_str: String = self
.fxy_chain
.iter()
.map(|fxy| format!("{:02}{:02}{:03}", fxy.f, fxy.x, fxy.y))
.collect::<Vec<_>>()
.join(", ");
let title = self.title_en.as_deref().unwrap_or("N/A");
let truncated_title = if title.len() > 50 {
format!("{}...", &title[..47])
} else {
title.to_string()
};
write!(
f,
"{:02}{:02}{:03} | {:<50} | {:<12} | [{}]",
self.fxy.f,
self.fxy.x,
self.fxy.y,
truncated_title,
self.status().unwrap_or("N/A"),
fxy_chain_str
)
}
}
impl TableEntry for DTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}
impl TableEntry for BTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}

View File

@ -1,6 +1,10 @@
use crate::{FXY, TableEntry, TableEntryLoader};
use rkyv::Archive;
use super::EntryLoader;
use crate::{
FXY,
tables::{BTable, BTableEntry},
};
#[derive(Default)]
pub struct BTableCsvLoader;
#[derive(Debug, serde::Deserialize)]
@ -32,7 +36,7 @@ pub struct RawBTableEntry {
#[serde(rename = "noteIDs")]
pub note_ids: Option<String>,
#[serde(rename = "Status")]
pub status: String,
pub status: Option<String>,
}
// Helper function to deserialize empty strings as None
@ -57,102 +61,12 @@ where
}
}
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct BTableEntry {
fxy: FXY,
class_name_en: String,
element_name_en: String,
bufr_unit: String,
bufr_scale: i32,
bufr_reference_value: i32,
bufr_datawidth_bits: u32,
note_en: Option<String>,
note_ids: Option<String>,
status: String,
}
impl BTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
pub fn class_name_en(&self) -> &str {
&self.class_name_en
}
pub fn element_name_en(&self) -> &str {
&self.element_name_en
}
pub fn bufr_unit(&self) -> &str {
&self.bufr_unit
}
pub fn bufr_scale(&self) -> i32 {
self.bufr_scale
}
pub fn bufr_reference_value(&self) -> i32 {
self.bufr_reference_value
}
pub fn bufr_datawidth_bits(&self) -> u32 {
self.bufr_datawidth_bits
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> Option<&str> {
self.note_ids.as_deref()
}
pub fn status(&self) -> &str {
&self.status
}
}
impl std::fmt::Display for BTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let element_name = if self.element_name_en.len() > 40 {
format!("{}...", &self.element_name_en[..37])
} else {
self.element_name_en.clone()
};
let unit = if self.bufr_unit.len() > 15 {
format!("{}...", &self.bufr_unit[..12])
} else {
self.bufr_unit.clone()
};
write!(
f,
"{:02}{:02}{:03} | {:<40} | {:<15} | {:>5} | {:>8} | {:>8} | {}",
self.fxy.f,
self.fxy.x,
self.fxy.y,
element_name,
unit,
self.bufr_scale,
self.bufr_reference_value,
self.bufr_datawidth_bits,
self.status
)
}
}
impl TableEntryLoader for BTableCsvLoader {
impl EntryLoader for BTableCsvLoader {
type RawEntry = RawBTableEntry;
type TableEntry = BTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::B;
type Output = BTableEntry;
type TableType = BTable;
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::TableEntry>> {
// Parse FXY string (e.g., "001001") to u32
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::Output>> {
let fxy = FXY::from_str(&raw.fxy)?;
let entry = BTableEntry {
@ -171,9 +85,3 @@ impl TableEntryLoader for BTableCsvLoader {
Ok(Some(entry))
}
}
impl TableEntry for BTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}

View File

@ -1,5 +1,8 @@
use crate::{FXY, TableEntry, TableEntryLoader};
use rkyv::Archive;
use super::EntryLoader;
use crate::{
FXY,
tables::{DTable, DTableEntry},
};
#[derive(Debug, Clone, Default)]
pub struct DTableCsvLoader {
@ -9,9 +12,9 @@ pub struct DTableCsvLoader {
#[derive(Debug, serde::Deserialize)]
pub struct RawDTableEntry {
#[serde(rename = "Category")]
pub category: String,
pub category: Option<String>,
#[serde(rename = "CategoryOfSequences_en")]
pub category_of_sequences_en: String,
pub category_of_sequences_en: Option<String>,
#[serde(rename = "FXY1")]
pub fxy1: String,
#[serde(rename = "Title_en")]
@ -27,17 +30,17 @@ pub struct RawDTableEntry {
#[serde(rename = "Note_en")]
pub note_en: Option<String>,
#[serde(rename = "noteIDs")]
pub note_ids: String,
pub note_ids: Option<String>,
#[serde(rename = "Status")]
pub status: String,
pub status: Option<String>,
}
impl TableEntryLoader for DTableCsvLoader {
impl EntryLoader for DTableCsvLoader {
type RawEntry = RawDTableEntry;
type TableEntry = DTableEntry;
const TABLE_TYPE: crate::TableType = crate::TableType::D;
type Output = DTableEntry;
type TableType = DTable;
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::TableEntry>> {
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::Output>> {
// Process the raw entry as needed
if self.current_chain.is_none() {
let entry = DTableEntry {
@ -87,91 +90,7 @@ impl TableEntryLoader for DTableCsvLoader {
}
}
fn finish(&mut self) -> anyhow::Result<Option<Self::TableEntry>> {
fn finish(&mut self) -> anyhow::Result<Option<Self::Output>> {
Ok(self.current_chain.take())
}
}
#[derive(
Debug, Clone, serde::Deserialize, serde::Serialize, Archive, rkyv::Serialize, rkyv::Deserialize,
)]
#[rkyv(compare(PartialEq), derive(Debug))]
pub struct DTableEntry {
fxy: FXY,
fxy_chain: Vec<FXY>,
category: String,
category_of_sequences_en: String,
title_en: Option<String>,
subtitle_en: Option<String>,
note_en: Option<String>,
note_ids: String,
status: String,
}
impl DTableEntry {
pub fn fxy(&self) -> FXY {
self.fxy
}
pub fn fxy_chain(&self) -> &[FXY] {
&self.fxy_chain
}
pub fn category(&self) -> &str {
&self.category
}
pub fn category_of_sequences_en(&self) -> &str {
&self.category_of_sequences_en
}
pub fn title_en(&self) -> Option<&str> {
self.title_en.as_deref()
}
pub fn subtitle_en(&self) -> Option<&str> {
self.subtitle_en.as_deref()
}
pub fn note_en(&self) -> Option<&str> {
self.note_en.as_deref()
}
pub fn note_ids(&self) -> &str {
&self.note_ids
}
pub fn status(&self) -> &str {
&self.status
}
}
impl std::fmt::Display for DTableEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fxy_chain_str: String = self
.fxy_chain
.iter()
.map(|fxy| format!("{:02}{:02}{:03}", fxy.f, fxy.x, fxy.y))
.collect::<Vec<_>>()
.join(", ");
let title = self.title_en.as_deref().unwrap_or("N/A");
let truncated_title = if title.len() > 50 {
format!("{}...", &title[..47])
} else {
title.to_string()
};
write!(
f,
"{:02}{:02}{:03} | {:<50} | {:<12} | [{}]",
self.fxy.f, self.fxy.x, self.fxy.y, truncated_title, self.status, fxy_chain_str
)
}
}
impl TableEntry for DTableEntry {
fn fxy(&self) -> FXY {
self.fxy
}
}

View File

@ -1,2 +1,82 @@
pub mod btable;
pub mod dtable;
use crate::{
TableConverter,
tables::{TableEntryFull, TableTypeTrait},
};
pub use btable::BTableCsvLoader as WMOBTableLoader;
use csv::ReaderBuilder;
pub use dtable::DTableCsvLoader as WMODTableLoader;
use std::fmt::Debug;
#[derive(Default)]
pub struct TableLoader<C: EntryLoader> {
_marker: std::marker::PhantomData<C>,
}
impl<C: EntryLoader> TableLoader<C> {
pub fn load_table<P: AsRef<std::path::Path>>(
&self,
path: P,
loader: &mut C,
) -> anyhow::Result<Vec<C::Output>> {
let mut entries = vec![];
let mut rdr = ReaderBuilder::new()
.has_headers(true)
.delimiter(b',')
.flexible(true) // Allow variable number of fields
.from_path(path.as_ref())?;
let mut line_num = 1; // Start at 1 for header
for result in rdr.deserialize() {
line_num += 1;
match result {
Ok(record) => {
let record: C::RawEntry = record;
if let Some(processed_entry) = loader.process_entry(record)? {
entries.push(processed_entry);
}
}
Err(e) => {
// Log the error but continue processing
eprintln!(
"Warning: Skipping line {} in {}: {}",
line_num,
path.as_ref().display(),
e
);
}
}
}
if let Some(processed_entry) = loader.finish()? {
entries.push(processed_entry);
}
Ok(entries)
}
}
pub trait EntryLoader: Default {
type Output: TableEntryFull;
type RawEntry: for<'de> serde::Deserialize<'de> + Debug;
type TableType: TableTypeTrait;
fn process_entry(&mut self, raw: Self::RawEntry) -> anyhow::Result<Option<Self::Output>>;
fn finish(&mut self) -> anyhow::Result<Option<Self::Output>> {
Ok(None)
}
}
impl<T: EntryLoader> TableConverter for TableLoader<T> {
type OutputEntry = T::Output;
type TableType = T::TableType;
fn convert<P: AsRef<std::path::Path>>(
&self,
path: P,
) -> anyhow::Result<Vec<Self::OutputEntry>> {
let mut loader = T::default();
self.load_table(path, &mut loader)
}
}

View File

@ -1,4 +1,5 @@
use genlib::{BUFRTableMPH, TableEntryLoader};
use genlib::BUFRTableMPH;
use genlib::tables::TableTypeTrait;
use crate::errors::Result;
use crate::structs::versions::BUFRMessage;
@ -44,7 +45,7 @@ impl MessageBlock {
// master_b_table.load_table(TT::Standard);
}
fn load_first_validable_table<E: TableEntryLoader>(
fn load_first_validable_table<E: TableTypeTrait>(
&self,
table_version: u8,
) -> Result<BUFRTableMPH<E>> {

View File

@ -1,6 +1,6 @@
use crate::errors::Result;
pub use genlib::prelude::{BUFRTableB, BUFRTableD, TableType};
use genlib::{TableEntryLoader, prelude::*};
use genlib::{prelude::*, tables::TableTypeTrait};
use std::path::PathBuf;
pub trait TableTrait {
@ -94,7 +94,7 @@ pub struct TableLoader;
impl TableLoader {
pub fn load_table<T>(&self, table_type: impl TableTrait) -> Result<BUFRTableMPH<T>>
where
T: TableEntryLoader,
T: TableTypeTrait,
{
let path = table_type.file_path(T::TABLE_TYPE);
BUFRTableMPH::<T>::load_from_disk(path).map_err(|e| e.into())