Skip to content

Commit c0f39a3

Browse files
committed
fix: Support writing keys with invalid chars
This actually fixes a few of things related to expressions for object keys: 1. It allows they keys contain characters that aren't valid as identifiers, if the key is a plain string, for example: ":" 2. It removes the need for superflouous parenthesis around expresions in the keys 3. It no longer puts double quotes around an interpolated string in the key. The second two were actually kind of side-affects of my fix for 1. If we want to preserve the previous behavior for 2 and 3, I think it wouldn't be too hard to do though.
1 parent f034ef2 commit c0f39a3

4 files changed

Lines changed: 41 additions & 13 deletions

File tree

hcl2/reconstructor.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,6 @@ def _should_add_space(self, rule, current_terminal, is_block_label: bool = False
261261
if isinstance(self._last_rule, str) and re.match(
262262
r"^__(tuple|arguments)_(star|plus)_.*", self._last_rule
263263
):
264-
265264
# string literals, decimals, and identifiers should always be
266265
# preceded by a space if they're following a comma in a tuple or
267266
# function arg
@@ -362,6 +361,10 @@ def _name_to_identifier(name: str) -> Tree:
362361
"""Converts a string to a NAME token within an identifier rule."""
363362
return Tree(Token("RULE", "identifier"), [Token("NAME", name)])
364363

364+
@staticmethod
365+
def _is_valid_identifier(name: str) -> bool:
366+
return re.match(r"^\w[\w\d]*$", name) is not None
367+
365368
@staticmethod
366369
def _escape_interpolated_str(interp_s: str) -> str:
367370
if interp_s.strip().startswith("<<-") or interp_s.strip().startswith("<<"):
@@ -620,14 +623,14 @@ def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
620623
continue
621624

622625
value_expr_term = self._transform_value_to_expr_term(dict_v, level + 1)
623-
k = self._unwrap_interpolation(k)
626+
k = self._transform_value_to_key(k, level + 1)
624627
elements.append(
625628
Tree(
626629
Token("RULE", "object_elem"),
627630
[
628631
Tree(
629632
Token("RULE", "object_elem_key"),
630-
[Tree(Token("RULE", "identifier"), [Token("NAME", k)])],
633+
[k],
631634
),
632635
Token("EQ", " ="),
633636
value_expr_term,
@@ -732,3 +735,20 @@ def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
732735

733736
# otherwise, we don't know the type
734737
raise RuntimeError(f"Unknown type to transform {type(value)}")
738+
739+
def _transform_value_to_key(self, value, level) -> Tree:
740+
"""
741+
Convert any value to a suitable key for an object
742+
"""
743+
if self._is_valid_identifier(value):
744+
return self._name_to_identifier(value)
745+
expr = self._transform_value_to_expr_term(value, level)
746+
# If the expression is a string, then return an object_elem_key of the string
747+
if expr.data == "expr_term" and expr.children[0].data == "string":
748+
return expr.children[0]
749+
else:
750+
# Otherwise return an expression
751+
return Tree(
752+
Token("RULE", "object_elem_key_expression"),
753+
[Token("LPAR", "("), expr, Token("RPAR", ")")],
754+
)

hcl2/transformer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""A Lark Transformer for transforming a Lark parse tree into a Python dict"""
2+
23
import json
34
import re
45
import sys
@@ -103,9 +104,7 @@ def object_elem(self, args: List) -> Dict:
103104
# into a bigger dict that is returned by the "object" function
104105

105106
key = str(args[0].children[0])
106-
if not re.match(r".*?(\${).*}.*", key):
107-
# do not strip quotes of a interpolation string
108-
key = self.strip_quotes(key)
107+
key = self.strip_quotes(key)
109108

110109
value = self.to_string_dollar(args[2])
111110
return {key: value}
@@ -114,7 +113,8 @@ def object_elem_key_dot_accessor(self, args: List) -> str:
114113
return "".join(args)
115114

116115
def object_elem_key_expression(self, args: List) -> str:
117-
return self.to_string_dollar("".join(args))
116+
# the first and third arguments are parentheses
117+
return self.to_string_dollar(args[1])
118118

119119
def object(self, args: List) -> Dict:
120120
args = self.strip_new_line_tokens(args)

test/helpers/terraform-config-json/variables.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,13 @@
4747
"foo": "${var.account}_bar",
4848
"bar": {
4949
"baz": 1,
50-
"${(var.account)}": 2,
51-
"${(format(\"key_prefix_%s\", local.foo))}": 3,
52-
"\"prefix_${var.account}:${var.user}_suffix\"": "interpolation"
50+
"${var.account}": 2,
51+
"${format(\"key_prefix_%s\", local.foo)}": 3,
52+
"prefix_${var.account}:${var.user}_suffix": "interpolation",
53+
"${var.start}-mid-${var.end}": 4,
54+
"a:b": 5,
55+
"123": 6,
56+
"${var.x + 1}": 7
5357
},
5458
"tuple": ["${local.foo}"],
5559
"empty_tuple": []

test/helpers/terraform-config/variables.tf

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@ locals {
1010
baz : 1
1111
(var.account) : 2
1212
(format("key_prefix_%s", local.foo)) : 3
13-
"prefix_${var.account}:${var.user}_suffix":"interpolation",
13+
"prefix_${var.account}:${var.user}_suffix" : "interpolation",
14+
"${var.start}-mid-${var.end}" : 4
15+
"a:b" : 5
16+
123 : 6
17+
(var.x + 1) : 7
1418
}
15-
tuple = [local.foo]
19+
tuple = [local.foo]
1620
empty_tuple = []
1721
}
1822

@@ -76,7 +80,7 @@ locals {
7680

7781
for_whitespace = { for i in [1, 2, 3] :
7882
i =>
79-
i ...
83+
i...
8084
}
8185
}
8286

0 commit comments

Comments
 (0)