python-hcl2 7.1.0__tar.gz → 7.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/CHANGELOG.md +13 -1
  2. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/PKG-INFO +8 -13
  3. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/README.md +7 -12
  4. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/hcl2.lark +10 -8
  5. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/reconstructor.py +34 -5
  6. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/transformer.py +32 -19
  7. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/version.py +2 -2
  8. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/PKG-INFO +8 -13
  9. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.codacy.yml +0 -0
  10. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.coveragerc +0 -0
  11. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/CODEOWNERS +0 -0
  12. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/ISSUE_TEMPLATE/hcl2-parsing-error.md +0 -0
  13. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/codeql-analysis.yml +0 -0
  14. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/pr_check.yml +0 -0
  15. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.github/workflows/publish.yml +0 -0
  16. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.gitignore +0 -0
  17. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.pre-commit-config.yaml +0 -0
  18. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/.yamllint.yml +0 -0
  19. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/LICENSE +0 -0
  20. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/MANIFEST.in +0 -0
  21. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/bin/terraform_test +0 -0
  22. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/__init__.py +0 -0
  23. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/__main__.py +0 -0
  24. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/api.py +0 -0
  25. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/builder.py +0 -0
  26. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/const.py +0 -0
  27. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/parser.py +0 -0
  28. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/hcl2/py.typed +0 -0
  29. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/mypy.ini +0 -0
  30. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/pylintrc +0 -0
  31. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/pyproject.toml +0 -0
  32. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/SOURCES.txt +0 -0
  33. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/dependency_links.txt +0 -0
  34. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/entry_points.txt +0 -0
  35. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/not-zip-safe +0 -0
  36. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/requires.txt +0 -0
  37. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/python_hcl2.egg-info/top_level.txt +0 -0
  38. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/reports/.gitignore +0 -0
  39. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/requirements.txt +0 -0
  40. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/setup.cfg +0 -0
  41. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/test-requirements.txt +0 -0
  42. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/tox.ini +0 -0
  43. {python_hcl2-7.1.0 → python_hcl2-7.2.0}/tree-to-hcl2-reconstruction.md +0 -0
@@ -7,7 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## \[Unreleased\]
9
9
 
