Skip to content

Commit 719c168

Browse files
committed
Nits + SafelyIndexMut.
1 parent 0dac51e commit 719c168

File tree

6 files changed

+169
-60
lines changed

6 files changed

+169
-60
lines changed

saphyr/src/annotated/marked_yaml.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ use std::borrow::Cow;
77
use hashlink::LinkedHashMap;
88
use saphyr_parser::{Marker, ScalarStyle, Span, Tag};
99

10-
use crate::{Accessor, LoadableYamlNode, SafelyIndex, Scalar, Yaml, YamlData};
10+
use crate::{
11+
index::SafelyIndexMut, Accessor, LoadableYamlNode, SafelyIndex, Scalar, Yaml, YamlData,
12+
};
1113

1214
/// A YAML node with [`Span`]s pointing to the start of the node.
1315
///
@@ -129,6 +131,15 @@ impl SafelyIndex for MarkedYaml<'_> {
129131
}
130132
}
131133

134+
impl SafelyIndexMut for MarkedYaml<'_> {
135+
fn get_mut(&mut self, key: impl Into<Accessor>) -> Option<&mut Self> {
136+
match key.into() {
137+
Accessor::Field(f) => self.data.as_mapping_get_mut(f.as_str()),
138+
Accessor::Index(i) => self.data.as_sequence_get_mut(i),
139+
}
140+
}
141+
}
142+
132143
impl<'input> LoadableYamlNode<'input> for MarkedYaml<'input> {
133144
type HashKey = MarkedYaml<'input>;
134145

saphyr/src/annotated/marked_yaml_owned.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use std::borrow::Cow;
77
use hashlink::LinkedHashMap;
88
use saphyr_parser::{ScalarStyle, Span, Tag};
99

10-
use crate::{Accessor, LoadableYamlNode, SafelyIndex, ScalarOwned, Yaml, YamlDataOwned};
10+
use crate::{
11+
index::SafelyIndexMut, Accessor, LoadableYamlNode, SafelyIndex, ScalarOwned, Yaml,
12+
YamlDataOwned,
13+
};
1114

1215
/// A YAML node with [`Span`]s pointing to the start of the node.
1316
///
@@ -135,6 +138,15 @@ impl SafelyIndex for MarkedYamlOwned {
135138
}
136139
}
137140

141+
impl SafelyIndexMut for MarkedYamlOwned {
142+
fn get_mut(&mut self, key: impl Into<Accessor>) -> Option<&mut Self> {
143+
match key.into() {
144+
Accessor::Field(f) => self.data.as_mapping_get_mut(f.as_str()),
145+
Accessor::Index(i) => self.data.as_sequence_get_mut(i),
146+
}
147+
}
148+
}
149+
138150
impl LoadableYamlNode<'_> for MarkedYamlOwned {
139151
type HashKey = MarkedYamlOwned;
140152

saphyr/src/index.rs

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,46 @@
1-
/// A trait to safely index into a structure with an `Accessor`.
2-
/// This will never panic and return an `Option::None` on failure.
3-
pub trait SafelyIndex<X = Self> {
4-
/// Attempt to access a field
5-
fn get(&self, key: impl Into<Accessor>) -> Option<&X>;
1+
/// A trait to index without panicking into a structure through an [`Accessor`].
2+
///
3+
/// [`SafelyIndex`] is implemented on YAML objects to provide the [`get`] method to conveniently
4+
/// access sequence or mapping elements. This is similar to [`Index`]ing, except that the [`get`]
5+
/// method returns an [`Option`], using [`None`] rather than panicking when the requested index is
6+
/// out of range.
7+
///
8+
/// [`get`]: SafelyIndex::get
9+
/// [`Index`]: std::ops::Index
10+
pub trait SafelyIndex<Node = Self> {
11+
/// Access a field of the given YAML object.
12+
///
13+
/// # Return
14+
/// If the given index is valid within `self`, [`Some`] is returned with a reference to the
15+
/// indexed object. If `self` is not indexable or the index is out of bounds, this function
16+
/// returns [`None`].
17+
fn get(&self, key: impl Into<Accessor>) -> Option<&Node>;
618
}
719

8-
/// A way to access fields via the [`SafelyIndex`] trait
20+
/// A trait to index without panicking into a structure through an [`Accessor`] (mutable).
21+
///
22+
/// [`SafelyIndexMut`] is implemented on YAML objects to provide the [`get_mut`] method to
23+
/// conveniently access sequence or mapping elements. This is similar to [`IndexMut`]ing, except
24+
/// that the [`get_mut`] method returns an [`Option`], using [`None`] rather than panicking when
25+
/// the requested index is out of range.
26+
///
27+
/// [`get_mut`]: SafelyIndexMut::get_mut
28+
/// [`IndexMut`]: std::ops::Index
29+
pub trait SafelyIndexMut<Node = Self> {
30+
/// Access a field of the given YAML object (mutable).
31+
///
32+
/// # Return
33+
/// If the given index is valid within `self`, [`Some`] is returned with a reference to the
34+
/// indexed object. If `self` is not indexable or the index is out of bounds, this function
35+
/// returns [`None`].
36+
fn get_mut(&mut self, key: impl Into<Accessor>) -> Option<&mut Node>;
37+
}
38+
39+
/// A [`SafelyIndex`] / [`SafelyIndexMut`] accessor.
940
pub enum Accessor {
10-
/// Accessing a string field from a mapping
41+
/// Accessing a string field from a mapping.
1142
Field(String),
12-
/// Accessing an element from a sequence
43+
/// Accessing an element from a sequence or a mapping.
1344
Index(usize),
1445
}
1546

@@ -31,63 +62,26 @@ impl From<&str> for Accessor {
3162
}
3263
}
3364

