python-hcl2 6.1.0__tar.gz → 7.0.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.
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.github/workflows/publish.yml +3 -3
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/CHANGELOG.md +26 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/PKG-INFO +25 -3
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/README.md +21 -1
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/__init__.py +0 -1
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/api.py +14 -29
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/hcl2.lark +15 -12
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/reconstructor.py +82 -87
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/transformer.py +21 -13
- python_hcl2-7.0.0/hcl2/version.py +21 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/PKG-INFO +25 -3
- python-hcl2-6.1.0/hcl2/version.py +0 -4
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.codacy.yml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.coveragerc +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.github/CODEOWNERS +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.github/workflows/codeql-analysis.yml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.github/workflows/pr_check.yml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.gitignore +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.pre-commit-config.yaml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/.yamllint.yml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/LICENSE +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/MANIFEST.in +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/bin/terraform_test +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/__main__.py +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/builder.py +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/parser.py +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/hcl2/py.typed +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/mypy.ini +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/pylintrc +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/pyproject.toml +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/SOURCES.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/dependency_links.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/entry_points.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/not-zip-safe +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/requires.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/python_hcl2.egg-info/top_level.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/reports/.gitignore +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/requirements.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/setup.cfg +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/test-requirements.txt +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/tox.ini +0 -0
- {python-hcl2-6.1.0 → python_hcl2-7.0.0}/tree-to-hcl2-reconstruction.md +0 -0
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
- name: Set up Python
|
|
14
14
|
uses: actions/setup-python@v2
|
|
15
15
|
with:
|
|
16
|
-
python-version: 3.
|
|
16
|
+
python-version: 3.13
|
|
17
17
|
- name: Install dependencies
|
|
18
18
|
run: python -m pip install --upgrade pip build
|
|
19
19
|
- name: Generate Lark Parser
|
|
@@ -23,12 +23,12 @@ jobs:
|
|
|
23
23
|
- name: Build tarball
|
|
24
24
|
run: python3 -m build
|
|
25
25
|
- name: Publish to Test PyPI
|
|
26
|
-
uses: pypa/gh-action-pypi-publish@
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
27
|
with:
|
|
28
28
|
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
29
29
|
repository_url: https://test.pypi.org/legacy/
|
|
30
30
|
skip_existing: true
|
|
31
31
|
- name: Publish to PyPI
|
|
32
|
-
uses: pypa/gh-action-pypi-publish@
|
|
32
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
33
33
|
with:
|
|
34
34
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## \[7.0.0\] - 2025-03-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `Limitations` section to README.md ([#200](https://github.com/amplify-education/python-hcl2/pull/200))
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Issue handling heredoc with delimiter within text itself ([#194](https://github.com/amplify-education/python-hcl2/pull/194))
|
|
17
|
+
- Various issues with parsing object elements ([#197](https://github.com/amplify-education/python-hcl2/pull/197))
|
|
18
|
+
- Dictionary -> hcl2 reconstruction of `null` values ([#198](https://github.com/amplify-education/python-hcl2/pull/198))
|
|
19
|
+
- Inaccurate parsing of `null` values in some cases ([#206](https://github.com/amplify-education/python-hcl2/pull/206))
|
|
20
|
+
- Missing parenthesis in arithemetic expressions ([#194](https://github.com/amplify-education/python-hcl2/pull/199))
|
|
21
|
+
- Noticeable overhead when loading hcl2.reconstructor module ([#202](https://github.com/amplify-education/python-hcl2/pull/202))
|
|
22
|
+
- Escaped string interpolation (e.g. `"$${aws:username}"`) parsing ([#200](https://github.com/amplify-education/python-hcl2/pull/200))
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- Support for parsing interpolations nested more than 2 times (known-issue) ([#200](https://github.com/amplify-education/python-hcl2/pull/200))
|
|
27
|
+
|
|
28
|
+
## \[6.1.1\] - 2025-02-13
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `DictTransformer.to_tf_inline` - handle float type. ([#188](https://github.com/amplify-education/python-hcl2/pull/188))
|
|
33
|
+
|
|
8
34
|
## \[6.1.0\] - 2025-01-24
|
|
9
35
|
|
|
10
36
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
@@ -22,6 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Requires-Python: >=3.7.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
+
Requires-Dist: lark<2,>=1
|
|
26
|
+
Dynamic: license-file
|
|
25
27
|
|
|
26
28
|
[](https://app.codacy.com/gh/amplify-education/python-hcl2/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
27
29
|
[](https://travis-ci.org/amplify-education/python-hcl2)
|
|
@@ -92,7 +94,7 @@ To see all the available options, run `tox -l`.
|
|
|
92
94
|
|
|
93
95
|
## Releasing
|
|
94
96
|
|
|
95
|
-
To create a new
|
|
97
|
+
To create a new release go to Releases page, press 'Draft a new release', create a tag
|
|
96
98
|
with a version you want to be released, fill the release notes and press 'Publish release'.
|
|
97
99
|
Github actions will take care of publishing it to PyPi.
|
|
98
100
|
|
|
@@ -109,3 +111,23 @@ We welcome pull requests! For your pull request to be accepted smoothly, we sugg
|
|
|
109
111
|
- Create a pull request. Explain why you want to make the change and what it’s for.
|
|
110
112
|
|
|
111
113
|
We’ll try to answer any PR’s promptly.
|
|
114
|
+
|
|
115
|
+
## Limitations
|
|
116
|
+
|
|
117
|
+
### Error parsing string interpolations nested more than 2 times
|
|
118
|
+
|
|
119
|
+
- Parsing following example is expected to throw out an exception and fail:
|
|
120
|
+
```terraform
|
|
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"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
@@ -67,7 +67,7 @@ To see all the available options, run `tox -l`.
|
|
|
67
67
|
|
|
68
68
|
## Releasing
|
|
69
69
|
|
|
70
|
-
To create a new
|
|
70
|
+
To create a new release go to Releases page, press 'Draft a new release', create a tag
|
|
71
71
|
with a version you want to be released, fill the release notes and press 'Publish release'.
|
|
72
72
|
Github actions will take care of publishing it to PyPi.
|
|
73
73
|
|
|
@@ -84,3 +84,23 @@ We welcome pull requests! For your pull request to be accepted smoothly, we sugg
|
|
|
84
84
|
- Create a pull request. Explain why you want to make the change and what it’s for.
|
|
85
85
|
|
|
86
86
|
We’ll try to answer any PR’s promptly.
|
|
87
|
+
|
|
88
|
+
## Limitations
|
|
89
|
+
|
|
90
|
+
### Error parsing string interpolations nested more than 2 times
|
|
91
|
+
|
|
92
|
+
- Parsing following example is expected to throw out an exception and fail:
|
|
93
|
+
```terraform
|
|
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"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""The API that will be exposed to users of this package"""
|
|
2
2
|
from typing import TextIO
|
|
3
3
|
|
|
4
|
-
from lark.tree import Tree
|
|
5
|
-
from hcl2.parser import parser
|
|
4
|
+
from lark.tree import Tree
|
|
5
|
+
from hcl2.parser import parser, reconstruction_parser
|
|
6
6
|
from hcl2.transformer import DictTransformer
|
|
7
|
+
from hcl2.reconstructor import HCLReconstructor, HCLReverseTransformer
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def load(file: TextIO, with_meta=False) -> dict:
|
|
@@ -22,61 +23,45 @@ def loads(text: str, with_meta=False) -> dict:
|
|
|
22
23
|
parameters to the output dict. Default to false.
|
|
23
24
|
"""
|
|
24
25
|
# append new line as a workaround for https://github.com/lark-parser/lark/issues/237
|
|
25
|
-
# Lark doesn't support
|
|
26
|
+
# Lark doesn't support EOF token so our grammar can't look for "new line or end of file"
|
|
26
27
|
# This means that all blocks must end in a new line even if the file ends
|
|
27
28
|
# Append a new line as a temporary fix
|
|
28
29
|
tree = parser().parse(text + "\n")
|
|
29
30
|
return DictTransformer(with_meta=with_meta).transform(tree)
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def parse(file: TextIO) ->
|
|
33
|
+
def parse(file: TextIO) -> Tree:
|
|
33
34
|
"""Load HCL2 syntax tree from a file.
|
|
34
35
|
:param file: File with hcl2 to be loaded as a dict.
|
|
35
36
|
"""
|
|
36
37
|
return parses(file.read())
|
|
37
38
|
|
|
38
39
|
|
|
39
|
-
def parses(text: str) ->
|
|
40
|
+
def parses(text: str) -> Tree:
|
|
40
41
|
"""Load HCL2 syntax tree from a string.
|
|
41
42
|
:param text: Text with hcl2 to be loaded as a dict.
|
|
42
43
|
"""
|
|
43
|
-
# defer this import until this method is called, due to the performance hit
|
|
44
|
-
# of rebuilding the grammar without cache
|
|
45
|
-
from hcl2.parser import ( # pylint: disable=import-outside-toplevel
|
|
46
|
-
reconstruction_parser,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
44
|
return reconstruction_parser().parse(text)
|
|
50
45
|
|
|
51
46
|
|
|
52
|
-
def transform(ast:
|
|
47
|
+
def transform(ast: Tree, with_meta=False) -> dict:
|
|
53
48
|
"""Convert an HCL2 AST to a dictionary.
|
|
54
49
|
:param ast: HCL2 syntax tree, output from `parse` or `parses`
|
|
50
|
+
:param with_meta: If set to true then adds `__start_line__` and `__end_line__`
|
|
51
|
+
parameters to the output dict. Default to false.
|
|
55
52
|
"""
|
|
56
53
|
return DictTransformer(with_meta=with_meta).transform(ast)
|
|
57
54
|
|
|
58
55
|
|
|
59
|
-
def reverse_transform(hcl2_dict: dict) ->
|
|
56
|
+
def reverse_transform(hcl2_dict: dict) -> Tree:
|
|
60
57
|
"""Convert a dictionary to an HCL2 AST.
|
|
61
|
-
:param
|
|
58
|
+
:param hcl2_dict: a dictionary produced by `load` or `transform`
|
|
62
59
|
"""
|
|
63
|
-
|
|
64
|
-
# of rebuilding the grammar without cache
|
|
65
|
-
from hcl2.reconstructor import ( # pylint: disable=import-outside-toplevel
|
|
66
|
-
hcl2_reverse_transformer,
|
|
67
|
-
)
|
|
60
|
+
return HCLReverseTransformer().transform(hcl2_dict)
|
|
68
61
|
|
|
69
|
-
return hcl2_reverse_transformer.transform(hcl2_dict)
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
def writes(ast: AST) -> str:
|
|
63
|
+
def writes(ast: Tree) -> str:
|
|
73
64
|
"""Convert an HCL2 syntax tree to a string.
|
|
74
65
|
:param ast: HCL2 syntax tree, output from `parse` or `parses`
|
|
75
66
|
"""
|
|
76
|
-
|
|
77
|
-
# of rebuilding the grammar without cache
|
|
78
|
-
from hcl2.reconstructor import ( # pylint: disable=import-outside-toplevel
|
|
79
|
-
hcl2_reconstructor,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
return hcl2_reconstructor.reconstruct(ast)
|
|
67
|
+
return HCLReconstructor(reconstruction_parser()).reconstruct(ast)
|
|
@@ -1,8 +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
|
|
5
|
-
new_line_and_or_comma: new_line_or_comment | "," | "," new_line_or_comment
|
|
4
|
+
block : identifier (identifier | STRING_LIT)* new_line_or_comment? "{" body "}"
|
|
6
5
|
new_line_or_comment: ( NL_OR_COMMENT )+
|
|
7
6
|
NL_OR_COMMENT: /\n[ \t]*/ | /#.*\n/ | /\/\/.*\n/ | /\/\*(.|\n)*?(\*\/)/
|
|
8
7
|
|
|
@@ -36,12 +35,14 @@ PERCENT : "%"
|
|
|
36
35
|
DOUBLE_AMP : "&&"
|
|
37
36
|
DOUBLE_PIPE : "||"
|
|
38
37
|
PLUS : "+"
|
|
38
|
+
LPAR : "("
|
|
39
|
+
RPAR : ")"
|
|
40
|
+
COMMA : ","
|
|
39
41
|
|
|
40
|
-
expr_term :
|
|
42
|
+
expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
|
|
41
43
|
| float_lit
|
|
42
44
|
| int_lit
|
|
43
45
|
| STRING_LIT
|
|
44
|
-
| string_with_interpolation
|
|
45
46
|
| tuple
|
|
46
47
|
| object
|
|
47
48
|
| function_call
|
|
@@ -56,10 +57,11 @@ expr_term : "(" new_line_or_comment? expression new_line_or_comment? ")"
|
|
|
56
57
|
| for_tuple_expr
|
|
57
58
|
| for_object_expr
|
|
58
59
|
|
|
59
|
-
STRING_LIT : "\"" STRING_CHARS
|
|
60
|
-
STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
STRING_LIT : "\"" (STRING_CHARS | INTERPOLATION)* "\""
|
|
61
|
+
STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))+/+ // any character except '"" unless inside a interpolation string
|
|
62
|
+
NESTED_INTERPOLATION : "${" /[^}]+/ "}"
|
|
63
|
+
INTERPOLATION : "${" (/(?:(?!\${)([^}]))+/ | NESTED_INTERPOLATION)+ "}"
|
|
64
|
+
|
|
63
65
|
|
|
64
66
|
int_lit : NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+
|
|
65
67
|
!float_lit: (NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+) "." DECIMAL+ (EXP_MARK)?
|
|
@@ -70,12 +72,13 @@ EXP_MARK : ("e" | "E") ("+" | "-")? DECIMAL+
|
|
|
70
72
|
EQ : /[ \t]*=(?!=|>)/
|
|
71
73
|
|
|
72
74
|
tuple : "[" (new_line_or_comment* expression new_line_or_comment* ",")* (new_line_or_comment* expression)? new_line_or_comment* "]"
|
|
73
|
-
object : "{" new_line_or_comment? (object_elem (
|
|
74
|
-
object_elem :
|
|
75
|
+
object : "{" new_line_or_comment? (new_line_or_comment* (object_elem | (object_elem COMMA)) new_line_or_comment*)* "}"
|
|
76
|
+
object_elem : object_elem_key ( EQ | ":") expression
|
|
77
|
+
object_elem_key : float_lit | int_lit | identifier | STRING_LIT
|
|
75
78
|
|
|
76
79
|
|
|
77
|
-
heredoc_template : /<<(?P<heredoc>[a-zA-Z][a-zA-Z0-9._-]+)\n(?:.|\n)
|
|
78
|
-
heredoc_template_trim : /<<-(?P<heredoc_trim>[a-zA-Z][a-zA-Z0-9._-]+)\n(?:.|\n)
|
|
80
|
+
heredoc_template : /<<(?P<heredoc>[a-zA-Z][a-zA-Z0-9._-]+)\n?(?:.|\n)*?\n\s*(?P=heredoc)\n/
|
|
81
|
+
heredoc_template_trim : /<<-(?P<heredoc_trim>[a-zA-Z][a-zA-Z0-9._-]+)\n?(?:.|\n)*?\n\s*(?P=heredoc_trim)\n/
|
|
79
82
|
|
|
80
83
|
function_call : identifier "(" new_line_or_comment? arguments? new_line_or_comment? ")"
|
|
81
84
|
arguments : (expression (new_line_or_comment* "," new_line_or_comment* expression)* ("," | "...")? new_line_or_comment*)
|
|
@@ -22,7 +22,7 @@ def reverse_quotes_within_interpolation(interp_s: str) -> str:
|
|
|
22
22
|
method removes any erroneous escapes within interpolated segments of a
|
|
23
23
|
string.
|
|
24
24
|
"""
|
|
25
|
-
return re.sub(r"\$\{(.*)
|
|
25
|
+
return re.sub(r"\$\{(.*)}", lambda m: m.group(0).replace('\\"', '"'), interp_s)
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class WriteTokensAndMetaTransformer(Transformer_InPlace):
|
|
@@ -43,6 +43,7 @@ class WriteTokensAndMetaTransformer(Transformer_InPlace):
|
|
|
43
43
|
tokens: Dict[str, TerminalDef],
|
|
44
44
|
term_subs: Dict[str, Callable[[Symbol], str]],
|
|
45
45
|
) -> None:
|
|
46
|
+
super().__init__()
|
|
46
47
|
self.tokens = tokens
|
|
47
48
|
self.term_subs = term_subs
|
|
48
49
|
|
|
@@ -91,14 +92,6 @@ class WriteTokensAndMetaTransformer(Transformer_InPlace):
|
|
|
91
92
|
class HCLReconstructor(Reconstructor):
|
|
92
93
|
"""This class converts a Lark.Tree AST back into a string representing the underlying HCL code."""
|
|
93
94
|
|
|
94
|
-
# these variables track state during reconstruction to enable us to make
|
|
95
|
-
# informed decisions about formatting output. They are primarily used
|
|
96
|
-
# by the _should_add_space(...) method.
|
|
97
|
-
last_char_space = True
|
|
98
|
-
last_terminal = None
|
|
99
|
-
last_rule = None
|
|
100
|
-
deferred_item = None
|
|
101
|
-
|
|
102
95
|
def __init__(
|
|
103
96
|
self,
|
|
104
97
|
parser: Lark,
|
|
@@ -106,32 +99,38 @@ class HCLReconstructor(Reconstructor):
|
|
|
106
99
|
):
|
|
107
100
|
Reconstructor.__init__(self, parser, term_subs)
|
|
108
101
|
|
|
109
|
-
self.write_tokens =
|
|
110
|
-
|
|
102
|
+
self.write_tokens: WriteTokensAndMetaTransformer = (
|
|
103
|
+
WriteTokensAndMetaTransformer(
|
|
104
|
+
{token.name: token for token in self.tokens}, term_subs or {}
|
|
105
|
+
)
|
|
111
106
|
)
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Terminal
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
108
|
+
# these variables track state during reconstruction to enable us to make
|
|
109
|
+
# informed decisions about formatting output. They are primarily used
|
|
110
|
+
# by the _should_add_space(...) method.
|
|
111
|
+
self._last_char_space = True
|
|
112
|
+
self._last_terminal: Union[Terminal, None] = None
|
|
113
|
+
self._last_rule: Union[Tree, Token, None] = None
|
|
114
|
+
self._deferred_item = None
|
|
115
|
+
|
|
116
|
+
def should_be_wrapped_in_spaces(self, terminal: Terminal) -> bool:
|
|
117
|
+
"""Whether given terminal should be wrapped in spaces"""
|
|
118
|
+
return terminal.name in {
|
|
119
|
+
"IF",
|
|
120
|
+
"IN",
|
|
121
|
+
"FOR",
|
|
122
|
+
"FOR_EACH",
|
|
123
|
+
"FOR_OBJECT_ARROW",
|
|
124
|
+
"COLON",
|
|
125
|
+
"QMARK",
|
|
126
|
+
"BINARY_OP",
|
|
127
|
+
}
|
|
129
128
|
|
|
130
129
|
def _is_equals_sign(self, terminal) -> bool:
|
|
131
130
|
return (
|
|
132
|
-
isinstance(self.
|
|
133
|
-
and self.
|
|
134
|
-
and self.
|
|
131
|
+
isinstance(self._last_rule, Token)
|
|
132
|
+
and self._last_rule.value in ("attribute", "object_elem")
|
|
133
|
+
and self._last_terminal == Terminal("EQ")
|
|
135
134
|
and terminal != Terminal("NL_OR_COMMENT")
|
|
136
135
|
)
|
|
137
136
|
|
|
@@ -155,11 +154,11 @@ class HCLReconstructor(Reconstructor):
|
|
|
155
154
|
This should be sufficient to make a spacing decision.
|
|
156
155
|
"""
|
|
157
156
|
# we don't need to add multiple spaces
|
|
158
|
-
if self.
|
|
157
|
+
if self._last_char_space:
|
|
159
158
|
return False
|
|
160
159
|
|
|
161
160
|
# we don't add a space at the start of the file
|
|
162
|
-
if not self.
|
|
161
|
+
if not self._last_terminal or not self._last_rule:
|
|
163
162
|
return False
|
|
164
163
|
|
|
165
164
|
if self._is_equals_sign(current_terminal):
|
|
@@ -173,20 +172,20 @@ class HCLReconstructor(Reconstructor):
|
|
|
173
172
|
"conditional",
|
|
174
173
|
"binary_operator",
|
|
175
174
|
]
|
|
176
|
-
and
|
|
175
|
+
and self.should_be_wrapped_in_spaces(current_terminal)
|
|
177
176
|
):
|
|
178
177
|
return True
|
|
179
178
|
|
|
180
179
|
# if we just left a ternary or binary operator, add space around the
|
|
181
180
|
# operator unless there's a newline already
|
|
182
181
|
if (
|
|
183
|
-
isinstance(self.
|
|
184
|
-
and self.
|
|
182
|
+
isinstance(self._last_rule, Token)
|
|
183
|
+
and self._last_rule.value
|
|
185
184
|
in [
|
|
186
185
|
"conditional",
|
|
187
186
|
"binary_operator",
|
|
188
187
|
]
|
|
189
|
-
and self.
|
|
188
|
+
and self.should_be_wrapped_in_spaces(self._last_terminal)
|
|
190
189
|
and current_terminal != Terminal("NL_OR_COMMENT")
|
|
191
190
|
):
|
|
192
191
|
return True
|
|
@@ -200,21 +199,21 @@ class HCLReconstructor(Reconstructor):
|
|
|
200
199
|
"for_cond",
|
|
201
200
|
"for_intro",
|
|
202
201
|
]
|
|
203
|
-
and
|
|
202
|
+
and self.should_be_wrapped_in_spaces(current_terminal)
|
|
204
203
|
):
|
|
205
204
|
return True
|
|
206
205
|
|
|
207
206
|
# if we've just left a for or if statement and find a keyword, add a
|
|
208
207
|
# space, unless we have a newline
|
|
209
208
|
if (
|
|
210
|
-
isinstance(self.
|
|
211
|
-
and self.
|
|
209
|
+
isinstance(self._last_rule, Token)
|
|
210
|
+
and self._last_rule.value
|
|
212
211
|
in [
|
|
213
212
|
"for_object_expr",
|
|
214
213
|
"for_cond",
|
|
215
214
|
"for_intro",
|
|
216
215
|
]
|
|
217
|
-
and self.
|
|
216
|
+
and self.should_be_wrapped_in_spaces(self._last_terminal)
|
|
218
217
|
and current_terminal != Terminal("NL_OR_COMMENT")
|
|
219
218
|
):
|
|
220
219
|
return True
|
|
@@ -230,7 +229,7 @@ class HCLReconstructor(Reconstructor):
|
|
|
230
229
|
# always add space before the closing brace
|
|
231
230
|
if current_terminal == Terminal(
|
|
232
231
|
"RBRACE"
|
|
233
|
-
) and self.
|
|
232
|
+
) and self._last_terminal != Terminal("LBRACE"):
|
|
234
233
|
return True
|
|
235
234
|
|
|
236
235
|
# always add space between string literals
|
|
@@ -240,20 +239,20 @@ class HCLReconstructor(Reconstructor):
|
|
|
240
239
|
# if we just opened a block, add a space, unless the block is empty
|
|
241
240
|
# or has a newline
|
|
242
241
|
if (
|
|
243
|
-
isinstance(self.
|
|
244
|
-
and self.
|
|
245
|
-
and self.
|
|
242
|
+
isinstance(self._last_rule, Token)
|
|
243
|
+
and self._last_rule.value == "block"
|
|
244
|
+
and self._last_terminal == Terminal("LBRACE")
|
|
246
245
|
and current_terminal not in [Terminal("RBRACE"), Terminal("NL_OR_COMMENT")]
|
|
247
246
|
):
|
|
248
247
|
return True
|
|
249
248
|
|
|
250
249
|
# if we're in a tuple or function arguments (this rule matches commas between items)
|
|
251
|
-
if isinstance(self.
|
|
252
|
-
r"^__(tuple|arguments)_(star|plus)_.*", self.
|
|
250
|
+
if isinstance(self._last_rule, str) and re.match(
|
|
251
|
+
r"^__(tuple|arguments)_(star|plus)_.*", self._last_rule
|
|
253
252
|
):
|
|
254
253
|
|
|
255
254
|
# string literals, decimals, and identifiers should always be
|
|
256
|
-
#
|
|
255
|
+
# preceded by a space if they're following a comma in a tuple or
|
|
257
256
|
# function arg
|
|
258
257
|
if current_terminal in [
|
|
259
258
|
Terminal("STRING_LIT"),
|
|
@@ -279,12 +278,12 @@ class HCLReconstructor(Reconstructor):
|
|
|
279
278
|
rule, terminal, value = item
|
|
280
279
|
|
|
281
280
|
# first, handle any deferred items
|
|
282
|
-
if self.
|
|
281
|
+
if self._deferred_item is not None:
|
|
283
282
|
(
|
|
284
283
|
deferred_rule,
|
|
285
284
|
deferred_terminal,
|
|
286
285
|
deferred_value,
|
|
287
|
-
) = self.
|
|
286
|
+
) = self._deferred_item
|
|
288
287
|
|
|
289
288
|
# if we deferred a comma and the next character ends a
|
|
290
289
|
# parenthesis or block, we can throw it out
|
|
@@ -298,32 +297,32 @@ class HCLReconstructor(Reconstructor):
|
|
|
298
297
|
yield deferred_value
|
|
299
298
|
|
|
300
299
|
# and do our bookkeeping
|
|
301
|
-
self.
|
|
302
|
-
self.
|
|
300
|
+
self._last_terminal = deferred_terminal
|
|
301
|
+
self._last_rule = deferred_rule
|
|
303
302
|
if deferred_value and not deferred_value[-1].isspace():
|
|
304
|
-
self.
|
|
303
|
+
self._last_char_space = False
|
|
305
304
|
|
|
306
305
|
# clear the deferred item
|
|
307
|
-
self.
|
|
306
|
+
self._deferred_item = None
|
|
308
307
|
|
|
309
308
|
# potentially add a space before the next token
|
|
310
309
|
if self._should_add_space(rule, terminal):
|
|
311
310
|
yield " "
|
|
312
|
-
self.
|
|
311
|
+
self._last_char_space = True
|
|
313
312
|
|
|
314
|
-
# potentially defer the item if
|
|
313
|
+
# potentially defer the item if needed
|
|
315
314
|
if terminal in [Terminal("COMMA")]:
|
|
316
|
-
self.
|
|
315
|
+
self._deferred_item = item
|
|
317
316
|
else:
|
|
318
317
|
# otherwise print the next token
|
|
319
318
|
yield value
|
|
320
319
|
|
|
321
320
|
# and do our bookkeeping so we can make an informed
|
|
322
321
|
# decision about formatting next time
|
|
323
|
-
self.
|
|
324
|
-
self.
|
|
322
|
+
self._last_terminal = terminal
|
|
323
|
+
self._last_rule = rule
|
|
325
324
|
if value:
|
|
326
|
-
self.
|
|
325
|
+
self._last_char_space = value[-1].isspace()
|
|
327
326
|
|
|
328
327
|
else:
|
|
329
328
|
raise RuntimeError(f"Unknown bare token type: {item}")
|
|
@@ -375,7 +374,7 @@ class HCLReverseTransformer:
|
|
|
375
374
|
@staticmethod
|
|
376
375
|
def _is_string_wrapped_tf(interp_s: str) -> bool:
|
|
377
376
|
"""
|
|
378
|
-
Determines whether a string is a complex HCL
|
|
377
|
+
Determines whether a string is a complex HCL data structure
|
|
379
378
|
wrapped in ${ interpolation } characters.
|
|
380
379
|
"""
|
|
381
380
|
if not interp_s.startswith("${") or not interp_s.endswith("}"):
|
|
@@ -396,15 +395,7 @@ class HCLReverseTransformer:
|
|
|
396
395
|
|
|
397
396
|
return True
|
|
398
397
|
|
|
399
|
-
def _newline(self, level: int,
|
|
400
|
-
# some rules expect the `new_line_and_or_comma` token
|
|
401
|
-
if comma:
|
|
402
|
-
return Tree(
|
|
403
|
-
Token("RULE", "new_line_and_or_comma"),
|
|
404
|
-
[self._newline(level=level, comma=False, count=count)],
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
# otherwise, return the `new_line_or_comment` token
|
|
398
|
+
def _newline(self, level: int, count: int = 1) -> Tree:
|
|
408
399
|
return Tree(
|
|
409
400
|
Token("RULE", "new_line_or_comment"),
|
|
410
401
|
[Token("NL_OR_COMMENT", f"\n{' ' * level}") for _ in range(count)],
|
|
@@ -548,35 +539,48 @@ class HCLReverseTransformer:
|
|
|
548
539
|
)
|
|
549
540
|
return Tree(Token("RULE", "expr_term"), [tuple_tree])
|
|
550
541
|
|
|
542
|
+
if value is None:
|
|
543
|
+
return Tree(
|
|
544
|
+
Token("RULE", "expr_term"),
|
|
545
|
+
[Tree(Token("RULE", "identifier"), [Token("NAME", "null")])],
|
|
546
|
+
)
|
|
547
|
+
|
|
551
548
|
# for dicts, recursively turn the child k/v pairs into object elements
|
|
552
549
|
# and store within an object
|
|
553
550
|
if isinstance(value, dict):
|
|
554
|
-
|
|
551
|
+
elements = []
|
|
555
552
|
|
|
556
553
|
# if the object has elements, put it on a newline
|
|
557
554
|
if len(value) > 0:
|
|
558
|
-
|
|
555
|
+
elements.append(self._newline(level + 1))
|
|
559
556
|
|
|
560
557
|
# iterate through the items and add them to the object
|
|
561
558
|
for i, (k, dict_v) in enumerate(value.items()):
|
|
562
559
|
if k in ["__start_line__", "__end_line__"]:
|
|
563
560
|
continue
|
|
564
|
-
|
|
561
|
+
|
|
565
562
|
value_expr_term = self._transform_value_to_expr_term(dict_v, level + 1)
|
|
566
|
-
|
|
563
|
+
elements.append(
|
|
567
564
|
Tree(
|
|
568
565
|
Token("RULE", "object_elem"),
|
|
569
|
-
[
|
|
566
|
+
[
|
|
567
|
+
Tree(
|
|
568
|
+
Token("RULE", "object_elem_key"),
|
|
569
|
+
[Tree(Token("RULE", "identifier"), [Token("NAME", k)])],
|
|
570
|
+
),
|
|
571
|
+
Token("EQ", " ="),
|
|
572
|
+
value_expr_term,
|
|
573
|
+
],
|
|
570
574
|
)
|
|
571
575
|
)
|
|
572
576
|
|
|
573
577
|
# add indentation appropriately
|
|
574
578
|
if i < len(value) - 1:
|
|
575
|
-
|
|
579
|
+
elements.append(self._newline(level + 1))
|
|
576
580
|
else:
|
|
577
|
-
|
|
581
|
+
elements.append(self._newline(level))
|
|
578
582
|
return Tree(
|
|
579
|
-
Token("RULE", "expr_term"), [Tree(Token("RULE", "object"),
|
|
583
|
+
Token("RULE", "expr_term"), [Tree(Token("RULE", "object"), elements)]
|
|
580
584
|
)
|
|
581
585
|
|
|
582
586
|
# treat booleans appropriately
|
|
@@ -626,12 +630,7 @@ class HCLReverseTransformer:
|
|
|
626
630
|
raise RuntimeError("Token must be `EQ (=)` rule")
|
|
627
631
|
|
|
628
632
|
parsed_value = attribute.children[2]
|
|
629
|
-
|
|
630
|
-
if parsed_value.data == Token("RULE", "expr_term"):
|
|
631
|
-
return parsed_value
|
|
632
|
-
|
|
633
|
-
# wrap other types of syntax as an expression (in parenthesis)
|
|
634
|
-
return Tree(Token("RULE", "expr_term"), [parsed_value])
|
|
633
|
+
return parsed_value
|
|
635
634
|
|
|
636
635
|
# otherwise it's just a string.
|
|
637
636
|
return Tree(
|
|
@@ -641,7 +640,3 @@ class HCLReverseTransformer:
|
|
|
641
640
|
|
|
642
641
|
# otherwise, we don't know the type
|
|
643
642
|
raise RuntimeError(f"Unknown type to transform {type(value)}")
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
hcl2_reconstructor = HCLReconstructor(reconstruction_parser())
|
|
647
|
-
hcl2_reverse_transformer = HCLReverseTransformer()
|
|
@@ -5,6 +5,7 @@ import sys
|
|
|
5
5
|
from collections import namedtuple
|
|
6
6
|
from typing import List, Dict, Any
|
|
7
7
|
|
|
8
|
+
from lark import Token
|
|
8
9
|
from lark.tree import Meta
|
|
9
10
|
from lark.visitors import Transformer, Discard, _DiscardType, v_args
|
|
10
11
|
|
|
@@ -51,7 +52,6 @@ class DictTransformer(Transformer):
|
|
|
51
52
|
def expr_term(self, args: List) -> Any:
|
|
52
53
|
args = self.strip_new_line_tokens(args)
|
|
53
54
|
|
|
54
|
-
#
|
|
55
55
|
if args[0] == "true":
|
|
56
56
|
return True
|
|
57
57
|
if args[0] == "false":
|
|
@@ -59,10 +59,9 @@ class DictTransformer(Transformer):
|
|
|
59
59
|
if args[0] == "null":
|
|
60
60
|
return None
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# otherwise return the value itself
|
|
62
|
+
if args[0] == "(" and args[-1] == ")":
|
|
63
|
+
return "".join(str(arg) for arg in args)
|
|
64
|
+
|
|
66
65
|
return args[0]
|
|
67
66
|
|
|
68
67
|
def index_expr_term(self, args: List) -> str:
|
|
@@ -99,18 +98,24 @@ class DictTransformer(Transformer):
|
|
|
99
98
|
def object_elem(self, args: List) -> Dict:
|
|
100
99
|
# This returns a dict with a single key/value pair to make it easier to merge these
|
|
101
100
|
# into a bigger dict that is returned by the "object" function
|
|
102
|
-
key = self.strip_quotes(args[0])
|
|
101
|
+
key = self.strip_quotes(str(args[0].children[0]))
|
|
103
102
|
if len(args) == 3:
|
|
104
|
-
value =
|
|
103
|
+
value = args[2]
|
|
105
104
|
else:
|
|
106
|
-
value =
|
|
105
|
+
value = args[1]
|
|
107
106
|
|
|
107
|
+
value = self.to_string_dollar(value)
|
|
108
108
|
return {key: value}
|
|
109
109
|
|
|
110
110
|
def object(self, args: List) -> Dict:
|
|
111
111
|
args = self.strip_new_line_tokens(args)
|
|
112
112
|
result: Dict[str, Any] = {}
|
|
113
113
|
for arg in args:
|
|
114
|
+
if (
|
|
115
|
+
isinstance(arg, Token) and arg.type == "COMMA"
|
|
116
|
+
): # skip optional comma at the end of object element
|
|
117
|
+
continue
|
|
118
|
+
|
|
114
119
|
result.update(arg)
|
|
115
120
|
return result
|
|
116
121
|
|
|
@@ -134,10 +139,7 @@ class DictTransformer(Transformer):
|
|
|
134
139
|
return f"{provider_func}({args_str})"
|
|
135
140
|
|
|
136
141
|
def arguments(self, args: List) -> List:
|
|
137
|
-
return args
|
|
138
|
-
|
|
139
|
-
def new_line_and_or_comma(self, args: List) -> _DiscardType:
|
|
140
|
-
return Discard
|
|
142
|
+
return self.process_nulls(args)
|
|
141
143
|
|
|
142
144
|
@v_args(meta=True)
|
|
143
145
|
def block(self, meta: Meta, args: List) -> Dict:
|
|
@@ -167,16 +169,19 @@ class DictTransformer(Transformer):
|
|
|
167
169
|
|
|
168
170
|
def conditional(self, args: List) -> str:
|
|
169
171
|
args = self.strip_new_line_tokens(args)
|
|
172
|
+
args = self.process_nulls(args)
|
|
170
173
|
return f"{args[0]} ? {args[1]} : {args[2]}"
|
|
171
174
|
|
|
172
175
|
def binary_op(self, args: List) -> str:
|
|
173
176
|
return " ".join([self.to_tf_inline(arg) for arg in args])
|
|
174
177
|
|
|
175
178
|
def unary_op(self, args: List) -> str:
|
|
179
|
+
args = self.process_nulls(args)
|
|
176
180
|
return "".join([self.to_tf_inline(arg) for arg in args])
|
|
177
181
|
|
|
178
182
|
def binary_term(self, args: List) -> str:
|
|
179
183
|
args = self.strip_new_line_tokens(args)
|
|
184
|
+
args = self.process_nulls(args)
|
|
180
185
|
return " ".join([self.to_tf_inline(arg) for arg in args])
|
|
181
186
|
|
|
182
187
|
def body(self, args: List) -> Dict[str, List]:
|
|
@@ -334,6 +339,9 @@ class DictTransformer(Transformer):
|
|
|
334
339
|
# for now, but this method can be extended in the future
|
|
335
340
|
return value
|
|
336
341
|
|
|
342
|
+
def process_nulls(self, args: List) -> List:
|
|
343
|
+
return ["null" if arg is None else arg for arg in args]
|
|
344
|
+
|
|
337
345
|
def to_tf_inline(self, value: Any) -> str:
|
|
338
346
|
"""
|
|
339
347
|
Converts complex objects (e.g.) dicts to an "inline" HCL syntax
|
|
@@ -349,7 +357,7 @@ class DictTransformer(Transformer):
|
|
|
349
357
|
return "true" if value else "false"
|
|
350
358
|
if isinstance(value, str):
|
|
351
359
|
return value
|
|
352
|
-
if isinstance(value, int):
|
|
360
|
+
if isinstance(value, (int, float)):
|
|
353
361
|
return str(value)
|
|
354
362
|
if value is None:
|
|
355
363
|
return "None"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '7.0.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (7, 0, 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
@@ -22,6 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Requires-Python: >=3.7.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
+
Requires-Dist: lark<2,>=1
|
|
26
|
+
Dynamic: license-file
|
|
25
27
|
|
|
26
28
|
[](https://app.codacy.com/gh/amplify-education/python-hcl2/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
27
29
|
[](https://travis-ci.org/amplify-education/python-hcl2)
|
|
@@ -92,7 +94,7 @@ To see all the available options, run `tox -l`.
|
|
|
92
94
|
|
|
93
95
|
## Releasing
|
|
94
96
|
|
|
95
|
-
To create a new
|
|
97
|
+
To create a new release go to Releases page, press 'Draft a new release', create a tag
|
|
96
98
|
with a version you want to be released, fill the release notes and press 'Publish release'.
|
|
97
99
|
Github actions will take care of publishing it to PyPi.
|
|
98
100
|
|
|
@@ -109,3 +111,23 @@ We welcome pull requests! For your pull request to be accepted smoothly, we sugg
|
|
|
109
111
|
- Create a pull request. Explain why you want to make the change and what it’s for.
|
|
110
112
|
|
|
111
113
|
We’ll try to answer any PR’s promptly.
|
|
114
|
+
|
|
115
|
+
## Limitations
|
|
116
|
+
|
|
117
|
+
### Error parsing string interpolations nested more than 2 times
|
|
118
|
+
|
|
119
|
+
- Parsing following example is expected to throw out an exception and fail:
|
|
120
|
+
```terraform
|
|
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"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
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
|
|
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
|