diff --git a/CHANGELOG.md b/CHANGELOG.md index 43492db..60ba3b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## \[Unreleased\] + +### Changed + +- Replace tomlkit with tomlrt as the TOML reader/writer +- Read and write toml files in binary mode, so that CRLF line endings are preserved + ## \[0.8.2\] ### Fixed diff --git a/pyproject.toml b/pyproject.toml index 024f2da..daba5e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires-python = ">=3.10" dependencies = [ "jmespath>=1.0.1", "regex>=2020.7.14", - "tomlkit>=0.13.3", + "tomlrt>=2.2.2", "typer>=0.16.0", ] authors = [{ name = "Marc Rijken", email = "marc@rijken.org" }] diff --git a/tests/toml_cli/test_init.py b/tests/toml_cli/test_init.py index a52f10f..f4b295a 100644 --- a/tests/toml_cli/test_init.py +++ b/tests/toml_cli/test_init.py @@ -262,7 +262,7 @@ def test_add_section(tmp_path: pathlib.Path): result = runner.invoke(app, ["add_section", "--toml-path", str(test_toml_path), "address.work"]) assert result.exit_code == 0 - assert "[address]\n[address.work]" in test_toml_path.read_text() + assert "[address]\n\n[address.work]" in test_toml_path.read_text() def test_set_in_out_of_order_table(tmp_path: pathlib.Path): @@ -308,4 +308,4 @@ def test_unset(tmp_path: pathlib.Path): result = runner.invoke(app, ["unset", "--toml-path", str(test_toml_path), "person"]) assert result.exit_code == 0 - assert len(test_toml_path.read_text()) == 1 # just a newline + assert test_toml_path.read_text() == "" diff --git a/toml_cli/__init__.py b/toml_cli/__init__.py index 6bf9e66..75d23d4 100644 --- a/toml_cli/__init__.py +++ b/toml_cli/__init__.py @@ -2,8 +2,7 @@ import pathlib import re -import tomlkit -import tomlkit.exceptions +import tomlrt import typer import jmespath @@ -21,6 +20,16 @@ def path_callback(value: pathlib.Path): return value +def read_toml(toml_path: pathlib.Path) -> tomlrt.Document: + with toml_path.open("rb") as toml_file: + return tomlrt.load(toml_file) + + +def write_toml(document: tomlrt.Document, toml_path: pathlib.Path) -> None: + with toml_path.open("wb") as toml_file: + tomlrt.dump(document, toml_file) + + @app.command("get") def get( key: str | None = typer.Argument(None), @@ -28,7 +37,7 @@ def get( default: str | None = typer.Option(None), ): """Get a value from a toml file.""" - toml_part = tomlkit.parse(toml_path.read_text()) + toml_part = read_toml(toml_path) if key is not None: for key_part in key.split("."): @@ -64,7 +73,7 @@ def get( typer.echo(f"error: key '{key_part}' not found", err=True) raise typer.Exit(code=1) from err - typer.echo(toml_part.unwrap() if hasattr(toml_part, "unwrap") else toml_part) + typer.echo(toml_part) @app.command("set") @@ -81,12 +90,12 @@ def set_( ), ): """Set a value to a toml file.""" - toml_part = toml_file = tomlkit.parse(toml_path.read_text()) + toml_part = toml_file = read_toml(toml_path) for key_part in key.split(".")[:-1]: try: toml_part = toml_part[key_part] - except tomlkit.exceptions.NonExistentKey as err: + except KeyError as err: typer.echo( f"error: non-existent key '{key}' can not be set to value '{value}'", err=True, @@ -115,7 +124,7 @@ def set_( array = match.group("array") try: toml_part = toml_part[array] - except tomlkit.exceptions.NonExistentKey as err: + except KeyError as err: typer.echo(f"error: non-existent array '{array}'", err=True) raise typer.Exit(code=1) from err @@ -127,7 +136,7 @@ def set_( else: toml_part[last_key] = parsed_value - toml_path.write_text(tomlkit.dumps(toml_file)) + write_toml(toml_file, toml_path) @app.command("add_section") @@ -136,14 +145,15 @@ def add_section( toml_path: pathlib.Path = typer.Option(DEFAULT_TOML_PATH, callback=path_callback), ): """Add a section with the given key.""" - toml_part = toml_file = tomlkit.parse(toml_path.read_text()) + toml_file = read_toml(toml_path) - for key_part in key.split("."): - if key_part not in toml_part: - toml_part[key_part] = tomlkit.table() - toml_part = toml_part[key_part] + try: + toml_file.ensure_table(key) + except tomlrt.TOMLError as err: + typer.echo(f"error: can not add section '{key}' - {err}", err=True) + raise typer.Exit(code=1) from err - toml_path.write_text(tomlkit.dumps(toml_file)) + write_toml(toml_file, toml_path) @app.command("unset") @@ -152,17 +162,17 @@ def unset( toml_path: pathlib.Path = typer.Option(DEFAULT_TOML_PATH, callback=path_callback), ): """Unset a value from a toml file.""" - toml_part = toml_file = tomlkit.parse(toml_path.read_text()) + toml_part = toml_file = read_toml(toml_path) for key_part in key.split(".")[:-1]: try: toml_part = toml_part[key_part] - except tomlkit.exceptions.NonExistentKey: + except KeyError: typer.echo(f"Key {key} can not unset", err=True) del toml_part[key.split(".")[-1]] - toml_path.write_text(tomlkit.dumps(toml_file)) + write_toml(toml_file, toml_path) @app.command("search") @@ -171,7 +181,7 @@ def search( toml_path: pathlib.Path = typer.Option(DEFAULT_TOML_PATH, callback=path_callback), ): """Search for a value in a toml file using JMESPath query.""" - toml_data = tomlkit.parse(toml_path.read_text()) + toml_data = read_toml(toml_path) try: result = jmespath.search(jmespath_expression, toml_data) or "No result found" diff --git a/uv.lock b/uv.lock index 6465454..65f12bd 100644 --- a/uv.lock +++ b/uv.lock @@ -252,7 +252,7 @@ source = { editable = "." } dependencies = [ { name = "jmespath" }, { name = "regex" }, - { name = "tomlkit" }, + { name = "tomlrt" }, { name = "typer" }, ] @@ -266,7 +266,7 @@ dev = [ requires-dist = [ { name = "jmespath", specifier = ">=1.0.1" }, { name = "regex", specifier = ">=2020.7.14" }, - { name = "tomlkit", specifier = ">=0.13.3" }, + { name = "tomlrt", specifier = ">=2.2.2" }, { name = "typer", specifier = ">=0.16.0" }, ] @@ -316,12 +316,15 @@ wheels = [ ] [[package]] -name = "tomlkit" -version = "0.13.3" +name = "tomlrt" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207 } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/87/e562c97681fba128969622bfbf41e0dba0be6339ab4d943c6946414d8303/tomlrt-2.2.2.tar.gz", hash = "sha256:1ab5c1187c2e2f4ee0dd078142f531efbd623aaaed09c350bc9ee8150cb23c4f", size = 406967 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901 }, + { url = "https://files.pythonhosted.org/packages/7e/45/8d2905d2388fab4f62b4d45ed3b7b71a40e2d35fa2f513d071607f5ec13f/tomlrt-2.2.2-py3-none-any.whl", hash = "sha256:7edf2a55a5afccc76b971add88e4163f80bceefbfee3697b5566f17963cd1200", size = 144429 }, ] [[package]]