diff --git a/Cargo.lock b/Cargo.lock index c73e836..e7ae655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,6 +707,15 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -884,6 +893,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -946,7 +964,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", ] @@ -1182,6 +1200,67 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "pyo3" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" +dependencies = [ + "indoc", + "libc", + "memoffset 0.9.1", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.111", +] + [[package]] name = "quote" version = "1.0.42" @@ -1270,6 +1349,14 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "rbufrp" +version = "0.1.0" +dependencies = [ + "pyo3", + "rbufr", +] + [[package]] name = "rdst" version = "0.20.14" @@ -1573,6 +1660,12 @@ dependencies = [ "windows 0.61.3", ] +[[package]] +name = "target-lexicon" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" + [[package]] name = "tempfile" version = "3.23.0" @@ -1718,6 +1811,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 1b26e76..a734d3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["rbufr", "gen"] +members = ["rbufr", "gen", "rbufrp"] diff --git a/rbufr/src/block.rs b/rbufr/src/block.rs index e73c0d3..3816094 100644 --- a/rbufr/src/block.rs +++ b/rbufr/src/block.rs @@ -12,6 +12,7 @@ use crate::structs::GENCENTER; use crate::structs::versions::{BUFRMessage, MessageVersion}; use crate::tables::*; +#[derive(Clone)] pub struct MessageBlock { message: BUFRMessage, } diff --git a/rbufr/src/decoder.rs b/rbufr/src/decoder.rs index 734f945..c8f49dc 100644 --- a/rbufr/src/decoder.rs +++ b/rbufr/src/decoder.rs @@ -11,7 +11,6 @@ use genlib::{ prelude::{BUFRTableB, BUFRTableBitMap, BUFRTableD}, tables::{ArchivedBTableEntry, ArchivedDTableEntry}, }; -use rustc_hash::FxHashMap; use std::{fmt::Display, ops::Deref}; const MISS_VAL: f64 = 99999.999999; @@ -33,10 +32,6 @@ struct Cache<'a> { master_d: &'a BUFRTableD, local_b: Option<&'a BUFRTableB>, local_d: Option<&'a BUFRTableD>, - - // Cache - b_cache: FxHashMap, - d_cache: FxHashMap, } impl<'a> Cache<'a> { @@ -51,8 +46,6 @@ impl<'a> Cache<'a> { master_d, local_b, local_d, - b_cache: FxHashMap::default(), - d_cache: FxHashMap::default(), } } diff --git a/rbufr/src/structs/mod.rs b/rbufr/src/structs/mod.rs index 2046d77..c1d451f 100644 --- a/rbufr/src/structs/mod.rs +++ b/rbufr/src/structs/mod.rs @@ -3,11 +3,11 @@ use nom::{ bytes::complete::{tag, take}, number::complete::{be_u8, be_u16, be_u24}, }; -pub mod bit; +pub(super) mod bit; pub(super) mod tools; pub mod versions; #[cfg(feature = "opera")] -pub(super) const GENCENTER: u16 = 247; +pub const GENCENTER: u16 = 247; #[inline] pub fn skip(n: usize) -> impl Fn(&[u8]) -> IResult<&[u8], ()> { diff --git a/rbufr/tests/test_rc.rs b/rbufr/tests/test_rc.rs index 86e0f79..ca322f0 100644 --- a/rbufr/tests/test_rc.rs +++ b/rbufr/tests/test_rc.rs @@ -1,7 +1,8 @@ use librbufr::{decoder::Decoder, parser::parse}; +#[test] fn test_dec() { - let file = parse("/Users/xiang.li1/Downloads/36_2025-12-22T11_00_00.bufr").unwrap(); + let file = parse("/Users/tsuki/Downloads/36_2025-12-17T09_00_00.bufr.nc").unwrap(); for msg in file.messages() { let mut decoder = Decoder::from_message(msg).unwrap(); let record = decoder.decode(msg).unwrap(); diff --git a/rbufrp/.python-version b/rbufrp/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/rbufrp/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/rbufrp/Cargo.toml b/rbufrp/Cargo.toml new file mode 100644 index 0000000..af607b0 --- /dev/null +++ b/rbufrp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rbufrp" +version = "0.1.0" +edition = "2024" + +[lib] +name = "_core" +# "cdylib" is necessary to produce a shared library for Python to import from. +crate-type = ["cdylib"] + +[dependencies] +# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so) +# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9 +pyo3 = { version = "0.27.1", features = ["extension-module", "abi3-py38"] } + +rbufr = { path = "../rbufr" } diff --git a/rbufrp/README.md b/rbufrp/README.md new file mode 100644 index 0000000..e69de29 diff --git a/rbufrp/pyproject.toml b/rbufrp/pyproject.toml new file mode 100644 index 0000000..cb1f34a --- /dev/null +++ b/rbufrp/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "rbufrp" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [ + { name = "Tsuki", email = "qwin7989@gmail.com" } +] +requires-python = ">=3.11" +dependencies = [] + +[project.scripts] +rbufrp = "rbufrp:main" + +[tool.maturin] +module-name = "rbufrp._core" +python-packages = ["rbufrp"] +python-source = "src" + +[tool.uv] +cache-keys = [{ file = "pyproject.toml" }, { file = "src/**/*.rs" }, { file = "Cargo.toml" }, { file = "Cargo.lock" }] + +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" diff --git a/rbufrp/src/lib.rs b/rbufrp/src/lib.rs new file mode 100644 index 0000000..90f34be --- /dev/null +++ b/rbufrp/src/lib.rs @@ -0,0 +1,81 @@ +use librbufr::{Decoder, parse}; +use pyo3::prelude::*; + +#[pymodule] +mod _core { + use librbufr::{ + block::{BUFRFile as IB, MessageBlock as IM}, + decoder::BUFRData, + errors::Error, + parse, + }; + use pyo3::prelude::*; + + #[pyfunction] + fn hello_from_bin() -> String { + "Hello from rbufrp!".to_string() + } + + #[pyclass] + struct BUFRDecoder {} + + #[pymethods] + impl BUFRDecoder { + #[new] + fn new() -> Self { + BUFRDecoder {} + } + + fn decode_bufr(&self, file_path: &str) -> PyResult { + let parsed = parse(file_path).map_err(|e| match e { + Error::Io(io_err) => { + PyErr::new::(format!("IO Error: {}", io_err)) + } + + Error::ParseError(parse_err) => PyErr::new::( + format!("Parse Error: {}", parse_err), + ), + + Error::Nom(nom_err) => PyErr::new::(format!( + "Parse Error: {}", + nom_err + )), + + _ => PyErr::new::( + "An unknown error occurred during BUFR decoding.", + ), + })?; + + Ok(BUFRFile(parsed)) + } + } + + #[pyclass] + struct BUFRFile(IB); + + #[pymethods] + impl BUFRFile { + fn __repr__(&self) -> String { + format!("BUFRFile with {} messages", self.0.message_count()) + } + + fn message_count(&self) -> usize { + self.0.message_count() + } + + fn get_message(&self, index: usize) -> PyResult { + let message = self.0.message_at(index).ok_or_else(|| { + PyErr::new::("Message index out of range") + })?; + + Ok(BUFRMessage { + message: message.clone(), + }) + } + } + + #[pyclass] + struct BUFRMessage { + message: IM, + } +} diff --git a/rbufrp/src/rbufrp/__init__.py b/rbufrp/src/rbufrp/__init__.py new file mode 100644 index 0000000..57dbcc7 --- /dev/null +++ b/rbufrp/src/rbufrp/__init__.py @@ -0,0 +1,5 @@ +from rbufrp._core import hello_from_bin + + +def main() -> None: + print(hello_from_bin()) diff --git a/rbufrp/src/rbufrp/__pycache__/__init__.cpython-311.pyc b/rbufrp/src/rbufrp/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..a54378b Binary files /dev/null and b/rbufrp/src/rbufrp/__pycache__/__init__.cpython-311.pyc differ diff --git a/rbufrp/src/rbufrp/_core.abi3.so b/rbufrp/src/rbufrp/_core.abi3.so new file mode 100755 index 0000000..bf5a962 Binary files /dev/null and b/rbufrp/src/rbufrp/_core.abi3.so differ diff --git a/rbufrp/src/rbufrp/_core.pyi b/rbufrp/src/rbufrp/_core.pyi new file mode 100644 index 0000000..d52129e --- /dev/null +++ b/rbufrp/src/rbufrp/_core.pyi @@ -0,0 +1 @@ +def hello_from_bin() -> str: ... diff --git a/rbufrp/uv.lock b/rbufrp/uv.lock new file mode 100644 index 0000000..1edd99f --- /dev/null +++ b/rbufrp/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "rbufrp" +version = "0.1.0" +source = { editable = "." }