Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[run]
branch = true
# tox measures `coverage=hcl2`, so hcl2/cli/* only came into scope when the CLI moved
# under the package in 8.2.0. Omitting it keeps the gate on library code, as before.
omit =
hcl2/lark_parser.py
hcl2/version.py
hcl2/__main__.py
hcl2/__init__.py
hcl2/rules/__init__.py
cli/__init__.py
hcl2/cli/*

[report]
show_missing = true
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## \[Unreleased\]

### Changed

- **Breaking for direct `cli.*` imports.** The CLI modules moved from a top-level `cli` package
to `hcl2.cli`, so installing python-hcl2 no longer claims the generic top-level `cli` name.
Since 8.0 the distribution installed `cli/` into `site-packages`, where it collided with
projects that have a top-level `cli` package of their own and made their `cli.*` imports
resolve to the wrong module. The `hcl2tojson`, `jsontohcl2`, and `hq` commands and
`python -m hcl2` are unaffected; only code importing `cli.hcl_to_json`, `cli.json_to_hcl`,
`cli.hq`, or `cli.helpers` needs to add the `hcl2.` prefix. No compatibility shim ships,
because a shim would still occupy the colliding name.
- The redundant `cli/py.typed` marker is gone; `hcl2/py.typed` already covers `hcl2.cli`.

### Added

- Python 3.14 is now tested and declared as supported. No source changes were needed; the full
Expand Down
12 changes: 6 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reco
| `hcl2/walk.py` | Generic tree-walking primitives for the LarkElement IR tree |
| `hcl2/utils.py` | `SerializationOptions`, `SerializationContext`, string helpers |
| `hcl2/const.py` | Constants: `IS_BLOCK`, `COMMENTS_KEY`, `INLINE_COMMENTS_KEY` |
| `cli/helpers.py` | File/directory/stdin conversion helpers |
| `cli/hcl_to_json.py` | `hcl2tojson` entry point |
| `cli/json_to_hcl.py` | `jsontohcl2` entry point |
| `cli/hq.py` | `hq` CLI entry point — query dispatch, formatting, optional operator |
| `hcl2/cli/helpers.py` | File/directory/stdin conversion helpers |
| `hcl2/cli/hcl_to_json.py` | `hcl2tojson` entry point |
| `hcl2/cli/json_to_hcl.py` | `jsontohcl2` entry point |
| `hcl2/cli/hq.py` | `hq` CLI entry point — query dispatch, formatting, optional operator |
| `hcl2/query/__init__.py` | Public query API exports |
| `hcl2/query/_base.py` | `NodeView` base class, view registry, `view_for()` factory |
| `hcl2/query/body.py` | `DocumentView`, `BodyView` facades for top-level and body queries |
Expand All @@ -48,7 +48,7 @@ The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reco
| `hcl2/query/safe_eval.py` | AST-validated Python expression eval for hybrid/eval modes |
| `hcl2/query/introspect.py` | `--describe` and `--schema` output generation |

`hcl2/__main__.py` is a thin wrapper that imports `cli.hcl_to_json:main`.
`hcl2/__main__.py` is a thin wrapper that imports `hcl2.cli.hcl_to_json:main`.

### Rules (one class per grammar rule)

Expand Down Expand Up @@ -179,7 +179,7 @@ python -m unittest discover -s test -p "test_*.py" -v
**Unit tests** (`test/unit/`): instantiate rule objects directly (no parsing).

- `rules/` — one file per rules module
- `cli/` — one file per CLI module
- `cli/` — one file per module in `hcl2/cli/`
- `test_*.py` — tests for corresponding files from `hcl2/` directory

Use concrete stubs when testing ABCs (e.g., `StubExpression(ExpressionRule)`).
Expand Down
Empty file removed cli/py.typed
Empty file.
4 changes: 3 additions & 1 deletion docs/06_migrating_to_v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ Reverse: dict -> from_dict() -> reconstruct() -> HCL text

## CLI changes

The `hcl2tojson` entry point moved from `hcl2.__main__:main` to `cli.hcl_to_json:main`. A shim keeps `python -m hcl2` working, but direct imports from `hcl2.__main__` should be updated.
The `hcl2tojson` entry point moved from `hcl2.__main__:main` to `hcl2.cli.hcl_to_json:main`. A shim keeps `python -m hcl2` working, but direct imports from `hcl2.__main__` should be updated.

In 8.0 through 8.1.x these modules lived in a top-level `cli` package; 8.2.0 moved them under `hcl2.cli` so the distribution no longer claims the generic `cli` name. Only direct `import cli.*` is affected — the `hcl2tojson`, `jsontohcl2`, and `hq` commands are unchanged.

Two new CLI tools ship with v8:

Expand Down
2 changes: 1 addition & 1 deletion hcl2/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Allow ``python -m hcl2`` to run the hcl2tojson command."""

from cli.hcl_to_json import main
from hcl2.cli.hcl_to_json import main

if __name__ == "__main__":
main()
File renamed without changes.
9 changes: 5 additions & 4 deletions cli/hcl_to_json.py → hcl2/cli/hcl_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import sys
from typing import IO, List, Optional, TextIO

from cli.helpers import (
from hcl2 import load
from hcl2.utils import SerializationOptions
from hcl2.version import __version__

from .helpers import (
EXIT_IO_ERROR,
EXIT_PARSE_ERROR,
EXIT_PARTIAL,
Expand All @@ -20,9 +24,6 @@
_expand_file_args,
_install_sigpipe_handler,
)
from hcl2 import load
from hcl2.utils import SerializationOptions
from hcl2.version import __version__

_HCL_EXTENSIONS = {".tf", ".hcl"}

Expand Down
File renamed without changes.
File renamed without changes.
15 changes: 8 additions & 7 deletions cli/json_to_hcl.py → hcl2/cli/json_to_hcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
from typing import TextIO

import hcl2
from cli.helpers import (
from hcl2 import dump
from hcl2.deserializer import DeserializerOptions
from hcl2.formatter import FormatterOptions
from hcl2.query.diff import diff_dicts, format_diff_json, format_diff_text
from hcl2.utils import SerializationOptions
from hcl2.version import __version__

from .helpers import (
EXIT_DIFF,
EXIT_IO_ERROR,
EXIT_PARSE_ERROR,
Expand All @@ -22,12 +29,6 @@
_expand_file_args,
_install_sigpipe_handler,
)
from hcl2 import dump
from hcl2.deserializer import DeserializerOptions
from hcl2.formatter import FormatterOptions
from hcl2.query.diff import diff_dicts, format_diff_json, format_diff_text
from hcl2.utils import SerializationOptions
from hcl2.version import __version__


def _json_to_hcl(
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ content-type = "text/markdown"
Homepage = "https://github.com/amplify-education/python-hcl2"

[project.scripts]
hcl2tojson = "cli.hcl_to_json:main"
jsontohcl2 = "cli.json_to_hcl:main"
hq = "cli.hq:main"
hcl2tojson = "hcl2.cli.hcl_to_json:main"
jsontohcl2 = "hcl2.cli.json_to_hcl:main"
hq = "hcl2.cli.hq:main"

[tool.setuptools]
packages = ["hcl2", "hcl2.rules", "hcl2.query", "cli"]
packages = ["hcl2", "hcl2.rules", "hcl2.query", "hcl2.cli"]
zip-safe = false
include-package-data = true

Expand Down
4 changes: 2 additions & 2 deletions test/integration/test_cli_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
JSON_RESERIALIZED_DIR = INTEGRATION_DIR / "json_reserialized"
PROJECT_ROOT = INTEGRATION_DIR.parent.parent

_HCL2TOJSON = [sys.executable, "-c", "from cli.hcl_to_json import main; main()"]
_JSONTOHCL2 = [sys.executable, "-c", "from cli.json_to_hcl import main; main()"]
_HCL2TOJSON = [sys.executable, "-c", "from hcl2.cli.hcl_to_json import main; main()"]
_JSONTOHCL2 = [sys.executable, "-c", "from hcl2.cli.json_to_hcl import main; main()"]

_TIMEOUT = 30

Expand Down
4 changes: 2 additions & 2 deletions test/unit/cli/test_hcl_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from unittest import TestCase
from unittest.mock import patch

from cli.hcl_to_json import main
from cli.helpers import EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL
from hcl2.cli.hcl_to_json import main
from hcl2.cli.helpers import EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL

SIMPLE_HCL = "x = 1\n"
SIMPLE_JSON_DICT = {"x": 1}
Expand Down
2 changes: 1 addition & 1 deletion test/unit/cli/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from unittest import TestCase
from unittest.mock import patch

from cli.helpers import (
from hcl2.cli.helpers import (
_collect_files,
_convert_directory,
_convert_multiple_files,
Expand Down
2 changes: 1 addition & 1 deletion test/unit/cli/test_hq.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from unittest import TestCase
from unittest.mock import patch

from cli.hq import (
from hcl2.cli.hq import (
EXIT_IO_ERROR,
EXIT_NO_RESULTS,
EXIT_PARSE_ERROR,
Expand Down
6 changes: 3 additions & 3 deletions test/unit/cli/test_json_to_hcl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from unittest import TestCase
from unittest.mock import patch

from cli.helpers import EXIT_DIFF, EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL
from cli.json_to_hcl import main
from hcl2.cli.helpers import EXIT_DIFF, EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL
from hcl2.cli.json_to_hcl import main

SIMPLE_JSON_DICT = {"x": 1}
SIMPLE_JSON = json.dumps(SIMPLE_JSON_DICT)
Expand Down Expand Up @@ -753,7 +753,7 @@ def test_structure_error_exits_2(self):

stderr = StringIO()
with patch("sys.argv", ["jsontohcl2", path]):
with patch("cli.json_to_hcl.dump", side_effect=TypeError("bad structure")):
with patch("hcl2.cli.json_to_hcl.dump", side_effect=TypeError("bad structure")):
with patch("sys.stderr", stderr):
with self.assertRaises(SystemExit) as cm:
main()
Expand Down
Loading