Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make common API for &str/DimensionIdentifier #131

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 171 additions & 1 deletion netcdf/src/dimension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,118 @@ use netcdf_sys::*;

use super::error;

mod sealed {
pub trait Sealed {}
}

/// Types which can be used to distinguish dimensions
/// in a netCDF file. This can be `&str` (the normal use case)
/// or a special identifier when using nested groups.
///
/// This trait is not expected to be implemented elsewhere and is therefore sealed.
///
/// # Examples
/// (helper function to show consumption of type)
/// ```rust,no_run
/// # use netcdf::AsNcDimensions;
/// fn take(d: impl AsNcDimensions) {}
/// ```
/// Normally one uses the name of the dimension to specify the dimension
/// ```rust,no_run
/// # use netcdf::AsNcDimensions;
/// # fn take(d: impl AsNcDimensions) {}
/// take(()); // scalar
/// take("x"); // single dimension
/// take(["x", "y"]); // multiple dimensions
/// ```
/// When working with dimensions across groups, it might be necessary
/// to use dimension identifiers to get the correct group dimension
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use netcdf::AsNcDimensions;
/// # fn take(d: impl AsNcDimensions) {}
/// let file = netcdf::open("test.nc")?;
/// let dim = file.dimension("x").expect("File does not contain dimension");
/// let dimid = dim.identifier();
///
/// take(dimid); // from a dimension identifier
/// take([dimid, dimid]); // from multiple identifiers
/// # Ok(()) }
/// ```
pub trait AsNcDimensions: sealed::Sealed {
/// Convert from a slice of [`&str`]/[`DimensionIdentifier`] to concrete dimensions
/// which are guaranteed to exist in this file
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>>;
}

impl sealed::Sealed for &[&str] {}
impl AsNcDimensions for &[&str] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.iter()
.map(|&name| match dimension_from_name(ncid, name) {
Ok(Some(x)) => Ok(x),
Ok(None) => Err(format!("Dimension {name} not found").into()),
Err(e) => Err(e),
})
.collect()
}
}
impl<const N: usize> sealed::Sealed for [&str; N] {}
impl<const N: usize> AsNcDimensions for [&str; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl<const N: usize> sealed::Sealed for &[&str; N] {}
impl<const N: usize> AsNcDimensions for &[&str; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}

impl sealed::Sealed for &[DimensionIdentifier] {}
impl AsNcDimensions for &[DimensionIdentifier] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.iter()
.map(|dimid| match dimension_from_identifier(ncid, *dimid) {
Ok(Some(x)) => Ok(x),
Ok(None) => Err("Dimension id does not exist".into()),
Err(e) => Err(e),
})
.collect()
}
}
impl<const N: usize> sealed::Sealed for [DimensionIdentifier; N] {}
impl<const N: usize> AsNcDimensions for [DimensionIdentifier; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl<const N: usize> sealed::Sealed for &[DimensionIdentifier; N] {}
impl<const N: usize> AsNcDimensions for &[DimensionIdentifier; N] {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
self.as_slice().get_dimensions(ncid)
}
}
impl sealed::Sealed for () {}
impl AsNcDimensions for () {
fn get_dimensions<'g>(&self, _ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
Ok(Vec::new())
}
}
impl sealed::Sealed for &str {}
impl AsNcDimensions for &str {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
([*self]).get_dimensions(ncid)
}
}
impl sealed::Sealed for DimensionIdentifier {}
impl AsNcDimensions for DimensionIdentifier {
fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
([*self]).get_dimensions(ncid)
}
}

/// Represents a netcdf dimension
#[derive(Debug, Clone)]
pub struct Dimension<'g> {
Expand All @@ -25,9 +137,23 @@ pub struct DimensionIdentifier {
pub(crate) dimid: nc_type,
}

impl DimensionIdentifier {
// Internal netcdf detail, the top 16 bits gives the corresponding
// file handle. This to ensure dimensions are not added from another
// file which is unrelated to self
pub(crate) fn belongs_to(&self, ncid: nc_type) -> bool {
self.ncid >> 16 == ncid >> 16
}
}

#[allow(clippy::len_without_is_empty)]
impl<'g> Dimension<'g> {
/// Get current length of this dimension
///
/// ## Note
/// A dimension can be unlimited (growable) and changes size
/// if putting values to a variable which uses this
/// dimension
pub fn len(&self) -> usize {
if let Some(x) = self.len {
x.get()
Expand Down Expand Up @@ -66,7 +192,10 @@ impl<'g> Dimension<'g> {
}

/// Grabs the unique identifier for this dimension, which
/// can be used in `add_variable_from_identifiers`
/// can be used in [`add_variable`](crate::FileMut::add_variable).
///
/// This is useful when working with nested groups and need
/// to distinguish between names at different levels.
pub fn identifier(&self) -> DimensionIdentifier {
self.id
}
Expand Down Expand Up @@ -272,6 +401,47 @@ pub(crate) fn dimension_from_name<'f>(
}))
}

pub(crate) fn dimension_from_identifier<'f>(
ncid: nc_type,
dimid: DimensionIdentifier,
) -> error::Result<Option<Dimension<'f>>> {
if !dimid.belongs_to(ncid) {
return Err(error::Error::WrongDataset);
}
let dimid = dimid.dimid;

let mut dimlen = 0;
unsafe {
error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap();
}
if dimlen != 0 {
// Have to check if this dimension is unlimited
let mut nunlim = 0;
unsafe {
error::checked(super::with_lock(|| {
nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut())
}))?;
}
if nunlim != 0 {
let mut unlimdims = Vec::with_capacity(nunlim.try_into()?);
unsafe {
error::checked(super::with_lock(|| {
nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr())
}))?;
}
unsafe { unlimdims.set_len(nunlim.try_into()?) }
if unlimdims.contains(&dimid) {
dimlen = 0;
}
}
}
Ok(Some(Dimension {
len: core::num::NonZeroUsize::new(dimlen),
id: super::dimension::DimensionIdentifier { ncid, dimid },
_group: PhantomData,
}))
}

