Current section
Files
Jump to
Current section
Files
README.md
# Cooper
Cooper loads [CASC](guides/casc/CASC.md) config files — a hierarchical,
extensible config language with imports, variables, string
interpolation, environment/config references, loops, and
consumer-registered resolvers and tags — into native Elixir terms.
```text
# config.casc
#@version = 1.0
@region = "eu-west"
server {
host = "0.0.0.0"
port = 8080
}
database {
*password = ${DB_PASSWORD}
host = "db.@{region}.internal"
pool_size = 10
timeout = 500ms
}
```
```elixir
iex> Cooper.load_file("config.casc", env: %{"DB_PASSWORD" => "hunter2"})
{:ok,
%{
"server" => %{"host" => "0.0.0.0", "port" => 8080},
"database" => %{
"password" => [~~REDACTED~~],
"host" => "db.eu-west.internal",
"pool_size" => 10,
"timeout" => {:duration, 500_000_000}
}
}}
```
Maps come back with string keys, durations/byte sizes as tagged
`{:duration, nanoseconds}`/`{:bytes, count}` tuples, tuples as real
Elixir tuples (never coerced to lists, CASC.md §6.11), and any
`*key`-prefixed value wrapped in `Cooper.Secret` — redacted by
`inspect/1` and `to_string/1` by default; call
`Cooper.Secret.reveal/1` to get the real value back.
## Why
Most config libraries either parse a fixed format (JSON/YAML/TOML) or
hand you a bespoke DSL with no formal grammar behind it. CASC has a
real specification ([`guides/casc/CASC.md`](guides/casc/CASC.md)), and
Cooper implements it via Ichor, a sibling grammar-compiler library,
rather than hand-rolled string parsing — so the interpreter's behavior
tracks the grammar, not the other way around.
## How it fits together
Loading a file runs one pipeline, each stage handing off to the next:
1. **Lex + parse** — `Cooper.Grammar` and `Cooper.Actions` turn CASC
source into a flat list of assignments and variable declarations,
with every literal value (numbers, durations, dates, strings, ...)
already converted to its native Elixir form.
2. **Loop expansion** — `Cooper.Loop` expands each `for` statement
(CASC.md §5.5) into the ops it generates.
3. **Import resolution** — `Cooper.Loader` recursively loads each
`import` statement (CASC.md §5.1), splicing the imported file's own
ops into the importer's list and propagating its public variables.
4. **Merge and secret-wrapping** — `Cooper.Merge` folds the flattened
ops into a tree, honoring the merge-control sigils (`~`/`+`/`-`,
CASC.md §5.7/§8), and wraps every `*key`-prefixed value in
`Cooper.Secret` so it can't leak through an accidental
`inspect`/log call.
5. **Reference resolution** — `Cooper.Resolver` resolves everything
left unresolved: `@{}` variables, `${}` environment reads, `%{}`
config references, `!{resolver:...}` dispatch, and `!Name(...)`
tagged values (CASC.md §7) — including re-wrapping a secret that
gets copied elsewhere by one of those references, so the redaction
travels with the value, not just its original declared path.
See the [tutorial](guides/TUTORIAL.md) for a full walkthrough of both
the library and the CASC syntax it parses, or the
[cheatsheet](guides/CHEATSHEET.md) for a terse API reference.
## Installation
Add `cooper` to your list of dependencies in `mix.exs`. `ichor` comes
along as its own dependency automatically — no separate line needed:
```elixir
def deps do
[
{:cooper, "~> 0.1.0"}
]
end
```
## Where to go next
- **[Tutorial](guides/TUTORIAL.md)** — loading config, secrets, error
handling, test-time env/import injection, and enough CASC syntax to
follow along.
- **[Examples](guides/EXAMPLES.md)** — layered per-environment config,
a real secrets manager, IP allowlisting, generating per-shard config
from a template, testing config-loading code without touching disk.
- **[Cheatsheet](guides/CHEATSHEET.md)** — quick reference for
`Cooper`'s public API.
- **[CASC tutorial](guides/casc/TUTORIAL.md)** and
**[CASC reference](guides/casc/CASC.md)** — everything about the
language itself, independent of the surrounding library.
## Development
```sh
mix deps.get
mix precommit
```
`mix precommit` runs everything a change needs to pass before review —
formatting, `--warnings-as-errors` compilation, Credo, Sobelow, the
test suite, and Dialyzer, in that order. See
[CONTRIBUTION.md](CONTRIBUTION.md) for how to propose changes, and
[CHANGELOG.md](CHANGELOG.md) for release history.
## License
MIT — see [LICENSE](LICENSE).