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.
Files changed (44) hide show
  1. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/CHANGELOG.md +14 -0
  2. {python-hcl2-5.1.1/python_hcl2.egg-info → python-hcl2-6.1.0}/PKG-INFO +4 -4
  3. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/README.md +3 -3
  4. python-hcl2-6.1.0/hcl2/__init__.py +19 -0
  5. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/api.py +18 -5
  6. python-hcl2-6.1.0/hcl2/builder.py +63 -0
  7. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/hcl2.lark +27 -12
  8. python-hcl2-6.1.0/hcl2/parser.py +42 -0
  9. python-hcl2-6.1.0/hcl2/reconstructor.py +647 -0
  10. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/transformer.py +79 -15
  11. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/version.py +2 -2
  12. {python-hcl2-5.1.1 → python-hcl2-6.1.0/python_hcl2.egg-info}/PKG-INFO +4 -4
  13. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/SOURCES.txt +1 -0
  14. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/tree-to-hcl2-reconstruction.md +121 -3
  15. python-hcl2-5.1.1/hcl2/__init__.py +0 -8
  16. python-hcl2-5.1.1/hcl2/parser.py +0 -16
  17. python-hcl2-5.1.1/hcl2/reconstructor.py +0 -162
  18. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.codacy.yml +0 -0
  19. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.coveragerc +0 -0
  20. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/CODEOWNERS +0 -0
  21. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/codeql-analysis.yml +0 -0
  22. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/pr_check.yml +0 -0
  23. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.github/workflows/publish.yml +0 -0
  24. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.gitignore +0 -0
  25. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.pre-commit-config.yaml +0 -0
  26. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/.yamllint.yml +0 -0
  27. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/LICENSE +0 -0
  28. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/MANIFEST.in +0 -0
  29. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/bin/terraform_test +0 -0
  30. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/__main__.py +0 -0
  31. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/hcl2/py.typed +0 -0
  32. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/mypy.ini +0 -0
  33. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/pylintrc +0 -0
  34. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/pyproject.toml +0 -0
  35. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/dependency_links.txt +0 -0
  36. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/entry_points.txt +0 -0
  37. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/not-zip-safe +0 -0
  38. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/requires.txt +0 -0
  39. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/python_hcl2.egg-info/top_level.txt +0 -0
  40. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/reports/.gitignore +0 -0
  41. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/requirements.txt +0 -0
  42. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/setup.cfg +0 -0
  43. {python-hcl2-5.1.1 → python-hcl2-6.1.0}/test-requirements.txt +0 -0
  44. {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: 5.1.1
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 5.0.0 the possibility of HCL2 reconstruction from Lark Parse Tree was introduced.
72
+ With version 6.x the possibility of HCL2 reconstruction from the Lark Parse Tree and Python dictionaries directly was introduced.
73
73
 
74
- 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.
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 this [PR](https://github.com/amplify-education/python-hcl2/pull/169).
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 5.0.0 the possibility of HCL2 reconstruction from Lark Parse Tree was introduced.
47
+ With version 6.x the possibility of HCL2 reconstruction from the Lark Parse Tree and Python dictionaries directly was introduced.
48
48
 
49
- 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.
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 this [PR](https://github.com/amplify-education/python-hcl2/pull/169).
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 hcl2
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 = hcl2.parse(text + "\n")
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.reconstructor import ( # pylint: disable=import-outside-toplevel
46
- hcl2 as uncached_hcl2,
45
+ from hcl2.parser import ( # pylint: disable=import-outside-toplevel
46
+ reconstruction_parser,
47
47
  )
48
48
 
49
- return uncached_hcl2.parse(text)
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
- STRING_LIT : "\"" (STRING_CHARS | INTERPOLATION)* "\""
47
- STRING_CHARS : /(?:(?!\${)([^"\\]|\\.))+/+ // any character except '"" unless inside a interpolation string
48
- NESTED_INTERPOLATION : "${" /[^}]+/ "}"
49
- INTERPOLATION : "${" (/(?:(?!\${)([^}]))+/ | NESTED_INTERPOLATION)+ "}"
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 "=>" new_line_or_comment? expression "..."? new_line_or_comment? for_cond? new_line_or_comment? "}"
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
+ )