<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).
