# `SiteMaps`
[🔗](https://github.com/marcosgz/site_maps_ex/blob/v0.1.0/lib/site_maps.ex#L1)

Sitemap generation: streaming, parallel and incremental.

    defmodule MyApp.Sitemaps do
      use SiteMaps, otp_app: :my_app, adapter: {SiteMaps.Adapters.FileSystem, directory: "priv/sitemaps"}

      process :static
      process :posts, "posts/%{year}-%{month}/sitemap.xml",
        args: [year: :integer, month: :integer],
        defaults: [year: &MyApp.Clock.year/0, month: &MyApp.Clock.month/0]

      @impl SiteMaps
      def urls(:static, _args, _context), do: [url("/", priority: 1.0)]

      def urls(:posts, %{year: year, month: month}, _context) do
        MyApp.Posts.stream(year, month) |> Stream.map(&url("/posts/#{&1.slug}", lastmod: &1.updated_at))
      end
    end

    # config/runtime.exs
    config :my_app, MyApp.Sitemaps, url: "https://example.com/sitemap.xml.gz"

    SiteMaps.generate(MyApp.Sitemaps)
    SiteMaps.generate(MyApp.Sitemaps, only: [posts: [year: 2026, month: 3]])

Every process returns an `Enumerable` of `SiteMaps.URL` entries, consumed lazily and written to
files that rotate at the sitemap protocol limits. Processes run in parallel, and runs that only
regenerate some processes keep the other entries of the existing index.

For several sites in one app, pass a `:context` identifying the site to `generate/2` and
implement `c:config/1` to build each site's options from it. See
[multitenancy_phoenix](https://github.com/marcosgz/multitenancy_phoenix) for host→site→theme
Phoenix apps.

# `config`
*optional* 

```elixir
@callback config(context :: map()) :: keyword()
```

Returns the configuration options for `context`. See `SiteMaps.Config`.

# `external_sitemaps`
*optional* 

```elixir
@callback external_sitemaps(context :: map()) :: [
  {String.t(), DateTime.t() | Date.t() | nil} | String.t()
]
```

Sitemaps added to the index as they are, e.g. `[{"https://cdn.example.com/legacy.xml", ~D[2026-01-01]}]`.

# `load_context`
*optional* 

```elixir
@callback load_context(map()) :: map()
```

Builds the context from its serialized form (mix task and Oban arguments).

# `url_filter`
*optional* 

```elixir
@callback url_filter(SiteMaps.URL.t(), context :: map()) :: SiteMaps.URL.t() | nil
```

Drops (`nil`) or replaces every URL before it is written.

# `urls`

```elixir
@callback urls(name :: atom(), args :: map(), context :: map()) :: Enumerable.t()
```

Returns the URLs of the process `name` executed with `args`.

# `__using__`
*macro* 

Defines a sitemap module.

Configuration options (see `SiteMaps.Config`) can be given here, read from the application
environment with `:otp_app`, and returned by `c:config/1`. They are merged in that order, so
later sources win:

    use SiteMaps, otp_app: :my_app, adapter: {SiteMaps.Adapters.FileSystem, directory: "priv/sitemaps"}

    # config/runtime.exs
    config :my_app, MyApp.Sitemaps, url: "https://example.com/sitemap.xml"

`c:config/1` is only needed when options depend on the context, e.g. one sitemap per site.

# `config!`

```elixir
@spec config!(module(), map()) :: SiteMaps.Config.t()
```

Builds the validated configuration of `module` for `context`: the `use SiteMaps` options, then
the application environment (`:otp_app`), then `c:config/1` when the module defines it.

# `generate`

```elixir
@spec generate(module(), keyword()) :: {:ok, SiteMaps.Result.t()} | {:error, term()}
```

Generates the sitemaps of `module`.

## Options

  * `:context` - map passed to every callback (default `%{}`)
  * `:only` - processes to run, e.g. `[:static, posts: [year: 2026, month: 3]]`. By default every
    process runs once with its default arguments.
  * `:prune` - delete files of the regenerated processes that are no longer listed (default `false`)
  * `:max_concurrency` - overrides the configured concurrency

# `generate!`

```elixir
@spec generate!(module(), keyword()) :: SiteMaps.Result.t()
```

Same as `generate/2` but raises on errors.

# `process`
*macro* 

Declares a process.

    process :static
    process :news, "sitemap_news.xml"
    process :posts, "posts/%{year}/sitemap.xml", args: [year: :integer], defaults: [year: 2026]

The location is relative to the index directory and must interpolate every argument (`%{arg}`),
so each execution writes its own files. Without location, files are written next to the index as
`sitemap_<name>1.xml`, ... Two processes cannot share a location. Argument types are `:integer`, `:string`, `:date`, `:boolean` and
`:atom`; string values (from the CLI or job arguments) are cast. Defaults may be values or 0-arity
functions evaluated when the process is enqueued.

# `processes`

```elixir
@spec processes(module()) :: [SiteMaps.Process.t()]
```

Declared processes of `module`.

# `url`

```elixir
@spec url(String.t(), keyword()) :: SiteMaps.URL.t()
```

Builds a `SiteMaps.URL`. Paths are resolved against the site origin.

---

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