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
use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::{CoerceInto, ShellError}; use nu_protocol::{Primitive, Signature, UnspannedPathMember, UntaggedValue, Value}; pub struct ToYaml; impl WholeStreamCommand for ToYaml { fn name(&self) -> &str { "to yaml" } fn signature(&self) -> Signature { Signature::build("to yaml") } fn usage(&self) -> &str { "Convert table into .yaml/.yml text" } fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { to_yaml(args) } } pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> { Ok(match &v.value { UntaggedValue::Primitive(Primitive::Boolean(b)) => serde_yaml::Value::Bool(*b), UntaggedValue::Primitive(Primitive::Filesize(b)) => { serde_yaml::Value::Number(serde_yaml::Number::from(b.to_f64().ok_or_else(|| { ShellError::labeled_error( "Could not convert to bytes", "could not convert to bytes", &v.tag, ) })?)) } UntaggedValue::Primitive(Primitive::Duration(i)) => { serde_yaml::Value::String(i.to_string()) } UntaggedValue::Primitive(Primitive::Date(d)) => serde_yaml::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => serde_yaml::Value::Null, UntaggedValue::Primitive(Primitive::BeginningOfStream) => serde_yaml::Value::Null, UntaggedValue::Primitive(Primitive::Decimal(f)) => { serde_yaml::Value::Number(serde_yaml::Number::from(f.to_f64().ok_or_else(|| { ShellError::labeled_error( "Could not convert to decimal", "could not convert to decimal", &v.tag, ) })?)) } UntaggedValue::Primitive(Primitive::Int(i)) => { serde_yaml::Value::Number(serde_yaml::Number::from(*i)) } UntaggedValue::Primitive(Primitive::BigInt(i)) => { serde_yaml::Value::Number(serde_yaml::Number::from(CoerceInto::<i64>::coerce_into( i.tagged(&v.tag), "converting to YAML number", )?)) } UntaggedValue::Primitive(Primitive::Nothing) => serde_yaml::Value::Null, UntaggedValue::Primitive(Primitive::GlobPattern(s)) => serde_yaml::Value::String(s.clone()), UntaggedValue::Primitive(Primitive::String(s)) => serde_yaml::Value::String(s.clone()), UntaggedValue::Primitive(Primitive::ColumnPath(path)) => { let mut out = vec![]; for member in path.iter() { match &member.unspanned { UnspannedPathMember::String(string) => { out.push(serde_yaml::Value::String(string.clone())) } UnspannedPathMember::Int(int) => { out.push(serde_yaml::Value::Number(serde_yaml::Number::from(*int))) } } } serde_yaml::Value::Sequence(out) } UntaggedValue::Primitive(Primitive::FilePath(s)) => { serde_yaml::Value::String(s.display().to_string()) } UntaggedValue::Table(l) => { let mut out = vec![]; for value in l { out.push(value_to_yaml_value(value)?); } serde_yaml::Value::Sequence(out) } UntaggedValue::Error(e) => return Err(e.clone()), UntaggedValue::Block(_) | UntaggedValue::Primitive(Primitive::Range(_)) => { serde_yaml::Value::Null } #[cfg(feature = "dataframe")] UntaggedValue::DataFrame(_) => serde_yaml::Value::Null, UntaggedValue::Primitive(Primitive::Binary(b)) => serde_yaml::Value::Sequence( b.iter() .map(|x| serde_yaml::Value::Number(serde_yaml::Number::from(*x))) .collect(), ), UntaggedValue::Row(o) => { let mut m = serde_yaml::Mapping::new(); for (k, v) in o.entries.iter() { m.insert( serde_yaml::Value::String(k.clone()), value_to_yaml_value(v)?, ); } serde_yaml::Value::Mapping(m) } }) } fn to_yaml(args: CommandArgs) -> Result<OutputStream, ShellError> { let name_tag = args.name_tag(); let name_span = name_tag.span; let input: Vec<Value> = args.input.collect(); let to_process_input = match input.len() { x if x > 1 => { let tag = input[0].tag.clone(); vec![Value { value: UntaggedValue::Table(input), tag, }] } 1 => input, _ => vec![], }; Ok((to_process_input.into_iter().map(move |value| { let value_span = value.tag.span; match value_to_yaml_value(&value) { Ok(yaml_value) => match serde_yaml::to_string(&yaml_value) { Ok(x) => UntaggedValue::Primitive(Primitive::String(x)).into_value(&name_tag), _ => Value::error(ShellError::labeled_error_with_secondary( "Expected a table with YAML-compatible structure from pipeline", "requires YAML-compatible input", name_span, "originates from here".to_string(), value_span, )), }, _ => Value::error(ShellError::labeled_error( "Expected a table with YAML-compatible structure from pipeline", "requires YAML-compatible input", &name_tag, )), } })) .into_output_stream()) } #[cfg(test)] mod tests { use super::ShellError; use super::ToYaml; #[test] fn examples_work_as_expected() -> Result<(), ShellError> { use crate::examples::test as test_examples; test_examples(ToYaml {}) } }