python-hcl2 5.1.1__tar.gz → 6.1.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-5.1.1 → python-hcl2-6.1.0}/CHANGELOG.md +14 -0
- {python-hcl2-5.1.1/python_hcl2.egg-info → python-hcl2-6.1.0}/PKG-INFO +4 -4
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/README.md +3 -3
- python-hcl2-6.1.0/hcl2/__init__.py +19 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/api.py +18 -5
- python-hcl2-6.1.0/hcl2/builder.py +63 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/hcl2.lark +27 -12
- python-hcl2-6.1.0/hcl2/parser.py +42 -0
- python-hcl2-6.1.0/hcl2/reconstructor.py +647 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/transformer.py +79 -15
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/version.py +2 -2
- {python-hcl2-5.1.1 → python-hcl2-6.1.0/python_hcl2.egg-info}/PKG-INFO +4 -4
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/SOURCES.txt +1 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/tree-to-hcl2-reconstruction.md +121 -3
- python-hcl2-5.1.1/hcl2/__init__.py +0 -8
- python-hcl2-5.1.1/hcl2/parser.py +0 -16
- python-hcl2-5.1.1/hcl2/reconstructor.py +0 -162
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.codacy.yml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.coveragerc +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/CODEOWNERS +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/codeql-analysis.yml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/pr_check.yml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/publish.yml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.gitignore +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.pre-commit-config.yaml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.yamllint.yml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/LICENSE +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/MANIFEST.in +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/bin/terraform_test +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/__main__.py +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/py.typed +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/mypy.ini +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/pylintrc +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/pyproject.toml +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/dependency_links.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/entry_points.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/not-zip-safe +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/requires.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/top_level.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/reports/.gitignore +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/requirements.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/setup.cfg +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/test-requirements.txt +0 -0
- {python-hcl2-5.1.1 → python-hcl2-6.1.0}/tox.ini +0 -0
|
@@ -5,6 +5,20 @@ 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
|
+
## \[6.1.0\] - 2025-01-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- fix e-notation and negative numbers literals. ([#182](https://github.com/amplify-education/python-hcl2/pull/182))
|
|
13
|
+
- fix parsing of `null`. ([#184](https://github.com/amplify-education/python-hcl2/pull/184))
|
|
14
|
+
- DictTransformer - do not wrap type literals into `${` and `}`. ([#186](https://github.com/amplify-education/python-hcl2/pull/186))
|
|
15
|
+
|
|
16
|
+
## \[6.0.0\] - 2025-01-15
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Support full reconstruction of HCL from Python structures. Thanks, @weaversam8, @Nfsaavedra ([#177](https://github.com/amplify-education/python-hcl2/pull/177))
|
|
21
|
+
|
|
8
22
|
## \[5.1.1\] - 2024-10-15
|
|
9
23
|
|
|
10
24
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.1.0
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
@@ -69,11 +69,11 @@ with open('foo.tf', 'r') as file:
|
|
|
69
69
|
|
|
70
70
|
### Parse Tree to HCL2 reconstruction
|
|
71
71
|
|
|
72
|
-
With version
|
|
72
|
+
With version 6.x the possibility of HCL2 reconstruction from the Lark Parse Tree and Python dictionaries directly was introduced.
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Documentation and an example of manipulating Lark Parse Tree and reconstructing it back into valid HCL2 can be found in [tree-to-hcl2-reconstruction.md](https://github.com/amplify-education/python-hcl2/blob/main/tree-to-hcl2-reconstruction.md) file.
|
|
75
75
|
|
|
76
|
-
More details about reconstruction implementation can be found in
|
|
76
|
+
More details about reconstruction implementation can be found in PRs #169 and #177.
|
|
77
77
|
|
|
78
78
|
## Building From Source
|
|
79
79
|
|
|
@@ -44,11 +44,11 @@ with open('foo.tf', 'r') as file:
|
|
|
44
44
|
|
|
45
45
|
### Parse Tree to HCL2 reconstruction
|
|
46
46
|
|
|
47
|
-
With version
|
|
47
|
+
With version 6.x the possibility of HCL2 reconstruction from the Lark Parse Tree and Python dictionaries directly was introduced.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Documentation and an example of manipulating Lark Parse Tree and reconstructing it back into valid HCL2 can be found in [tree-to-hcl2-reconstruction.md](https://github.com/amplify-education/python-hcl2/blob/main/tree-to-hcl2-reconstruction.md) file.
|
|
50
50
|
|
|
51
|
-
More details about reconstruction implementation can be found in
|
|
51
|
+
More details about reconstruction implementation can be found in PRs #169 and #177.
|
|
52
52
|
|
|
53
53
|
## Building From Source
|
|
54
54
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""For package documentation, see README"""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from .version import version as __version__
|
|
5
|
+
except ImportError:
|
|
6
|
+
__version__ = "unknown"
|
|
7
|
+
|
|
8
|
+
from .api import (
|
|
9
|
+
load,
|
|
10
|
+
loads,
|
|
11
|
+
parse,
|
|
12
|
+
parses,
|
|
13
|
+
transform,
|
|
14
|
+
reverse_transform,
|
|
15
|
+
writes,
|
|
16
|
+
AST,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .builder import Builder
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from typing import TextIO
|
|
3
3
|
|
|
4
4
|
from lark.tree import Tree as AST
|
|
5
|
-
from hcl2.parser import
|
|
5
|
+
from hcl2.parser import parser
|
|
6
6
|
from hcl2.transformer import DictTransformer
|
|
7
7
|
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ def loads(text: str, with_meta=False) -> dict:
|
|
|
25
25
|
# Lark doesn't support a EOF token so our grammar can't look for "new line or end of file"
|
|
26
26
|
# This means that all blocks must end in a new line even if the file ends
|
|
27
27
|
# Append a new line as a temporary fix
|
|
28
|
-
tree =
|
|
28
|
+
tree = parser().parse(text + "\n")
|
|
29
29
|
return DictTransformer(with_meta=with_meta).transform(tree)
|
|
30
30
|
|
|
31
31
|
|
|
@@ -42,11 +42,11 @@ def parses(text: str) -> AST:
|
|
|
42
42
|
"""
|
|
43
43
|
# defer this import until this method is called, due to the performance hit
|
|
44
44
|
# of rebuilding the grammar without cache
|
|
45
|
-
from hcl2.
|
|
46
|
-
|
|
45
|
+
from hcl2.parser import ( # pylint: disable=import-outside-toplevel
|
|
46
|
+
reconstruction_parser,
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
return
|
|
49
|
+
return reconstruction_parser().parse(text)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def transform(ast: AST, with_meta=False) -> dict:
|
|
@@ -56,6 +56,19 @@ def transform(ast: AST, with_meta=False) -> dict:
|
|
|
56
56
|
return DictTransformer(with_meta=with_meta).transform(ast)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def reverse_transform(hcl2_dict: dict) -> AST:
|
|
60
|
+
"""Convert a dictionary to an HCL2 AST.
|
|
61
|
+
:param dict: a dictionary produced by `load` or `transform`
|
|
62
|
+
"""
|
|
63
|
+
# defer this import until this method is called, due to the performance hit
|
|
64
|
+
# of rebuilding the grammar without cache
|
|
65
|
+
from hcl2.reconstructor import ( # pylint: disable=import-outside-toplevel
|
|
66
|
+
hcl2_reverse_transformer,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return hcl2_reverse_transformer.transform(hcl2_dict)
|
|
70
|
+
|
|
71
|
+
|
|
59
72
|
def writes(ast: AST) -> str:
|
|
60
73
|
"""Convert an HCL2 syntax tree to a string.
|
|
61
74
|
:param ast: HCL2 syntax tree, output from `parse` or `parses`
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""A utility class for constructing HCL documents from Python code."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Builder:
|
|
7
|
+
"""
|
|
8
|
+
The `hcl2.Builder` class produces a dictionary that should be identical to the
|
|
9
|
+
output of `hcl2.load(example_file, with_meta=True)`. The `with_meta` keyword
|
|
10
|
+
argument is important here. HCL "blocks" in the Python dictionary are
|
|
11
|
+
identified by the presence of `__start_line__` and `__end_line__` metadata
|
|
12
|
+
within them. The `Builder` class handles adding that metadata. If that metadata
|
|
13
|
+
is missing, the `hcl2.reconstructor.HCLReverseTransformer` class fails to
|
|
14
|
+
identify what is a block and what is just an attribute with an object value.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, attributes: Optional[dict] = None):
|
|
18
|
+
self.blocks: dict = {}
|
|
19
|
+
self.attributes = attributes or {}
|
|
20
|
+
|
|
21
|
+
def block(
|
|
22
|
+
self, block_type: str, labels: Optional[List[str]] = None, **attributes
|
|
23
|
+
) -> "Builder":
|
|
24
|
+
"""Create a block within this HCL document."""
|
|
25
|
+
labels = labels or []
|
|
26
|
+
block = Builder(attributes)
|
|
27
|
+
|
|
28
|
+
# initialize a holder for blocks of that type
|
|
29
|
+
if block_type not in self.blocks:
|
|
30
|
+
self.blocks[block_type] = []
|
|
31
|
+
|
|
32
|
+
# store the block in the document
|
|
33
|
+
self.blocks[block_type].append((labels.copy(), block))
|
|
34
|
+
|
|
35
|
+
return block
|
|
36
|
+
|
|
37
|
+
def build(self):
|
|
38
|
+
"""Return the Python dictionary for this HCL document."""
|
|
39
|
+
body = {
|
|
40
|
+
"__start_line__": -1,
|
|
41
|
+
"__end_line__": -1,
|
|
42
|
+
**self.attributes,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for block_type, blocks in self.blocks.items():
|
|
46
|
+
|
|
47
|
+
# initialize a holder for blocks of that type
|
|
48
|
+
if block_type not in body:
|
|
49
|
+
body[block_type] = []
|
|
50
|
+
|
|
51
|
+
for labels, block_builder in blocks:
|
|
52
|
+
# build the sub-block
|
|
53
|
+
block = block_builder.build()
|
|
54
|
+
|
|
55
|
+
# apply any labels
|
|
56
|
+
labels.reverse()
|
|
57
|
+
for label in labels:
|
|
58
|
+
block = {label: block}
|
|
59
|
+
|
|
60
|
+
# store it in the body
|
|
61
|
+
body[block_type].append(block)
|
|
62
|
+
|
|
63
|
+
return body
|
|
@@ -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_and_or_comma: new_line_or_comment | "," | "," new_line_or_comment
|
|
6
6
|
new_line_or_comment: ( NL_OR_COMMENT )+
|
|
7
7
|
NL_OR_COMMENT: /\n[ \t]*/ | /#.*\n/ | /\/\/.*\n/ | /\/\*(.|\n)*?(\*\/)/
|
|
@@ -22,12 +22,26 @@ conditional : expression "?" new_line_or_comment? expression new_line_or_comment
|
|
|
22
22
|
binary_op : expression binary_term new_line_or_comment?
|
|
23
23
|
!binary_operator : BINARY_OP
|
|
24
24
|
binary_term : binary_operator new_line_or_comment? expression
|
|
25
|
-
BINARY_OP :
|
|
25
|
+
BINARY_OP : DOUBLE_EQ | NEQ | LT | GT | LEQ | GEQ | MINUS | ASTERISK | SLASH | PERCENT | DOUBLE_AMP | DOUBLE_PIPE | PLUS
|
|
26
|
+
DOUBLE_EQ : "=="
|
|
27
|
+
NEQ : "!="
|
|
28
|
+
LT : "<"
|
|
29
|
+
GT : ">"
|
|
30
|
+
LEQ : "<="
|
|
31
|
+
GEQ : ">="
|
|
32
|
+
MINUS : "-"
|
|
33
|
+
ASTERISK : "*"
|
|
34
|
+
SLASH : "/"
|
|
35
|
+
PERCENT : "%"
|
|
36
|
+
DOUBLE_AMP : "&&"
|
|
37
|
+
DOUBLE_PIPE : "||"
|
|
38
|
+
PLUS : "+"
|
|
26
39
|
|
|
27
40
|
expr_term : "(" new_line_or_comment? expression new_line_or_comment? ")"
|
|
28
41
|
| float_lit
|
|
29
42
|
| int_lit
|
|
30
43
|
| STRING_LIT
|
|
44
|
+
| string_with_interpolation
|
|
31
45
|
| tuple
|
|
32
46
|
| object
|
|
33
47
|
| function_call
|
|
@@ -42,17 +56,17 @@ expr_term : "(" new_line_or_comment? expression new_line_or_comment? ")"
|
|
|
42
56
|
| for_tuple_expr
|
|
43
57
|
| for_object_expr
|
|
44
58
|
|
|
59
|
+
STRING_LIT : "\"" STRING_CHARS? "\""
|
|
60
|
+
STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))+/ // any character except '"'
|
|
61
|
+
string_with_interpolation: "\"" (STRING_CHARS)* interpolation_maybe_nested (STRING_CHARS | interpolation_maybe_nested)* "\""
|
|
62
|
+
interpolation_maybe_nested: "${" expression "}"
|
|
45
63
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
int_lit : DECIMAL+
|
|
52
|
-
!float_lit: DECIMAL+ "." DECIMAL+ (EXP_MARK DECIMAL+)?
|
|
53
|
-
| DECIMAL+ ("." DECIMAL+)? EXP_MARK DECIMAL+
|
|
64
|
+
int_lit : NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+
|
|
65
|
+
!float_lit: (NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+) "." DECIMAL+ (EXP_MARK)?
|
|
66
|
+
| (NEGATIVE_DECIMAL? DECIMAL+ | NEGATIVE_DECIMAL+) ("." DECIMAL+)? (EXP_MARK)
|
|
67
|
+
NEGATIVE_DECIMAL : "-" DECIMAL
|
|
54
68
|
DECIMAL : "0".."9"
|
|
55
|
-
EXP_MARK : ("e" | "E") ("+" | "-")?
|
|
69
|
+
EXP_MARK : ("e" | "E") ("+" | "-")? DECIMAL+
|
|
56
70
|
EQ : /[ \t]*=(?!=|>)/
|
|
57
71
|
|
|
58
72
|
tuple : "[" (new_line_or_comment* expression new_line_or_comment* ",")* (new_line_or_comment* expression)? new_line_or_comment* "]"
|
|
@@ -77,8 +91,9 @@ get_attr : "." identifier
|
|
|
77
91
|
attr_splat : ".*" get_attr*
|
|
78
92
|
full_splat : "[*]" (get_attr | index)*
|
|
79
93
|
|
|
94
|
+
FOR_OBJECT_ARROW : "=>"
|
|
80
95
|
!for_tuple_expr : "[" new_line_or_comment? for_intro new_line_or_comment? expression new_line_or_comment? for_cond? new_line_or_comment? "]"
|
|
81
|
-
!for_object_expr : "{" new_line_or_comment? for_intro new_line_or_comment? expression
|
|
96
|
+
!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? "}"
|
|
82
97
|
!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?
|
|
83
98
|
!for_cond : "if" new_line_or_comment? expression
|
|
84
99
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""A parser for HCL2 implemented using the Lark parser"""
|
|
2
|
+
import functools
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from lark import Lark
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PARSER_FILE = Path(__file__).absolute().resolve().parent / ".lark_cache.bin"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@functools.lru_cache()
|
|
12
|
+
def parser() -> Lark:
|
|
13
|
+
"""Build standard parser for transforming HCL2 text into python structures"""
|
|
14
|
+
return Lark.open(
|
|
15
|
+
"hcl2.lark",
|
|
16
|
+
parser="lalr",
|
|
17
|
+
cache=str(PARSER_FILE), # Disable/Delete file to effect changes to the grammar
|
|
18
|
+
rel_to=__file__,
|
|
19
|
+
propagate_positions=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@functools.lru_cache()
|
|
24
|
+
def reconstruction_parser() -> Lark:
|
|
25
|
+
"""
|
|
26
|
+
Build parser for transforming python structures into HCL2 text.
|
|
27
|
+
This is duplicated from `parser` because we need different options here for
|
|
28
|
+
the reconstructor. Please make sure changes are kept in sync between the two
|
|
29
|
+
if necessary.
|
|
30
|
+
"""
|
|
31
|
+
return Lark.open(
|
|
32
|
+
"hcl2.lark",
|
|
33
|
+
parser="lalr",
|
|
34
|
+
# Caching must be disabled to allow for reconstruction until lark-parser/lark#1472 is fixed:
|
|
35
|
+
#
|
|
36
|
+
# https://github.com/lark-parser/lark/issues/1472
|
|
37
|
+
#
|
|
38
|
+
# cache=str(PARSER_FILE), # Disable/Delete file to effect changes to the grammar
|
|
39
|
+
rel_to=__file__,
|
|
40
|
+
propagate_positions=True,
|
|
41
|
+
maybe_placeholders=False, # Needed for reconstruction
|
|
42
|
+
)
|