nu_plugin_hcl 0.81.0

A nushell plugin for parsing Hashicorp Configuration Language file format
use nu_plugin::{EvaluatedCall, LabeledError, Plugin};
use nu_protocol::{Category, PluginSignature, PluginExample, Type, Value, Span};
use serde_json::Value as SerdeJsonValue;

pub struct FromHcl;

impl FromHcl {
    pub fn new() -> Self {
        Self {}
    }
}

pub fn examples() -> Vec<PluginExample> {
    vec![PluginExample {
        description: "Convert .hcl data into record".into(),
        example: "'provider \"aws\" {
  region = \"us-east-1\"
}
resource \"aws_instance\" \"web\" {
  ami           = \"ami-a1b2c3d4\"
  instance_type = \"t2.micro\"
}' | from hcl"
            .into(),
        result: Some(Value::Record {
            cols: vec!["provider".to_string(), "resource".to_string()],
            vals: vec![
                Value::Record {
                    cols: vec!["aws".to_string()],
                    vals: vec![Value::Record {
                        cols: vec!["region".to_string()],
                        vals: vec![Value::test_string("us-east-1")],
                        span: Span::test_data(),
                    }],
                    span: Span::test_data(),
                },
                Value::Record {
                    cols: vec!["aws_instance".to_string()],
                    vals: vec![Value::Record {
                        cols: vec!["web".to_string()],
                        vals: vec![Value::Record {
                            cols: vec!["ami".to_string(), "instance_type".to_string()],
                            vals: vec![
                                Value::test_string("ami-a1b2c3d4"),
                                Value::test_string("t2.micro"),
                            ],
                            span: Span::test_data(),
                        }],
                        span: Span::test_data(),
                    }],
                    span: Span::test_data(),
                },
            ],
            span: Span::test_data(),
        }),
    }]
}


pub fn convert_sjson_to_value(value: &SerdeJsonValue, span: Span) -> Value {
    match value {
        SerdeJsonValue::Array(array) => {
            let v: Vec<Value> = array
                .iter()
                .map(|x| convert_sjson_to_value(x, span))
                .collect();

            Value::List { vals: v, span }
        }
        SerdeJsonValue::Bool(b) => Value::Bool { val: *b, span },
        SerdeJsonValue::Number(f) => {
            if f.is_f64() {
                Value::Float {
                    val: f.as_f64().unwrap(),
                    span,
                }
            } else {
                Value::Int {
                    val: f.as_i64().unwrap(),
                    span,
                }
            }
        }
        SerdeJsonValue::Null => Value::Nothing { span },
        SerdeJsonValue::Object(k) => {
            let mut cols = vec![];
            let mut vals = vec![];

            for item in k {
                cols.push(item.0.clone());
                vals.push(convert_sjson_to_value(item.1, span));
            }

            Value::Record { cols, vals, span }
        }
        SerdeJsonValue::String(s) => Value::String {
            val: s.clone(),
            span,
        },
    }
}

impl Plugin for FromHcl {
    fn signature(&self) -> Vec<PluginSignature> {
        vec![PluginSignature::build("from hcl")
        .input_output_types(vec![(Type::String, Type::Record(vec![]))])
        .usage("Parse text as .hcl and create a record")
        .plugin_examples(examples())
        .category(Category::Formats)]
    }

    fn run(
        &mut self,
        _name: &str,
        call: &EvaluatedCall,
        input: &Value,
    ) -> Result<Value, LabeledError> {
        let span = call.head;
        let input_string = input.as_string()?;

        let parse_result: SerdeJsonValue = hcl::from_str(&input_string).map_err(|e| LabeledError {
            label: "Error parsing hcl".into(),
            msg: e.to_string(),
            span: Some(span),
        })?;

        Ok(convert_sjson_to_value(&parse_result, span))
    }
}