sync
Some checks failed
CI / Rust Tests (push) Waiting to run
CI / Python Tests (macos-latest, 3.11) (push) Waiting to run
CI / Python Tests (macos-latest, 3.13) (push) Waiting to run
CI / Python Tests (macos-latest, 3.8) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.11) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.13) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.8) (push) Waiting to run
CI / Python Tests (windows-latest, 3.11) (push) Waiting to run
CI / Python Tests (windows-latest, 3.13) (push) Waiting to run
CI / Python Tests (windows-latest, 3.8) (push) Waiting to run
CI / Check Formatting (push) Waiting to run
Build Wheels (cibuildwheel) / Build wheels on Linux (aarch64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on Linux (x86_64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on macOS (arm64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on macOS (x86_64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on Windows (push) Has been cancelled
Build Wheels (cibuildwheel) / Build source distribution (push) Has been cancelled
Build Wheels (cibuildwheel) / Publish to GitHub Release (push) Has been cancelled
Some checks failed
CI / Rust Tests (push) Waiting to run
CI / Python Tests (macos-latest, 3.11) (push) Waiting to run
CI / Python Tests (macos-latest, 3.13) (push) Waiting to run
CI / Python Tests (macos-latest, 3.8) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.11) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.13) (push) Waiting to run
CI / Python Tests (ubuntu-latest, 3.8) (push) Waiting to run
CI / Python Tests (windows-latest, 3.11) (push) Waiting to run
CI / Python Tests (windows-latest, 3.13) (push) Waiting to run
CI / Python Tests (windows-latest, 3.8) (push) Waiting to run
CI / Check Formatting (push) Waiting to run
Build Wheels (cibuildwheel) / Build wheels on Linux (aarch64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on Linux (x86_64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on macOS (arm64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on macOS (x86_64) (push) Has been cancelled
Build Wheels (cibuildwheel) / Build wheels on Windows (push) Has been cancelled
Build Wheels (cibuildwheel) / Build source distribution (push) Has been cancelled
Build Wheels (cibuildwheel) / Publish to GitHub Release (push) Has been cancelled
This commit is contained in:
parent
2ae2d579b8
commit
d3f020b6c3
154
.github/workflows/build-wheels-cibuildwheel.yml
vendored
Normal file
154
.github/workflows/build-wheels-cibuildwheel.yml
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
name: Build Wheels (cibuildwheel)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*
|
||||||
|
CIBW_SKIP: "*-musllinux_* *-win32 *-manylinux_i686"
|
||||||
|
CIBW_ARCHS_MACOS: "x86_64 arm64"
|
||||||
|
CIBW_ARCHS_LINUX: "x86_64 aarch64"
|
||||||
|
CIBW_ARCHS_WINDOWS: "AMD64"
|
||||||
|
# Build with maturin
|
||||||
|
CIBW_BEFORE_BUILD: pip install maturin
|
||||||
|
CIBW_BUILD_FRONTEND: build
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-wheels-linux:
|
||||||
|
name: Build wheels on Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [x86_64, aarch64]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: matrix.target == 'aarch64'
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.16
|
||||||
|
with:
|
||||||
|
package-dir: rbufrp
|
||||||
|
output-dir: wheelhouse
|
||||||
|
env:
|
||||||
|
CIBW_ARCHS_LINUX: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wheels-linux-${{ matrix.target }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-wheels-macos:
|
||||||
|
name: Build wheels on macOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [x86_64, arm64]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.16
|
||||||
|
with:
|
||||||
|
package-dir: rbufrp
|
||||||
|
output-dir: wheelhouse
|
||||||
|
env:
|
||||||
|
CIBW_ARCHS_MACOS: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wheels-macos-${{ matrix.target }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-wheels-windows:
|
||||||
|
name: Build wheels on Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: pypa/cibuildwheel@v2.16
|
||||||
|
with:
|
||||||
|
package-dir: rbufrp
|
||||||
|
output-dir: wheelhouse
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wheels-windows-amd64
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-sdist:
|
||||||
|
name: Build source distribution
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install maturin build
|
||||||
|
|
||||||
|
- name: Build sdist
|
||||||
|
working-directory: rbufrp
|
||||||
|
run: maturin sdist --out dist
|
||||||
|
|
||||||
|
- name: Upload sdist
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sdist
|
||||||
|
path: rbufrp/dist/*.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
publish-to-github-release:
|
||||||
|
name: Publish to GitHub Release
|
||||||
|
needs: [build-wheels-linux, build-wheels-macos, build-wheels-windows, build-sdist]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: dist/*
|
||||||
|
generate_release_notes: true
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
103
.github/workflows/ci.yml
vendored
Normal file
103
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rust-test:
|
||||||
|
name: Rust Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Cache cargo registry
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cargo/registry
|
||||||
|
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Cache cargo index
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cargo/git
|
||||||
|
key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Cache cargo build
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: target
|
||||||
|
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Run cargo check
|
||||||
|
run: cargo check --all --verbose
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
run: cargo test --all --verbose
|
||||||
|
|
||||||
|
- name: Run cargo clippy
|
||||||
|
run: cargo clippy --all -- -D warnings
|
||||||
|
|
||||||
|
- name: Run cargo fmt check
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
python-test:
|
||||||
|
name: Python Tests
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
python-version: ['3.8', '3.11', '3.13']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Set up Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Install maturin
|
||||||
|
run: pip install maturin pytest
|
||||||
|
|
||||||
|
- name: Build Python package
|
||||||
|
working-directory: rbufrp
|
||||||
|
run: maturin develop --release
|
||||||
|
|
||||||
|
- name: Test Python package
|
||||||
|
working-directory: rbufrp
|
||||||
|
run: |
|
||||||
|
python -c "import rbufrp; print(rbufrp.__version__)"
|
||||||
|
python -c "import rbufrp; print(rbufrp.get_tables_path())"
|
||||||
|
|
||||||
|
check-formatting:
|
||||||
|
name: Check Formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install ruff
|
||||||
|
run: pip install ruff
|
||||||
|
|
||||||
|
- name: Check Python formatting with ruff
|
||||||
|
run: |
|
||||||
|
ruff check rbufrp/src/rbufrp/
|
||||||
|
ruff format --check rbufrp/src/rbufrp/
|
||||||
1
BUFR4
Submodule
1
BUFR4
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit a6b7ab078d4c70c69565655f7cf7c7d3913d6d78
|
||||||
@ -21,6 +21,7 @@ rustc-hash = "2.1.1"
|
|||||||
[features]
|
[features]
|
||||||
default = ["opera"]
|
default = ["opera"]
|
||||||
opera = ["gentools/opera"]
|
opera = ["gentools/opera"]
|
||||||
|
python_bindings = []
|
||||||
|
|
||||||
|
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
|
|||||||
@ -11,7 +11,11 @@ use genlib::{
|
|||||||
prelude::{BUFRTableB, BUFRTableBitMap, BUFRTableD},
|
prelude::{BUFRTableB, BUFRTableBitMap, BUFRTableD},
|
||||||
tables::{ArchivedBTableEntry, ArchivedDTableEntry},
|
tables::{ArchivedBTableEntry, ArchivedDTableEntry},
|
||||||
};
|
};
|
||||||
use std::{fmt::Display, ops::Deref};
|
use std::{
|
||||||
|
borrow::{Borrow, Cow},
|
||||||
|
fmt::Display,
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
|
||||||
const MISS_VAL: f64 = 99999.999999;
|
const MISS_VAL: f64 = 99999.999999;
|
||||||
|
|
||||||
@ -1376,6 +1380,7 @@ impl<'a, 'b> Container<'a> for Repeating<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BUFRParsed<'a> {
|
pub struct BUFRParsed<'a> {
|
||||||
records: Vec<BUFRRecord<'a>>,
|
records: Vec<BUFRRecord<'a>>,
|
||||||
}
|
}
|
||||||
@ -1385,11 +1390,11 @@ impl<'a> BUFRParsed<'a> {
|
|||||||
Self { records: vec![] }
|
Self { records: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, value: Value, element_name: &'a str, unit: &'a str) {
|
fn push(&mut self, value: Value, element_name: &'a str, unit: &'a str) {
|
||||||
self.records.push(BUFRRecord {
|
self.records.push(BUFRRecord {
|
||||||
name: Some(element_name),
|
name: Some(Cow::Borrowed(element_name)),
|
||||||
values: BUFRData::Single(value),
|
values: BUFRData::Single(value),
|
||||||
unit: Some(unit),
|
unit: Some(Cow::Borrowed(unit)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1406,6 +1411,12 @@ impl<'a> BUFRParsed<'a> {
|
|||||||
values: Vec::with_capacity(time),
|
values: Vec::with_capacity(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(&self) -> BUFRParsed<'static> {
|
||||||
|
BUFRParsed {
|
||||||
|
records: self.records.iter().map(|r| r.into_owned()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Array<'a, 's> {
|
struct Array<'a, 's> {
|
||||||
@ -1424,9 +1435,9 @@ impl<'a> Array<'a, '_> {
|
|||||||
|
|
||||||
fn finish(self, name: Option<&'a str>, unit: Option<&'a str>) {
|
fn finish(self, name: Option<&'a str>, unit: Option<&'a str>) {
|
||||||
let recording = BUFRRecord {
|
let recording = BUFRRecord {
|
||||||
name,
|
name: name.map(|n| Cow::Borrowed(n)),
|
||||||
values: BUFRData::Array(self.values),
|
values: BUFRData::Array(self.values),
|
||||||
unit,
|
unit: unit.map(|u| Cow::Borrowed(u)),
|
||||||
};
|
};
|
||||||
self.parsed.records.push(recording);
|
self.parsed.records.push(recording);
|
||||||
}
|
}
|
||||||
@ -1452,21 +1463,38 @@ impl<'a, 's> Repeating<'a, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum BUFRData {
|
pub enum BUFRData {
|
||||||
Repeat(Vec<Value>),
|
Repeat(Vec<Value>),
|
||||||
Single(Value),
|
Single(Value),
|
||||||
Array(Vec<f64>),
|
Array(Vec<f64>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BUFRRecord<'a> {
|
pub struct BUFRRecord<'a> {
|
||||||
pub name: Option<&'a str>,
|
// pub name: Option<&'a str>,
|
||||||
|
pub name: Option<Cow<'a, str>>,
|
||||||
pub values: BUFRData,
|
pub values: BUFRData,
|
||||||
pub unit: Option<&'a str>,
|
pub unit: Option<Cow<'a, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BUFRRecord<'_> {
|
||||||
|
pub fn into_owned(&self) -> BUFRRecord<'static> {
|
||||||
|
BUFRRecord {
|
||||||
|
name: self.name.as_ref().map(|s| Cow::Owned(s.to_string())),
|
||||||
|
values: match &self.values {
|
||||||
|
BUFRData::Single(v) => BUFRData::Single(v.clone()),
|
||||||
|
BUFRData::Repeat(vs) => BUFRData::Repeat(vs.clone()),
|
||||||
|
BUFRData::Array(a) => BUFRData::Array(a.clone()),
|
||||||
|
},
|
||||||
|
unit: self.unit.as_ref().map(|s| Cow::Owned(s.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for BUFRRecord<'_> {
|
impl Display for BUFRRecord<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let is_print_unit = match self.unit {
|
let is_print_unit = match self.unit.as_ref().map(|s| &**s) {
|
||||||
Some("CAITT IA5" | "code table" | "code-table" | "flag table" | "flag-table") => false,
|
Some("CAITT IA5" | "code table" | "code-table" | "flag table" | "flag-table") => false,
|
||||||
None => false,
|
None => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
@ -1477,79 +1505,33 @@ impl Display for BUFRRecord<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = self.name.as_ref().unwrap();
|
let name = self.name.as_ref().unwrap();
|
||||||
|
let width = f.width().unwrap_or(0);
|
||||||
|
|
||||||
match &self.values {
|
match &self.values {
|
||||||
BUFRData::Single(v) => {
|
BUFRData::Single(v) => {
|
||||||
if is_print_unit {
|
if width > 0 {
|
||||||
write!(f, "{}: {} {}", name, v, self.unit.as_ref().unwrap())?;
|
write!(f, "{:<width$} : ", name, width = width)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}: {}", name, v)?;
|
write!(f, "{} : ", name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match v {
|
||||||
|
Value::Missing => write!(f, "MISSING")?,
|
||||||
|
Value::String(s) => write!(f, "\"{}\"", s)?,
|
||||||
|
Value::Number(n) => {
|
||||||
|
if is_print_unit {
|
||||||
|
write!(f, "{:>12.6} {}", n, self.unit.as_ref().unwrap())?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", n)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BUFRData::Repeat(vs) => {
|
BUFRData::Repeat(vs) => {
|
||||||
if vs.len() < 8 {
|
self.format_sequence(f, name, vs, is_print_unit, width)?;
|
||||||
write!(f, "{}: [", name)?;
|
|
||||||
for v in vs {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "]")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}: [", name)?;
|
|
||||||
for v in &vs[0..5] {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "... ")?;
|
|
||||||
for v in &vs[vs.len() - 2..vs.len()] {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "]")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BUFRData::Array(a) => {
|
BUFRData::Array(a) => {
|
||||||
if a.len() < 8 {
|
self.format_array(f, name, a, is_print_unit, width)?;
|
||||||
write!(f, "{}: [", name)?;
|
|
||||||
for v in a {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "]")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}: [", name)?;
|
|
||||||
for v in &a[0..5] {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "... ")?;
|
|
||||||
for v in &a[a.len() - 2..a.len()] {
|
|
||||||
if is_print_unit {
|
|
||||||
write!(f, "{} {}, ", v, self.unit.as_ref().unwrap())?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{}, ", v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "]")?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1557,15 +1539,283 @@ impl Display for BUFRRecord<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BUFRRecord<'_> {
|
||||||
|
fn format_sequence(
|
||||||
|
&self,
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
name: &str,
|
||||||
|
values: &[Value],
|
||||||
|
is_print_unit: bool,
|
||||||
|
width: usize,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let missing_count = values.iter().filter(|v| v.is_missing()).count();
|
||||||
|
|
||||||
|
if width > 0 {
|
||||||
|
write!(f, "{:<width$} : ", name, width = width)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{} : ", name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "[len={}", values.len())?;
|
||||||
|
if missing_count > 0 {
|
||||||
|
write!(f, ", missing={}", missing_count)?;
|
||||||
|
}
|
||||||
|
write!(f, "] ")?;
|
||||||
|
|
||||||
|
if values.is_empty() {
|
||||||
|
write!(f, "[]")?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let show_limit = 6;
|
||||||
|
if values.len() <= show_limit {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, v) in values.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
self.format_value(f, v, is_print_unit)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, v) in values.iter().take(3).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
self.format_value(f, v, is_print_unit)?;
|
||||||
|
}
|
||||||
|
write!(f, " ... ")?;
|
||||||
|
for (i, v) in values.iter().skip(values.len() - 2).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
self.format_value(f, v, is_print_unit)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_array(
|
||||||
|
&self,
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
name: &str,
|
||||||
|
values: &[f64],
|
||||||
|
is_print_unit: bool,
|
||||||
|
width: usize,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
let missing_count = values.iter().filter(|&&v| v == MISS_VAL).count();
|
||||||
|
let valid_values: Vec<f64> = values.iter().copied().filter(|&v| v != MISS_VAL).collect();
|
||||||
|
|
||||||
|
if width > 0 {
|
||||||
|
write!(f, "{:<width$} : ", name, width = width)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{} : ", name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "[len={}", values.len())?;
|
||||||
|
if missing_count > 0 {
|
||||||
|
write!(f, ", missing={}", missing_count)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示统计信息
|
||||||
|
if !valid_values.is_empty() {
|
||||||
|
let min = valid_values.iter().copied().fold(f64::INFINITY, f64::min);
|
||||||
|
let max = valid_values
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.fold(f64::NEG_INFINITY, f64::max);
|
||||||
|
let mean = valid_values.iter().sum::<f64>() / valid_values.len() as f64;
|
||||||
|
|
||||||
|
write!(f, ", min={:.3}, max={:.3}, mean={:.3}", min, max, mean)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
|
||||||
|
if is_print_unit {
|
||||||
|
if let Some(unit) = &self.unit {
|
||||||
|
write!(f, " {}", unit)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示样例值
|
||||||
|
if !values.is_empty() {
|
||||||
|
let show_limit = 6;
|
||||||
|
if values.len() <= show_limit {
|
||||||
|
write!(f, "\n [")?;
|
||||||
|
for (i, v) in values.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
if *v == MISS_VAL {
|
||||||
|
write!(f, "MISSING")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{:.3}", v)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "\n [")?;
|
||||||
|
for (i, v) in values.iter().take(3).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
if *v == MISS_VAL {
|
||||||
|
write!(f, "MISSING")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{:.3}", v)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, " ... ")?;
|
||||||
|
for (i, v) in values.iter().skip(values.len() - 2).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
if *v == MISS_VAL {
|
||||||
|
write!(f, "MISSING")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{:.3}", v)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_value(
|
||||||
|
&self,
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
value: &Value,
|
||||||
|
is_print_unit: bool,
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
match value {
|
||||||
|
Value::Missing => write!(f, "MISSING"),
|
||||||
|
Value::String(s) => write!(f, "\"{}\"", s),
|
||||||
|
Value::Number(n) => {
|
||||||
|
if is_print_unit {
|
||||||
|
write!(f, "{:.3}", n)
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for BUFRParsed<'_> {
|
impl Display for BUFRParsed<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "BUFR Parsed Data ({} records)", self.records.len())?;
|
||||||
|
|
||||||
|
// 计算最长的名称长度用于对齐
|
||||||
|
let max_name_len = self
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| r.name.as_ref())
|
||||||
|
.map(|n| n.len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(50); // 限制最大宽度
|
||||||
|
|
||||||
for record in &self.records {
|
for record in &self.records {
|
||||||
|
writeln!(f, "{:<max_name_len$}", record, max_name_len = max_name_len)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BUFRParsed<'_> {
|
||||||
|
/// 获取记录数量
|
||||||
|
pub fn record_count(&self) -> usize {
|
||||||
|
self.records.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取所有记录
|
||||||
|
pub fn records(&self) -> &[BUFRRecord<'_>] {
|
||||||
|
&self.records
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 紧凑格式显示(不带边框和统计信息)
|
||||||
|
pub fn display_compact(&self) -> CompactDisplay<'_> {
|
||||||
|
CompactDisplay(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 详细格式显示(包含更多元数据)
|
||||||
|
pub fn display_detailed(&self) -> DetailedDisplay<'_> {
|
||||||
|
DetailedDisplay(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 紧凑显示包装器
|
||||||
|
pub struct CompactDisplay<'a>(&'a BUFRParsed<'a>);
|
||||||
|
|
||||||
|
impl Display for CompactDisplay<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for record in &self.0.records {
|
||||||
writeln!(f, "{}", record)?;
|
writeln!(f, "{}", record)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 详细显示包装器
|
||||||
|
pub struct DetailedDisplay<'a>(&'a BUFRParsed<'a>);
|
||||||
|
|
||||||
|
impl Display for DetailedDisplay<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "BUFR Parsed Data - Detailed View")?;
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
let total_records = self.0.records.len();
|
||||||
|
let single_count = self
|
||||||
|
.0
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r.values, BUFRData::Single(_)))
|
||||||
|
.count();
|
||||||
|
let array_count = self
|
||||||
|
.0
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r.values, BUFRData::Array(_)))
|
||||||
|
.count();
|
||||||
|
let repeat_count = self
|
||||||
|
.0
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r.values, BUFRData::Repeat(_)))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
writeln!(f, "Statistics:")?;
|
||||||
|
writeln!(f, " Total records: {}", total_records)?;
|
||||||
|
writeln!(f, " Single values: {}", single_count)?;
|
||||||
|
writeln!(f, " Arrays: {}", array_count)?;
|
||||||
|
writeln!(f, " Repeated values: {}", repeat_count)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
// 详细记录
|
||||||
|
let max_name_len = self
|
||||||
|
.0
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| r.name.as_ref())
|
||||||
|
.map(|n| n.len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(50);
|
||||||
|
|
||||||
|
for (idx, record) in self.0.records.iter().enumerate() {
|
||||||
|
writeln!(f, "Record {}: {:<max_name_len$}", idx + 1, record, max_name_len = max_name_len)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Frame<'v, 'a> {
|
enum Frame<'v, 'a> {
|
||||||
Slice {
|
Slice {
|
||||||
descs: Descs<'v>,
|
descs: Descs<'v>,
|
||||||
|
|||||||
@ -5,7 +5,9 @@ pub mod errors;
|
|||||||
pub mod opera;
|
pub mod opera;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
pub mod table_path;
|
||||||
pub mod tables;
|
pub mod tables;
|
||||||
|
|
||||||
pub use crate::decoder::Decoder;
|
pub use crate::decoder::Decoder;
|
||||||
pub use crate::parser::*;
|
pub use crate::parser::*;
|
||||||
|
pub use crate::table_path::{get_tables_base_path, set_tables_base_path};
|
||||||
|
|||||||
65
rbufr/src/table_path.rs
Normal file
65
rbufr/src/table_path.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
static TABLES_BASE_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn set_tables_base_path<P: AsRef<Path>>(path: P) {
|
||||||
|
let _ = TABLES_BASE_PATH.set(path.as_ref().to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tables_base_path() -> PathBuf {
|
||||||
|
if let Some(path) = TABLES_BASE_PATH.get() {
|
||||||
|
return path.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(env_path) = std::env::var("RBUFR_TABLES_PATH") {
|
||||||
|
return PathBuf::from(env_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "python_bindings")]
|
||||||
|
if let Some(python_path) = try_find_python_package_path() {
|
||||||
|
return python_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathBuf::from("tables")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn try_find_python_package_path() -> Option<PathBuf> {
|
||||||
|
if let Ok(exe_path) = std::env::current_exe() {
|
||||||
|
let mut candidate = exe_path.parent()?.to_path_buf();
|
||||||
|
candidate.push("rbufrp");
|
||||||
|
candidate.push("tables");
|
||||||
|
if candidate.exists() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_table_path<P: AsRef<Path>>(relative_path: P) -> PathBuf {
|
||||||
|
let base = get_tables_base_path();
|
||||||
|
base.join(relative_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_and_get_path() {
|
||||||
|
set_tables_base_path("/custom/tables/path");
|
||||||
|
let path = get_tables_base_path();
|
||||||
|
assert_eq!(path, PathBuf::from("/custom/tables/path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_table_path() {
|
||||||
|
set_tables_base_path("/base");
|
||||||
|
let table_path = get_table_path("master/BUFR_TableB_0.bufrtbl");
|
||||||
|
assert_eq!(
|
||||||
|
table_path,
|
||||||
|
PathBuf::from("/base/master/BUFR_TableB_0.bufrtbl")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -52,18 +52,16 @@ impl LocalTable {
|
|||||||
}
|
}
|
||||||
impl TableTrait for MasterTable {
|
impl TableTrait for MasterTable {
|
||||||
fn file_path(&self, table_type: TableType) -> PathBuf {
|
fn file_path(&self, table_type: TableType) -> PathBuf {
|
||||||
|
use crate::table_path::get_table_path;
|
||||||
|
|
||||||
match table_type {
|
match table_type {
|
||||||
TableType::B => {
|
TableType::B => {
|
||||||
let mut base_dir = PathBuf::new();
|
let file_name = format!("master/BUFR_TableB_{}.bufrtbl", self.version);
|
||||||
base_dir.push("tables/master");
|
get_table_path(file_name)
|
||||||
let file_name = format!("BUFR_TableB_{}.bufrtbl", self.version);
|
|
||||||
base_dir.join(file_name)
|
|
||||||
}
|
}
|
||||||
TableType::D => {
|
TableType::D => {
|
||||||
let mut base_dir = PathBuf::new();
|
let file_name = format!("master/BUFR_TableD_{}.bufrtbl", self.version);
|
||||||
base_dir.push("tables/master");
|
get_table_path(file_name)
|
||||||
let file_name = format!("BUFR_TableD_{}.bufrtbl", self.version);
|
|
||||||
base_dir.join(file_name)
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Table type not supported for MasterTable")
|
unreachable!("Table type not supported for MasterTable")
|
||||||
@ -74,26 +72,24 @@ impl TableTrait for MasterTable {
|
|||||||
|
|
||||||
impl TableTrait for LocalTable {
|
impl TableTrait for LocalTable {
|
||||||
fn file_path(&self, table_type: TableType) -> PathBuf {
|
fn file_path(&self, table_type: TableType) -> PathBuf {
|
||||||
|
use crate::table_path::get_table_path;
|
||||||
|
|
||||||
match table_type {
|
match table_type {
|
||||||
TableType::B => {
|
TableType::B => {
|
||||||
let mut base_dir = PathBuf::new();
|
|
||||||
base_dir.push("tables/local");
|
|
||||||
let sub_center_str = match self.sub_center {
|
let sub_center_str = match self.sub_center {
|
||||||
Some(sc) => format!("{}", sc),
|
Some(sc) => format!("{}", sc),
|
||||||
None => "0".to_string(),
|
None => "0".to_string(),
|
||||||
};
|
};
|
||||||
let file_name = format!("BUFR_TableB_{}_{}.bufrtbl", sub_center_str, self.version);
|
let file_name = format!("local/BUFR_TableB_{}_{}.bufrtbl", sub_center_str, self.version);
|
||||||
base_dir.join(file_name)
|
get_table_path(file_name)
|
||||||
}
|
}
|
||||||
TableType::D => {
|
TableType::D => {
|
||||||
let mut base_dir = PathBuf::new();
|
|
||||||
base_dir.push("tables/local");
|
|
||||||
let sub_center_str = match self.sub_center {
|
let sub_center_str = match self.sub_center {
|
||||||
Some(sc) => format!("{}", sc),
|
Some(sc) => format!("{}", sc),
|
||||||
None => "0".to_string(),
|
None => "0".to_string(),
|
||||||
};
|
};
|
||||||
let file_name = format!("BUFR_TableD_{}_{}.bufrtbl", sub_center_str, self.version);
|
let file_name = format!("local/BUFR_TableD_{}_{}.bufrtbl", sub_center_str, self.version);
|
||||||
base_dir.join(file_name)
|
get_table_path(file_name)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Table type not supported for LocalTable")
|
unreachable!("Table type not supported for LocalTable")
|
||||||
@ -104,12 +100,12 @@ impl TableTrait for LocalTable {
|
|||||||
|
|
||||||
impl TableTrait for BitmapTable {
|
impl TableTrait for BitmapTable {
|
||||||
fn file_path(&self, table_type: TableType) -> PathBuf {
|
fn file_path(&self, table_type: TableType) -> PathBuf {
|
||||||
|
use crate::table_path::get_table_path;
|
||||||
|
|
||||||
match table_type {
|
match table_type {
|
||||||
TableType::BitMap => {
|
TableType::BitMap => {
|
||||||
let mut base_dir = PathBuf::new();
|
let file_name = format!("opera/BUFR_Opera_Bitmap_{}.bufrtbl", self.center);
|
||||||
base_dir.push("tables/opera");
|
get_table_path(file_name)
|
||||||
let file_name = format!("BUFR_Opera_Bitmap_{}.bufrtbl", self.center);
|
|
||||||
base_dir.join(file_name)
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Table type not supported for BitmapTable")
|
unreachable!("Table type not supported for BitmapTable")
|
||||||
|
|||||||
@ -13,4 +13,4 @@ crate-type = ["cdylib"]
|
|||||||
# "abi3-py39" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.9
|
# "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"] }
|
pyo3 = { version = "0.27.1", features = ["extension-module", "abi3-py38"] }
|
||||||
|
|
||||||
rbufr = { path = "../rbufr" }
|
rbufr = { path = "../rbufr", features = ["python_bindings"] }
|
||||||
|
|||||||
1
rbufrp/MANIFEST.in
Normal file
1
rbufrp/MANIFEST.in
Normal file
@ -0,0 +1 @@
|
|||||||
|
recursive-include ../rbufr/tables *.bufrtbl
|
||||||
@ -3,9 +3,7 @@ name = "rbufrp"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [{ name = "Tsuki", email = "qwin7989@gmail.com" }]
|
||||||
{ name = "Tsuki", email = "qwin7989@gmail.com" }
|
|
||||||
]
|
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
@ -16,9 +14,18 @@ rbufrp = "rbufrp:main"
|
|||||||
module-name = "rbufrp._core"
|
module-name = "rbufrp._core"
|
||||||
python-packages = ["rbufrp"]
|
python-packages = ["rbufrp"]
|
||||||
python-source = "src"
|
python-source = "src"
|
||||||
|
include = [
|
||||||
|
{ path = "../rbufr/tables", format = "sdist" },
|
||||||
|
{ path = "../rbufr/tables", format = "wheel" },
|
||||||
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
cache-keys = [{ file = "pyproject.toml" }, { file = "src/**/*.rs" }, { file = "Cargo.toml" }, { file = "Cargo.lock" }]
|
cache-keys = [
|
||||||
|
{ file = "pyproject.toml" },
|
||||||
|
{ file = "src/**/*.rs" },
|
||||||
|
{ file = "Cargo.toml" },
|
||||||
|
{ file = "Cargo.lock" },
|
||||||
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["maturin>=1.0,<2.0"]
|
requires = ["maturin>=1.0,<2.0"]
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
use librbufr::{Decoder, parse};
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
mod _core {
|
mod _core {
|
||||||
use librbufr::{
|
use librbufr::{
|
||||||
|
Decoder,
|
||||||
block::{BUFRFile as IB, MessageBlock as IM},
|
block::{BUFRFile as IB, MessageBlock as IM},
|
||||||
decoder::BUFRData,
|
decoder::BUFRParsed as _BUFRParsed,
|
||||||
errors::Error,
|
errors::Error,
|
||||||
parse,
|
get_tables_base_path, parse, set_tables_base_path,
|
||||||
};
|
};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
fn hello_from_bin() -> String {
|
fn set_tables_path(path: &str) -> PyResult<()> {
|
||||||
"Hello from rbufrp!".to_string()
|
set_tables_base_path(path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyfunction]
|
||||||
|
fn get_tables_path() -> PyResult<String> {
|
||||||
|
let path = get_tables_base_path();
|
||||||
|
Ok(path.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
@ -26,7 +33,7 @@ mod _core {
|
|||||||
BUFRDecoder {}
|
BUFRDecoder {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_bufr(&self, file_path: &str) -> PyResult<BUFRFile> {
|
fn decode(&self, file_path: &str) -> PyResult<BUFRFile> {
|
||||||
let parsed = parse(file_path).map_err(|e| match e {
|
let parsed = parse(file_path).map_err(|e| match e {
|
||||||
Error::Io(io_err) => {
|
Error::Io(io_err) => {
|
||||||
PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("IO Error: {}", io_err))
|
PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("IO Error: {}", io_err))
|
||||||
@ -48,6 +55,24 @@ mod _core {
|
|||||||
|
|
||||||
Ok(BUFRFile(parsed))
|
Ok(BUFRFile(parsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_message(&self, message: &BUFRMessage) -> PyResult<BUFRParsed> {
|
||||||
|
self._parse_message(message).map_err(|e| {
|
||||||
|
PyErr::new::<pyo3::exceptions::PyException, _>(format!(
|
||||||
|
"Error parsing BUFR message: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BUFRDecoder {
|
||||||
|
fn _parse_message(&self, message: &BUFRMessage) -> librbufr::errors::Result<BUFRParsed> {
|
||||||
|
let _message = &message.message;
|
||||||
|
let mut decoder = Decoder::from_message(_message)?;
|
||||||
|
let record = decoder.decode(_message)?.into_owned();
|
||||||
|
Ok(BUFRParsed(record))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
@ -78,4 +103,25 @@ mod _core {
|
|||||||
struct BUFRMessage {
|
struct BUFRMessage {
|
||||||
message: IM,
|
message: IM,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl BUFRMessage {
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("{}", self.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> u8 {
|
||||||
|
self.message.version()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct BUFRParsed(_BUFRParsed<'static>);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl BUFRParsed {
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("{}", &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,93 @@
|
|||||||
from rbufrp._core import hello_from_bin
|
"""
|
||||||
|
rbufrp - BUFR (Binary Universal Form for the Representation of meteorological data) decoder
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# Import the Rust extension module
|
||||||
|
from ._core import (
|
||||||
|
set_tables_path,
|
||||||
|
get_tables_path,
|
||||||
|
BUFRDecoder,
|
||||||
|
BUFRFile,
|
||||||
|
BUFRMessage,
|
||||||
|
BUFRParsed,
|
||||||
|
)
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
__all__ = [
|
||||||
|
"BUFRDecoder",
|
||||||
|
"BUFRFile",
|
||||||
|
"BUFRMessage",
|
||||||
|
"BUFRParsed",
|
||||||
|
"set_tables_path",
|
||||||
|
"get_tables_path",
|
||||||
|
"initialize_tables_path",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_tables_directory() -> Optional[Path]:
|
||||||
|
env_path = os.environ.get("RBUFR_TABLES_PATH")
|
||||||
|
if env_path:
|
||||||
|
tables_path = Path(env_path)
|
||||||
|
if tables_path.exists() and tables_path.is_dir():
|
||||||
|
return tables_path
|
||||||
|
|
||||||
|
package_dir = Path(__file__).parent
|
||||||
|
installed_tables = package_dir / "tables"
|
||||||
|
if installed_tables.exists() and installed_tables.is_dir():
|
||||||
|
return installed_tables
|
||||||
|
|
||||||
|
dev_tables = package_dir.parent.parent.parent / "rbufr" / "tables"
|
||||||
|
if dev_tables.exists() and dev_tables.is_dir():
|
||||||
|
return dev_tables
|
||||||
|
|
||||||
|
cwd_tables = Path.cwd() / "tables"
|
||||||
|
if cwd_tables.exists() and cwd_tables.is_dir():
|
||||||
|
return cwd_tables
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_tables_path(custom_path: Optional[str | Path] = None) -> None:
|
||||||
|
if custom_path:
|
||||||
|
custom_path = Path(custom_path)
|
||||||
|
if not custom_path.exists():
|
||||||
|
raise RuntimeError(f"指定的 tables 路径不存在: {custom_path}")
|
||||||
|
set_tables_path(str(custom_path.absolute()))
|
||||||
|
return
|
||||||
|
|
||||||
|
tables_dir = _find_tables_directory()
|
||||||
|
if tables_dir is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"无法找到 BUFR tables 目录。请执行以下操作之一:\n"
|
||||||
|
"1. 设置环境变量 RBUFR_TABLES_PATH\n"
|
||||||
|
"2. 使用 initialize_tables_path('/path/to/tables') 手动指定\n"
|
||||||
|
"3. 确保在包含 tables 目录的位置运行"
|
||||||
|
)
|
||||||
|
|
||||||
|
set_tables_path(str(tables_dir.absolute()))
|
||||||
|
|
||||||
|
|
||||||
|
# 自动初始化 tables 路径
|
||||||
|
try:
|
||||||
|
initialize_tables_path()
|
||||||
|
except RuntimeError as e:
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
f"Tables 路径自动初始化失败: {e}\n"
|
||||||
|
"您可以稍后手动调用 rbufrp.initialize_tables_path() 来设置",
|
||||||
|
UserWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
print(hello_from_bin())
|
"""命令行入口点"""
|
||||||
|
print(f"Tables path: {get_tables_path()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
BIN
rbufrp/src/rbufrp/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
rbufrp/src/rbufrp/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -1 +1,151 @@
|
|||||||
def hello_from_bin() -> str: ...
|
"""
|
||||||
|
Type stubs for rbufrp._core
|
||||||
|
|
||||||
|
This file provides type hints for the Rust extension module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
class BUFRDecoder:
|
||||||
|
"""BUFR decoder for parsing BUFR files."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Create a new BUFR decoder instance."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def decode(self, file_path: str) -> BUFRFile:
|
||||||
|
"""
|
||||||
|
Decode a BUFR file from the given path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the BUFR file to decode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BUFRFile: Parsed BUFR file containing messages
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
IOError: If the file cannot be read
|
||||||
|
ValueError: If the file is not a valid BUFR file
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def parse_message(self, message: BUFRMessage) -> BUFRParsed:
|
||||||
|
"""
|
||||||
|
Parse a single BUFR message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: The BUFR message to parse
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BUFRParsed: Parsed data from the message
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: If parsing fails
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class BUFRFile:
|
||||||
|
"""
|
||||||
|
Represents a parsed BUFR file containing one or more messages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return a string representation of the BUFR file."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def message_count(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the number of messages in the file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Number of BUFR messages
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_message(self, index: int) -> BUFRMessage:
|
||||||
|
"""
|
||||||
|
Get a specific message by index.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
index: Zero-based index of the message
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BUFRMessage: The requested message
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
IndexError: If the index is out of range
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class BUFRMessage:
|
||||||
|
"""
|
||||||
|
Represents a single BUFR message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return a string representation of the message."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def version(self) -> int:
|
||||||
|
"""
|
||||||
|
Get the BUFR edition/version number.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: BUFR edition (typically 2, 3, or 4)
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
class BUFRParsed:
|
||||||
|
"""
|
||||||
|
Represents parsed BUFR data.
|
||||||
|
|
||||||
|
This class contains the decoded meteorological data from a BUFR message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""
|
||||||
|
Return a formatted string representation of the parsed data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Human-readable representation of all records
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def set_tables_path(path: str) -> None:
|
||||||
|
"""
|
||||||
|
Set the base path for BUFR table files.
|
||||||
|
|
||||||
|
This function configures where the decoder should look for BUFR table files
|
||||||
|
(Table B, Table D, etc.) needed for decoding messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Absolute path to the directory containing BUFR tables
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> import rbufrp
|
||||||
|
>>> rbufrp.set_tables_path("/usr/share/bufr/tables")
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_tables_path() -> str:
|
||||||
|
"""
|
||||||
|
Get the currently configured base path for BUFR table files.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Current tables directory path
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> import rbufrp
|
||||||
|
>>> print(rbufrp.get_tables_path())
|
||||||
|
/usr/share/bufr/tables
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"BUFRDecoder",
|
||||||
|
"BUFRFile",
|
||||||
|
"BUFRMessage",
|
||||||
|
"BUFRParsed",
|
||||||
|
"set_tables_path",
|
||||||
|
"get_tables_path",
|
||||||
|
]
|
||||||
|
|||||||
0
rbufrp/src/rbufrp/py.typed
Normal file
0
rbufrp/src/rbufrp/py.typed
Normal file
Loading…
Reference in New Issue
Block a user