<p align="center">
<img src="docs/assets/numi-logo.png" alt="Numi logo" width="320">
</p>
# Numi
Numi is a blazingly fast CLI for generating code from Apple project resources.
It turns asset catalogs, localization files, and file lists into generated accessors and helpers using built-in or custom templates.
## Install
```bash
cargo install numi
```
The installed binary is named `numi`.
## What Numi Does
- reads `.xcassets` and generates image and color accessors
- reads `.strings` and `.xcstrings` and generates localization helpers
- reads `files` inputs and generates file-oriented helpers
- renders built-in templates or custom Minijinja templates
- supports workspace orchestration when a repo has multiple `numi.toml` files
Numi is built for deterministic generation workflows: check in the outputs you want, regenerate them locally, and verify them in CI with `numi check`.
## Quick Start
Initialize a starter config in your project:
```bash
numi init
```
Generate code:
```bash
numi generate
```
Check whether committed generated files are up to date:
```bash
numi check
```
Workspace orchestration is also available when a repo has multiple `numi.toml` files:
```bash
numi generate --workspace
numi check --workspace
```
## Minimal Config
Numi uses `numi.toml` as its config filename.
```toml
version = 1
[defaults]
access_level = "internal"
[defaults.bundle]
mode = "module"
[jobs.assets]
output = "Generated/Assets.swift"
[[jobs.assets.inputs]]
type = "xcassets"
path = "Resources/Assets.xcassets"
[jobs.assets.template.builtin]
language = "swift"
name = "swiftui-assets"
[jobs.l10n]
output = "Generated/L10n.swift"
[[jobs.l10n.inputs]]
type = "strings"
path = "Resources/Localization"
[jobs.l10n.template.builtin]
language = "swift"
name = "l10n"
```
You can also point localization generation at `.xcstrings`:
```toml
[jobs.l10n]
output = "Generated/L10n.swift"
[[jobs.l10n.inputs]]
type = "xcstrings"
path = "Resources/Localization"
[jobs.l10n.template.builtin]
language = "swift"
name = "l10n"
```
The starter config shipped with `numi init` lives in [docs/examples/starter-numi.toml](docs/examples/starter-numi.toml).
The same shape also works for Objective-C built-ins when you want an ObjC output:
```toml
[jobs.assets.template.builtin]
language = "objc"
name = "assets"
```
## Commands
`numi generate`
- discovers the nearest manifest unless `--config` is passed
- uses the nearest local `numi.toml` first
- runs one config for `[jobs]` manifests and the whole workspace for `[workspace]` manifests
- generates outputs for all named jobs, or only selected jobs when `--job` is repeated
- prints non-fatal warnings to stderr
- may reuse cached parser outputs when inputs are unchanged
`numi check`
- computes what `generate` would write without modifying files
- exits `0` when outputs are current
- exits `2` when outputs are stale
- prints warnings to stderr without turning the run into a failure
`numi dump-context`
- prints the exact JSON context a job template receives
- only supports single-config (`[jobs]`) manifests and rejects workspace manifests
- is the fastest way to debug or author custom templates
`numi config locate`
- prints the resolved config path
`numi config print`
- prints the resolved config with defaults materialized
- only supports single-config (`[jobs]`) manifests and rejects workspace manifests
## Built-In Templates
Numi currently ships these built-in templates:
- Swift:
- `language = "swift"`, `name = "swiftui-assets"`
- `language = "swift"`, `name = "l10n"`
- `language = "swift"`, `name = "files"`
- Objective-C:
- `language = "objc"`, `name = "assets"`
- `language = "objc"`, `name = "l10n"`
- `language = "objc"`, `name = "files"`
Fonts are supported in the template context and in custom-template workflows, but the first public release does not ship a dedicated built-in Swift template for fonts.
## Current Limitations
- `.xcstrings` plural and device-specific variations are skipped with warnings
- the shipped `l10n` template currently emits simple no-argument accessors even when placeholder metadata is present in template context
## Workspace Manifests
Repos with more than one `numi.toml` can orchestrate them from a repo-level `numi.toml`:
```toml
version = 1
[workspace]
members = ["AppUI", "Core"]
[workspace.defaults.jobs.assets.template.builtin]
language = "objc"
[workspace.member_overrides.Core]
jobs = ["assets"]
```
Then each member job can keep only the built-in name:
```toml
[jobs.assets.template.builtin]
name = "assets"
```
Workspace members are directory roots, not config-file paths. From the repo root, plain `numi generate` and `numi check` use that nearest workspace `numi.toml` automatically. From inside a member directory, add `--workspace` when you want the ancestor workspace instead of the local member manifest. Workspace defaults can provide `template.builtin.language`, but each job still needs to pick its own built-in `name`.
## Custom Templates
Custom templates use Minijinja:
```toml
[jobs.l10n.template]
path = "Templates/l10n.jinja"
```
Numi supports `{% include %}` from:
- the including template's local directory
- the config-root search path
If the same include path exists in both places, Numi errors instead of guessing.
Start custom-template work with:
```bash
numi dump-context --job l10n
```
The stable context contract is documented in [docs/context-schema.md](docs/context-schema.md).
## Development
Useful local commands:
```bash
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
```
crates.io release notes for the workspace live in [docs/crates-io-release.md](docs/crates-io-release.md).