Type-safe code from your
Apple
resources.
A blazingly fast Rust CLI that turns asset catalogs, localization files, and file lists into generated accessors. Swift and Objective-C built in — or write your own Minijinja templates to target any language.
- Written in
- Rust · 2024
- License
- MIT
- Runtime
- Zero
Three commands. Generated code you can trust.
- Step 01 1Scaffold$ numi init
Drops a starter numi.toml into your project root, preconfigured for common asset layouts.
- Step 02 2Generate$ numi generate
Parses your resources, renders templates, and writes output files — only when they changed.
- Step 03 3Verify$ numi check
Exits non-zero if committed files are stale. Wire it into CI and never merge drift again.
Built for repos that take generated code seriously.
Deterministic outputs
Byte-stable generation. Unchanged inputs never rewrite files — commit the output and trust it.
CI-safe with numi check
Exit 0 when fresh, exit 2 when stale. Drop it into any workflow and fail the build on drift.
Swift + Obj-C built-ins
Ship-ready templates for SwiftUI assets, localization, and file catalogs in both languages.
Minijinja templates
Bring your own templates with a stable context schema. Full access to jobs, inputs, and metadata.
Workspace orchestration
One manifest at the repo root, or many across modules. numi --workspace runs them coherently.
Cached incremental parses
Asset catalogs and strings files are parsed once, cached on disk, and skipped when untouched.
One manifest in. Type-safe code out.
Declare jobs in numi.toml. Point at inputs, pick a built-in template, and commit the generated file. That's the whole loop.
version = 1
[defaults]
access_level = "internal"
[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 = "xcstrings"
path = "Resources/Localization"
[jobs.l10n.template.builtin]
language = "swift"
name = "l10n"// Generated by numi. DO NOT EDIT.
import SwiftUI
public enum Asset {
public enum Image {
public static let appIcon = ImageResource(name: "AppIcon", bundle: .module)
public static let heroBanner = ImageResource(name: "HeroBanner", bundle: .module)
public static let emptyState = ImageResource(name: "EmptyState", bundle: .module)
}
public enum Color {
public static let brandPrimary = ColorResource(name: "BrandPrimary", bundle: .module)
public static let surface = ColorResource(name: "Surface", bundle: .module)
public static let onSurface = ColorResource(name: "OnSurface", bundle: .module)
}
}// Generated by numi. DO NOT EDIT.
import Foundation
public enum L10n {
public enum Onboarding {
/// Welcome to Numi
public static let title = String(localized: "onboarding.title", bundle: .module)
/// Get started in seconds.
public static let subtitle = String(localized: "onboarding.subtitle", bundle: .module)
}
public enum Errors {
/// Something went wrong. Please try again.
public static let generic = String(localized: "errors.generic", bundle: .module)
public static func notFound(_ p0: String) -> String {
String(localized: "errors.not_found \(p0)", bundle: .module)
}
}
}// Generated by numi. DO NOT EDIT.
import Foundation
public enum Files {
public enum Fixtures {
public static let sampleJSON = Bundle.module.url(
forResource: "sample",
withExtension: "json"
)!
public static let seedDB = Bundle.module.url(
forResource: "seed",
withExtension: "sqlite"
)!
}
}Ship-ready. Or bring your own.
Every built-in is a Minijinja template backed by a stable context schema. Fork one, or write your own — the same data is available either way.
Strongly-typed accessors for image and color resources, ready to drop into SwiftUI views.
Generates localization enums with argument-preserving helpers for parameterized strings.
Emits Bundle.url helpers for arbitrary resource files — fonts, fixtures, seed data, anything.
Gate drift at the pull request.
numi check exits 0 when committed files are up to date and 2 when they're stale. Drop it into any workflow and your CI will fail the build before reviewers do.
- Deterministic output — no timestamps, no ordering noise.
- Workspace mode checks every manifest in one pass.
- No-op writes: unchanged files never get rewritten.
name: verify
on: [push, pull_request]
jobs:
numi-check:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: cargo install numi
- name: Verify generated files are up to date
run: numi check --workspaceGenerate once. Trust forever.
numi is MIT licensed, on Homebrew and crates.io. One command to install, one to try.