From 36ec88f0dfd17c7b21c14ee49ddb6b5f92bd5af7 Mon Sep 17 00:00:00 2001 From: Kamil Kozik Date: Fri, 11 Apr 2025 13:51:21 +0200 Subject: [PATCH] allow expression as an object key (limitation: expression must be wrapped in parentheses) --- CHANGELOG.md | 4 +++- README.md | 13 +++++++++++++ hcl2/hcl2.lark | 7 ++++--- hcl2/transformer.py | 14 ++++++-------- test/helpers/terraform-config-json/variables.json | 3 ++- test/helpers/terraform-config/variables.tf | 1 + 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeb6abaf..08a37db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## \[Unreleased\] -- Nothing yet +### Fixed + +- Issue parsing inline expression as an object key; **see Limitations in README.md** ([#222](https://github.com/amplify-education/python-hcl2/pull/222)) ## \[7.1.0\] - 2025-04-10 diff --git a/README.md b/README.md index 12cebd2e..0454e9d5 100644 --- a/README.md +++ b/README.md @@ -104,3 +104,16 @@ We’ll try to answer any PR’s promptly. name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar" } ``` + +### Using inline expression as an object key + +- Object key can be an expression as long as it is wrapped in parentheses: + ```terraform + locals { + foo = "bar" + baz = { + (format("key_prefix_%s", local.foo)) : "value" + # format("key_prefix_%s", local.foo) : "value" this will fail + } + } + ``` diff --git a/hcl2/hcl2.lark b/hcl2/hcl2.lark index 99df91e8..64769aac 100644 --- a/hcl2/hcl2.lark +++ b/hcl2/hcl2.lark @@ -75,8 +75,9 @@ EQ : /[ \t]*=(?!=|>)/ tuple : "[" (new_line_or_comment* expression new_line_or_comment* ",")* (new_line_or_comment* expression)? new_line_or_comment* "]" object : "{" new_line_or_comment? (new_line_or_comment* (object_elem | (object_elem COMMA)) new_line_or_comment*)* "}" -object_elem : LPAR? object_elem_key RPAR? ( EQ | COLON ) expression -object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor +object_elem : object_elem_key ( EQ | COLON ) expression +object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor | object_elem_key_expression +object_elem_key_expression : LPAR expression RPAR object_elem_key_dot_accessor : identifier (DOT identifier)+ heredoc_template : /<<(?P[a-zA-Z][a-zA-Z0-9._-]+)\n?(?:.|\n)*?\n\s*(?P=heredoc)\n/ @@ -98,7 +99,7 @@ full_splat : "[*]" (get_attr | index)* FOR_OBJECT_ARROW : "=>" !for_tuple_expr : "[" new_line_or_comment? for_intro new_line_or_comment? expression new_line_or_comment? for_cond? new_line_or_comment? "]" -!for_object_expr : "{" new_line_or_comment? for_intro new_line_or_comment? expression FOR_OBJECT_ARROW new_line_or_comment? expression "..."? new_line_or_comment? for_cond? new_line_or_comment? "}" +!for_object_expr : "{" new_line_or_comment? for_intro new_line_or_comment? expression FOR_OBJECT_ARROW new_line_or_comment? expression new_line_or_comment? "..."? new_line_or_comment? for_cond? new_line_or_comment? "}" !for_intro : "for" new_line_or_comment? identifier ("," identifier new_line_or_comment?)? new_line_or_comment? "in" new_line_or_comment? expression new_line_or_comment? ":" new_line_or_comment? !for_cond : "if" new_line_or_comment? expression diff --git a/hcl2/transformer.py b/hcl2/transformer.py index a26b5f31..7c7e4bd8 100644 --- a/hcl2/transformer.py +++ b/hcl2/transformer.py @@ -98,14 +98,9 @@ def tuple(self, args: List) -> List: def object_elem(self, args: List) -> Dict: # This returns a dict with a single key/value pair to make it easier to merge these # into a bigger dict that is returned by the "object" function - if args[0] == Token("LPAR", "("): - key = self.strip_quotes(str(args[1].children[0])) - key = f"({key})" - key = self.to_string_dollar(key) - value = args[4] - else: - key = self.strip_quotes(str(args[0].children[0])) - value = args[2] + + key = self.strip_quotes(str(args[0].children[0])) + value = args[2] value = self.to_string_dollar(value) return {key: value} @@ -113,6 +108,9 @@ def object_elem(self, args: List) -> Dict: def object_elem_key_dot_accessor(self, args: List) -> str: return "".join(args) + def object_elem_key_expression(self, args: List) -> str: + return self.to_string_dollar("".join(args)) + def object(self, args: List) -> Dict: args = self.strip_new_line_tokens(args) result: Dict[str, Any] = {} diff --git a/test/helpers/terraform-config-json/variables.json b/test/helpers/terraform-config-json/variables.json index d388080a..3346f201 100644 --- a/test/helpers/terraform-config-json/variables.json +++ b/test/helpers/terraform-config-json/variables.json @@ -47,7 +47,8 @@ "foo": "${var.account}_bar", "bar": { "baz": 1, - "${(var.account)}": 2 + "${(var.account)}": 2, + "${(format(\"key_prefix_%s\", local.foo))}": 3 }, "tuple": ["${local.foo}"], "empty_tuple": [] diff --git a/test/helpers/terraform-config/variables.tf b/test/helpers/terraform-config/variables.tf index 2942b8c2..d5c9a69b 100644 --- a/test/helpers/terraform-config/variables.tf +++ b/test/helpers/terraform-config/variables.tf @@ -9,6 +9,7 @@ locals { bar = { baz : 1 (var.account) : 2 + (format("key_prefix_%s", local.foo)) : 3 } tuple = [local.foo] empty_tuple = []