Skip to main content

outram_foam_basic_lib/io/
mod.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// Copyright (C) 2026 OUTRAM PARK contributors
3// Derived from OpenFOAM (www.openfoam.com)
4// Copyright (C) 2004-2023 OpenFOAM Foundation
5// Copyright (C) 2016-2023 OpenCFD Ltd.
6//
7// This file is part of OUTRAM PARK.
8//
9// OUTRAM PARK is free software: you can redistribute it and/or modify it
10// under the terms of the GNU General Public License as published by the
11// Free Software Foundation, either version 3 of the License, or (at your
12// option) any later version.
13//
14// OUTRAM PARK is distributed in the hope that it will be useful, but
15// WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17// General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License along
20// with OUTRAM PARK.  If not, see <https://www.gnu.org/licenses/>.
21
22//! OpenFOAM ASCII **case I/O** — read and write OpenFOAM cases the way the
23//! upstream utilities do.
24//!
25//! This module is the foundation the OUTRAM PARK CLI reads/writes OpenFOAM
26//! cases with. The format and algorithms are OpenFOAM-derived (the `FoamFile`
27//! dictionary grammar, the `polyMesh` list layout, the time-directory field
28//! layout); this is an independent Rust re-implementation of the ASCII reader
29//! and writer, not the official OpenFOAM software.
30//!
31//! ## What lives here
32//!
33//! - [`dict`](crate::io::dict) — the `FoamFile` **dictionary** format: a tokeniser (strips
34//!   `//` and `/* */` comments; treats `( ) { } ; [ ]` as delimiters), an
35//!   in-memory AST ([`FoamDict`](crate::io::dict::FoamDict),
36//!   [`FoamEntry`](crate::io::dict::FoamEntry),
37//!   [`FoamValue`](crate::io::dict::FoamValue),
38//!   [`Dimensioned`](crate::io::dict::Dimensioned)), the
39//!   [`FoamHeader`](crate::io::dict::FoamHeader) block, and an exact-round-trip
40//!   writer. Handles `system/controlDict`, `fvSchemes`, `fvSolution`-style
41//!   dictionaries.
42//! - [`poly_mesh`](crate::io::poly_mesh) — [`PolyMesh`](crate::io::poly_mesh::PolyMesh): read/write `constant/polyMesh/{points,
43//!   faces, owner, neighbour, boundary}` and convert to the crate's
44//!   geometry-carrying [`crate::mesh::FvMesh`] via
45//!   [`PolyMesh::to_fv_mesh`](crate::io::poly_mesh::PolyMesh::to_fv_mesh).
46//! - [`field`](crate::io::field) — read/write a time-directory field file
47//!   (`0/p` volScalarField, `0/U` volVectorField): the `dimensions`,
48//!   `internalField`, and `boundaryField` blocks ↔ the crate's
49//!   [`crate::fields::VolScalarField`] / [`crate::fields::VolVectorField`].
50//! - [`case`](crate::io::case) — [`FoamCase`](crate::io::case::FoamCase): read a whole case directory (`system/…`,
51//!   `constant/polyMesh`, `0/…`) into memory, with a best-effort writer.
52//!
53//! ## Round-trip guarantee
54//!
55//! The writer and parser are inverse at the **AST level**: constructing a
56//! [`FoamDict`](crate::io::dict::FoamDict) /
57//! [`PolyMesh`](crate::io::poly_mesh::PolyMesh) / field, serialising it, and parsing it back
58//! reproduces an equal in-memory value. (It is not a byte-for-byte re-emitter
59//! of an arbitrary pre-existing file — comment banners and incidental
60//! whitespace are normalised — but every value round-trips.)
61
62pub mod case;
63pub mod dict;
64pub mod field;
65pub mod poly_mesh;
66
67pub use case::FoamCase;
68pub use dict::{Dimensioned, FoamDict, FoamEntry, FoamHeader, FoamValue};
69pub use field::{
70    read_vol_scalar_field, read_vol_vector_field, write_vol_scalar_field, write_vol_vector_field,
71};
72pub use poly_mesh::{MeshFace, PolyMesh};
73
74/// Errors raised while reading or writing OpenFOAM ASCII case files.
75#[derive(Debug, thiserror::Error)]
76pub enum IoError {
77    /// An underlying filesystem error (file missing, permission denied, …).
78    #[error("I/O error on {path}: {source}")]
79    Io {
80        /// The path being read or written when the error occurred.
81        path: String,
82        /// The underlying `std::io` error.
83        source: std::io::Error,
84    },
85
86    /// The token stream did not match the expected grammar.
87    #[error("parse error in {context}: {message}")]
88    Parse {
89        /// What was being parsed (file name / entry / block).
90        context: String,
91        /// Human-readable description of the mismatch.
92        message: String,
93    },
94
95    /// A boundary-condition or field type that this reader does not yet
96    /// support was encountered.
97    #[error("unsupported {kind} `{name}` in {context}")]
98    Unsupported {
99        /// Category of the unsupported item (e.g. `"boundaryField type"`).
100        kind: String,
101        /// The offending type/keyword.
102        name: String,
103        /// Where it was found.
104        context: String,
105    },
106
107    /// The parsed topology could not be assembled into a valid mesh.
108    #[error("mesh construction error: {0}")]
109    Mesh(String),
110}
111
112impl IoError {
113    /// Build a [`IoError::Parse`] from a context label and message.
114    pub(crate) fn parse(context: impl Into<String>, message: impl Into<String>) -> Self {
115        Self::Parse {
116            context: context.into(),
117            message: message.into(),
118        }
119    }
120
121    /// Build a [`IoError::Io`] wrapping a filesystem error at `path`.
122    pub(crate) fn io(path: impl Into<String>, source: std::io::Error) -> Self {
123        Self::Io {
124            path: path.into(),
125            source,
126        }
127    }
128}