README.md

# Jetons

A compile-time design token library for Elixir.

Jetons generates fast, type-safe accessor functions from design token JSON files at compile time. Instead of runtime map lookups, tokens become individual function clauses that use pattern matching for maximum performance.

## Features

- **Compile-time generation** — Tokens become pattern-matched functions, not data
- **Multi-theme support** — Light/dark/custom themes with zero runtime overhead
- **DTCG format** — Follows the [Design Tokens Community Group](https://tr.designtokens.org/format/) specification
- **Token references** — `{path.to.token}` and JSON Pointer (`#/path/to/token`) resolution with circular-reference detection
- **Structural inheritance** — `$extends` merges parent groups into children with deep override semantics
- **Type-based transforms** — Apply functions to tokens by their `$type` (e.g. downcase all colors, convert px to rem)
- **Type inheritance** — `$type` propagates from parent groups to descendant tokens, with override at any level
- **Fast lookups** — O(1) access via pattern matching, not map traversal

## Installation

Add `jetons` to your dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:jetons, "~> 0.1.1"},
    {:jason, "~> 1.4"}  # Required for JSON parsing
  ]
end
```

## Quick Start

### Single Theme

```elixir
defmodule MyApp.Tokens do
  use Jetons,
    main: File.read!("tokens.json") |> Jason.decode!()
end

MyApp.Tokens.token("colors.primary")       # => "#1B66B3"
MyApp.Tokens.token!("colors.primary")      # => "#1B66B3" (raises if not found)
MyApp.Tokens.list_colors()                 # => [{"colors.primary", "#1B66B3"}, ...]
```

### Multiple Themes

```elixir
defmodule MyApp.Tokens do
  use Jetons,
    light: File.read!("tokens/light.json") |> Jason.decode!(),
    dark: File.read!("tokens/dark.json") |> Jason.decode!()
end

MyApp.Tokens.token("colors.background")            # Uses first theme (light)
MyApp.Tokens.token("colors.background", :dark)     # Explicit theme
MyApp.Tokens.themes()                               # => [:light, :dark]
MyApp.Tokens.list_colors(:dark)                     # List colors for dark theme
```

## Token Format

Jetons expects tokens in [DTCG format](https://tr.designtokens.org/format/): nested maps where leaf nodes have `"$value"` keys. Keys starting with `$` (`$type`, `$description`, etc.) are metadata and filtered from the output.

```json
{
  "colors": {
    "$type": "color",
    "brand": {
      "primary": { "$value": "#1B66B3" }
    },
    "grey": {
      "500": { "$value": "#676767" }
    }
  },
  "spacing": {
    "small": { "$value": "8px" }
  }
}
```

Nested keys become dot-notation paths:
- `"colors.brand.primary"` → `"#1B66B3"`
- `"colors.grey.500"` → `"#676767"`
- `"spacing.small"` → `"8px"`

## Token References

Tokens can reference other tokens using curly-brace syntax or JSON Pointers. References are resolved transitively, and circular references raise at compile time.

```json
{
  "colors": {
    "palette": { "red": { "$value": "#FF0000" } },
    "brand":   { "$value": "{colors.palette.red}" },
    "button":  { "$value": "#/colors/brand" }
  }
}
```

Both `colors.brand` and `colors.button` resolve to `"#FF0000"`.

## Structural Inheritance (`$extends`)

Groups can inherit tokens from a parent group using `$extends`. Child properties override inherited ones, and `$extends` chains are resolved transitively.

```json
{
  "colors": {
    "base": {
      "$type": "color",
      "primary": { "$value": "#FF0000" }
    },
    "brand": {
      "$extends": "colors.base",
      "secondary": { "$value": "#00FF00" }
    }
  }
}
```

`colors.brand` inherits `$type` and `primary` from `colors.base`, and adds its own `secondary`. Cross-category extends (e.g. `"brand.palette"` extending `"colors.base"`) is also supported.

## Type-Based Transforms

The `$type` field propagates from parent groups to descendant tokens (a child can override with its own `$type`). You can then apply transform functions that target specific types:

```elixir
defmodule MyApp.Tokens do
  use Jetons,
    transforms: [
      {"color", fn v -> String.downcase(v) end},
      {"dimension", fn v -> String.replace(v, "px", "rem") end}
    ],
    main: %{
      "colors" => %{
        "$type" => "color",
        "primary" => %{"$value" => "#FF0000"}
      },
      "spacing" => %{
        "$type" => "dimension",
        "small" => %{"$value" => "8px"}
      }
    }
end

MyApp.Tokens.token("colors.primary")  # => "#ff0000"
MyApp.Tokens.token("spacing.small")   # => "8rem"
```

Transforms run after reference resolution, so referenced values are transformed correctly. Tokens whose type doesn't match any transform pass through unchanged.

## Generated API

### Single Theme

| Function | Description |
|---|---|
| `token/1` | Get token value by path (returns `nil` if not found) |
| `token!/1` | Get token value by path (raises `KeyError` if not found) |
| `list_<category>/0` | List all tokens in a category as `{path, value}` tuples |
| `group_by_path/2` | Group tokens by path depth into a nested map |
| `get_groups/1` | Get sorted list of top-level groups for a category |
| `get_in_group/2` | Get all tokens in a specific group as `{key, value}` tuples |

### Multi-Theme

All single-theme functions plus:

| Function | Description |
|---|---|
| `token/2` | Get token value with explicit theme parameter |
| `token!/2` | Get token value with theme (raises if not found) |
| `list_<category>/1` | List category tokens for a specific theme |
| `themes/0` | List all available theme names |
| `group_by_path/3` | Group tokens by path depth for a specific theme |
| `get_groups/2` | Get top-level groups for a category and theme |
| `get_in_group/3` | Get tokens in a group for a specific theme |

### Parser Functions

These are available on the `Jetons` module directly for runtime use:

| Function | Description |
|---|---|
| `from_config/1` | Flatten a DTCG config map into `{path, value}` tuples |
| `from_config/2` | Same, with options (e.g. `transforms:`) |
| `parse_tokens/1` | Parse a JSON string or file path into token tuples |
| `extract_categories/1` | Extract sorted unique category names from token tuples |
| `extract_type_map/1` | Build a `%{path => type}` map with `$type` inheritance |
| `resolve_extends/1` | Resolve `$extends` inheritance in a config map |
| `resolve_token_refs/1` | Resolve `{ref}` and `#/pointer` references in token tuples |
| `flatten_tokens/2` | Flatten a nested map under a given prefix |
| `deep_merge/2` | Recursively merge two maps (right side wins) |

## Performance

Jetons generates individual function clauses for each token:

```elixir
def token("colors.primary"), do: "#1B66B3"
def token("colors.secondary"), do: "#FCE531"
# ... one clause per token
def token(_), do: nil
```

This means:
- **Token access is O(1)** — Direct function call with pattern matching
- **No runtime overhead** — All processing happens at compile time
- **Memory efficient** — Functions are code, not data

## License

MIT

## Links

- [DTCG Specification](https://tr.designtokens.org/format/)
- [HexDocs](https://hexdocs.pm/jetons)