Skip to content

Commit

Permalink
docs: add interpolation unwrapping example (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinohmann authored May 4, 2023
1 parent 04c78f8 commit 8409fea
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 0 deletions.
4 changes: 4 additions & 0 deletions crates/hcl-edit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ winnow = "0.4.4"
indoc = "2.0"
pretty_assertions = "1.3.0"
testdata = { path = "../testdata" }

[[example]]
name = "interpolation-unwrapping"
test = true
108 changes: 108 additions & 0 deletions crates/hcl-edit/examples/interpolation-unwrapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! This example demonstrates interpolation unwrapping as described in the HCL spec:
//!
//! https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md#template-interpolation-unwrapping
//!
//! Templates containing only a single interpolation element can be unwrapped into the expression
//! contained in the interpolation.
//!
//! # Usage
//!
//! ```shell
//! cargo run --example interpolation-unwrapping file.hcl
//! ```
//!
//! # Example
//!
//! In the following attribute, the right-hand-side template interpolation can be unwrapped:
//!
//! ```hcl
//! foo = "${var.bar}"
//! ```
//!
//! After interpolation unwrapping the result will look like this:
//!
//! ```hcl
//! foo = var.bar
//! ```
//!
//! But the following cannot be unwrapped since it consists of two interpolations separated by a
//! literal string (`/`):
//!
//! ```hcl
//! foo = "${var.bar}/${var.baz}"
//! ```
use hcl_edit::expr::Expression;
use hcl_edit::repr::Decorate;
use hcl_edit::structure::Body;
use hcl_edit::template::{Element, StringTemplate};
use hcl_edit::visit_mut::{visit_expr_mut, VisitMut};

struct InterpolationUnwrapper;

impl<'ast> VisitMut<'ast> for InterpolationUnwrapper {
fn visit_expr_mut(&mut self, expr: &'ast mut Expression) {
// Only templates containing a single interpolation can be unwrapped.
if let Some(interpolation) = expr
.as_template()
.and_then(StringTemplate::as_single_element)
.and_then(Element::as_interpolation)
{
let mut unwrapped_expr = interpolation.expr.clone();

// Apply the existing decor to the unwrapped expression.
std::mem::swap(expr.decor_mut(), unwrapped_expr.decor_mut());
*expr = unwrapped_expr;
} else {
// Recurse further down the AST.
visit_expr_mut(self, expr);
}
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let Some(filename) = std::env::args().into_iter().skip(1).next() else {
eprintln!("filename argument required");
std::process::exit(1);
};

let input = std::fs::read_to_string(filename)?;
let mut body: Body = input.parse()?;

let mut visitor = InterpolationUnwrapper;
visitor.visit_body_mut(&mut body);

println!("{body}");

Ok(())
}

#[cfg(test)]
use pretty_assertions::assert_eq;

#[test]
fn interpolations_are_unwrapped() {
let input = indoc::indoc! {r#"
// Subscribe the SQS queue to my SNS topic.
resource "aws_sns_topic_subscription" "my_subscription" {
topic_arn = "${aws_sns_topic.my_topic.arn}" // This comment will be preserved
protocol = "sqs"
endpoint = "${aws_sqs_queue.my_queue.arn}"
}
"#};

let mut body: Body = input.parse().unwrap();

let mut visitor = InterpolationUnwrapper;
visitor.visit_body_mut(&mut body);

let expected = indoc::indoc! {r#"
// Subscribe the SQS queue to my SNS topic.
resource "aws_sns_topic_subscription" "my_subscription" {
topic_arn = aws_sns_topic.my_topic.arn // This comment will be preserved
protocol = "sqs"
endpoint = aws_sqs_queue.my_queue.arn
}
"#};

assert_eq!(body.to_string(), expected);
}

0 comments on commit 8409fea

Please sign in to comment.