Metadata-Version: 2.4
Name: lang-mu
Version: 0.2.0
Summary: Mu configuration language parser and typed decoder for Python.
License: AGPL-3.0-or-later
License-File: LICENSE.md
Keywords: configuration,parser,sexpr,typed-decoding,mu
Author: Sir Wabbit
Author-email: wabbit@wabbit.one
Requires-Python: >=3.10
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Project-URL: Homepage, https://github.com/wabbit-corp/python-lang-mu
Project-URL: Issue Tracker, https://github.com/wabbit-corp/python-lang-mu/issues
Project-URL: Repository, https://github.com/wabbit-corp/python-lang-mu
Description-Content-Type: text/markdown

# lang-mu

`lang-mu` is a Python distribution for the `mu` Python package, an implementation of the Mu configuration language.
It provides:

- A parser that preserves Mu syntax as an AST.
- A typed decoder that maps Mu expressions into Python dataclasses and typing constructs.
- An experimental runtime evaluator (`mu.exec`) for callable execution semantics.

## Why this library

Mu configuration files can describe nested structures, tagged records, and mixed positional/named arguments.
This package gives you a strict, testable way to parse and decode those configs into Python types.

## Installation

```bash
pip install lang-mu
```

## Quickstart: Parsing

```python
from mu import Document, parse

doc = parse('(app-jvm "demo" :main "demo.Main")')
assert isinstance(doc, Document)
assert len(doc.exprs) == 1
```

## Quickstart: Typed decoding

```python
from dataclasses import dataclass
from typing import Annotated

from mu import ZeroOrMore, parse_one


@dataclass
class Demo:
    name: str
    aliases: Annotated[list[str], ZeroOrMore]


cfg = parse_one('(demo :name "x" :aliases "a" "b")', Demo)
assert cfg == Demo(name="x", aliases=["a", "b"])
```

## Error handling

Typed decoding raises `DecodeError` with structured context:

- `path`: decode path (for example `$.field[0]`)
- `expected`: human-readable expected target/type
- `got`: actual Mu expression description
- `span`: optional source span/token information
- `cause`: optional underlying exception

```python
from dataclasses import dataclass
from mu import DecodeError, parse_one


@dataclass
class Counter:
    value: int


try:
    parse_one('(counter :value "not-an-int")', Counter)
except DecodeError as e:
    print(e.path, e.expected, e.got)
```

## API contract

### Stable API (`from mu import ...`)

- Parser: `parse`, `ParseError`
- AST: `Expr`, `Document`, `AtomExpr`, `StringExpr`, `GroupExpr`, `SequenceExpr`, `MappingExpr`, `MappingField`
- Typed decoding:
  - `parse_one`, `parse_many`, `decode`
  - `DecodeError`, `DecodeContext`
  - `DecoderRegistry`, `DecoderFn`, `DecodeWith`
  - `FieldName`, `OptionalArg`, `ZeroOrMore`, `OneOrMore`, `tag`
  - `Quoted`

### Experimental API (`from mu.exec import ...`)

- `EvalContext`
- `eval_expr`
- `EvalNameError`

The experimental runtime API is available but not considered stable yet.
Non-exported internals (for example `mu.arg_match` and parser private helpers) are unsupported and may change without notice.

## Python support

- Python `>=3.10`

## License

This project is licensed under **AGPL-3.0-or-later**.
See `LICENSE.md` for full text.

## Development and release checks

```bash
pytest -q
ruff check .
python -m build --sdist --wheel
python -m twine check dist/*
./scripts/check_wheel_contents.sh
```