34-
impl<Z: SafelyIndex> SafelyIndex<Z> for Option<Z> {
35-
fn get(&self, key: impl Into<Accessor>) -> Option<&Z> {
65+
impl<Node: SafelyIndex> SafelyIndex<Node> for Option<Node> {
66+
fn get(&self, key: impl Into<Accessor>) -> Option<&Node> {
3667
self.as_ref().and_then(|data| data.get(key))
3768
}
3869
}
3970

40-
impl<Z: SafelyIndex> SafelyIndex<Z> for Option<&Z> {
41-
fn get(&self, key: impl Into<Accessor>) -> Option<&Z> {
42-
self.as_ref().and_then(|data| data.get(key))
71+
impl<Node: SafelyIndexMut> SafelyIndexMut<Node> for Option<Node> {
72+
fn get_mut(&mut self, key: impl Into<Accessor>) -> Option<&mut Node> {
73+
self.as_mut().and_then(|data| data.get_mut(key))
4374
}
4475
}
4576

46-
impl<T: SafelyIndex> SafelyIndex<T> for &T {
47-
fn get(&self, key: impl Into<Accessor>) -> Option<&T> {
48-
(*self).get(key)
77+
impl<Node: SafelyIndex> SafelyIndex<Node> for Option<&Node> {
78+
fn get(&self, key: impl Into<Accessor>) -> Option<&Node> {
79+
self.as_ref().and_then(|data| data.get(key))
4980
}
5081
}
5182

52-
#[cfg(test)]
53-
mod test {
54-
use std::string::ToString;
55-
56-
use crate::{LoadableYamlNode, SafelyIndex, Yaml};
57-
58-
#[test]
59-
fn indexing_into_available_structures() {
60-
let node = Yaml::load_from_str(
61-
r#"
62-
person:
63-
name: "Jim Halpert"
64-
workplace:
65-
- name: "Dunder Mifflin"
66-
role: "Manager"
67-
tenure_years: 5
68-
"#,
69-
)
70-
.unwrap()
71-
.first()
72-
.cloned()
73-
.unwrap();
74-
75-
let name = node
76-
.get("person")
77-
.get("name")
78-
.and_then(|name| name.as_str())
79-
.map(ToString::to_string);
80-
81-
assert!(name.is_some_and(|n| n == "Jim Halpert"));
82-
83-
let role = node
84-
.get("person")
85-
.get("workplace")
86-
.get(0)
87-
.get("role".to_string()) //just for example...
88-
.and_then(|role| role.as_str())
89-
.map(ToString::to_string);
90-
91-
assert!(role.is_some_and(|r| r == "Manager"));
83+
impl<Node: SafelyIndexMut> SafelyIndexMut<Node> for Option<&mut Node> {
84+
fn get_mut(&mut self, key: impl Into<Accessor>) -> Option<&mut Node> {
85+
self.as_mut().and_then(|data| data.get_mut(key))
9286
}
9387
}

saphyr/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pub use crate::annotated::{
145145
AnnotatedSequenceOwned, AnnotatedYamlIter, YamlData, YamlDataOwned,
146146
};
147147
pub use crate::emitter::{EmitError, YamlEmitter};
148-
pub use crate::index::{Accessor, SafelyIndex};
148+
pub use crate::index::{Accessor, SafelyIndex, SafelyIndexMut};
149149
pub use crate::loader::{LoadError, LoadableYamlNode, YamlLoader};
150150
pub use crate::scalar::{parse_core_schema_fp, Scalar, ScalarOwned};
151151
pub use crate::yaml::{Mapping, Sequence, Yaml, YamlIter};

saphyr/src/macros.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,16 @@ impl $(< $( $generic ),+ >)? crate::index::SafelyIndex<$nodetype> for $yaml $(wh
711711
}
712712
}
713713
}
714+
715+
#[allow(clippy::needless_lifetimes)]
716+
impl $(< $( $generic ),+ >)? crate::index::SafelyIndexMut<$nodetype> for $yaml $(where $($whereclause)+)? {
717+
fn get_mut(&mut self, key: impl Into<crate::index::Accessor>) -> Option<&mut $nodetype> {
718+
match key.into() {
719+
crate::index::Accessor::Field(f) => self.as_mapping_get_mut(f.as_str()),
720+
crate::index::Accessor::Index(i) => self.as_sequence_get_mut(i),
721+
}
722+
}
723+
}
714724
);
715725
);
716726

saphyr/tests/index.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use saphyr::{LoadableYamlNode, MarkedYaml, Scalar, Yaml, YamlData};
1+
use std::string::ToString;
2+
3+
use saphyr::{LoadableYamlNode, MarkedYaml, SafelyIndex, SafelyIndexMut, Scalar, Yaml, YamlData};
24

35
fn get_yaml_mapping() -> Yaml<'static> {
46
let s = "
@@ -188,3 +190,83 @@ fn marked_yaml_index_integer_wrong_variant() {
188190
let node: MarkedYaml<'_> = YamlData::Value(Scalar::Integer(3)).into();
189191
let _ = node.data[12];
190192
}
193+
194+
#[test]
195+
fn safely_indexing_into_available_structures() {
196+
let node = Yaml::load_from_str(
197+
r#"
198+
person:
199+
name: "Jim Halpert"
200+
workplace:
201+
- name: "Dunder Mifflin"
202+
role: "Manager"
203+
tenure_years: 5
204+
"#,
205+
)
206+
.unwrap()
207+
.first()
208+
.cloned()
209+
.unwrap();
210+
211+
let name = node
212+
.get("person")
213+
.get("name")
214+
.and_then(|name| name.as_str())
215+
.map(ToString::to_string);
216+
217+
assert!(name.is_some_and(|n| n == "Jim Halpert"));
218+
219+
let role = node
220+
.get("person")
221+
.get("workplace")
222+
.get(0)
223+
.get("role".to_string()) //just for example...
224+
.and_then(|role| role.as_str())
225+
.map(ToString::to_string);
226+
227+
assert!(role.is_some_and(|r| r == "Manager"));
228+
}
229+
230+
#[test]
231+
fn safely_indexing_mut_into_available_structures() {
232+
let mut node = Yaml::load_from_str(
233+
r#"
234+
person:
235+
name: "Jim Halpert"
236+
workplace:
237+
- name: "Dunder Mifflin"
238+
role: "Manager"
239+
tenure_years: 5
240+
"#,
241+
)
242+
.unwrap()
243+
.first()
244+
.cloned()
245+
.unwrap();
246+
247+
*node.get_mut("person").get_mut("name").unwrap() = Yaml::value_from_str("Dwight Schrute");
248+
*node
249+
.get_mut("person")
250+
.get_mut("workplace")
251+
.get_mut(0)
252+
.get_mut("role")
253+
.unwrap() = Yaml::value_from_str("Assistant");
254+
255+
let name = node
256+
.get("person")
257+
.get("name")
258+
.and_then(|name| name.as_str())
259+
.map(ToString::to_string);
260+
261+
assert!(name.is_some_and(|n| n == "Dwight Schrute"));
262+
263+
let role = node
264+
.get("person")
265+
.get("workplace")
266+
.get(0)
267+
.get("role".to_string()) //just for example...
268+
.and_then(|role| role.as_str())
269+
.map(ToString::to_string);
270+
271+
assert!(role.is_some_and(|r| r == "Assistant"));
272+
}

0 commit comments

Comments
 (0)