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`] — the `FoamFile` **dictionary** format: a tokeniser (strips
34//!   `//` and `/* */` comments; treats `( ) { } ; [ ]` as delimiters), an
35//!   in-memory AST ([`FoamDict`], [`FoamEntry`], [`FoamValue`],
36//!   [`Dimensioned`]), the [`FoamHeader`] block, and an exact-round-trip
37//!   writer. Handles `system/controlDict`, `fvSchemes`, `fvSolution`-style
38//!   dictionaries.
39//! - [`poly_mesh`] — [`PolyMesh`]: read/write `constant/polyMesh/{points,
40//!   faces, owner, neighbour, boundary}` and convert to the crate's
41//!   geometry-carrying [`crate::mesh::FvMesh`] via [`PolyMesh::to_fv_mesh`].
42//! - [`field`] — read/write a time-directory field file
43//!   (`0/p` volScalarField, `0/U` volVectorField): the `dimensions`,
44//!   `internalField`, and `boundaryField` blocks ↔ the crate's
45//!   [`crate::fields::VolScalarField`] / [`crate::fields::VolVectorField`].
46//! - [`case`] — [`FoamCase`]: read a whole case directory (`system/…`,
47//!   `constant/polyMesh`, `0/…`) into memory, with a best-effort writer.
48//!
49//! ## Round-trip guarantee
50//!
51//! The writer and parser are inverse at the **AST level**: constructing a
52//! [`FoamDict`] / [`PolyMesh`] / field, serialising it, and parsing it back
53//! reproduces an equal in-memory value. (It is not a byte-for-byte re-emitter
54//! of an arbitrary pre-existing file — comment banners and incidental
55//! whitespace are normalised — but every value round-trips.)
56
57pub mod case;
58pub mod dict;
59pub mod field;
60pub mod poly_mesh;
61
62pub use case::FoamCase;
63pub use dict::{Dimensioned, FoamDict, FoamEntry, FoamHeader, FoamValue};
64pub use field::{read_vol_scalar_field, read_vol_vector_field, write_vol_scalar_field, write_vol_vector_field};
65pub use poly_mesh::{MeshFace, PolyMesh};
66
67/// Errors raised while reading or writing OpenFOAM ASCII case files.
68#[derive(Debug, thiserror::Error)]
69pub enum IoError {
70    /// An underlying filesystem error (file missing, permission denied, …).
71    #[error("I/O error on {path}: {source}")]
72    Io {
73        /// The path being read or written when the error occurred.
74        path: String,
75        /// The underlying `std::io` error.
76        source: std::io::Error,
77    },
78
79    /// The token stream did not match the expected grammar.
80    #[error("parse error in {context}: {message}")]
81    Parse {
82        /// What was being parsed (file name / entry / block).
83        context: String,
84        /// Human-readable description of the mismatch.
85        message: String,
86    },
87
88    /// A boundary-condition or field type that this reader does not yet
89    /// support was encountered.
90    #[error("unsupported {kind} `{name}` in {context}")]
91    Unsupported {
92        /// Category of the unsupported item (e.g. `"boundaryField type"`).
93        kind: String,
94        /// The offending type/keyword.
95        name: String,
96        /// Where it was found.
97        context: String,
98    },
99
100    /// The parsed topology could not be assembled into a valid mesh.
101    #[error("mesh construction error: {0}")]
102    Mesh(String),
103}
104
105impl IoError {
106    /// Build a [`IoError::Parse`] from a context label and message.
107    pub(crate) fn parse(context: impl Into<String>, message: impl Into<String>) -> Self {
108        Self::Parse {
109            context: context.into(),
110            message: message.into(),
111        }
112    }
113
114    /// Build a [`IoError::Io`] wrapping a filesystem error at `path`.
115    pub(crate) fn io(path: impl Into<String>, source: std::io::Error) -> Self {
116        Self::Io {
117            path: path.into(),
118            source,
119        }
120    }
121}