nu-command 0.115.1

Nushell's built-in commands
Documentation
use nu_engine::command_prelude::*;
use nu_heavy_utils::yaml::ParseOptions;
use nu_protocol::{ast::PathMember, casing::Casing};

#[derive(Clone)]
pub struct FromYamlLike(&'static str);
pub const FROM_YAML: FromYamlLike = FromYamlLike("from yaml");
pub const FROM_YML: FromYamlLike = FromYamlLike("from yml");

impl Command for FromYamlLike {
    fn name(&self) -> &str {
        self.0
    }

    fn signature(&self) -> Signature {
        Signature::build(self.name())
            .input_output_types(vec![(Type::String, Type::Any)])
            .category(Category::Formats)
            .param(
                Flag::new("spec")
                    .arg(SyntaxShape::String)
                    .desc("YAML spec version ('1.1' or '1.2' (default)).")
                    .completion(Completion::new_list(&["1.1", "1.2"])),
            )
            .param(
                Flag::new("multiple")
                    .arg(SyntaxShape::String)
                    .desc("Handle multiple documents ('auto', 'list', 'single').")
                    .completion(Completion::new_list(&["auto", "list", "single"])),
            )
            .switch("ignore-tags", "Ignore any tags", None)
            .param(
                Flag::new("key-resolution")
                    .arg(SyntaxShape::String)
                    .desc("Handle plain scalar keys ('strict' (default), 'verbatim').")
                    .completion(Completion::new_list(&["strict", "verbatim"])),
            )
    }

    fn description(&self) -> &str {
        "Parse text as .yaml/.yml and create table."
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: match self.name() {
                    "from yaml" => "'a: 1' | from yaml",
                    "from yml" => "'a: 1' | from yml",
                    _ => unreachable!("only implemented for `yaml` and `yml`"),
                },
                description: "Converts YAML formatted string to table.",
                result: Some(test_record! {
                    "a" => 1
                }),
            },
            Example {
                example: match self.name() {
                    "from yaml" => "'[ a: 1, b: [1, 2] ]' | from yaml",
                    "from yml" => "'[ a: 1, b: [1, 2] ]' | from yml",
                    _ => unreachable!("only implemented for `yaml` and `yml`"),
                },
                description: "Converts YAML formatted string to table.",
                result: Some(Value::test_list(vec![
                    test_record! { "a" => 1 },
                    test_record! { "b" => [1, 2] },
                ])),
            },
            Example {
                example: match self.name() {
                    "from yaml" => "'!cell-path $.1.abc?.def!' | from yaml",
                    "from yml" => "'!cell-path $.1.abc?.def!' | from yml",
                    _ => unreachable!("only implemented for `yaml` and `yml`"),
                },
                description: "Convert nushell values from YAML.",
                result: Some(Value::test_cell_path(CellPath {
                    members: vec![
                        PathMember::test_int(1, false),
                        PathMember::test_string("abc", true, Casing::Sensitive),
                        PathMember::test_string("def", false, Casing::Insensitive),
                    ],
                })),
            },
        ]
    }

    fn run(
        &self,
        engine_state: &EngineState,
        stack: &mut Stack,
        call: &Call,
        mut input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        let metadata = input
            .take_metadata()
            .map(|meta| meta.with_content_type(None));
        let (yaml, yaml_span, ..) = input.collect_string_strict(call.head)?;
        let yaml = yaml.as_str().into_spanned(yaml_span);
        let spec = call.get_flag(engine_state, stack, "spec")?;
        let multiple = call.get_flag(engine_state, stack, "multiple")?;
        let ignore_tags = call.has_flag(engine_state, stack, "ignore-tags")?;
        let plain_scalar_key_mode = call.get_flag(engine_state, stack, "key-resolution")?;
        let options = ParseOptions::default()
            .with_spec(spec.unwrap_or_default())
            .with_multiple(multiple.unwrap_or_default())
            .with_ignore_tags(ignore_tags)
            .with_key_resolution(plain_scalar_key_mode.unwrap_or_default());
        nu_heavy_utils::yaml::parse(yaml, call.head, options)
            .map(|val| PipelineData::value(val, metadata))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use nu_test_support::prelude::{Result, *};

    #[test]
    fn test_examples() -> Result {
        test().examples(FROM_YAML)?;
        test().examples(FROM_YML)?;
        Ok(())
    }

    #[test]
    fn test_content_type_metadata() -> Result {
        let code = r#"
          "a: 1\nb: 2"
          | metadata set --content-type 'application/yaml' --path-columns [name]
          | from yaml
          | metadata
          | reject span
        "#;

        test().run(code).expect_value_eq(test_record! {
            "path_columns" => ["name"]
        })
    }
}