toml_ops/
lib.rs

1//! Implement Toml pointer following the json path syntax, with type `Option<&toml::Value>`.
2//! Overload `/` as path operator to point into a node in toml tree, as well as some other
3//! meaningfull operator overload.
4//! Such as pipe operator `|` to get primitive value from scalar leaf node,
5//! push operator `<<` to overwrite scalar node or push new item to array or table,
6//! and push assign operator `<<=` to re-assign to toml node unconditionally.
7//! While `/` or operator `<<` may invalidate the pointer, we can use `!` operator
8//! or `is_none()` method to test such failed case.
9//! 
10//! # Expample
11//! ```rust
12//! use toml_ops::PathOperator;
13//! let tv = r#"
14//! [host]
15//! ip="127.0.0.1"
16//! port=8080
17//! proto=["tcp", "udp"]
18//! "#;
19//! let mut v: toml::Value = tv.parse().unwrap(); 
20//!
21//! let port = v.path() / "host" / "port" | 0;
22//! assert_eq!(port, 8080);
23//!
24//! let node = v.path_mut() / "host" / "port" << 8989;
25//! let port = node | 0;
26//! assert_eq!(port, 8989);
27//!
28//! let proto = v.path() / "host" / "proto" / 0 | "";
29//! assert_eq!(proto, "tcp");
30//!
31//! let host = v.path_mut() / "host";
32//! let host = host << ("newkey", "newval") << ("morekey", 1234);
33//! assert_eq!(host.is_none(), false);
34//! assert_eq!(!host, false);
35//!
36//! let mut proto = v.path_mut() / "host" / "proto";
37//! proto = proto << ("json", ) << ["protobuf"];
38//! assert_eq!(proto.as_ref().unwrap().as_array().unwrap().len(), 4);
39//!
40//! proto <<= "default";
41//! assert_eq!(proto.as_ref().unwrap().is_str(), true);
42//! let proto = v.path() / "host" / "proto" | "";
43//! assert_eq!(proto, "default");
44//!
45//! let invalid = v.path() / "host" / "no-key";
46//! assert_eq!(!invalid, true);
47//! assert_eq!(invalid.is_none(), true);
48//! ```
49//!
50
51use toml::Value;
52use toml::value::Index;
53use std::ops::{Div, BitOr, Shl, ShlAssign, Not, Deref, DerefMut};
54
55/// Resolve path into a `toml::Value` tree.
56/// Return `None` if the path if invalid.
57/// Note the input is aslo `Option`, for symmetrical implementation reason.
58fn path<'tr, B>(v: Option<&'tr Value>, p: B) -> Option<&'tr Value>
59where B: PathBuilder + Index + Copy
60{
61    if v.is_none() {
62        return None;
63    }
64
65    let v = v.unwrap();
66    let from_index = v.get(p);
67    if from_index.is_some() {
68        return from_index;
69    }
70
71    let path_segment = p.build_path();
72    if path_segment.paths.len() > 1 {
73        return path_segment.apply(v);
74    }
75
76    return None;
77}
78
79/// Resolve path into a mutable `toml::Value` tree.
80fn path_mut<'tr, B>(v: Option<&'tr mut Value>, p: B) -> Option<&'tr mut Value>
81where B: PathBuilder + Index + Copy
82{
83    if v.is_none() {
84        return None;
85    }
86
87    let v = v.unwrap();
88
89    // Note: use immutable version of get() to determiner path is valid first,
90    // otherwise get_mut() and aplly_mut() would trow E0499 as mut ref twice.
91    let target = v.get(p);
92    if target.is_some() {
93        return v.get_mut(p);
94    }
95    else {
96        let path_segment = p.build_path();
97        if path_segment.paths.len() > 1 {
98            return path_segment.apply_mut(v);
99        }
100        else {
101            return None;
102        }
103    }
104}
105
106/// Path segment break on slash(/) or dot(.).
107/// eg: `table.subtable.key` or `table/subtable/key` or `array/index/key`
108struct PathSegment
109{
110    paths: Vec<String>,
111}
112
113impl PathSegment
114{
115    /// Resolve path readonly for readonly `toml::Value`.
116    fn apply<'tr>(&self, v: &'tr Value) -> Option<&'tr Value> {
117        let mut target = Some(v);
118        for p in &self.paths {
119            if target.is_none() {
120                return None;
121            }
122            if p.is_empty() {
123                continue;
124            }
125            match target.unwrap() {
126                Value::Table(table) => { target = table.get(p); },
127                Value::Array(array) => {
128                    if let Ok(index) = p.parse::<usize>() {
129                        target = array.get(index); 
130                    }
131                },
132                _ => { return None; }
133            }
134        }
135        return target;
136    }
137
138    /// Resolve path readonly for mutable `toml::Value`.
139    /// Bug: if some table key is all numerical char, would mistake as array index.
140    fn apply_mut<'tr>(&self, v: &'tr mut Value) -> Option<&'tr mut Value> {
141        let mut target = Some(v);
142        for p in &self.paths {
143            if target.is_none() {
144                return None;
145            }
146            if p.is_empty() {
147                continue;
148            }
149            match p.parse::<usize>() {
150                Ok(index) => { target = target.unwrap().get_mut(index); },
151                Err(_) => { target = target.unwrap().get_mut(p); },
152            }
153        }
154        return target;
155    }
156}
157
158/// Type trait that can build `PathSegment` from.
159trait PathBuilder {
160    fn build_path(&self) -> PathSegment {
161        PathSegment { paths: Vec::new() }
162    }
163}
164
165/// split string to get path segment vector.
166impl PathBuilder for &str {
167    fn build_path(&self) -> PathSegment {
168        let paths = self
169            .split(|c| c == '/' || c == '.')
170            .map(|s| s.to_string())
171            .collect();
172        PathSegment { paths }
173    }
174}
175
176/// usize index only act path on it's own, but cannot split to more path segment.
177impl PathBuilder for usize {}
178
179/// Provide toml pointer to supported operator overload.
180pub trait PathOperator
181{
182    /// Construct immutable toml pointer to some initial node.
183    fn path<'tr>(&'tr self) -> TomlPtr<'tr>;
184
185    /// Construct immutable toml pointer and move it follwoing sub path.
186    fn pathto<'tr>(&'tr self, p: &str) -> TomlPtr<'tr>;
187
188    /// Construct mutable toml pointer to some initial node.
189    fn path_mut<'tr>(&'tr mut self) -> TomlPtrMut<'tr>;
190
191    /// Construct mutable toml pointer and move it follwoing sub path.
192    fn pathto_mut<'tr>(&'tr mut self, p: &str) -> TomlPtrMut<'tr>;
193}
194
195/// Create toml pointer directely from `toml::Value`.
196impl PathOperator for Value
197{
198    fn path<'tr>(&'tr self) -> TomlPtr<'tr> {
199        TomlPtr::path(self)
200    }
201    fn pathto<'tr>(&'tr self, p: &str) -> TomlPtr<'tr> {
202        let valop = p.build_path().apply(self);
203        TomlPtr { valop }
204    }
205
206    fn path_mut<'tr>(&'tr mut self) -> TomlPtrMut<'tr> {
207        TomlPtrMut::path(self)
208    }
209    fn pathto_mut<'tr>(&'tr mut self, p: &str) -> TomlPtrMut<'tr> {
210        let valop = p.build_path().apply_mut(self);
211        TomlPtrMut { valop }
212    }
213}
214
215/// Wrapper pointer to `toml::Value` for operator overload.
216/// Must refer to an existed toml tree, `Option::None` to refer non-exist node.
217#[derive(Copy, Clone)]
218pub struct TomlPtr<'tr> {
219    valop: Option<&'tr Value>,
220}
221
222impl<'tr> TomlPtr<'tr> {
223    /// As constructor, to build path operand object from a `toml::Value` node.
224    pub fn path(v: &'tr Value) -> Self {
225        Self { valop: Some(v) }
226    }
227    
228    /// As unwrapper, to get the underling `Option<&toml::Value>`.
229    pub fn unpath(&self) -> &Option<&'tr Value> {
230        &self.valop
231    }
232}
233
234/// Overload `!` operator to test the pointer is invalid.
235impl<'tr> Not for TomlPtr<'tr> {
236    type Output = bool;
237    fn not(self) -> Self::Output {
238        self.valop.is_none()
239    }
240}
241
242/// Overload `*` deref operator to treate pointer as `Option<&toml::Value>`.
243impl<'tr> Deref for TomlPtr<'tr>
244{
245    type Target = Option<&'tr Value>;
246    fn deref(&self) -> &Self::Target {
247        self.unpath()
248    }
249}
250
251/// Path operator `/`, visit sub-node by string key for table or index for array.
252/// Can chained as `tomlptr / "path" / "to" / "node"` or `tomlptr / "path/to/node"`.
253impl<'tr, Rhs> Div<Rhs> for TomlPtr<'tr>
254where Rhs: PathBuilder + Index + Copy
255{
256    type Output = Self;
257    fn div(self, rhs: Rhs) -> Self::Output {
258        TomlPtr { valop: path(self.valop, rhs) }
259    }
260}
261
262// pipe operator, get primitive scalar value for leaf node in toml tree.
263// return rhs as default if the node is mistype.
264// support | &str, String, i64, f64, bool,
265// not support datetime type of toml.
266// Note: pipe operator(|) is the vertical form of path operator(/),
267// and usually stand on the end of path chain.
268// eg. `let scalar = toml.path() / "path" / "to" / "leaf" | "default-value"; `
269
270/// Pipe operator `|` with `String`, to get value from string node, 
271/// or return `rhs` as default value if pointer is invalid or type mistach.
272/// Note that the `rhs` string would be moved.
273impl<'tr> BitOr<String> for TomlPtr<'tr>
274{
275    type Output = String;
276    fn bitor(self, rhs: String) -> Self::Output {
277        if self.valop.is_none() {
278            return rhs;
279        }
280        match self.valop.unwrap().as_str() {
281            Some(s) => s.to_string(),
282            None => rhs
283        }
284    }
285}
286
287/// Pipe operator `|` with string literal, to get string value or `rhs` as default.
288impl<'tr> BitOr<&'static str> for TomlPtr<'tr>
289{
290    type Output = &'tr str;
291    fn bitor(self, rhs: &'static str) -> Self::Output {
292        match self.valop {
293            Some(v) => v.as_str().unwrap_or(rhs),
294            None => rhs,
295        }
296    }
297}
298
299/// Pipe operator to get integer value or `rhs` as default.
300impl<'tr> BitOr<i64> for TomlPtr<'tr>
301{
302    type Output = i64;
303    fn bitor(self, rhs: i64) -> Self::Output {
304        match self.valop {
305            Some(v) => v.as_integer().unwrap_or(rhs),
306            None => rhs,
307        }
308    }
309}
310
311/// Pipe operator to get float value or `rhs` as default.
312impl<'tr> BitOr<f64> for TomlPtr<'tr>
313{
314    type Output = f64;
315    fn bitor(self, rhs: f64) -> Self::Output {
316        match self.valop {
317            Some(v) => v.as_float().unwrap_or(rhs),
318            None => rhs,
319        }
320    }
321}
322
323/// Pipe operator to get bool value or `rhs` as default.
324impl<'tr> BitOr<bool> for TomlPtr<'tr>
325{
326    type Output = bool;
327    fn bitor(self, rhs: bool) -> Self::Output {
328        match self.valop {
329            Some(v) => v.as_bool().unwrap_or(rhs),
330            None => rhs,
331        }
332    }
333}
334
335/// Mutable version of pointer wrapper of `toml::Value` for operator overload.
336/// Must refer to existed toml tree, `Option::None` to refer non-exist node.
337/// Note that mutable reference don't support copy.
338pub struct TomlPtrMut<'tr> {
339    valop: Option<&'tr mut Value>,
340}
341
342impl<'tr> TomlPtrMut<'tr> {
343    /// As constructor, to build path operand object from a `toml::Value` node.
344    pub fn path(v: &'tr mut Value) -> Self {
345        Self { valop: Some(v) }
346    }
347
348    /// As unwrapper, to get the underling `Option<&mut toml::Value>`.
349    pub fn unpath(&self) -> &Option<&'tr mut Value> {
350        &self.valop
351    }
352
353    /// Assign any supported value to toml.
354    /// But canno overload operator=, will choose <<= instead.
355    pub fn assign<T>(&mut self, rhs: T) where Value: From<T> {
356        if let Some(ref mut v) = self.valop {
357            **v = Value::from(rhs);
358        }
359    }
360
361    /// Construct new null pointer.
362    fn none() -> Self {
363        Self { valop: None }
364    }
365
366    /// Put a value to toml and return pointer to it.
367    fn put_val<T>(v: &'tr mut Value, rhs: T) -> Self
368    where Value: From<T>
369    {
370        *v = Value::from(rhs);
371        Self::path(v)
372    }
373
374    /// Put value to string toml node pointer, would invalidate it when type mismatch.
375    /// Implement for << String and << &str.
376    fn put_string(&mut self, rhs: String) -> Self {
377        if self.valop.is_none() {
378            return Self::none();
379        }
380        let v = self.valop.take().unwrap();
381        if v.is_str() {
382            return Self::put_val(v, rhs);
383        }
384        return Self::none();
385    }
386
387    /// Implement for << i64.
388    fn put_integer(&mut self, rhs: i64) -> Self {
389        if self.valop.is_none() {
390            return Self::none();
391        }
392        let v = self.valop.take().unwrap();
393        if v.is_integer() {
394            return Self::put_val(v, rhs);
395        }
396        return Self::none();
397    }
398
399    /// Implement for << f64.
400    fn put_float(&mut self, rhs: f64) -> Self {
401        if self.valop.is_none() {
402            return Self::none();
403        }
404        let v = self.valop.take().unwrap();
405        if v.is_float() {
406            return Self::put_val(v, rhs);
407        }
408        return Self::none();
409    }
410
411    /// Implement for << bool.
412    fn put_bool(&mut self, rhs: bool) -> Self {
413        if self.valop.is_none() {
414            return Self::none();
415        }
416        let v = self.valop.take().unwrap();
417        if v.is_bool() {
418            return Self::put_val(v, rhs);
419        }
420        return Self::none();
421    }
422
423    /// Implment for table << (key, val) pair.
424    fn push_table<K: ToString, T>(&mut self, key: K, val: T) -> Self
425    where Value: From<T>
426    {
427        if self.valop.is_none() {
428            return Self::none();
429        }
430        let v = self.valop.take().unwrap();
431        if v.is_table() {
432            v.as_table_mut().unwrap().insert(key.to_string(), Value::from(val));
433            return Self::path(v);
434        }
435        return Self::none();
436    }
437
438    /// Implment for array << (val, ) << [item] .
439    fn push_array<T>(&mut self, val: T) -> Self
440    where Value: From<T>
441    {
442        if self.valop.is_none() {
443            return Self::none();
444        }
445        let v = self.valop.take().unwrap();
446        if v.is_array() {
447            v.as_array_mut().unwrap().push(Value::from(val));
448            return Self::path(v);
449        }
450        return Self::none();
451    }
452}
453
454/// Overload `!` operator to test the pointer is invalid.
455impl<'tr> Not for TomlPtrMut<'tr> {
456    type Output = bool;
457    fn not(self) -> Self::Output {
458        self.valop.is_none()
459    }
460}
461
462/// Overload `*` deref operator to treate pointer as `Option<&mut toml::Value>`.
463impl<'tr> Deref for TomlPtrMut<'tr> {
464    type Target = Option<&'tr mut Value>;
465    fn deref(&self) -> &Self::Target {
466        &self.valop
467    }
468}
469
470/// Overload `*` deref operator to treate pointer as `Option<&mut toml::Value>`.
471impl<'tr> DerefMut for TomlPtrMut<'tr> {
472    fn deref_mut(&mut self) -> &mut Self::Target {
473        &mut self.valop
474    }
475}
476
477/// Path operator `/`, visit sub-node by string key for table or index for array.
478/// Can chained as `tomlptr / "path" / "to" / "node"` or `tomlptr / "path/to/node"`.
479/// Hope to change the node it point to.
480impl<'tr, Rhs> Div<Rhs> for TomlPtrMut<'tr>
481where Rhs: PathBuilder + Index + Copy
482{
483    type Output = Self;
484
485    fn div(self, rhs: Rhs) -> Self::Output {
486        TomlPtrMut { valop: path_mut(self.valop, rhs) }
487    }
488}
489
490/// Pipe operator `|` with `String`, to get value from string node, 
491/// or return `rhs` as default value if pointer is invalid or type mistach.
492/// Note that the `rhs` string , as well as the pointer itself would be moved.
493impl<'tr> BitOr<String> for TomlPtrMut<'tr>
494{
495    type Output = String;
496    fn bitor(self, rhs: String) -> Self::Output {
497        if self.valop.is_none() {
498            return rhs;
499        }
500        match self.valop.unwrap().as_str() {
501            Some(s) => s.to_string(),
502            None => rhs
503        }
504    }
505}
506
507/// Pipe operator `|` with string literal, to get string value or `rhs` as default.
508impl<'tr> BitOr<&'static str> for TomlPtrMut<'tr>
509{
510    type Output = &'tr str;
511    fn bitor(self, rhs: &'static str) -> Self::Output {
512        match self.valop {
513            Some(v) => v.as_str().unwrap_or(rhs),
514            None => rhs,
515        }
516    }
517}
518
519/// Pipe operator to get integer value or `rhs` as default.
520impl<'tr> BitOr<i64> for TomlPtrMut<'tr>
521{
522    type Output = i64;
523    fn bitor(self, rhs: i64) -> Self::Output {
524        match self.valop {
525            Some(v) => v.as_integer().unwrap_or(rhs),
526            None => rhs,
527        }
528    }
529}
530
531/// Pipe operator to get float value or `rhs` as default.
532impl<'tr> BitOr<f64> for TomlPtrMut<'tr>
533{
534    type Output = f64;
535    fn bitor(self, rhs: f64) -> Self::Output {
536        match self.valop {
537            Some(v) => v.as_float().unwrap_or(rhs),
538            None => rhs,
539        }
540    }
541}
542
543/// Pipe operator to get bool value or `rhs` as default.
544impl<'tr> BitOr<bool> for TomlPtrMut<'tr>
545{
546    type Output = bool;
547    fn bitor(self, rhs: bool) -> Self::Output {
548        match self.valop {
549            Some(v) => v.as_bool().unwrap_or(rhs),
550            None => rhs,
551        }
552    }
553}
554
555/// Operator `<<` to put a string into toml leaf node.
556/// While the data type mismatch the node, set self pointer to `None`.
557impl<'tr> Shl<&str> for TomlPtrMut<'tr> {
558    type Output = Self;
559    fn shl(mut self, rhs: &str) -> Self::Output {
560        self.put_string(rhs.to_string())
561    }
562}
563
564/// Operator `<<` to put and move a string into toml leaf node.
565/// While the data type mismatch the node, set self pointer to `None`.
566impl<'tr> Shl<String> for TomlPtrMut<'tr> {
567    type Output = Self;
568    fn shl(mut self, rhs: String) -> Self::Output {
569        self.put_string(rhs)
570    }
571}
572
573/// Operator `<<` to put a integer value into toml leaf node.
574/// While the data type mismatch the node, set self pointer to `None`.
575impl<'tr> Shl<i64> for TomlPtrMut<'tr> {
576    type Output = Self;
577    fn shl(mut self, rhs: i64) -> Self::Output {
578        self.put_integer(rhs)
579    }
580}
581
582/// Operator `<<` to put a float value into toml leaf node.
583/// While the data type mismatch the node, set self pointer to `None`.
584impl<'tr> Shl<f64> for TomlPtrMut<'tr> {
585    type Output = Self;
586    fn shl(mut self, rhs: f64) -> Self::Output {
587        self.put_float(rhs)
588    }
589}
590
591/// Operator `<<` to put a bool value into toml leaf node.
592/// While the data type mismatch the node, set self pointer to `None`.
593impl<'tr> Shl<bool> for TomlPtrMut<'tr> {
594    type Output = Self;
595    fn shl(mut self, rhs: bool) -> Self::Output {
596        self.put_bool(rhs)
597    }
598}
599
600/// Operator `<<` to push key-value pair (tuple) into toml table.
601/// eg: `toml/table/node << (k, v)` where the k v will be moved.
602impl<'tr, K: ToString, T> Shl<(K, T)> for TomlPtrMut<'tr> where Value: From<T>
603{
604    type Output = Self;
605    fn shl(mut self, rhs: (K, T)) -> Self::Output {
606        self.push_table(rhs.0, rhs.1)
607    }
608}
609
610/// Operator `<<` to push one value tuple into toml array.
611/// eg: `toml/array/node << (v,)`.
612/// Note that use single tuple to distinguish with pushing scalar to leaf node.
613impl<'tr, T> Shl<(T,)> for TomlPtrMut<'tr> where Value: From<T>
614{
615    type Output = Self;
616    fn shl(mut self, rhs: (T,)) -> Self::Output {
617        self.push_array(rhs.0)
618    }
619}
620
621/// Operator `<<` to push one item to toml array.
622/// eg: `toml/array/node << [v1]`
623impl<'tr, T: Copy> Shl<[T;1]> for TomlPtrMut<'tr> where Value: From<T>
624{
625    type Output = Self;
626    fn shl(mut self, rhs: [T;1]) -> Self::Output {
627        self.push_array(rhs[0])
628    }
629}
630
631/// Operator `<<` to push a slice to toml array.
632/// eg: `toml/array/node << &[v1, v2, v3, ...][..]`
633impl<'tr, T: Copy> Shl<&[T]> for TomlPtrMut<'tr> where Value: From<T>
634{
635    type Output = Self;
636    fn shl(mut self, rhs: &[T]) -> Self::Output {
637        for item in rhs {
638            self = self.push_array(*item);
639        }
640        self
641    }
642}
643
644/// Operator `<<=` re-assign to an node unconditionally, may change it data type.
645/// Note donot use chained `<<=` as `<<` can because `<<=` is right associated.
646impl<'tr, T> ShlAssign<T> for TomlPtrMut<'tr> where Value: From<T> 
647{
648    fn shl_assign(&mut self, rhs: T) {
649        self.assign(rhs);
650    }
651}
652
653#[cfg(test)]
654mod tests; // { move to tests.rs }