# `DocShell.Ast`
[🔗](https://github.com/futhr/doc_shell/blob/v2.0.0/lib/doc_shell/ast.ex#L1)

Parses Markdown into the recursive, JSON-safe node shape renderers consume.

DocShell preserves source structure without choosing a renderer. The AST can
contain raw HTML tags and unsafe URL schemes; consumers must validate tags,
attributes, and URLs and escape text for their rendering context. Every source —
module documentation, guides, notebooks — is parsed here into a tree
of plain maps:

    %{
      "tag" => "p",
      "attrs" => %{},
      "content" => ["Some ", %{"tag" => "code", ...}],
      "meta" => %{}
    }

Text nodes are bare strings; everything else is a four-key map. That is the
entire grammar. A renderer writes one recursive function over it and decides
for itself whether an `h2` is a heading component or an anchor target.

All four keys are always present, even when empty, so consumers can pattern
match without `Map.get/3` guards. Keys and attribute names are strings rather
than atoms because the tree is destined for JSON and atoms would be created
from untrusted document content on the way back in.

## Errors

Earmark is forgiving and parses most malformed Markdown into something. When
it does report problems, `from_markdown/1` returns
`{:error, %{partial_ast: nodes, messages: messages}}` — the nodes it managed
to parse alongside the diagnostics — rather than discarding the work. Callers
in `DocShell.Generate` treat that as a build failure, because silently
publishing a partially-parsed document is worse than a failed build.

## Examples

    iex> DocShell.Ast.from_markdown("# Title")
    {:ok, [%{"tag" => "h1", "attrs" => %{}, "content" => ["Title"], "meta" => %{}}]}

# `ast_node`

```elixir
@type ast_node() :: String.t() | element()
```

One node in the documentation tree.

Either a bare string of text, or a map with `tag`, `attrs`, `content`, and
`meta`, where `content` holds child nodes of the same shape.

# `element`

```elixir
@type element() :: %{required(String.t()) =&gt; String.t() | map() | [ast_node()]}
```

An element with required string keys `tag`, `attrs`, `content`, and `meta`.

# `from_markdown`

```elixir
@spec from_markdown(String.t()) :: {:ok, [ast_node()]} | {:error, term()}
```

Parses a Markdown string into renderer-neutral nodes.

See the module documentation for the node shape and the partial-parse error
contract.

# `valid?`

```elixir
@spec valid?(term()) :: boolean()
```

Checks a list of recursive AST nodes without coercing malformed content.

---

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