Metadata-Version: 2.4
Name: hyprvalidate
Version: 0.2.0
Summary: Convert and validate Hyprland Lua configs against Hyprland's real API schema
Author: Paritsingla7
License-Expression: MIT
Project-URL: Homepage, https://paritsingla7.github.io/hyprvalidate/
Project-URL: Source, https://github.com/Paritsingla7/hyprvalidate
Project-URL: Issues, https://github.com/Paritsingla7/hyprvalidate/issues
Project-URL: Changelog, https://github.com/Paritsingla7/hyprvalidate/blob/main/CHANGELOG.md
Keywords: hyprland,hyprlang,lua,config,converter,validator,wayland
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Desktop Environment :: Window Managers
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: luaparser>=4.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

<h1 align="center">hyprvalidate</h1>

<p align="center">
  Migrate old Hyprland configs to Lua.<br>
  Validate Lua configs against Hyprland's own API schema.
</p>

<p align="center">
  <a href="https://paritsingla7.github.io/hyprvalidate/"><strong>Try it online</strong></a> ·
  <a href="#install"><strong>Install</strong></a> ·
  <a href="docs/COMPARISON.md"><strong>Why it's different</strong></a>
</p>

<p align="center">
  <a href="https://github.com/Paritsingla7/hyprvalidate/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Paritsingla7/hyprvalidate/actions/workflows/ci.yml/badge.svg"></a>
  <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg">
  <img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue.svg">
  <img alt="Hyprland 0.55+" src="https://img.shields.io/badge/hyprland-0.55%2B-1a9fb2.svg">
  <img alt="tests" src="https://img.shields.io/badge/tests-170%20passing-brightgreen.svg">
</p>

---

Hyprland 0.55 replaced the old `hyprland.conf` (hyprlang) format with a Lua
config. If you have a config from before that, it needs migrating — and if you
already wrote a Lua one, nothing currently tells you whether it's actually
valid until Hyprland refuses to load it.

hyprvalidate does both, by reading the API description Hyprland already ships
(`/usr/share/hypr/stubs/hl.meta.lua` — autogenerated from the compositor's own
source on every build) rather than a table someone typed out by hand.

```console
$ hyprvalidate convert ~/.config/hypr/hyprland.conf --split ~/.config/hypr
wrote 10 file(s) to /home/you/.config/hypr/ (entry point: hyprland.lua)
  appearance.lua
  autostart.lua
  devices.lua
  env.lua
  hyprland.lua
  input.lua
  keybinds.lua
  monitors.lua
  plugins.lua
  windowrules.lua

$ hyprvalidate check ~/.config/hypr
10 file(s) checked, no issues found.
```

## Install

**pipx** (recommended — no clone, isolated environment):

```bash
pipx install git+https://github.com/Paritsingla7/hyprvalidate.git
```

<details>
<summary>Other methods, and Arch/PEP-668 notes</summary>

If `pipx` isn't installed, on Arch use `sudo pacman -S python-pipx` —
`pip install pipx` is blocked by default because the system Python is
externally managed (PEP 668).

**From a clone:**

```bash
git clone https://github.com/Paritsingla7/hyprvalidate.git
cd hyprvalidate
python3 -m venv .venv && .venv/bin/pip install .
```

Prefer a venv over `--break-system-packages` on Arch and other
externally-managed Pythons.

**Arch (PKGBUILD):** a ready `packaging/PKGBUILD` is in the repo.
</details>

**Requires:** Python 3.10+, and `luac` (`pacman -S lua`) for the syntax gate.
Hyprland 0.55+ is needed to read the live schema — or pass
`--stub schema.json` to run without Hyprland installed at all.

## Usage

### Convert an old config

```bash
# straight to stdout
hyprvalidate convert hyprland.conf

# one file
hyprvalidate convert hyprland.conf -o hyprland.lua

# a modular directory (recommended)
hyprvalidate convert hyprland.conf --split ~/.config/hypr
```

`--split` writes one file per config area with a `hyprland.lua` entry point
that `require()`s the rest — the layout Hyprland's own docs recommend
("you can (and should!!) split this configuration into multiple files"),
rather than a single 400-line dump.

Symbol names and value types are looked up in the schema. Anything the
converter can't resolve confidently becomes a comment, never a silent guess:

```lua
-- TODO(hyprvalidate convert): unrecognized old dispatcher 'frobnicate' -
-- no confident rename, convert manually (hyprland.conf line 84)
```

The converter runs the full validator on its own output before reporting
success. If a conversion doesn't come out clean, you find out immediately
rather than when Hyprland reloads.

### Validate a Lua config

```bash
hyprvalidate check ~/.config/hypr/hyprland.lua
hyprvalidate check ~/.config/hypr          # a whole directory
```

Catches unknown dispatchers, invalid config keys, wrong value types, bad call
arity, and invalid fields inside a spec table:

```console
$ hyprvalidate check hyprland.lua
hyprland.lua:14: [unknown_spec_field] 'resolution' is not a field of HL.MonitorSpec
hyprland.lua:47: [type_mismatch] 'animations.enabled' expects boolean, got string ('yes')

2 issue(s) found.
```

