Skip to content

Commit 2056d37

Browse files
add section about Lark Parse Tree -> HCL2 reconstruction to the README.md (#174)
1 parent 31b7883 commit 2056d37

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ with open('foo.tf', 'r') as file:
4242
dict = hcl2.load(file)
4343
```
4444

45+
### Parse Tree to HCL2 reconstruction
46+
47+
With version 5.0.0 the possibility of HCL2 reconstruction from Lark Parse Tree was introduced.
48+
49+
Example of manipulating Lark Parse Tree and reconstructing it back into valid HCL2 can be found in [tree-to-hcl2-reconstruction.md](tree-to-hcl2-reconstruction.md) file.
50+
51+
More details about reconstruction implementation can be found in this [PR](https://github.com/amplify-education/python-hcl2/pull/169).
52+
4553
## Building From Source
4654

4755
For development, `tox>=4.0.9` is recommended.

tree-to-hcl2-reconstruction.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
Given `example.tf` file with following content
2+
3+
```terraform
4+
resource "aws_s3_bucket" "bucket" {
5+
bucket = "bucket_id"
6+
force_destroy = true
7+
}
8+
```
9+
10+
below code will add a `tags` object to the S3 bucket definition. The code can also be used to print out readable representation of **any** Parse Tree (any valid HCL2 file), which can be useful when working on your own logic for arbitrary Parse Tree manipulation.
11+
12+
```python
13+
from copy import deepcopy
14+
from lark import Token, Tree
15+
import hcl2
16+
17+
18+
def build_tags_tree(base_indent: int = 0) -> Tree:
19+
# build Tree representing following HCL2 structure
20+
# tags = {
21+
# Name = "My bucket"
22+
# Environment = "Dev"
23+
# }
24+
return Tree('attribute', [
25+
Tree('identifier', [
26+
Token('NAME', 'tags')
27+
]),
28+
Token('EQ', '='),
29+
Tree('expr_term', [
30+
Tree('object', [
31+
Tree('new_line_or_comment', [
32+
Token('NL_OR_COMMENT', '\n' + ' ' * (base_indent + 1)),
33+
]),
34+
Tree('object_elem', [
35+
Tree('identifier', [
36+
Token('NAME', 'Name')
37+
]),
38+
Token('EQ', '='),
39+
Tree('expr_term', [
40+
Token('STRING_LIT', '"My bucket"')
41+
])
42+
]),
43+
Tree('new_line_and_or_comma', [
44+
Tree('new_line_or_comment', [
45+
Token('NL_OR_COMMENT', '\n' + ' ' * (base_indent + 1)),
46+
]),
47+
]),
48+
Tree('object_elem', [
49+
Tree('identifier', [
50+
Token('NAME', 'Environment')
51+
]),
52+
Token('EQ', '='),
53+
Tree('expr_term', [
54+
Token('STRING_LIT', '"Dev"')
55+
])
56+
]),
57+
Tree('new_line_and_or_comma', [
58+
Tree('new_line_or_comment', [
59+
Token('NL_OR_COMMENT', '\n' + ' ' * base_indent),
60+
]),
61+
]),
62+
]),
63+
])
64+
])
65+
66+
67+
def is_bucket_block(tree: Tree) -> bool:
68+
# check whether given Tree represents `resource "aws_s3_bucket" "bucket"`
69+
try:
70+
return tree.data == 'block' and tree.children[2].value == '"bucket"'
71+
except IndexError:
72+
return False
73+
74+
75+
def insert_tags(tree: Tree, indent: int = 0) -> Tree:
76+
# Insert tags tree and adjust surrounding whitespaces to match indentation
77+
new_children = [*tree.children.copy(), build_tags_tree(indent)]
78+
# add indentation before tags tree
79+
new_children[len(tree.children) - 1] = Tree('new_line_or_comment', [
80+
Token('NL_OR_COMMENT', '\n ')
81+
])
82+
# move closing bracket to the new line
83+
new_children.append(
84+
Tree('new_line_or_comment', [
85+
Token('NL_OR_COMMENT', '\n')
86+
])
87+
)
88+
return Tree(tree.data, new_children)
89+
90+
91+
def process_token(node: Token, indent=0):
92+
# Print details of this token and return its copy
93+
print(f'[{indent}] (token)\t|', ' ' * indent, node.type, node.value)
94+
return deepcopy(node)
95+
96+
97+
def process_tree(node: Tree, depth=0) -> Tree:
98+
# Recursively iterate over tree's children
99+
# the depth parameter represents recursion depth,
100+
# it's used to deduce indentation for printing tree and for adjusting whitespace after adding tags
101+
new_children = []
102+
print(f'[{depth}] (tree)\t|', ' ' * depth, node.data)
103+
for child in node.children:
104+
if isinstance(child, Tree):
105+
if is_bucket_block(child):
106+
block_children = child.children.copy()
107+
# this child is the Tree representing block's actual body
108+
block_children[3] = insert_tags(block_children[3], depth)
109+
# replace original Tree with new one including the modified body
110+
child = Tree(child.data, block_children)
111+
112+
new_children.append(process_tree(child, depth + 1))
113+
114+
else:
115+
new_children.append(process_token(child, depth + 1))
116+
117+
return Tree(node.data, new_children)
118+
119+
120+
def main():
121+
tree = hcl2.parse(open('example.tf'))
122+
new_tree = process_tree(tree)
123+
reconstructed = hcl2.writes(new_tree)
124+
open('example_reconstructed.tf', 'w').write(reconstructed)
125+
126+
127+
if __name__ == "__main__":
128+
main()
129+
130+
```

0 commit comments

Comments
 (0)