# `DocShell.Generate.ExDoc`
[🔗](https://github.com/futhr/doc_shell/blob/v2.0.0/lib/doc_shell/generate/ex_doc.ex#L1)

Extracts documentation from compiled modules through the BEAM docs chunk.

This reads the same `Docs` chunk used by IEx and ExDoc, then extracts English
documentation and normalizes its metadata for JSON. Modules must be compiled
and loadable; `mix doc_shell.build` arranges this inside the host application.
Source files are not required once their BEAM documentation is available.

Each module becomes one entry:

    %{
      "id" => "MyApp.Accounts",
      "title" => "MyApp.Accounts",
      "kind" => "module",
      "ast" => [...],
      "meta" => %{
        "module" => "MyApp.Accounts",
        "language" => "elixir",
        "moduledoc" => "present",
        "members" => [...]
      }
    }

`ast` is the module documentation parsed by `DocShell.Ast`. `members` lists
every function, macro, callback, and type in the docs chunk with its kind,
name, arity, signatures, raw documentation, `doc_state`, and metadata such as
`since` or `deprecated`. Member documentation is left as Markdown text rather
than parsed, because a page usually renders a member list lazily and parsing
every member of every module up front is work most renderers throw away.

`doc_state` uses the same vocabulary as the module's `moduledoc`: `"present"`,
`"hidden"` for `@doc false`, or `"none"`. Hidden members stay in the
extraction for coverage reporting; renderers and the navigation leave them
out, as ExDoc does.

## Member metadata

The docs chunk carries whatever metadata the compiler and macros attached,
and some libraries attach a lot of it. Spark, which generates Ash code
interfaces, stores each function's full option schema under `spark_opts` —
tens of kilobytes per function that no reader sees. By default only the keys
in `default_member_metadata/0` are kept, which are the ones ExDoc itself
renders. Pass `member_metadata: :all` to keep every key, or a list of key
names to choose your own. Filtering happens before normalization, so dropped
values are never converted.

## Modules without documentation

A module compiled with `--no-docs`, or from Erlang without a chunk, is
skipped rather than treated as an error — `extract/1` simply omits it. A
module that has a chunk but fails to read is an error, tagged with the module
name.

Modules that *have* a chunk but no prose are a different case, and they are
deliberately still returned, with an empty `ast`. `meta["moduledoc"]` says
which situation each one is in:

  * `"present"` — the module has documentation
  * `"hidden"` — the author wrote `@moduledoc false`
  * `"none"` — no `@moduledoc` at all

Extraction stays complete because callers use it for more than rendering: a
documentation-coverage report needs the undocumented modules precisely
because they are undocumented, and dropping them here would make every
codebase look fully documented.

Filtering for display happens later, in
`DocShell.Presentation.StaticGenerator`, which skips empty entries by
default so `mix doc_shell.build` does not fill a navigation tree with
internal modules that opted out.

Modules are sorted by name so the artifact is stable across builds. Each
module is read independently, so extraction runs on every scheduler; results
are still returned in sorted order and the first failure in that order is
the one reported.

# `default_member_metadata`

```elixir
@spec default_member_metadata() :: [String.t()]
```

Returns the member metadata keys kept unless `:member_metadata` says otherwise.

    iex> "since" in DocShell.Generate.ExDoc.default_member_metadata()
    true

# `extract`

```elixir
@spec extract([module()], keyword()) :: {:ok, [map()]} | {:error, term()}
```

Extracts documentation for a list of compiled modules.

Modules without a documentation chunk are omitted. Modules with hidden or
absent moduledocs are retained with empty ASTs and a metadata status. The
first read failure returns `{:error, {module, reason}}`.

`:member_metadata` selects the member metadata keys to keep: a list of key
names, or `:all`. It defaults to `default_member_metadata/0`.

# `extract_module`

```elixir
@spec extract_module(module(), keyword()) :: {:ok, map() | nil} | {:error, term()}
```

Extracts one compiled module.

Returns `{:ok, nil}` when the module carries no docs chunk, which callers
treat as "nothing to document" rather than as a failure. Accepts the same
`:member_metadata` option as `extract/2`.

---

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