python-hcl2 7.3.1__tar.gz → 8.0.0rc2__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-8.0.0rc2/.coveragerc +15 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/ISSUE_TEMPLATE/hcl2-parsing-error.md +9 -5
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/workflows/codeql-analysis.yml +1 -1
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/workflows/dependencies_check.yml +1 -1
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/workflows/pr_check.yml +2 -2
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/workflows/publish.yml +2 -2
- python_hcl2-8.0.0rc2/.github/workflows/security.yml +23 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.pre-commit-config.yaml +1 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/CHANGELOG.md +31 -0
- python_hcl2-8.0.0rc2/CLAUDE.md +144 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/PKG-INFO +53 -27
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/README.md +50 -23
- python_hcl2-8.0.0rc2/cli/hcl_to_json.py +138 -0
- python_hcl2-8.0.0rc2/cli/helpers.py +96 -0
- python_hcl2-8.0.0rc2/cli/json_to_hcl.py +136 -0
- python_hcl2-8.0.0rc2/docs/usage.md +306 -0
- python_hcl2-8.0.0rc2/hcl2/__init__.py +28 -0
- python_hcl2-8.0.0rc2/hcl2/__main__.py +5 -0
- python_hcl2-8.0.0rc2/hcl2/api.py +222 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/hcl2/builder.py +7 -10
- python_hcl2-8.0.0rc2/hcl2/const.py +5 -0
- python_hcl2-8.0.0rc2/hcl2/deserializer.py +375 -0
- python_hcl2-8.0.0rc2/hcl2/formatter.py +332 -0
- python_hcl2-8.0.0rc2/hcl2/hcl2.lark +206 -0
- python_hcl2-8.0.0rc2/hcl2/parser.py +23 -0
- python_hcl2-8.0.0rc2/hcl2/postlexer.py +79 -0
- python_hcl2-8.0.0rc2/hcl2/reconstructor.py +295 -0
- python_hcl2-8.0.0rc2/hcl2/rules/__init__.py +0 -0
- python_hcl2-8.0.0rc2/hcl2/rules/abstract.py +139 -0
- python_hcl2-8.0.0rc2/hcl2/rules/base.py +172 -0
- python_hcl2-8.0.0rc2/hcl2/rules/containers.py +229 -0
- python_hcl2-8.0.0rc2/hcl2/rules/expressions.py +300 -0
- python_hcl2-8.0.0rc2/hcl2/rules/for_expressions.py +321 -0
- python_hcl2-8.0.0rc2/hcl2/rules/functions.py +113 -0
- python_hcl2-8.0.0rc2/hcl2/rules/indexing.py +293 -0
- python_hcl2-8.0.0rc2/hcl2/rules/literal_rules.py +86 -0
- python_hcl2-8.0.0rc2/hcl2/rules/strings.py +187 -0
- python_hcl2-8.0.0rc2/hcl2/rules/tokens.py +150 -0
- python_hcl2-8.0.0rc2/hcl2/rules/whitespace.py +87 -0
- python_hcl2-8.0.0rc2/hcl2/transformer.py +306 -0
- python_hcl2-8.0.0rc2/hcl2/utils.py +94 -0
- python_hcl2-8.0.0rc2/hcl2/version.py +34 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/pylintrc +1 -1
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/pyproject.toml +5 -5
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/PKG-INFO +53 -27
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/SOURCES.txt +23 -2
- python_hcl2-8.0.0rc2/python_hcl2.egg-info/entry_points.txt +3 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/top_level.txt +1 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/tox.ini +2 -3
- python_hcl2-7.3.1/.coveragerc +0 -9
- python_hcl2-7.3.1/hcl2/__init__.py +0 -18
- python_hcl2-7.3.1/hcl2/__main__.py +0 -106
- python_hcl2-7.3.1/hcl2/api.py +0 -67
- python_hcl2-7.3.1/hcl2/const.py +0 -4
- python_hcl2-7.3.1/hcl2/hcl2.lark +0 -108
- python_hcl2-7.3.1/hcl2/parser.py +0 -42
- python_hcl2-7.3.1/hcl2/reconstructor.py +0 -734
- python_hcl2-7.3.1/hcl2/transformer.py +0 -399
- python_hcl2-7.3.1/hcl2/version.py +0 -21
- python_hcl2-7.3.1/python_hcl2.egg-info/entry_points.txt +0 -2
- python_hcl2-7.3.1/tree-to-hcl2-reconstruction.md +0 -248
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.codacy.yml +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.github/CODEOWNERS +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.gitignore +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/.yamllint.yml +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/LICENSE +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/MANIFEST.in +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/bin/check_deps.py +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/bin/terraform_test +0 -0
- /python_hcl2-7.3.1/hcl2/py.typed → /python_hcl2-8.0.0rc2/cli/__init__.py +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/mypy.ini +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/dependency_links.txt +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/not-zip-safe +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/python_hcl2.egg-info/requires.txt +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/reports/.gitignore +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/requirements.txt +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/setup.cfg +0 -0
- {python_hcl2-7.3.1 → python_hcl2-8.0.0rc2}/test-requirements.txt +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[run]
|
|
2
|
+
branch = true
|
|
3
|
+
omit =
|
|
4
|
+
hcl2/lark_parser.py
|
|
5
|
+
hcl2/version.py
|
|
6
|
+
hcl2/__main__.py
|
|
7
|
+
hcl2/__init__.py
|
|
8
|
+
hcl2/rules/__init__.py
|
|
9
|
+
cli/__init__.py
|
|
10
|
+
|
|
11
|
+
[report]
|
|
12
|
+
show_missing = true
|
|
13
|
+
fail_under = 95
|
|
14
|
+
exclude_lines =
|
|
15
|
+
raise NotImplementedError
|
|
@@ -1,27 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
______________________________________________________________________
|
|
2
|
+
|
|
2
3
|
name: HCL2 parsing error
|
|
3
4
|
about: Template for reporting a bug related to parsing HCL2 code
|
|
4
5
|
title: ''
|
|
5
6
|
labels: bug
|
|
6
7
|
assignees: kkozik-amplify
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
______________________________________________________________________
|
|
9
10
|
|
|
10
11
|
**Describe the bug**
|
|
11
12
|
|
|
12
13
|
A clear and concise description of what the bug is.
|
|
13
14
|
|
|
14
15
|
**Software:**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
- OS: \[macOS / Windows / Linux\]
|
|
18
|
+
- Python version (e.g. 3.9.21)
|
|
19
|
+
- python-hcl2 version (e.g. 7.0.0)
|
|
18
20
|
|
|
19
21
|
**Snippet of HCL2 code causing the unexpected behaviour:**
|
|
22
|
+
|
|
20
23
|
```terraform
|
|
21
24
|
locals {
|
|
22
25
|
foo = "bar"
|
|
23
26
|
}
|
|
24
27
|
```
|
|
28
|
+
|
|
25
29
|
**Expected behavior**
|
|
26
30
|
|
|
27
31
|
A clear and concise description of what you expected to happen, e.g. python dictionary or JSON you expected to receive as a result of parsing.
|
|
@@ -10,10 +10,10 @@ on:
|
|
|
10
10
|
jobs:
|
|
11
11
|
test:
|
|
12
12
|
name: Check code against linter/unit tests
|
|
13
|
-
runs-on:
|
|
13
|
+
runs-on: github-hosted-static-ip
|
|
14
14
|
strategy:
|
|
15
15
|
matrix:
|
|
16
|
-
python-version: ['3.
|
|
16
|
+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@master
|
|
19
19
|
- name: Set up Python ${{ matrix.python-version }}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
name: Publish
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
|
-
types: [
|
|
5
|
+
types: [published]
|
|
6
6
|
|
|
7
7
|
jobs:
|
|
8
8
|
build-publish:
|
|
9
9
|
name: Build and publish Python distributions
|
|
10
|
-
runs-on:
|
|
10
|
+
runs-on: github-hosted-static-ip
|
|
11
11
|
steps:
|
|
12
12
|
- uses: actions/checkout@master
|
|
13
13
|
- name: Set up Python
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Security Review
|
|
3
|
+
|
|
4
|
+
permissions:
|
|
5
|
+
pull-requests: write # Needed for leaving PR comments
|
|
6
|
+
contents: read
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
security:
|
|
13
|
+
runs-on: github-hosted-static-ip
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
18
|
+
fetch-depth: 2
|
|
19
|
+
|
|
20
|
+
- uses: anthropics/claude-code-security-review@main
|
|
21
|
+
with:
|
|
22
|
+
comment-pr: true
|
|
23
|
+
claude-api-key: ${{ secrets.ANTHROPIC_CLAUDE_API_KEY }}
|
|
@@ -9,6 +9,37 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
9
9
|
|
|
10
10
|
- Nothing yet.
|
|
11
11
|
|
|
12
|
+
## \[8.0.0\] - rc2
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Full architecture overhaul: bidirectional HCL2 ↔ JSON pipeline with typed rule classes. ([#203](https://github.com/amplify-education/python-hcl2/pull/203))
|
|
17
|
+
- add function tuples round-trip test suite ([#268](https://github.com/amplify-education/python-hcl2/pull/268))
|
|
18
|
+
- Add postlexer to support multiline binary operators and ternary expressions ([#270](https://github.com/amplify-education/python-hcl2/pull/270))
|
|
19
|
+
- more robust whitespace handling in reconstruction ([#271](https://github.com/amplify-education/python-hcl2/pull/271))
|
|
20
|
+
- SerializationOptions - add an option to strip string quotes in dict/JSON output ([#272](https://github.com/amplify-education/python-hcl2/pull/272))
|
|
21
|
+
- CLAUDE.md ([#260](https://github.com/amplify-education/python-hcl2/pull/260))
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Ternary with strings parse error ([#55](https://github.com/amplify-education/python-hcl2/issues/55))
|
|
26
|
+
- "No terminal matches '|' in the current parser context" when parsing multi-line conditional ([#142](https://github.com/amplify-education/python-hcl2/issues/142))
|
|
27
|
+
- reverse_transform not working with object-type variables ([#231](https://github.com/amplify-education/python-hcl2/issues/231))
|
|
28
|
+
- reverse_transform not handling nested functions ([#235](https://github.com/amplify-education/python-hcl2/issues/235))
|
|
29
|
+
- `writes` omits quotes around map keys with `/` ([#236](https://github.com/amplify-education/python-hcl2/issues/236))
|
|
30
|
+
- Operator precedence bug ([#248](https://github.com/amplify-education/python-hcl2/issues/248))
|
|
31
|
+
- Empty string dictionary keys can't be parsed twice ([#249](https://github.com/amplify-education/python-hcl2/issues/249))
|
|
32
|
+
- jsonencode not deserialized correctly ([#250](https://github.com/amplify-education/python-hcl2/issues/250))
|
|
33
|
+
- Literal string "string" incorrectly quoted ([#251](https://github.com/amplify-education/python-hcl2/issues/251))
|
|
34
|
+
- Interpolation literals added to locals/variables in maps ([#252](https://github.com/amplify-education/python-hcl2/issues/252))
|
|
35
|
+
- Object literal expression can't be serialized ([#253](https://github.com/amplify-education/python-hcl2/issues/253))
|
|
36
|
+
- Heredocs should interpret backslash literally ([#262](https://github.com/amplify-education/python-hcl2/issues/262))
|
|
37
|
+
- Parsing a multi-line multi-conditional expression causes exception - Unexpected token Token('QMARK', '?') ([#269](https://github.com/amplify-education/python-hcl2/issues/269))
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Updated package metadata: development status, dropped Python 3.7 support. ([#263](https://github.com/amplify-education/python-hcl2/pull/263))
|
|
42
|
+
|
|
12
43
|
## \[7.3.1\] - 2025-07-24
|
|
13
44
|
|
|
14
45
|
### Fixed
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# HCL2 Parser — CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Pipeline
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Forward: HCL2 Text → [PostLexer] → Lark Parse Tree → LarkElement Tree → Python Dict/JSON
|
|
7
|
+
Reverse: Python Dict/JSON → LarkElement Tree → Lark Tree → HCL2 Text
|
|
8
|
+
Direct: HCL2 Text → [PostLexer] → Lark Parse Tree → LarkElement Tree → Lark Tree → HCL2 Text
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reconstruct`) skips serialization to dict, so all IR nodes (including `NewLineOrCommentRule` nodes for whitespace/comments) directly influence the reconstructed output. Any information discarded before the IR is lost in this pipeline.
|
|
12
|
+
|
|
13
|
+
## Module Map
|
|
14
|
+
|
|
15
|
+
| Module | Role |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `hcl2/hcl2.lark` | Lark grammar definition |
|
|
18
|
+
| `hcl2/api.py` | Public API (`load/loads/dump/dumps` + intermediate stages) |
|
|
19
|
+
| `hcl2/postlexer.py` | Token stream transforms between lexer and parser |
|
|
20
|
+
| `hcl2/parser.py` | Lark parser factory with caching |
|
|
21
|
+
| `hcl2/transformer.py` | Lark parse tree → LarkElement tree |
|
|
22
|
+
| `hcl2/deserializer.py` | Python dict → LarkElement tree |
|
|
23
|
+
| `hcl2/formatter.py` | Whitespace alignment and spacing on LarkElement trees |
|
|
24
|
+
| `hcl2/reconstructor.py` | LarkElement tree → HCL2 text via Lark |
|
|
25
|
+
| `hcl2/builder.py` | Programmatic HCL document construction |
|
|
26
|
+
| `hcl2/utils.py` | `SerializationOptions`, `SerializationContext`, string helpers |
|
|
27
|
+
| `hcl2/const.py` | Constants: `IS_BLOCK`, `COMMENTS_KEY`, `INLINE_COMMENTS_KEY` |
|
|
28
|
+
| `cli/helpers.py` | File/directory/stdin conversion helpers |
|
|
29
|
+
| `cli/hcl_to_json.py` | `hcl2tojson` entry point |
|
|
30
|
+
| `cli/json_to_hcl.py` | `jsontohcl2` entry point |
|
|
31
|
+
|
|
32
|
+
`hcl2/__main__.py` is a thin wrapper that imports `cli.hcl_to_json:main`.
|
|
33
|
+
|
|
34
|
+
### Rules (one class per grammar rule)
|
|
35
|
+
|
|
36
|
+
| File | Domain |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `rules/abstract.py` | `LarkElement`, `LarkRule`, `LarkToken` base classes |
|
|
39
|
+
| `rules/tokens.py` | `StringToken` (cached factory), `StaticStringToken`, punctuation constants |
|
|
40
|
+
| `rules/base.py` | `StartRule`, `BodyRule`, `BlockRule`, `AttributeRule` |
|
|
41
|
+
| `rules/containers.py` | `TupleRule`, `ObjectRule`, `ObjectElemRule`, `ObjectElemKeyRule` |
|
|
42
|
+
| `rules/expressions.py` | `ExprTermRule`, `BinaryOpRule`, `UnaryOpRule`, `ConditionalRule` |
|
|
43
|
+
| `rules/literal_rules.py` | `IntLitRule`, `FloatLitRule`, `IdentifierRule`, `KeywordRule` |
|
|
44
|
+
| `rules/strings.py` | `StringRule`, `InterpolationRule`, `HeredocTemplateRule` |
|
|
45
|
+
| `rules/functions.py` | `FunctionCallRule`, `ArgumentsRule` |
|
|
46
|
+
| `rules/indexing.py` | `GetAttrRule`, `SqbIndexRule`, splat rules |
|
|
47
|
+
| `rules/for_expressions.py` | `ForTupleExprRule`, `ForObjectExprRule`, `ForIntroRule`, `ForCondRule` |
|
|
48
|
+
| `rules/whitespace.py` | `NewLineOrCommentRule`, `InlineCommentMixIn` |
|
|
49
|
+
|
|
50
|
+
## Public API (`api.py`)
|
|
51
|
+
|
|
52
|
+
Follows the `json` module convention. All option parameters are keyword-only.
|
|
53
|
+
|
|
54
|
+
- `load/loads` — HCL2 text → Python dict
|
|
55
|
+
- `dump/dumps` — Python dict → HCL2 text
|
|
56
|
+
- Intermediate stages: `parse/parses`, `parse_to_tree/parses_to_tree`, `transform`, `serialize`, `from_dict`, `from_json`, `reconstruct`
|
|
57
|
+
|
|
58
|
+
### Option Dataclasses
|
|
59
|
+
|
|
60
|
+
**`SerializationOptions`** (LarkElement → dict):
|
|
61
|
+
`with_comments`, `with_meta`, `wrap_objects`, `wrap_tuples`, `explicit_blocks`, `preserve_heredocs`, `force_operation_parentheses`, `preserve_scientific_notation`
|
|
62
|
+
|
|
63
|
+
**`DeserializerOptions`** (dict → LarkElement):
|
|
64
|
+
`heredocs_to_strings`, `strings_to_heredocs`, `object_elements_colon`, `object_elements_trailing_comma`
|
|
65
|
+
|
|
66
|
+
**`FormatterOptions`** (whitespace/alignment):
|
|
67
|
+
`indent_length`, `open_empty_blocks`, `open_empty_objects`, `open_empty_tuples`, `vertically_align_attributes`, `vertically_align_object_elements`
|
|
68
|
+
|
|
69
|
+
## CLI
|
|
70
|
+
|
|
71
|
+
Console scripts defined in `pyproject.toml`. Each uses argparse flags that map directly to the option dataclass fields above.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
hcl2tojson --json-indent 2 --with-meta file.tf
|
|
75
|
+
jsontohcl2 --indent 4 --no-align file.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Add new options as `parser.add_argument()` calls in the relevant entry point module.
|
|
79
|
+
|
|
80
|
+
## PostLexer (`postlexer.py`)
|
|
81
|
+
|
|
82
|
+
Lark's `postlex` parameter accepts a single object with a `process(stream)` method that transforms the token stream between the lexer and LALR parser. The `PostLexer` class is designed for extensibility: each transformation is a private method that accepts and yields tokens, and `process()` chains them together.
|
|
83
|
+
|
|
84
|
+
Current passes:
|
|
85
|
+
|
|
86
|
+
- `_merge_newlines_into_operators`
|
|
87
|
+
|
|
88
|
+
To add a new pass: create a private method with the same `(self, stream) -> generator` signature, and add a `yield from` call in `process()`.
|
|
89
|
+
|
|
90
|
+
## Hard Rules
|
|
91
|
+
|
|
92
|
+
These are project-specific constraints that must not be violated:
|
|
93
|
+
|
|
94
|
+
1. **Always use the LarkElement IR.** Never transform directly from Lark parse tree to Python dict or vice versa.
|
|
95
|
+
1. **Block vs object distinction.** Use `__is_block__` markers (`const.IS_BLOCK`) to preserve semantic intent during round-trips. The deserializer must distinguish blocks from regular objects.
|
|
96
|
+
1. **Bidirectional completeness.** Every serialization path must have a corresponding deserialization path. Test round-trip integrity: Parse → Serialize → Deserialize → Serialize produces identical results.
|
|
97
|
+
1. **One grammar rule = one `LarkRule` class.** Each class implements `lark_name()`, typed property accessors, `serialize()`, and declares `_children_layout: Tuple[...]` (annotation only, no assignment) to document child structure.
|
|
98
|
+
1. **Token caching.** Use the `StringToken` factory in `rules/tokens.py` — never create token instances directly.
|
|
99
|
+
1. **Interpolation context.** `${...}` generation depends on nesting depth — always pass and respect `SerializationContext`.
|
|
100
|
+
1. **Update both directions.** When adding language features, update transformer.py, deserializer.py, formatter.py and reconstructor.py.
|
|
101
|
+
|
|
102
|
+
## Adding a New Language Construct
|
|
103
|
+
|
|
104
|
+
1. Add grammar rules to `hcl2.lark`
|
|
105
|
+
1. If the new construct creates LALR ambiguities with `NL_OR_COMMENT`, add a postlexer pass in `postlexer.py`
|
|
106
|
+
1. Create rule class(es) in the appropriate `rules/` file
|
|
107
|
+
1. Add transformer method(s) in `transformer.py`
|
|
108
|
+
1. Implement `serialize()` in the rule class
|
|
109
|
+
1. Update `deserializer.py`, `formatter.py` and `reconstructor.py` for round-trip support
|
|
110
|
+
|
|
111
|
+
## Testing
|
|
112
|
+
|
|
113
|
+
Framework: `unittest.TestCase` (not pytest).
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
python -m unittest discover -s test -p "test_*.py" -v
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Unit tests** (`test/unit/`): instantiate rule objects directly (no parsing).
|
|
120
|
+
|
|
121
|
+
- `rules/` — one file per rules module
|
|
122
|
+
- `cli/` — one file per CLI module
|
|
123
|
+
- `test_*.py` — tests for corresponding files from `hcl2/` directory
|
|
124
|
+
|
|
125
|
+
Use concrete stubs when testing ABCs (e.g., `StubExpression(ExpressionRule)`).
|
|
126
|
+
|
|
127
|
+
**Integration tests** (`test/integration/`): full-pipeline tests with golden files.
|
|
128
|
+
|
|
129
|
+
- `test_round_trip.py` — iterates over all suites in `hcl2_original/`, tests HCL→JSON, JSON→JSON, JSON→HCL, and full round-trip
|
|
130
|
+
- `test_specialized.py` — feature-specific tests with golden files in `specialized/`
|
|
131
|
+
|
|
132
|
+
Always run round-trip full test suite after any modification.
|
|
133
|
+
|
|
134
|
+
## Pre-commit Checks
|
|
135
|
+
|
|
136
|
+
Hooks are defined in `.pre-commit-config.yaml` (includes black, mypy, pylint, and others). All changed files must pass these checks before committing. When writing or modifying code:
|
|
137
|
+
|
|
138
|
+
- Format Python with **black** (Python 3.8 target).
|
|
139
|
+
- Ensure **mypy** and **pylint** pass. Pylint config is in `pylintrc`, scoped to `hcl2/` and `test/`.
|
|
140
|
+
- End files with a newline; strip trailing whitespace (except under `test/integration/(hcl2_reconstructed|specialized)/`).
|
|
141
|
+
|
|
142
|
+
## Keeping Docs Current
|
|
143
|
+
|
|
144
|
+
Update this file when architecture, modules, API surface, or testing conventions change. Also update `README.md` and `docs/usage.md` when changes affect the public API, CLI flags, or option fields.
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hcl2
|
|
3
|
-
Version:
|
|
3
|
+
Version: 8.0.0rc2
|
|
4
4
|
Summary: A parser for HCL2
|
|
5
5
|
Author-email: Amplify Education <github@amplify.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/amplify-education/python-hcl2
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Requires-Python: >=3.
|
|
21
|
+
Requires-Python: >=3.8.0
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE
|
|
25
24
|
Requires-Dist: lark<2.0,>=1.1.5
|
|
@@ -27,7 +26,6 @@ Requires-Dist: regex>=2024.4.16
|
|
|
27
26
|
Dynamic: license-file
|
|
28
27
|
|
|
29
28
|
[](https://app.codacy.com/gh/amplify-education/python-hcl2/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
30
|
-
[](https://travis-ci.org/amplify-education/python-hcl2)
|
|
31
29
|
[](https://raw.githubusercontent.com/amplify-education/python-hcl2/master/LICENSE)
|
|
32
30
|
[](https://pypi.org/project/python-hcl2/)
|
|
33
31
|
[](https://pypi.python.org/pypi/python-hcl2)
|
|
@@ -52,7 +50,7 @@ Learn more at <https://www.amplify.com>
|
|
|
52
50
|
|
|
53
51
|
### Prerequisites
|
|
54
52
|
|
|
55
|
-
python-hcl2 requires Python 3.
|
|
53
|
+
python-hcl2 requires Python 3.8 or higher to run.
|
|
56
54
|
|
|
57
55
|
### Installing
|
|
58
56
|
|
|
@@ -64,19 +62,58 @@ pip3 install python-hcl2
|
|
|
64
62
|
|
|
65
63
|
### Usage
|
|
66
64
|
|
|
65
|
+
**HCL2 to Python dict:**
|
|
66
|
+
|
|
67
67
|
```python
|
|
68
68
|
import hcl2
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
|
|
70
|
+
with open("main.tf") as f:
|
|
71
|
+
data = hcl2.load(f)
|
|
71
72
|
```
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
**Python dict to HCL2:**
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import hcl2
|
|
78
|
+
|
|
79
|
+
hcl_string = hcl2.dumps(data)
|
|
80
|
+
|
|
81
|
+
with open("output.tf", "w") as f:
|
|
82
|
+
hcl2.dump(data, f)
|
|
83
|
+
```
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
**Building HCL from scratch:**
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
```python
|
|
88
|
+
import hcl2
|
|
89
|
+
|
|
90
|
+
doc = hcl2.Builder()
|
|
91
|
+
res = doc.block("resource", labels=["aws_instance", "web"], ami="abc-123", instance_type="t2.micro")
|
|
92
|
+
res.block("tags", Name="HelloWorld")
|
|
93
|
+
|
|
94
|
+
hcl_string = hcl2.dumps(doc.build())
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For the full API reference, option dataclasses, intermediate pipeline stages, and more examples
|
|
98
|
+
see [docs/usage.md](https://github.com/amplify-education/python-hcl2/blob/main/docs/usage.md).
|
|
99
|
+
|
|
100
|
+
### CLI Tools
|
|
101
|
+
|
|
102
|
+
python-hcl2 ships two command-line converters:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
# HCL2 → JSON
|
|
106
|
+
hcl2tojson main.tf # prints JSON to stdout
|
|
107
|
+
hcl2tojson main.tf output.json # writes to file
|
|
108
|
+
hcl2tojson terraform/ output/ # converts a directory
|
|
109
|
+
|
|
110
|
+
# JSON → HCL2
|
|
111
|
+
jsontohcl2 output.json # prints HCL2 to stdout
|
|
112
|
+
jsontohcl2 output.json main.tf # writes to file
|
|
113
|
+
jsontohcl2 output/ terraform/ # converts a directory
|
|
114
|
+
```
|
|
78
115
|
|
|
79
|
-
|
|
116
|
+
Both commands accept `-` as PATH to read from stdin. Run `hcl2tojson --help` or `jsontohcl2 --help` for the full list of flags.
|
|
80
117
|
|
|
81
118
|
## Building From Source
|
|
82
119
|
|
|
@@ -89,7 +126,7 @@ Running `tox` will automatically execute linters as well as the unit tests.
|
|
|
89
126
|
|
|
90
127
|
You can also run them individually with the `-e` argument.
|
|
91
128
|
|
|
92
|
-
For example, `tox -e
|
|
129
|
+
For example, `tox -e py310-unit` will run the unit tests for python 3.10
|
|
93
130
|
|
|
94
131
|
To see all the available options, run `tox -l`.
|
|
95
132
|
|
|
@@ -109,21 +146,10 @@ You can reach us at <mailto:github@amplify.com>
|
|
|
109
146
|
We welcome pull requests! For your pull request to be accepted smoothly, we suggest that you:
|
|
110
147
|
|
|
111
148
|
- For any sizable change, first open a GitHub issue to discuss your idea.
|
|
112
|
-
- Create a pull request.
|
|
149
|
+
- Create a pull request. Explain why you want to make the change and what it's for.
|
|
113
150
|
|
|
114
|
-
We
|
|
151
|
+
We'll try to answer any PR's promptly.
|
|
115
152
|
|
|
116
153
|
## Limitations
|
|
117
154
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- Object key can be an expression as long as it is wrapped in parentheses:
|
|
121
|
-
```terraform
|
|
122
|
-
locals {
|
|
123
|
-
foo = "bar"
|
|
124
|
-
baz = {
|
|
125
|
-
(format("key_prefix_%s", local.foo)) : "value"
|
|
126
|
-
# format("key_prefix_%s", local.foo) : "value" this will fail
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
155
|
+
None that are known.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
[](https://app.codacy.com/gh/amplify-education/python-hcl2/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
2
|
-
[](https://travis-ci.org/amplify-education/python-hcl2)
|
|
3
2
|
[](https://raw.githubusercontent.com/amplify-education/python-hcl2/master/LICENSE)
|
|
4
3
|
[](https://pypi.org/project/python-hcl2/)
|
|
5
4
|
[](https://pypi.python.org/pypi/python-hcl2)
|
|
@@ -24,7 +23,7 @@ Learn more at <https://www.amplify.com>
|
|
|
24
23
|
|
|
25
24
|
### Prerequisites
|
|
26
25
|
|
|
27
|
-
python-hcl2 requires Python 3.
|
|
26
|
+
python-hcl2 requires Python 3.8 or higher to run.
|
|
28
27
|
|
|
29
28
|
### Installing
|
|
30
29
|
|
|
@@ -36,19 +35,58 @@ pip3 install python-hcl2
|
|
|
36
35
|
|
|
37
36
|
### Usage
|
|
38
37
|
|
|
38
|
+
**HCL2 to Python dict:**
|
|
39
|
+
|
|
39
40
|
```python
|
|
40
41
|
import hcl2
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
with open("main.tf") as f:
|
|
44
|
+
data = hcl2.load(f)
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
**Python dict to HCL2:**
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import hcl2
|
|
51
|
+
|
|
52
|
+
hcl_string = hcl2.dumps(data)
|
|
53
|
+
|
|
54
|
+
with open("output.tf", "w") as f:
|
|
55
|
+
hcl2.dump(data, f)
|
|
56
|
+
```
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
**Building HCL from scratch:**
|
|
48
59
|
|
|
49
|
-
|
|
60
|
+
```python
|
|
61
|
+
import hcl2
|
|
62
|
+
|
|
63
|
+
doc = hcl2.Builder()
|
|
64
|
+
res = doc.block("resource", labels=["aws_instance", "web"], ami="abc-123", instance_type="t2.micro")
|
|
65
|
+
res.block("tags", Name="HelloWorld")
|
|
66
|
+
|
|
67
|
+
hcl_string = hcl2.dumps(doc.build())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For the full API reference, option dataclasses, intermediate pipeline stages, and more examples
|
|
71
|
+
see [docs/usage.md](https://github.com/amplify-education/python-hcl2/blob/main/docs/usage.md).
|
|
72
|
+
|
|
73
|
+
### CLI Tools
|
|
74
|
+
|
|
75
|
+
python-hcl2 ships two command-line converters:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# HCL2 → JSON
|
|
79
|
+
hcl2tojson main.tf # prints JSON to stdout
|
|
80
|
+
hcl2tojson main.tf output.json # writes to file
|
|
81
|
+
hcl2tojson terraform/ output/ # converts a directory
|
|
82
|
+
|
|
83
|
+
# JSON → HCL2
|
|
84
|
+
jsontohcl2 output.json # prints HCL2 to stdout
|
|
85
|
+
jsontohcl2 output.json main.tf # writes to file
|
|
86
|
+
jsontohcl2 output/ terraform/ # converts a directory
|
|
87
|
+
```
|
|
50
88
|
|
|
51
|
-
|
|
89
|
+
Both commands accept `-` as PATH to read from stdin. Run `hcl2tojson --help` or `jsontohcl2 --help` for the full list of flags.
|
|
52
90
|
|
|
53
91
|
## Building From Source
|
|
54
92
|
|
|
@@ -61,7 +99,7 @@ Running `tox` will automatically execute linters as well as the unit tests.
|
|
|
61
99
|
|
|
62
100
|
You can also run them individually with the `-e` argument.
|
|
63
101
|
|
|
64
|
-
For example, `tox -e
|
|
102
|
+
For example, `tox -e py310-unit` will run the unit tests for python 3.10
|
|
65
103
|
|
|
66
104
|
To see all the available options, run `tox -l`.
|
|
67
105
|
|
|
@@ -81,21 +119,10 @@ You can reach us at <mailto:github@amplify.com>
|
|
|
81
119
|
We welcome pull requests! For your pull request to be accepted smoothly, we suggest that you:
|
|
82
120
|
|
|
83
121
|
- For any sizable change, first open a GitHub issue to discuss your idea.
|
|
84
|
-
- Create a pull request.
|
|
122
|
+
- Create a pull request. Explain why you want to make the change and what it's for.
|
|
85
123
|
|
|
86
|
-
We
|
|
124
|
+
We'll try to answer any PR's promptly.
|
|
87
125
|
|
|
88
126
|
## Limitations
|
|
89
127
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- Object key can be an expression as long as it is wrapped in parentheses:
|
|
93
|
-
```terraform
|
|
94
|
-
locals {
|
|
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
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
128
|
+
None that are known.
|