**Exit codes:** `0` clean · `1` schema findings · `2` invalid Lua, unparseable
input, or a missing schema. Scriptable in CI.

### Validating dotfiles in CI (no Hyprland needed)

`--stub` accepts a `schema.json` snapshot as well as the live stub, so you can
check a dotfiles repo on a runner that has no compositor installed:

```yaml
- run: pipx install git+https://github.com/Paritsingla7/hyprvalidate.git
- run: hyprvalidate check hypr/ --stub schema.json
```

## Why this exists

Four community converters already existed when this started. Testing all four
against the same real config found that **three produce output that isn't valid
Lua at all**, and that all four hand-transcribe Hyprland's dispatcher and
config-key names into their own tables — tables which have drifted from the
real API in different directions (measured: 53, 11, and 2 dispatcher names,
against a real count of **51**).

Meanwhile Hyprland ships the correct answer, regenerated on every build. One of
those four tools — [hypr2lua](https://github.com/Phillezi/hypr2lua) — **already
had the right idea and tried to read it.** Its loader hits a one-line bug
(`strings.HasPrefix(line, "--")` discards all 1736 annotation lines in the
file, leaving 0 entries extracted), so the schema it threads through its
pipeline is always empty. The idea was right; the read failed silently.

That's the whole thesis here: extract the real schema properly, look everything
up against it, and never hardcode a name you could have asked for.

**Full measured comparison, with a script to reproduce every number, and proper
credit to the prior work: [`docs/COMPARISON.md`](docs/COMPARISON.md).**

## Limitations

Stated up front, because a tool whose whole point is honesty about uncertainty
shouldn't hide its own gaps.

- **`plugin { }` blocks aren't converted.** Plugin config is registered at
  runtime by each plugin and is genuinely absent from the schema, so there's
  nothing to validate against. Flagged as a TODO for manual conversion.
- **`source =` isn't inlined.** Sourced files are reported, not followed —
  convert them separately.
- **`animation` / `bezier` list directives** aren't mapped yet (they're not in
  the schema's flat config-key space).
- **Window-rule fields aren't deeply checked.** `HL.WindowRuleSpec` only types
  3 universal fields in the stub; per-rule-type fields are dispatched
  dynamically and aren't enumerable, so checking them would produce false
  positives. Deliberately skipped rather than guessed.
- **`$variables` are inlined**, not preserved as a shared Lua table — so you
  lose the single-point-of-change that `$terminal` gave you.
- **Untyped functions aren't arity-checked.** Many stub functions are typed
  `fun(...)` with no named parameters; there's nothing to check against, so
  they aren't.
- **No `--fix` mode yet.** The validator reports; it doesn't repair.

## How it works

```
hyprland.conf ──lexer──▶ hyprlang AST ──mapper──▶ Lua AST ──▶ .lua file(s)
                                            ▲                      │
                    hl.meta.lua ──extractor─┤                      │
                        (the schema)        │                      ▼
                                            └──────── checker ◀── luac -p
```

Everything routes through one extracted schema. The mapper asks it what a
symbol is called and what type a value should be; the checker asks it whether
what's on disk is real. Neither has a hardcoded API table.

| Component | What it does |
|---|---|
| `schema/extractor.py` | `hl.meta.lua` → queryable schema (79 classes, 353 config keys, 51 dispatchers) |
| `hyprlang/` | lexer + parser for the old `.conf` format |
| `luaast/` | Lua reader, writer, and the `luac -p` gate |
| `converter/` | block dispatch, type coercion, rename tables, TODO emission, mapper |
| `checker.py` | the validator: symbols, config keys, types, arity, spec fields |
| `cli.py` | `convert` / `check` |

## Contributing

Bug reports about wrong conversions are especially useful — attach the
hyprlang snippet and what you expected. See
[CONTRIBUTING.md](CONTRIBUTING.md).

```bash
git clone https://github.com/Paritsingla7/hyprvalidate.git && cd hyprvalidate
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest tests/ -q
```

The suite runs with or without Hyprland installed (it falls back to the
committed `schema.json`).

## Acknowledgements

- **[Hyprland](https://github.com/hyprwm/Hyprland)** — ships the
  machine-readable API stub this whole project reads; no schema, no project.
- **[hypr2lua](https://github.com/Phillezi/hypr2lua)** — reached the
  schema-driven idea first (see "Why this exists" above).
- **[hyprconf2lua](https://github.com/Prateek-squadron/hyprconf2lua)** —
  its MIT-licensed hyprlang lexer is adapted here (attributed in
  `hyprvalidate/hyprlang/lexer.py`) rather than rewritten from scratch.
- **hyprlang2lua** and **[hypr-migrate](https://github.com/loeclos/hypr-migrate)**
  — output worth studying; reading all four is what made the problem visible.
- **[luaparser](https://pypi.org/project/luaparser/)** (MIT) — the Lua grammar.

## License

[MIT](LICENSE).
