Metadata-Version: 2.4
Name: zig-zon
Version: 0.3.1
Summary: a native cpython extension to parse zig object notation strings
Author-email: Tobias Simetsreiter <dasimmet@gmail.com>
Maintainer-email: Tobias Simetsreiter <dasimmet@gmail.com>
License-Expression: MIT
Keywords: zig,ziglang,cpython,native,sdist,zon
Requires-Python: >=3.8
Project-URL: Homepage, https://codeberg.org/dasimmet/python-zig-zon
Project-URL: Issues, https://codeberg.org/dasimmet/python-zig-zon/issues
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Zig
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Description-Content-Type: text/markdown

# Python Zig Zon Parser

A native python extension built with zig to parse zig-object-notation strings
into python objects.

On PyPi it is only distributed in source, and built during install.
It therefore needs to download the `ziglang` python package.

Depending on your OS, you may need the python development headers
(e.g. `python3-dev`) installed.

## Installation 

```bash
# from PyPi: https://pypi.org/project/zig-zon
pip install zig-zon
# directly from the Codeberg Release:
pip install https://codeberg.org/dasimmet/python-zig-zon/releases/download/0.3.1/zig_zon-0.3.1.tar.gz
```

## Parsing

```python
>>> import zig_zon
>>> parsed = zig_zon.parse('.{.allyourcode = .are_belong_to_us, .asd = 123}')
>>> print(parsed)
{'allyourcode': 'are_belong_to_us', 'asd': 123}
```

### Parsing Options

- **`bytes`**: Controls how binary / non-UTF-8 string literals in ZON are handled:
  - `"error"` (default): Non-UTF-8 string literals raise a `UnicodeDecodeError`.
  - `"auto"`: Valid UTF-8 strings are returned as `zig_zon.String` (subclasses `str`); strings containing invalid UTF-8 bytes are returned as `zig_zon.ByteString` (subclasses `bytes`).
  - `"always"`: Always returns `zig_zon.ByteString` for all string literals.

or, look at the [test file](https://codeberg.org/dasimmet/python-zig-zon/src/branch/main/src/test_zig_zon.py)

## Stringification / Serialization

Use `zig_zon.stringify` to serialize Python objects to ZON strings, or `zig_zon.dump` to write directly to a file-like object:

```python
>>> import zig_zon
>>> data = {
...     "name": zig_zon.Enum("my_pkg"),
...     "version": "0.1.0",
...     "fingerprint": zig_zon.Hex(0x1234abcd),
...     "dependencies": {
...         "dep1": {
...             "url": "https://example.com/dep1.tar.gz",
...             "hash": "1220...",
...         }
...     },
...     "paths": ["build.zig", "build.zig.zon", "src"],
... }
>>> print(zig_zon.stringify(data))
.{
    .name = .my_pkg,
    .version = "0.1.0",
    .fingerprint = 0x1234abcd,
    .dependencies = .{
        .dep1 = .{
            .url = "https://example.com/dep1.tar.gz",
            .hash = "1220...",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}
```

Write to a file or stream:

```python
with open("build.zig.zon", "w", encoding="utf-8") as f:
    zig_zon.dump(data, f)
```

### Serialization Options

- **`integer_mode`**: Controls integer formatting:
  - `"auto"` (default): Uses `Hex` -> hex (`0x...`), `Integer` -> decimal, plain `int` -> decimal.
  - `"int"`: Always formats integers as decimal.
  - `"hex"`: Always formats integers as hex (`0x...`).
  - `"fallback_int"`: Uses `Hex` -> hex, `Integer` -> decimal, plain `int` -> decimal.
  - `"fallback_hex"`: Uses `Integer` -> decimal, `Hex` -> hex, plain `int` -> hex.
  - `"error"`: Errors out (`TypeError`) if an integer is not a `zig_zon.Integer` or `zig_zon.Hex` subclass.
- **`dict_as_tuples`** (bool, default `False`): If `True`, converts dictionaries to tuples of key-value tuples (`.{ .{ "key", value }, ... }`) rather than struct notation (`.{ .key = value }`).
- **`encoding`** (str, default `"utf-8"`): Specifies the encoding used for raw strings (`str`) and encoded strings (`bytes`/`bytearray`).
- **`whitespace`** (bool, default `True`): If `False`, omits unnecessary whitespace and newlines for compact output.

### Supported Python Types for Serialization

- `list`, `tuple`, `zig_zon.Tuple`: Serialized as ZON tuples `.{ ... }`.
- `set`, `frozenset`: Serialized as ZON tuples `.{ ... }`.
- `namedtuple`: Serialized as ZON structs `.{ .field = value }`.
- `dict`, `zig_zon.Struct`: Serialized as structs or key-value tuples (with `dict_as_tuples=True`).
- `str`, `zig_zon.String`: Serialized as string literals `"..."`.
- `bytes`, `bytearray`: Decoded using `encoding` and serialized as string literals `"..."`.
- `zig_zon.ByteString`: Serialized directly as binary string literals `"..."` preserving raw byte escapes.
- `zig_zon.BareEnum`: Serialized as bare enums `.identifier`.
- `zig_zon.QuotedEnum`: Serialized as quoted enums `.@"identifier"`.
- `zig_zon.Enum`: Serialized as enum literals (bare or quoted as necessary).
- `int`, `zig_zon.Integer`, `zig_zon.Hex`: Formatted according to `integer_mode`.
- `float`, `zig_zon.Float`: Serialized as float literals.
- `bool`: Serialized as `true` or `false`.
- `None`: Serialized as `null`.

## Supported Zon values and their python type

The parser returns custom classes for each Zig type that wrap and subclass standard Python types:

- `structs` -> `zig_zon.Struct` (subclasses `dict`)
- `tuples` -> `zig_zon.Tuple` (subclasses `list`)
- `integer literals` -> `zig_zon.Integer` (subclasses `int`)
- `hex literals` -> `zig_zon.Hex` (wraps / subclasses `int`)
- `float literals` -> `zig_zon.Float` (subclasses `float`)
- `booleans` -> `bool` (Python built-in `bool` is final and cannot be subclassed)
- `null` -> `None` (Python `NoneType` is final and cannot be subclassed)
- `string literals` -> `zig_zon.String` (subclasses `str`)
- `binary string literals` -> `zig_zon.ByteString` (subclasses `bytes`)
- `bare enum literals` -> `zig_zon.BareEnum` (subclasses `zig_zon.Enum` -> `str`)
- `quoted enum literals` -> `zig_zon.QuotedEnum` (subclasses `zig_zon.Enum` -> `str`)

Because the custom classes wrap and subclass their respective Python base types, both `isinstance(val, BaseType)` and `isinstance(val, zig_zon.CustomClass)` evaluate to `True`.

## Not supported values

- `bigint`

## How it works

The `zig build` commands produces a python source distribution archive ready
for uploading to Pypi. It includes the sources of [zig-python-sdist](https://codeberg.org/dasimmet/zig-python-sdist)
and a custom `setup.py` that handles the build of the cpython extension.

When you run pip install, the source distribution is downloaded, and since
this source distribution depends on the `ziglang` python package at build time,
a `zig build` command is used to compile the extension.

## Developing

as opposed to other solutions for building cpython extensions in zig,
i want to develop this using:

```
zig build
```

for linking against the local python installation [zig-python-sdist](https://codeberg.org/dasimmet/zig-python-sdist)
runs a python script that imports the cpython headers into zig's build cache,
run `translate-c` on the result and offers a `cpython` module to import
in our zig code.
On windows, the extension library is also linked against
the python runtime static lib and renamed to a `.pyd` file for importing.