pub(crate) fn add_dimension_at<'f>(
ncid: nc_type,
name: &str,
Expand Down
57 changes: 37 additions & 20 deletions netcdf/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::path;
use netcdf_sys::*;

use super::attribute::{Attribute, AttributeValue};
use super::dimension::{self, Dimension};
use super::dimension::{AsNcDimensions, Dimension};
use super::error;
use super::group::{Group, GroupMut};
use super::variable::{NcPutGet, Variable, VariableMut};
Expand Down Expand Up @@ -338,20 +338,50 @@ impl FileMut {
))
}

/// Create a Variable into the dataset, with no data written into it
/// Create a variable in the dataset, with no data written into it
///
/// Dimensions are identified using the name of the dimension, and will recurse upwards
/// if not found in the current group.
pub fn add_variable<'f, T>(
/// # Examples
/// ## Adding variables with different types
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("file.nc")?;
/// file.add_dimension("x", 10)?;
/// file.add_dimension("y", 11)?;
/// file.add_variable::<u8, _>("var_u8", &["y", "x"])?;
/// file.add_variable::<i8, _>("var_i8", &["y", "x"])?;
/// file.add_variable::<u64, _>("var_u64", &["y", "x"])?;
/// file.add_variable::<f64, _>("var_f64", &["y", "x"])?;
/// # Ok(())}
/// ```
/// ## Adding a scalar variable
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("scalar.nc")?;
/// file.add_variable::<u8, _>("var2", ())?;
/// # Ok(())}
/// ```
/// ## Using dimension identifiers in place of names
/// ```rust,no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut file = netcdf::create("dimids.nc")?;
/// let dimx = file.dimension("x").unwrap().identifier();
/// let dimy = file.dimension("y").unwrap().identifier();
/// file.add_variable::<u8, _>("var2", &[dimy, dimx])?;
/// # Ok(())}
///```
///
/// See [`AsNcDimensions`] for how to specify dimensions.
pub fn add_variable<'f, T, D>(
&'f mut self,
name: &str,
dims: &[&str],
dims: D,
) -> error::Result<VariableMut<'f>>
where
T: NcPutGet,
D: AsNcDimensions,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
}

/// Create a variable with the specified type
Expand Down Expand Up @@ -410,19 +440,6 @@ impl FileMut {
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
}
/// Adds a variable from a set of unique identifiers, recursing upwards
/// from the current group if necessary.
pub fn add_variable_from_identifiers<'f, T>(
&'f mut self,
name: &str,
dims: &[dimension::DimensionIdentifier],
) -> error::Result<VariableMut<'f>>
where
T: NcPutGet,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
}
}

#[cfg(feature = "has-mmap")]
Expand Down
28 changes: 10 additions & 18 deletions netcdf/src/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::marker::PhantomData;
use netcdf_sys::*;

use super::attribute::{Attribute, AttributeValue};
use super::dimension::Dimension;
use super::dimension::{AsNcDimensions, Dimension};
use super::error;
use super::variable::{NcPutGet, Variable, VariableMut};

Expand Down Expand Up @@ -244,18 +244,23 @@ impl<'f> GroupMut<'f> {
/// Create a Variable into the dataset, with no data written into it
///
/// Dimensions are identified using the name of the dimension, and will recurse upwards
/// if not found in the current group.
pub fn add_variable<'g, T>(
/// if not found in the current group. If the name is shadowed one can get an
/// [`DimensionIdentifier`](crate::DimensionIdentifier) to ensure the right dimension
/// is used
///
/// See [`AsNcDimensions`] for how to specify dimensions.
pub fn add_variable<'g, T, D>(
&'g mut self,
name: &str,
dims: &[&str],
dims: D,
) -> error::Result<VariableMut<'g>>
where
T: NcPutGet,
'f: 'g,
D: AsNcDimensions,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
}
/// Adds a variable with a basic type of string
pub fn add_string_variable<'g>(
Expand All @@ -266,19 +271,6 @@ impl<'f> GroupMut<'f> {
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
VariableMut::add_from_str(ncid, NC_STRING, name, dims)
}
/// Adds a variable from a set of unique identifiers, recursing upwards
/// from the current group if necessary.
pub fn add_variable_from_identifiers<'g, T>(
&'g mut self,
name: &str,
dims: &[super::dimension::DimensionIdentifier],
) -> error::Result<VariableMut<'g>>
where
T: NcPutGet,
{
let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
}

/// Create a variable with the specified type
pub fn add_variable_with_type(
Expand Down
4 changes: 2 additions & 2 deletions netcdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
//! file.add_unlimited_dimension("time")?;
//!
//! // A variable can now be declared, and must be created from the dimension names.
//! let mut var = file.add_variable::<i32>(
//! let mut var = file.add_variable::<i32, _>(
//! "crab_coolness_level",
//! &["time", "ncrabs"],
//! )?;
Expand Down Expand Up @@ -114,7 +114,7 @@ pub mod types;
pub(crate) mod variable;

pub use attribute::{Attribute, AttributeValue};
pub use dimension::{Dimension, DimensionIdentifier};
pub use dimension::{AsNcDimensions, Dimension, DimensionIdentifier};
pub use error::{Error, Result};
pub use extent::{Extent, Extents};
#[cfg(feature = "has-mmap")]
Expand Down
Loading