Skip to main content

nu_command/formats/from/
yaml.rs

1use nu_engine::command_prelude::*;
2use nu_heavy_utils::yaml::ParseOptions;
3use nu_protocol::{ast::PathMember, casing::Casing};
4
5#[derive(Clone)]
6pub struct FromYamlLike(&'static str);
7pub const FROM_YAML: FromYamlLike = FromYamlLike("from yaml");
8pub const FROM_YML: FromYamlLike = FromYamlLike("from yml");
9
10impl Command for FromYamlLike {
11    fn name(&self) -> &str {
12        self.0
13    }
14
15    fn signature(&self) -> Signature {
16        Signature::build(self.name())
17            .input_output_types(vec![(Type::String, Type::Any)])
18            .category(Category::Formats)
19            .param(
20                Flag::new("spec")
21                    .arg(SyntaxShape::String)
22                    .desc("YAML spec version ('1.1' or '1.2' (default)).")
23                    .completion(Completion::new_list(&["1.1", "1.2"])),
24            )
25            .param(
26                Flag::new("multiple")
27                    .arg(SyntaxShape::String)
28                    .desc("Handle multiple documents ('auto', 'list', 'single').")
29                    .completion(Completion::new_list(&["auto", "list", "single"])),
30            )
31            .switch("ignore-tags", "Ignore any tags", None)
32            .param(
33                Flag::new("key-resolution")
34                    .arg(SyntaxShape::String)
35                    .desc("Handle plain scalar keys ('strict' (default), 'verbatim').")
36                    .completion(Completion::new_list(&["strict", "verbatim"])),
37            )
38    }
39
40    fn description(&self) -> &str {
41        "Parse text as .yaml/.yml and create table."
42    }
43
44    fn examples(&self) -> Vec<Example<'_>> {
45        vec![
46            Example {
47                example: match self.name() {
48                    "from yaml" => "'a: 1' | from yaml",
49                    "from yml" => "'a: 1' | from yml",
50                    _ => unreachable!("only implemented for `yaml` and `yml`"),
51                },
52                description: "Converts YAML formatted string to table.",
53                result: Some(test_record! {
54                    "a" => 1
55                }),
56            },
57            Example {
58                example: match self.name() {
59                    "from yaml" => "'[ a: 1, b: [1, 2] ]' | from yaml",
60                    "from yml" => "'[ a: 1, b: [1, 2] ]' | from yml",
61                    _ => unreachable!("only implemented for `yaml` and `yml`"),
62                },
63                description: "Converts YAML formatted string to table.",
64                result: Some(Value::test_list(vec![
65                    test_record! { "a" => 1 },
66                    test_record! { "b" => [1, 2] },
67                ])),
68            },
69            Example {
70                example: match self.name() {
71                    "from yaml" => "'!cell-path $.1.abc?.def!' | from yaml",
72                    "from yml" => "'!cell-path $.1.abc?.def!' | from yml",
73                    _ => unreachable!("only implemented for `yaml` and `yml`"),
74                },
75                description: "Convert nushell values from YAML.",
76                result: Some(Value::test_cell_path(CellPath {
77                    members: vec![
78                        PathMember::test_int(1, false),
79                        PathMember::test_string("abc", true, Casing::Sensitive),
80                        PathMember::test_string("def", false, Casing::Insensitive),
81                    ],
82                })),
83            },
84        ]
85    }
86
87    fn run(
88        &self,
89        engine_state: &EngineState,
90        stack: &mut Stack,
91        call: &Call,
92        mut input: PipelineData,
93    ) -> Result<PipelineData, ShellError> {
94        let metadata = input
95            .take_metadata()
96            .map(|meta| meta.with_content_type(None));
97        let (yaml, yaml_span, ..) = input.collect_string_strict(call.head)?;
98        let yaml = yaml.as_str().into_spanned(yaml_span);
99        let spec = call.get_flag(engine_state, stack, "spec")?;
100        let multiple = call.get_flag(engine_state, stack, "multiple")?;
101        let ignore_tags = call.has_flag(engine_state, stack, "ignore-tags")?;
102        let plain_scalar_key_mode = call.get_flag(engine_state, stack, "key-resolution")?;
103        let options = ParseOptions::default()
104            .with_spec(spec.unwrap_or_default())
105            .with_multiple(multiple.unwrap_or_default())
106            .with_ignore_tags(ignore_tags)
107            .with_key_resolution(plain_scalar_key_mode.unwrap_or_default());
108        nu_heavy_utils::yaml::parse(yaml, call.head, options)
109            .map(|val| PipelineData::value(val, metadata))
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use nu_test_support::prelude::{Result, *};
117
118    #[test]
119    fn test_examples() -> Result {
120        test().examples(FROM_YAML)?;
121        test().examples(FROM_YML)?;
122        Ok(())
123    }
124
125    #[test]
126    fn test_content_type_metadata() -> Result {
127        let code = r#"
128          "a: 1\nb: 2"
129          | metadata set --content-type 'application/yaml' --path-columns [name]
130          | from yaml
131          | metadata
132          | reject span
133        "#;
134
135        test().run(code).expect_value_eq(test_record! {
136            "path_columns" => ["name"]
137        })
138    }
139}