# `ExQuality.Config`
[🔗](https://github.com/riddler/ex_quality/blob/v0.15.0/lib/ex_quality/config.ex#L1)

Loads and merges configuration from multiple sources.

Configuration is resolved in the following order (later wins):
1. Defaults
2. Auto-detected tool availability
3. Project config file (.quality.exs, read from the project root)
4. The selected profile, if `--profile` named one
5. CLI arguments

## Example .quality.exs

    [
      # Global options
      quick: false,

      # Override auto-detection: force disable dialyzer
      dialyzer: [enabled: false],

      # Credo options (enabled: :auto by default)
      credo: [
        strict: true,
        all: false
      ],

      # Doctor options
      doctor: [
        summary_only: true
      ],

      # Named bundles, selected with `mix quality --profile loop`
      profiles: [
        loop: [stages: [:format, :compile, :credo], test: [scope: :changed]],
        gate: []
      ]
    ]

## Configuration Options

### Global Options

- `quick` - Quick mode: skip dialyzer and coverage enforcement (default: false)
- `profiles` - Named option bundles, see "Profiles" below (default: `[]`)

### Stage Options

Each stage supports:
- `enabled` - :auto (use auto-detection) | true (force enable) | false (force disable)

Stage-specific options:
- `format.check` - Check formatting and fail on drift instead of rewriting
  files (default: false). See `ExQuality.Stages.Format`
- `compile.warnings_as_errors` - Treat warnings as errors (default: true)
- `compile.force` - Recompile from scratch (default: false)
- `credo.strict` - Use strict mode (default: true)
- `credo.all` - Check all files (default: false)
- `credo.configs` - Names of the `.credo.exs` configs to run, in order
  (default: `nil`, meaning one run with no `--config-name`)
- `dependencies.check_unused` - Check for unused dependencies (default: true)
- `dependencies.audit` - Run security audit if available (default: :auto)
- `doctor.summary_only` - Show only summary (default: false)
- `docs.enabled` - false (default) | :auto (on when `:ex_doc` is installed) |
  true (forced). Off by default, unlike the other tool-backed stages, so a
  gate does not turn red on upgrade just because the project publishes docs.
  See `ExQuality.Stages.Docs`
- `doc_links.enabled` - false (default) | :auto (on when `:ex_doc` is
  installed) | true (forced). Off by default like `docs`, for the same
  reason. See `ExQuality.Stages.DocLinks`
- `gettext.source_locale` - The locale the source is written in, whose `.po`
  files are not checked (default: `"en"`)
- `gettext.exclude` - Basenames to skip (default: `["errors.po"]`)
- `gettext.extract` - Run `mix gettext.extract --merge` first, which writes to
  the repository and recompiles the project (default: false)
- `sobelow.exit` - Confidence level that blocks, when `.sobelow-conf` sets no
  `exit:` of its own (default: "medium")
- `sobelow.show_informational` - Render findings below that level as well as
  counting them (default: false)
- `test.coverage` - :auto (measure when the project's config asks for it) |
  true (always measure) | false (never measure) (default: :auto)
- `test.scope` - :all (the whole suite) | :changed (only the test files
  covering changed code) | a glob string (default: :all)
- `test.base_ref` - What `scope: :changed` is measured against (default: the
  repository's default branch)

## Profiles

A profile is a named bundle of the options above, so the fast path a project
wants its agents to use has a name its docs can point at:

    profiles: [
      loop: [stages: [:format, :compile, :credo], test: [scope: :changed]],
      gate: []
    ]

`mix quality --profile loop` merges the profile over the config file and under
the CLI, so a switch still wins over the profile that a run selected.

`stages:` is the allow-list of stage keys for the profile. Every other stage,
built-in or custom, is reported as skipped naming the profile. A profile with
no `stages:` key narrows nothing and only carries options, which is what an
empty `gate: []` is for.

An invocation with no `--profile` behaves exactly as it does without any
profiles configured. An unknown profile name fails the run: falling back to
"run everything" would turn a typo into a slow green, and falling back to the
profile's intent would turn one into a fast green over nothing.

# `apply_profile`

```elixir
@spec apply_profile(
  keyword(),
  String.t() | nil
) :: keyword()
```

Merges the named profile into a config, or returns it unchanged for `nil`.

Called by `load/1` between the config file and the CLI. `name` is a string
because it comes from a switch, and the profile keys it is matched against are
atoms because they come from a config file.

    iex> ExQuality.Config.apply_profile([credo: [strict: true]], nil)
    [credo: [strict: true]]

    iex> config = [profiles: [loop: [test: [scope: :changed]]]]
    iex> config |> ExQuality.Config.apply_profile("loop") |> Keyword.take([:profile, :test])
    [profile: :loop, test: [scope: :changed]]

# `config_path`

```elixir
@spec config_path() :: String.t() | nil
```

Returns the path of the `.quality.exs` that applies here, or `nil`.

The file belongs to the project, not to wherever the shell happened to be
when `mix quality` was run. An umbrella child with no file of its own falls
back to the umbrella root's, because the settings describe the tree.

Finding the umbrella root needs `Mix.Project.parent_umbrella_project_file/0`,
which arrived in Elixir 1.15. On 1.14 only the current project's root is
looked at.

# `config_path`

```elixir
@spec config_path(String.t() | nil, String.t() | nil) :: String.t() | nil
```

Returns the `.quality.exs` under `project_root`, or under `umbrella_root`
when the project has none of its own, or `nil` when neither has one.

Either root may be `nil`, meaning there is no such directory to look in.

# `load`

```elixir
@spec load(keyword()) :: keyword()
```

Loads configuration with auto-detection and overrides.

## Resolution order (later wins):
1. Defaults
2. Auto-detected tool availability
3. .quality.exs file
4. The profile named by `--profile`, if any
5. CLI arguments

## Examples

    # Load with CLI options
    config = ExQuality.Config.load(quick: true, skip_dialyzer: true)

    # Load with defaults only
    config = ExQuality.Config.load()

# `profile`

```elixir
@spec profile(keyword()) :: atom() | nil
```

Returns the name of the profile a loaded config was resolved with, or `nil`.

    iex> ExQuality.Config.profile(ExQuality.Config.load())
    nil

# `skip`

```elixir
@spec skip(
  keyword(),
  atom()
) :: {String.t(), ExQuality.Stage.skip_kind()} | nil
```

Returns why a stage will not run and what kind of skip that is, or `nil`
when it will run.

The kind is `:run` when the skip came from the command line - a switch or a
profile selected for this invocation - because a full `mix quality` closes
it. It is `:project` when the skip came from `.quality.exs` or from the tool
not being installed, because a fuller run cannot close that. See
`t:ExQuality.Stage.skip_kind/0`.

## Examples

    config = ExQuality.Config.load(skip_credo: true)
    ExQuality.Config.skip(config, :credo)
    #=> {"--skip-credo", :run}

    config = ExQuality.Config.load()
    ExQuality.Config.skip(config, :doctor)
    #=> {":doctor not installed", :project}

# `skip_reason`

```elixir
@spec skip_reason(
  keyword(),
  atom()
) :: String.t() | nil
```

Returns why a stage will not run, or `nil` when it will.

The reason is meant to be shown to the reader, because a stage that is
silently omitted reads as a stage that passed.

## Examples

    config = ExQuality.Config.load(skip_credo: true)
    ExQuality.Config.skip_reason(config, :credo)
    #=> "--skip-credo"

    config = ExQuality.Config.load()
    ExQuality.Config.skip_reason(config, :doctor)
    #=> ":doctor not installed"

# `stage_enabled?`

```elixir
@spec stage_enabled?(
  keyword(),
  atom()
) :: boolean()
```

Determines if a stage should run based on config.

- `enabled: :auto` → use detected availability
- `enabled: true` → force enable (will error if tool missing)
- `enabled: false` → force disable

## Examples

    config = ExQuality.Config.load()
    ExQuality.Config.stage_enabled?(config, :credo)
    #=> true (if credo is installed)

    config = ExQuality.Config.load(skip_credo: true)
    ExQuality.Config.stage_enabled?(config, :credo)
    #=> false

---

*Consult [api-reference.md](api-reference.md) for complete listing*
