conspire 0.7.0

The Rust interface to conspire.
Documentation
#[cfg(test)]
mod test;

use crate::{
    geometry::{
        Coordinates,
        mesh::{Connectivity, Mesh},
    },
    math::Scalar,
};
use std::{
    fs::read_to_string,
    io::{Error, ErrorKind, Result},
    path::Path,
    str::FromStr,
};

pub trait ReadMedit<P>
where
    P: AsRef<Path>,
    Self: Sized,
{
    fn read_medit(input: P) -> Result<Self>;
}

impl<const D: usize, P> ReadMedit<P> for Mesh<D>
where
    P: AsRef<Path>,
{
    fn read_medit(input: P) -> Result<Self> {
        let text = read_to_string(input)?;
        let mut tokens = text.split_whitespace();
        let mut coordinates: Vec<[Scalar; D]> = Vec::new();
        let mut blocks: Vec<Connectivity> = Vec::new();
        while let Some(keyword) = tokens.next() {
            match keyword {
                "MeshVersionFormatted" | "MeshVersionUnformatted" => {
                    next_token(&mut tokens)?;
                }
                "Dimension" => {
                    let dimension: usize = next_parse(&mut tokens)?;
                    if dimension != D {
                        return Err(invalid(format!(
                            ".mesh Dimension {dimension} but Mesh was asked for D={D}"
                        )));
                    }
                }
                "Vertices" => {
                    let count: usize = next_parse(&mut tokens)?;
                    for _ in 0..count {
                        let mut point = [0.0; D];
                        for value in &mut point {
                            *value = next_parse(&mut tokens)?;
                        }
                        let _reference: i64 = next_parse(&mut tokens)?;
                        coordinates.push(point);
                    }
                }
                "Triangles" => blocks.push(Connectivity::Triangular(
                    read_elements::<3, _>(&mut tokens)?.into(),
                )),
                "Quadrilaterals" => blocks.push(Connectivity::Quadrilateral(
                    read_elements::<4, _>(&mut tokens)?.into(),
                )),
                "Tetrahedra" => blocks.push(Connectivity::Tetrahedral(
                    read_elements::<4, _>(&mut tokens)?.into(),
                )),
                "Pyramids" => blocks.push(Connectivity::Pyramidal(
                    read_elements::<5, _>(&mut tokens)?.into(),
                )),
                "Prisms" | "Pentahedra" => blocks.push(Connectivity::Wedge(
                    read_elements::<6, _>(&mut tokens)?.into(),
                )),
                "Hexahedra" => blocks.push(Connectivity::Hexahedral(
                    read_elements::<8, _>(&mut tokens)?.into(),
                )),
                "Edges" => skip_records(&mut tokens, 3)?,
                "Corners" | "Ridges" | "RequiredVertices" | "RequiredEdges" => {
                    skip_records(&mut tokens, 1)?
                }
                "End" => break,
                other => return Err(invalid(format!("unsupported .mesh keyword: {other}"))),
            }
        }
        let coordinates: Coordinates<D> =
            coordinates.into_iter().map(|point| point.into()).collect();
        Ok((blocks, coordinates).into())
    }
}

fn invalid(message: String) -> Error {
    Error::new(ErrorKind::InvalidData, message)
}

fn next_token<'a, I>(tokens: &mut I) -> Result<&'a str>
where
    I: Iterator<Item = &'a str>,
{
    tokens
        .next()
        .ok_or_else(|| Error::new(ErrorKind::UnexpectedEof, "unexpected end of .mesh file"))
}

fn next_parse<'a, I, T>(tokens: &mut I) -> Result<T>
where
    I: Iterator<Item = &'a str>,
    T: FromStr,
{
    let token = next_token(tokens)?;
    token
        .parse()
        .map_err(|_| invalid(format!("could not parse '{token}' in .mesh file")))
}

fn read_elements<'a, const N: usize, I>(tokens: &mut I) -> Result<Vec<[usize; N]>>
where
    I: Iterator<Item = &'a str>,
{
    let count: usize = next_parse(tokens)?;
    let mut elements = Vec::with_capacity(count);
    for _ in 0..count {
        let mut nodes = [0; N];
        for node in &mut nodes {
            let one_based: usize = next_parse(tokens)?;
            *node = one_based
                .checked_sub(1)
                .ok_or_else(|| invalid("node index 0 in .mesh (expected 1-based)".into()))?;
        }
        let _reference: i64 = next_parse(tokens)?;
        elements.push(nodes);
    }
    Ok(elements)
}

fn skip_records<'a, I>(tokens: &mut I, per_record: usize) -> Result<()>
where
    I: Iterator<Item = &'a str>,
{
    let count: usize = next_parse(tokens)?;
    for _ in 0..count * per_record {
        next_token(tokens)?;
    }
    Ok(())
}