1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
//! The path of an object in a file, parsed into the sequence of link names to walk.
//!
//! [`ObjectPath::parse`] decides the grammar for the reader's resolvers, both group walks and the
//! type builders, the way the C library decides it: a repeated or a trailing separator separates
//! nothing (`H5G__component` in `H5Gname.c`), a component that is exactly `.` identifies the group
//! a walk has reached, and a leading separator starts the walk at the root group
//! (`H5G__traverse_real` in `H5Gtraverse.c`, HDF5 1.14.6).
//!
//! [`LinkName`] is one component of a path, the name of a link in one group.
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
/// An object path: its components, and where the walk starts.
///
/// A path is absolute or relative, and either may be empty of components: `/`, `.` and the empty
/// string parse to none of them and identify the group the walk starts from, which is the root
/// group for an absolute path and the group the path is relative to for a relative one.
#[derive(Clone, Debug)]
pub(crate) struct ObjectPath<'a> {
/// The components, in order, with the separators and the `.` components dropped.
components: Vec<LinkName<'a>>,
/// Whether the spelling begins with a separator, which starts the walk at
/// the root group: `H5G__traverse_real` takes that branch before it reads
/// the first component (`H5Gtraverse.c`, HDF5 1.14.6).
absolute: bool,
}
impl<'a> ObjectPath<'a> {
/// Parses one spelling of an object path into its components.
///
/// `a/b`, `a//b`, `a/b/`, `./a/b` and `/a/b` parse to the same two components.
pub(crate) fn parse(path: &'a str) -> Self {
// The grammar is the C library's: `H5G__component` walks past the
// separators between two names, so a trailing or repeated one separates
// nothing, and `H5G__traverse_real` resolves a component that is `.` to
// the group it is already in (`H5Gname.c` and `H5Gtraverse.c`, HDF5
// 1.14.6). `LinkName::new` rejects exactly those components, so the
// filter drops them. A *leading* separator is the one that carries
// meaning, and `absolute` keeps it.
Self {
components: path.split(SEPARATOR).filter_map(LinkName::new).collect(),
absolute: path.starts_with(SEPARATOR),
}
}
/// Returns the path of the child `name` of the object this path identifies.
///
/// The child of an absolute path is absolute, so it starts the walk where this path starts it.
pub(crate) fn join(&self, name: LinkName<'a>) -> Self {
let mut components = self.components.clone();
components.push(name);
Self {
components,
absolute: self.absolute,
}
}
/// Resolves `rest` from the object this path identifies.
///
/// An absolute `rest` walks from the root group wherever this path ends, so it is the result
/// on its own.
pub(crate) fn join_path(&self, rest: &Self) -> Self {
if rest.absolute {
return rest.clone();
}
Self {
components: self
.components
.iter()
.chain(rest.components.iter())
.copied()
.collect(),
absolute: self.absolute,
}
}
/// Returns the path a walk has reached after `count` components, or this whole path
/// where it has fewer than that.
///
/// A walk that stops partway reports the object it had reached by this whole prefix, which is
/// a path the caller can go on to open: `a/b/c` stopped by a dataset at `a/b` reports `a/b`.
pub(crate) fn prefix(&self, count: usize) -> Self {
Self {
components: self.components.iter().copied().take(count).collect(),
absolute: self.absolute,
}
}
/// Returns the components, an empty slice for the path of the object a walk starts from.
pub(crate) fn components(&self) -> &[LinkName<'a>] {
&self.components
}
/// Returns `true` if the walk starts at the root group.
pub(crate) fn is_absolute(&self) -> bool {
self.absolute
}
}
/// Writes the components separated by `/`, the root-relative form a [`crate::Group`] handle
/// stores: `a/b` for every spelling of that path, and the empty string for the root group.
impl fmt::Display for ObjectPath<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (position, name) in self.components.iter().enumerate() {
if position > 0 {
f.write_str(SEPARATOR)?;
}
f.write_str(name.as_str())?;
}
Ok(())
}
}
/// The name of one link in one group, a single component of an [`ObjectPath`].
///
/// A lookup that takes one of these cannot take a whole path. `..` is an ordinary name here, and
/// the C library resolves `a/../b` through a link named `..` (`H5G__traverse_real` in
/// `H5Gtraverse.c`, HDF5 1.14.6).
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct LinkName<'a>(&'a str);
impl<'a> LinkName<'a> {
/// Returns `name` as the name of a link, or `None` for the empty string, for `.`, and for a
/// name holding a separator, none of which is one component of a path.
pub(crate) fn new(name: &'a str) -> Option<Self> {
(!name.is_empty() && name != CURRENT_GROUP && !name.contains(SEPARATOR))
.then_some(Self(name))
}
/// Returns the name as the group's link stores it.
pub(crate) fn as_str(self) -> &'a str {
self.0
}
}
/// Separates the components of an object path.
const SEPARATOR: &str = "/";
/// The component `H5G__traverse_real` resolves to the group it is already in
/// (`H5Gtraverse.c`, HDF5 1.14.6), so it identifies no link.
const CURRENT_GROUP: &str = ".";
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::{LinkName, ObjectPath};
#[rstest]
#[case("a/b")]
#[case("/a/b")]
#[case("a/b/")]
#[case("/a/b/")]
#[case("a//b")]
#[case("///a///b///")]
#[case("./a/b")]
#[case("a/./b")]
#[case("a/b/.")]
fn every_spelling_of_a_path_parses_to_the_same_components(#[case] spelling: &str) {
let path = ObjectPath::parse(spelling);
let components: Vec<&str> = path
.components()
.iter()
.copied()
.map(LinkName::as_str)
.collect();
assert_eq!(components, ["a", "b"]);
}
#[rstest]
#[case("")]
#[case("/")]
#[case("//")]
#[case(".")]
#[case("/./")]
#[case("./.")]
fn a_path_that_spells_no_link_has_no_components(#[case] spelling: &str) {
let path = ObjectPath::parse(spelling);
assert_eq!(path.components(), []);
assert_eq!(path.to_string(), "");
}
#[rstest]
#[case("/a//b/", "a/b")]
#[case("a", "a")]
fn a_parsed_path_displays_in_the_form_a_handle_stores(
#[case] spelling: &str,
#[case] stored: &str,
) {
assert_eq!(ObjectPath::parse(spelling).to_string(), stored);
}
#[test]
fn two_dots_is_an_ordinary_link_name() {
assert_eq!(ObjectPath::parse("a/../b").to_string(), "a/../b");
assert_eq!(LinkName::new("..").map(LinkName::as_str), Some(".."));
}
#[rstest]
#[case("a", Some("a"))]
#[case("", None)]
#[case(".", None)]
#[case("a/b", None)]
#[case("/", None)]
fn a_link_name_is_one_component_that_names_a_link(
#[case] name: &str,
#[case] link_name: Option<&str>,
) {
assert_eq!(LinkName::new(name).map(LinkName::as_str), link_name);
}
#[rstest]
#[case("", "a")]
#[case("g", "g/a")]
#[case("/g/h/", "g/h/a")]
fn joining_appends_one_component_to_a_path(#[case] base: &str, #[case] joined: &str) {
let name = LinkName::new("a").unwrap();
assert_eq!(ObjectPath::parse(base).join(name).to_string(), joined);
}
#[rstest]
#[case("/g/", "a//b", "g/a/b")]
#[case("/g/", "", "g")]
#[case("", "a", "a")]
fn joining_a_relative_path_appends_its_components(
#[case] base: &str,
#[case] rest: &str,
#[case] joined: &str,
) {
assert_eq!(
ObjectPath::parse(base)
.join_path(&ObjectPath::parse(rest))
.to_string(),
joined
);
}
#[rstest]
#[case("/g/", "/h/", "h")]
#[case("g/i", "/h/", "h")]
#[case("", "/g/", "g")]
fn joining_an_absolute_path_replaces_the_base(
#[case] base: &str,
#[case] rest: &str,
#[case] joined: &str,
) {
assert_eq!(
ObjectPath::parse(base)
.join_path(&ObjectPath::parse(rest))
.to_string(),
joined
);
}
#[rstest]
#[case(0, "")]
#[case(2, "a/b")]
#[case(3, "a/b/c")]
#[case(9, "a/b/c")]
fn a_prefix_names_the_object_reached_after_that_many_components(
#[case] count: usize,
#[case] prefix: &str,
) {
assert_eq!(ObjectPath::parse("a/b/c").prefix(count).to_string(), prefix);
}
}