This commit is contained in:
2025-03-07 16:42:22 +08:00
parent 3404b6b7e0
commit 807fcb2849
11 changed files with 587 additions and 88 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ wheels/
# Virtual environments # Virtual environments
.venv .venv
examples/*.json

142
README.md
View File

@ -1,39 +1,119 @@
# zig-fetch-py
A Python tool to parse Zig Object Notation (ZON) files and convert them to JSON.
## Installation
### Using uv (recommended)
[uv](https://github.com/astral-sh/uv) is a fast Python package installer and resolver. To install zig-fetch-py using uv:
```bash
# Install uv if you don't have it
curl -sSf https://astral.sh/uv/install.sh | bash
# Create and activate a virtual environment
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install the package in development mode
uv pip install -e .
# Install development dependencies
uv pip install -e ".[dev]"
```
### Using pip
```bash
# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install the package in development mode
pip install -e .
# Install development dependencies
pip install -e ".[dev]"
```
## Usage
### Command Line
```bash
# Basic usage
zon2json path/to/file.zon
# Output to a file
zon2json path/to/file.zon -o output.json
# Pretty print the JSON
zon2json path/to/file.zon -p
# Enable verbose logging
zon2json path/to/file.zon -v
```
### Python API
```python
from zig_fetch_py.parser import parse_zon_file, zon_to_json
# Parse a ZON file
result = parse_zon_file("path/to/file.zon")
print(result) # Python dictionary
# Convert ZON content to JSON
zon_content = """.{
.name = "test",
.version = "1.0.0",
}"""
json_str = zon_to_json(zon_content, indent=4)
print(json_str)
```
## Development
### Running Tests
```bash
# Run all tests
pytest
# Run tests with coverage
pytest --cov=zig_fetch_py
# Generate coverage report
pytest --cov=zig_fetch_py --cov-report=html
```
## ZON Format
Zig Object Notation (ZON) is a data format used by the Zig programming language. It's similar to JSON but with some differences in syntax:
- Objects are defined with `.{ ... }`
- Keys are prefixed with a dot: `.key = value`
- Arrays are defined with `.[ ... ]`
- Special identifiers can be quoted with `@`: `.@"special-name" = value`
- Comments use `//` syntax
Example:
```zon ```zon
.{ .{
.name = .zls, .name = "example",
// Must match the `zls_version` in `build.zig` .version = "1.0.0",
.version = "0.15.0-dev",
// Must be kept in line with the `minimum_build_zig_version` in `build.zig`.
// Should be a Zig version that is downloadable from https://ziglang.org/download/ or a mirror.
.minimum_zig_version = "0.14.0",
// If you do not use Nix, a ZLS maintainer can take care of this.
// Whenever the dependencies are updated, run the following command:
// ```bash
// nix run github:Cloudef/zig2nix#zon2nix -- build.zig.zon > deps.nix
// rm build.zig.zon2json-lock # this file is unnecessary
// ```
.dependencies = .{ .dependencies = .{
.known_folders = .{ .lib1 = .{
.url = "https://github.com/ziglibs/known-folders/archive/aa24df42183ad415d10bc0a33e6238c437fc0f59.tar.gz", .url = "https://example.com/lib1.tar.gz",
.hash = "known_folders-0.0.0-Fy-PJtLDAADGDOwYwMkVydMSTp_aN-nfjCZw6qPQ2ECL", .hash = "abcdef123456",
},
.diffz = .{
.url = "https://github.com/ziglibs/diffz/archive/ef45c00d655e5e40faf35afbbde81a1fa5ed7ffb.tar.gz",
.hash = "N-V-__8AABhrAQAQLLLGadghhPsdxTgBk9N9aLVOjXW3ay0V",
},
.@"lsp-codegen" = .{
.url = "https://github.com/zigtools/zig-lsp-codegen/archive/063a98c13a2293d8654086140813bdd1de6501bc.tar.gz",
.hash = "lsp_codegen-0.1.0-CMjjo0ZXCQB-rAhPYrlfzzpU0u0u2MeGvUucZ-_g32eg",
},
.tracy = .{
.url = "https://github.com/wolfpld/tracy/archive/refs/tags/v0.11.1.tar.gz",
.hash = "N-V-__8AAMeOlQEipHjcyu0TCftdAi9AQe7EXUDJOoVe0k-t",
.lazy = true,
}, },
}, },
.paths = .{""}, .tags = .["tag1", "tag2"],
.fingerprint = 0xa66330b97eb969ae, // Changing this has security and trust implications.
} }
``` ```
It's called `zon`, the Zig Object Notation. I think You need to parse it first (convert to json) and then parse the json. ## License
MIT

11
examples/example.zon Normal file
View File

@ -0,0 +1,11 @@
.{
.name = "example",
.version = "1.0.0",
.dependencies = .{
.lib1 = .{
.url = "https://example.com/lib1.tar.gz",
.hash = "abcdef123456",
},
},
.tags = .["tag1", "tag2"],
}

64
examples/simple_example.py Executable file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Simple example demonstrating how to use the zig-fetch-py library.
"""
import json
import sys
from pathlib import Path
from loguru import logger
from zig_fetch_py.parser import parse_zon_file, zon_to_json
# Configure logging
logger.remove()
logger.add(sys.stderr, level="INFO")
# Example ZON content
ZON_CONTENT = """.{
.name = "example",
.version = "1.0.0",
.dependencies = .{
.lib1 = .{
.url = "https://example.com/lib1.tar.gz",
.hash = "abcdef123456",
},
},
.tags = .["tag1", "tag2"],
}"""
def main():
# Create a temporary ZON file
example_dir = Path(__file__).parent
zon_file = example_dir / "example.zon"
zon_file.write_text(ZON_CONTENT)
logger.info(f"Created example ZON file: {zon_file}")
# Parse the ZON file
result = parse_zon_file(str(zon_file))
logger.info("Parsed ZON file to Python dictionary:")
logger.info(result)
# Convert to JSON
json_str = zon_to_json(ZON_CONTENT, indent=2)
logger.info("Converted ZON to JSON:")
logger.info(json_str)
# Save JSON to file
json_file = example_dir / "example.json"
with open(json_file, "w") as f:
f.write(json_str)
logger.info(f"Saved JSON to file: {json_file}")
# Access specific values from the parsed data
logger.info(f"Package name: {result['name']}")
logger.info(f"Package version: {result['version']}")
logger.info(f"Dependencies: {list(result['dependencies'].keys())}")
logger.info(f"Tags: {result['tags']}")
if __name__ == "__main__":
main()

View File

@ -4,10 +4,7 @@ version = "0.1.0"
description = "A tool to parse Zig Object Notation (ZON) files and convert them to JSON" description = "A tool to parse Zig Object Notation (ZON) files and convert them to JSON"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = ["click>=8.1.8", "loguru>=0.7.3"]
"click>=8.1.8",
"loguru>=0.7.3",
]
[project.scripts] [project.scripts]
zon2json = "zig_fetch_py.main:main" zon2json = "zig_fetch_py.main:main"
@ -19,5 +16,14 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["zig_fetch_py"] packages = ["zig_fetch_py"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"
python_classes = "Test*"
[tool.ruff] [tool.ruff]
line-length = 100 line-length = 100
[project.optional-dependencies]
dev = ["pytest>=8.3.5", "pytest-cov>=6.0.0"]

189
tests/test_parser.py Normal file
View File

@ -0,0 +1,189 @@
"""
Unit tests for the ZON parser.
"""
import json
import pytest
from pathlib import Path
from zig_fetch_py.parser import ZonParser, parse_zon_file, zon_to_json
class TestZonParser:
"""Test cases for the ZonParser class."""
def test_parse_empty_object(self):
"""Test parsing an empty object."""
parser = ZonParser(".{}")
result = parser.parse()
assert result == {}
def test_parse_simple_object(self):
"""Test parsing a simple object with string values."""
parser = ZonParser(
""".{
.name = "test",
.version = "1.0.0",
}"""
)
result = parser.parse()
assert result == {"name": "test", "version": "1.0.0"}
def test_parse_nested_object(self):
"""Test parsing a nested object."""
parser = ZonParser(
""".{
.metadata = .{
.name = "test",
.version = "1.0.0",
},
}"""
)
result = parser.parse()
assert result == {"metadata": {"name": "test", "version": "1.0.0"}}
def test_parse_array(self):
"""Test parsing an array."""
parser = ZonParser(
""".{
.tags = .["tag1", "tag2", "tag3"],
}"""
)
result = parser.parse()
assert result == {"tags": ["tag1", "tag2", "tag3"]}
def test_parse_numbers(self):
"""Test parsing different number formats."""
parser = ZonParser(
""".{
.integer = 42,
.negative = -10,
.float = 3.14,
.hex = 0xDEADBEEF,
}"""
)
result = parser.parse()
assert result == {
"integer": 42,
"negative": -10,
"float": 3.14,
"hex": 0xDEADBEEF,
}
def test_parse_boolean(self):
"""Test parsing boolean values."""
parser = ZonParser(
""".{
.is_true = true,
.is_false = false,
}"""
)
result = parser.parse()
assert result == {"is_true": True, "is_false": False}
def test_parse_null(self):
"""Test parsing null values."""
parser = ZonParser(
""".{
.nothing = null,
}"""
)
result = parser.parse()
assert result == {"nothing": None}
def test_parse_comments(self):
"""Test parsing with comments."""
parser = ZonParser(
""".{
// This is a comment
.name = "test", // Inline comment
// Another comment
.version = "1.0.0",
}"""
)
result = parser.parse()
assert result == {"name": "test", "version": "1.0.0"}
def test_parse_special_identifiers(self):
"""Test parsing special identifiers with @ symbol."""
parser = ZonParser(
""".{
.@"special-name" = "value",
}"""
)
result = parser.parse()
assert result == {"special-name": "value"}
def test_parse_shorthand_notation(self):
"""Test parsing shorthand notation where key is the same as value."""
parser = ZonParser(
""".{
.name,
.version,
}"""
)
result = parser.parse()
assert result == {"name": "name", "version": "version"}
def test_parse_escaped_strings(self):
"""Test parsing strings with escape sequences."""
parser = ZonParser(
""".{
.escaped = "Line 1\\nLine 2\\tTabbed\\r\\n",
}"""
)
result = parser.parse()
assert result == {"escaped": "Line 1\nLine 2\tTabbed\r\n"}
def test_parse_error_invalid_syntax(self):
"""Test that parser raises an error for invalid syntax."""
with pytest.raises(ValueError):
parser = ZonParser(".{,}")
parser.parse()
def test_parse_error_unterminated_string(self):
"""Test that parser raises an error for unterminated strings."""
with pytest.raises(ValueError):
parser = ZonParser(
""".{
.name = "unterminated,
}"""
)
parser.parse()
class TestZonFileParser:
"""Test cases for the file parsing functions."""
def test_zon_to_json(self, tmp_path):
"""Test converting ZON to JSON."""
zon_content = """.{
.name = "test",
.version = "1.0.0",
}"""
# Test without indentation
json_str = zon_to_json(zon_content)
parsed_json = json.loads(json_str)
assert parsed_json == {"name": "test", "version": "1.0.0"}
# Test with indentation
json_str_pretty = zon_to_json(zon_content, indent=2)
assert " " in json_str_pretty # Should have indentation
parsed_json = json.loads(json_str_pretty)
assert parsed_json == {"name": "test", "version": "1.0.0"}
def test_parse_zon_file(self, tmp_path):
"""Test parsing a ZON file."""
# Create a temporary ZON file
zon_file = tmp_path / "test.zon"
zon_file.write_text(
""".{
.name = "test",
.version = "1.0.0",
}"""
)
# Parse the file
result = parse_zon_file(str(zon_file))
assert result == {"name": "test", "version": "1.0.0"}

103
uv.lock generated
View File

@ -23,6 +23,54 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
] ]
[[package]]
name = "coverage"
version = "7.6.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 },
{ url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 },
{ url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 },
{ url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 },
{ url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 },
{ url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 },
{ url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 },
{ url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 },
{ url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 },
{ url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 },
{ url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 },
{ url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 },
{ url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 },
{ url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 },
{ url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 },
{ url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 },
{ url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 },
{ url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 },
{ url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 },
{ url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 },
{ url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 },
{ url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 },
{ url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 },
{ url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 },
{ url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 },
{ url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 },
{ url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 },
{ url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 },
{ url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 },
{ url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 },
{ url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 },
]
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]] [[package]]
name = "loguru" name = "loguru"
version = "0.7.3" version = "0.7.3"
@ -36,6 +84,52 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 },
] ]
[[package]]
name = "packaging"
version = "24.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
]
[[package]]
name = "pytest-cov"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "coverage" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
]
[[package]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.2.0" version = "1.2.0"
@ -54,8 +148,17 @@ dependencies = [
{ name = "loguru" }, { name = "loguru" },
] ]
[package.optional-dependencies]
dev = [
{ name = "pytest" },
{ name = "pytest-cov" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "click", specifier = ">=8.1.8" }, { name = "click", specifier = ">=8.1.8" },
{ name = "loguru", specifier = ">=0.7.3" }, { name = "loguru", specifier = ">=0.7.3" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.5" },
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" },
] ]
provides-extras = ["dev"]

5
zig_fetch_py/__init__.py Normal file
View File

@ -0,0 +1,5 @@
"""
zig-fetch-py - A tool to parse Zig Object Notation (ZON) files and convert them to JSON.
"""
__version__ = "0.1.0"

60
zig_fetch_py/main.py Normal file
View File

@ -0,0 +1,60 @@
"""
Command-line interface for the ZON parser.
"""
import json
import sys
from pathlib import Path
import click
from loguru import logger
from zig_fetch_py.parser import parse_zon_file
@click.command()
@click.argument("file", type=click.Path(exists=True, readable=True))
@click.option(
"-o",
"--output",
type=click.Path(writable=True),
help="Output JSON file path (default: stdout)",
)
@click.option("-p", "--pretty", is_flag=True, help="Pretty print JSON output")
@click.option("-v", "--verbose", is_flag=True, help="Enable verbose logging")
def main(file, output, pretty, verbose):
"""Parse ZON files and convert to JSON.
This tool parses Zig Object Notation (ZON) files and converts them to JSON format.
"""
# Configure logging
log_level = "DEBUG" if verbose else "INFO"
logger.remove() # Remove default handler
logger.add(sys.stderr, level=log_level)
logger.info(f"Processing file: {file}")
try:
result = parse_zon_file(file)
indent = 4 if pretty else None
json_str = json.dumps(result, indent=indent)
if output:
logger.info(f"Writing output to: {output}")
with open(output, "w") as f:
f.write(json_str)
else:
logger.debug("Writing output to stdout")
click.echo(json_str)
except Exception as e:
logger.error(f"Error: {e}")
sys.exit(1)
# This is only executed when the module is run directly
if __name__ == "__main__":
# When imported as a module, click will handle the function call
# When run directly, we need to call it explicitly
main()

View File

@ -1,11 +1,11 @@
#!/usr/bin/env python3 """
import json ZON parser module - Parses Zig Object Notation (ZON) files.
import re """
import sys
from pathlib import Path import json
from typing import Any, Dict, List, Union, Tuple from pathlib import Path
from typing import Any, Dict, List, Union, Tuple, Optional
import click
from loguru import logger from loguru import logger
@ -14,12 +14,13 @@ class ZonParser:
A parser for Zig Object Notation (ZON) files. A parser for Zig Object Notation (ZON) files.
""" """
content: str
pos: int
line: int
col: int
def __init__(self, content: str): def __init__(self, content: str):
"""
Initialize the parser with ZON content.
Args:
content: The ZON content to parse
"""
self.content = content self.content = content
self.pos = 0 self.pos = 0
self.line = 1 self.line = 1
@ -314,7 +315,15 @@ class ZonParser:
def parse_zon_file(file_path: str) -> Dict[str, Any]: def parse_zon_file(file_path: str) -> Dict[str, Any]:
"""Parse a ZON file and return a Python dictionary.""" """
Parse a ZON file and return a Python dictionary.
Args:
file_path: Path to the ZON file
Returns:
Dictionary representation of the ZON file
"""
logger.debug(f"Parsing ZON file: {file_path}") logger.debug(f"Parsing ZON file: {file_path}")
with open(file_path, "r") as f: with open(file_path, "r") as f:
content = f.read() content = f.read()
@ -325,46 +334,17 @@ def parse_zon_file(file_path: str) -> Dict[str, Any]:
return result return result
@click.command() def zon_to_json(zon_content: str, indent: Optional[int] = None) -> str:
@click.argument("file", type=click.Path(exists=True, readable=True))
@click.option(
"-o",
"--output",
type=click.Path(writable=True),
help="Output JSON file path (default: stdout)",
)
@click.option("-p", "--pretty", is_flag=True, help="Pretty print JSON output")
@click.option("-v", "--verbose", is_flag=True, help="Enable verbose logging")
def main(file, output, pretty, verbose):
"""Parse ZON files and convert to JSON.
This tool parses Zig Object Notation (ZON) files and converts them to JSON format.
""" """
# Configure logging Convert ZON content to JSON string.
log_level = "DEBUG" if verbose else "INFO"
logger.remove() # Remove default handler
logger.add(sys.stderr, level=log_level)
logger.info(f"Processing file: {file}") Args:
zon_content: ZON content as string
indent: Number of spaces for indentation (None for compact JSON)
try: Returns:
result = parse_zon_file(file) JSON string
"""
indent = 4 if pretty else None parser = ZonParser(zon_content)
json_str = json.dumps(result, indent=indent) result = parser.parse()
return json.dumps(result, indent=indent)
if output:
logger.info(f"Writing output to: {output}")
with open(output, "w") as f:
f.write(json_str)
else:
logger.debug("Writing output to stdout")
click.echo(json_str)
except Exception as e:
logger.error(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main() # pylint: disable=no-value-for-parameter