10
- - Nothing yet
10
+ - Nothing Yet
11
+
12
+ ## \[7.2.0\] - 2025-04-24
13
+
14
+ ### Added
15
+
16
+ - Possibility to parse deeply nested interpolations (formerly a Limitation), Thanks again, @weaversam8 ([#223](https://github.com/amplify-education/python-hcl2/pull/223))
17
+
18
+ ### Fixed
19
+
20
+ - Issue parsing ellipsis in a separate line within `for` expression ([#221](https://github.com/amplify-education/python-hcl2/pull/221))
21
+ - Issue parsing inline expression as an object key; **see Limitations in README.md** ([#222](https://github.com/amplify-education/python-hcl2/pull/222))
22
+ - Preserve literals of e-notation floats in parsing and reconstruction. Thanks, @eranor ([#226](https://github.com/amplify-education/python-hcl2/pull/226))
11
23
 
12
24
  ## \[7.1.0\] - 2025-04-10
13
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hcl2
3
- Version: 7.1.0
3
+ Version: 7.2.0
4
4
  Summary: A parser for HCL2
5
5
  Author-email: Amplify Education <github@amplify.com>
6
6
  License: MIT
@@ -114,20 +114,15 @@ We’ll try to answer any PR’s promptly.
114
114
 
115
115
  ## Limitations
116
116
 
117
- ### Error parsing string interpolations nested more than 2 times
117
+ ### Using inline expression as an object key
118
118
 
119
- - Parsing following example is expected to throw out an exception and fail:
119
+ - Object key can be an expression as long as it is wrapped in parentheses:
120
120
  ```terraform
121
121
  locals {
122
- foo = "foo"
123
- name = "prefix1-${"prefix2-${"${local.foo}-bar"}"}" //should interpolate into "prefix1-prefix2-foo-bar" but fails
124
- }
125
- ```
126
- We recommend working around this by modifying the configuration in the following manner:
127
- ```terraform
128
- locals {
129
- foo = "foo"
130
- foo_bar = "${local.foo}-bar"
131
- name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
122
+ foo = "bar"
123
+ baz = {
124
+ (format("key_prefix_%s", local.foo)) : "value"
125
+ # format("key_prefix_%s", local.foo) : "value" this will fail
126
+ }
132
127
  }
133
128
  ```
@@ -87,20 +87,15 @@ We’ll try to answer any PR’s promptly.
87
87
 
88
88
  ## Limitations
89
89
 
90
- ### Error parsing string interpolations nested more than 2 times
90
+ ### Using inline expression as an object key
91
91
 
92
- - Parsing following example is expected to throw out an exception and fail:
92
+ - Object key can be an expression as long as it is wrapped in parentheses:
93
93
  ```terraform
94
94
  locals {
95
- foo = "foo"
96
- name = "prefix1-${"prefix2-${"${local.foo}-bar"}"}" //should interpolate into "prefix1-prefix2-foo-bar" but fails
97
- }
98
- ```
99
- We recommend working around this by modifying the configuration in the following manner:
100
- ```terraform
101
- locals {
102
- foo = "foo"
103
- foo_bar = "${local.foo}-bar"
104
- name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
95
+ foo = "bar"
96
+ baz = {
97
+ (format("key_prefix_%s", local.foo)) : "value"
98
+ # format("key_prefix_%s", local.foo) : "value" this will fail
99
+ }
105
100
  }
106
101
  ```
@@ -1,7 +1,7 @@
1
1
  start : body
2
2
  body : (new_line_or_comment? (attribute | block))* new_line_or_comment?
3
3
  attribute : identifier EQ expression
4
- block : identifier (identifier | STRING_LIT)* new_line_or_comment? "{" body "}"
4
+ block : identifier (identifier | STRING_LIT | string_with_interpolation)* new_line_or_comment? "{" body "}"
5
5
  new_line_or_comment: ( NL_OR_COMMENT )+
6
6
  NL_OR_COMMENT: /\n[ \t]*/ | /#.*\n/ | /\/\/.*\n/ | /\/\*(.|\n)*?(\*\/)/
7
7
 
@@ -45,6 +45,7 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
45
45
  | float_lit
46
46
  | int_lit
47
47
  | STRING_LIT
48
+ | string_with_interpolation
48
49
  | tuple
49
50
  | object
50
51
  | function_call
@@ -59,10 +60,10 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
59
60
  | for_tuple_expr
60
61
  | for_object_expr
61
62
 
62
- STRING_LIT : "\"" (STRING_CHARS | INTERPOLATION)* "\""
63
- STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))+/+ // any character except '"" unless inside a interpolation string
64
- NESTED_INTERPOLATION : "${" /[^}]+/ "}"
65
- INTERPOLATION : "${" (/(?:(?!\${)([^}]))+/ | NESTED_INTERPOLATION)+ "}"
63
+ STRING_LIT : "\"" STRING_CHARS? "\""
64
+ STRING_CHARS : /(?:(?!\${)([^"\\]|\\.|\$\$))+/ // any character except '"', including escaped $$
65
+ string_with_interpolation: "\"" (STRING_CHARS)* interpolation_maybe_nested (STRING_CHARS | interpolation_maybe_nested)* "\""
66
+ interpolation_maybe_nested: "${" expression "}"
66
67
 
67
68
 
68
69
  int_lit : NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+
@@ -75,8 +76,9 @@ EQ : /[ \t]*=(?!=|>)/
75
76
 
76
77
  tuple : "[" (new_line_or_comment* expression new_line_or_comment* ",")* (new_line_or_comment* expression)? new_line_or_comment* "]"
77
78
  object : "{" new_line_or_comment? (new_line_or_comment* (object_elem | (object_elem COMMA)) new_line_or_comment*)* "}"
78
- object_elem : LPAR? object_elem_key RPAR? ( EQ | COLON ) expression
79
- object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor
79
+ object_elem : object_elem_key ( EQ | COLON ) expression
80
+ object_elem_key : float_lit | int_lit | identifier | STRING_LIT | object_elem_key_dot_accessor | object_elem_key_expression
81
+ object_elem_key_expression : LPAR expression RPAR
80
82
  object_elem_key_dot_accessor : identifier (DOT identifier)+
81
83
 
82
84
  heredoc_template : /<<(?P<heredoc>[a-zA-Z][a-zA-Z0-9._-]+)\n?(?:.|\n)*?\n\s*(?P=heredoc)\n/
@@ -98,7 +100,7 @@ full_splat : "[*]" (get_attr | index)*
98
100
 
99
101
  FOR_OBJECT_ARROW : "=>"
100
102
  !for_tuple_expr : "[" new_line_or_comment? for_intro new_line_or_comment? expression new_line_or_comment? for_cond? new_line_or_comment? "]"
101
- !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? "}"
103
+ !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? "}"
102
104
  !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?
103
105
  !for_cond : "if" new_line_or_comment? expression
104
106
 
@@ -260,6 +260,7 @@ class HCLReconstructor(Reconstructor):
260
260
  Terminal("STRING_LIT"),
261
261
  Terminal("DECIMAL"),
262
262
  Terminal("NAME"),
263
+ Terminal("NEGATIVE_DECIMAL"),
263
264
  ]:
264
265
  return True
265
266
 
@@ -412,10 +413,7 @@ class HCLReverseTransformer:
412
413
  def _is_block(self, value: Any) -> bool:
413
414
  if isinstance(value, dict):
414
415
  block_body = value
415
- if (
416
- START_LINE_KEY in block_body.keys()
417
- or END_LINE_KEY in block_body.keys()
418
- ):
416
+ if START_LINE_KEY in block_body.keys() or END_LINE_KEY in block_body.keys():
419
417
  return True
420
418
 
421
419
  try:
@@ -520,7 +518,7 @@ class HCLReverseTransformer:
520
518
 
521
519
  return Tree(Token("RULE", "body"), children)
522
520
 
523
- # pylint: disable=too-many-branches, too-many-return-statements
521
+ # pylint: disable=too-many-branches, too-many-return-statements too-many-statements
524
522
  def _transform_value_to_expr_term(self, value, level) -> Union[Token, Tree]:
525
523
  """Transforms a value from a dictionary into an "expr_term" (a value in HCL2)
526
524
 
@@ -611,6 +609,37 @@ class HCLReverseTransformer:
611
609
  ],
612
610
  )
613
611
 
612
+ if isinstance(value, float):
613
+ value = str(value)
614
+ literal = []
615
+
616
+ if value[0] == "-":
617
+ # pop two first chars - minus and a digit
618
+ literal.append(Token("NEGATIVE_DECIMAL", value[:2]))
619
+ value = value[2:]
620
+
621
+ while value != "":
622
+ char = value[0]
623
+
624
+ if char == ".":
625
+ # current char marks beginning of decimal part: pop all remaining chars and end the loop
626
+ literal.append(Token("DOT", char))
627
+ literal.extend(Token("DECIMAL", char) for char in value[1:])
628
+ break
629
+
630
+ if char == "e":
631
+ # current char marks beginning of e-notation: pop all remaining chars and end the loop
632
+ literal.append(Token("EXP_MARK", value))
633
+ break
634
+
635
+ literal.append(Token("DECIMAL", char))
636
+ value = value[1:]
637
+
638
+ return Tree(
639
+ Token("RULE", "expr_term"),
640
+ [Tree(Token("RULE", "float_lit"), literal)],
641
+ )
642
+
614
643
  # store strings as single literals
615
644
  if isinstance(value, str):
616
645
  # potentially unpack a complex syntax structure
@@ -44,7 +44,10 @@ class DictTransformer(Transformer):
44
44
  super().__init__()
45
45
 
46
46
  def float_lit(self, args: List) -> float:
47
- return float("".join([self.to_tf_inline(arg) for arg in args]))
47
+ value = "".join([self.to_tf_inline(arg) for arg in args])
48
+ if "e" in value:
49
+ return self.to_string_dollar(value)
50
+ return float(value)
48
51
 
49
52
  def int_lit(self, args: List) -> int:
50
53
  return int("".join([self.to_tf_inline(arg) for arg in args]))
@@ -98,14 +101,9 @@ class DictTransformer(Transformer):
98
101
  def object_elem(self, args: List) -> Dict:
99
102
  # This returns a dict with a single key/value pair to make it easier to merge these
100
103
  # into a bigger dict that is returned by the "object" function
101
- if args[0] == Token("LPAR", "("):
102
- key = self.strip_quotes(str(args[1].children[0]))
103
- key = f"({key})"
104
- key = self.to_string_dollar(key)
105
- value = args[4]
106
- else:
107
- key = self.strip_quotes(str(args[0].children[0]))
108
- value = args[2]
104
+
105
+ key = self.strip_quotes(str(args[0].children[0]))
106
+ value = args[2]
109
107
 
110
108
  value = self.to_string_dollar(value)
111
109
  return {key: value}
@@ -113,6 +111,9 @@ class DictTransformer(Transformer):
113
111
  def object_elem_key_dot_accessor(self, args: List) -> str:
114
112
  return "".join(args)
115
113
 
114
+ def object_elem_key_expression(self, args: List) -> str:
115
+ return self.to_string_dollar("".join(args))
116
+
116
117
  def object(self, args: List) -> Dict:
117
118
  args = self.strip_new_line_tokens(args)
118
119
  result: Dict[str, Any] = {}
@@ -179,7 +180,9 @@ class DictTransformer(Transformer):
179
180
  return f"{args[0]} ? {args[1]} : {args[2]}"
180
181
 
181
182
  def binary_op(self, args: List) -> str:
182
- return " ".join([self.to_tf_inline(arg) for arg in args])
183
+ return " ".join(
184
+ [self.unwrap_string_dollar(self.to_tf_inline(arg)) for arg in args]
185
+ )
183
186
 
184
187
  def unary_op(self, args: List) -> str:
185
188
  args = self.process_nulls(args)
@@ -306,21 +309,31 @@ class DictTransformer(Transformer):
306
309
  """
307
310
  return [arg for arg in args if arg != "\n" and arg is not Discard]
308
311
 
312
+ def is_string_dollar(self, value: str) -> bool:
313
+ if not isinstance(value, str):
314
+ return False
315
+ return value.startswith("${") and value.endswith("}")
316
+
309
317
  def to_string_dollar(self, value: Any) -> Any:
310
318
  """Wrap a string in ${ and }"""
311
- if isinstance(value, str):
319
+ if not isinstance(value, str):
320
+ return value
312
321
  # if it's already wrapped, pass it unmodified
313
- if value.startswith("${") and value.endswith("}"):
314
- return value
322
+ if self.is_string_dollar(value):
323
+ return value
315
324
 
316
- if value.startswith('"') and value.endswith('"'):
317
- value = str(value)[1:-1]
318
- return self.process_escape_sequences(value)
325
+ if value.startswith('"') and value.endswith('"'):
326
+ value = str(value)[1:-1]
327
+ return self.process_escape_sequences(value)
328
+
329
+ if self.is_type_keyword(value):
330
+ return value
319
331
 
320
- if self.is_type_keyword(value):
321
- return value
332
+ return f"${{{value}}}"
322
333
 
323
- return f"${{{value}}}"
334
+ def unwrap_string_dollar(self, value: str):
335
+ if self.is_string_dollar(value):
336
+ return value[2:-1]
324
337
  return value
325
338
 
326
339
  def strip_quotes(self, value: Any) -> Any:
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '7.1.0'
21
- __version_tuple__ = version_tuple = (7, 1, 0)
20
+ __version__ = version = '7.2.0'
21
+ __version_tuple__ = version_tuple = (7, 2, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hcl2
3
- Version: 7.1.0
3
+ Version: 7.2.0
4
4
  Summary: A parser for HCL2
5
5
  Author-email: Amplify Education <github@amplify.com>
6
6
  License: MIT
@@ -114,20 +114,15 @@ We’ll try to answer any PR’s promptly.
114
114
 
115
115
  ## Limitations
116
116
 
117
- ### Error parsing string interpolations nested more than 2 times
117
+ ### Using inline expression as an object key
118
118
 
119
- - Parsing following example is expected to throw out an exception and fail:
119
+ - Object key can be an expression as long as it is wrapped in parentheses:
120
120
  ```terraform
121
121
  locals {
122
- foo = "foo"
123
- name = "prefix1-${"prefix2-${"${local.foo}-bar"}"}" //should interpolate into "prefix1-prefix2-foo-bar" but fails
124
- }
125
- ```
126
- We recommend working around this by modifying the configuration in the following manner:
127
- ```terraform
128
- locals {
129
- foo = "foo"
130
- foo_bar = "${local.foo}-bar"
131
- name = "prefix1-${"prefix2-${local.foo_bar}"}" //interpolates into "prefix1-prefix2-foo-bar"
122
+ foo = "bar"
123
+ baz = {
124
+ (format("key_prefix_%s", local.foo)) : "value"
125
+ # format("key_prefix_%s", local.foo) : "value" this will fail
126
+ }
132
127
  }
133
128
  ```
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes