Skip to main content

ucglib/convert/
yaml.rs

1use std;
2use std::collections::BTreeSet;
3use std::error::Error;
4use std::io::Write;
5use std::rc::Rc;
6use std::result::Result;
7
8use serde_yaml;
9
10use super::traits::{ConvertResult, Converter, ImportResult, Importer};
11use crate::build::Val;
12
13pub struct YamlConverter {}
14
15impl YamlConverter {
16    pub fn new() -> Self {
17        YamlConverter {}
18    }
19
20    fn convert_list(&self, items: &Vec<Rc<Val>>) -> std::io::Result<serde_yaml::Value> {
21        let mut v = Vec::new();
22        for val in items.iter() {
23            v.push(self.convert_value(val)?);
24        }
25        Ok(serde_yaml::Value::Sequence(v))
26    }
27
28    fn convert_env(&self, items: &Vec<(Rc<str>, Rc<str>)>) -> std::io::Result<serde_yaml::Value> {
29        let mut mp = serde_yaml::Mapping::new();
30        for &(ref k, ref v) in items.iter() {
31            mp.insert(
32                serde_yaml::Value::String(k.to_string()),
33                serde_yaml::Value::String(v.to_string()),
34            );
35        }
36        Ok(serde_yaml::Value::Mapping(mp))
37    }
38
39    fn convert_tuple(&self, items: &Vec<(Rc<str>, Rc<Val>)>) -> std::io::Result<serde_yaml::Value> {
40        let mut mapping = serde_yaml::Mapping::new();
41        for &(ref k, ref v) in items.iter() {
42            mapping.insert(
43                serde_yaml::Value::String(k.to_string()),
44                self.convert_value(v)?,
45            );
46        }
47        Ok(serde_yaml::Value::Mapping(mapping))
48    }
49
50    fn convert_value(&self, v: &Val) -> std::io::Result<serde_yaml::Value> {
51        let yaml_val = match v {
52            &Val::Boolean(b) => serde_yaml::Value::Bool(b),
53            &Val::Empty => serde_yaml::Value::Null,
54            &Val::Float(f) => match serde_yaml::to_value(f) {
55                Ok(v) => v,
56                _ => panic!("Float is too large or not a Number {}", f),
57            },
58            &Val::Int(i) => match serde_yaml::to_value(i) {
59                Ok(v) => v,
60                _ => panic!("Int is too large or not a Number {}", i),
61            },
62            &Val::Str(ref s) => serde_yaml::Value::String(s.to_string()),
63            &Val::Env(ref fs) => self.convert_env(fs)?,
64            &Val::List(ref l) => self.convert_list(l)?,
65            &Val::Tuple(ref t) => self.convert_tuple(t)?,
66        };
67        Ok(yaml_val)
68    }
69
70    fn merge_mapping_keys(
71        &self,
72        mut fs: &mut Vec<(String, Rc<Val>)>,
73        m: &serde_yaml::Mapping,
74    ) -> Result<(), Box<dyn Error>> {
75        for (key, value) in m {
76            // This is a little gross but since yaml allows maps to be keyed
77            // by more than just a string it's necessary.
78            let key = match key {
79                serde_yaml::Value::Bool(b) => b.to_string(),
80                serde_yaml::Value::Null => "null".to_string(),
81                serde_yaml::Value::Number(n) => n.to_string(),
82                serde_yaml::Value::String(s) => s.clone(),
83                serde_yaml::Value::Sequence(_)
84                | serde_yaml::Value::Mapping(_)
85                | serde_yaml::Value::Tagged(_) => {
86                    eprintln!("Unsupported key type in yaml map key import skipping");
87                    continue;
88                }
89            };
90            if key == "<<" {
91                if let serde_yaml::Value::Mapping(merge_map) = value {
92                    self.merge_mapping_keys(&mut fs, merge_map)?;
93                }
94            } else {
95                fs.push((key, Rc::new(self.convert_yaml_val(&value)?)));
96            }
97        }
98        Ok(())
99    }
100
101    fn convert_yaml_val(&self, v: &serde_yaml::Value) -> Result<Val, Box<dyn Error>> {
102        Ok(match v {
103            serde_yaml::Value::String(s) => Val::Str(s.clone().into()),
104            serde_yaml::Value::Number(n) => {
105                if let Some(i) = n.as_i64() {
106                    Val::Int(i)
107                } else {
108                    Val::Float(n.as_f64().expect("Number was not an int or a float!!"))
109                }
110            }
111            serde_yaml::Value::Bool(b) => Val::Boolean(*b),
112            serde_yaml::Value::Null => Val::Empty,
113            serde_yaml::Value::Sequence(l) => {
114                let mut vs = Vec::with_capacity(l.len());
115                for aval in l {
116                    vs.push(Rc::new(self.convert_yaml_val(aval)?));
117                }
118                Val::List(vs)
119            }
120            serde_yaml::Value::Mapping(m) => {
121                let mut fs = Vec::with_capacity(m.len());
122                self.merge_mapping_keys(&mut fs, m)?;
123                fs.reverse();
124                let mut seen_keys = BTreeSet::new();
125                let mut collapsed = Vec::with_capacity(fs.len());
126                for (k, val) in fs {
127                    if !seen_keys.contains(&k) {
128                        collapsed.push((k.clone().into(), val));
129                        seen_keys.insert(k);
130                    }
131                }
132                collapsed.reverse();
133                Val::Tuple(collapsed)
134            }
135            serde_yaml::Value::Tagged(_) => {
136                eprintln!(
137                    "Tagged value types are not supported in yaml imports. Replacing with Empty..."
138                );
139                Val::Empty
140            }
141        })
142    }
143
144    pub fn write(&self, v: &Val, mut w: &mut dyn Write) -> ConvertResult {
145        let jsn_val = self.convert_value(v)?;
146        serde_yaml::to_writer(&mut w, &jsn_val)?;
147        writeln!(w, "")?;
148        Ok(())
149    }
150}
151
152impl Converter for YamlConverter {
153    fn convert(&self, v: Rc<Val>, mut w: &mut dyn Write) -> ConvertResult {
154        self.write(&v, &mut w)
155    }
156
157    fn file_ext(&self) -> String {
158        String::from("yaml")
159    }
160
161    fn description(&self) -> String {
162        "Convert ucg Vals into valid yaml.".to_string()
163    }
164
165    #[allow(unused_must_use)]
166    fn help(&self) -> String {
167        include_str!("yaml_help.txt").to_string()
168    }
169}
170
171impl Importer for YamlConverter {
172    fn import(&self, bytes: &[u8]) -> ImportResult {
173        let json_val = serde_yaml::from_slice(bytes)?;
174        Ok(Rc::new(self.convert_yaml_val(&json_val)?))
175    }
176}