Skip to content

Commit

Permalink
Add redirects config (#1952)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel authored Sep 17, 2024
1 parent b79ed25 commit 2c1a895
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 8 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,49 @@ Similarly to the example above, if your Markdown includes Mermaid graph specific
For more details and configuration options, see the [Mermaid usage docs](https://mermaid-js.github.io/mermaid/#/usage).
## Changing documentation over time
As your project grows, your documentation may very likely change, even structurally. There are a few important things to consider in this regard:
- Links to your *extras* will break if you change or move file names.
- Links to your *modules, and mix tasks* will change if you change their name.
- Links to *functions* are actually links to modules with anchor links. If you change the function name, the link does
not break but will leave users at the top of the module's documentation.
Because these docs are static files, the behavior of a missing page will depend on where they are hosted.
In particular, [hexdocs.pm](https://hexdocs.pm) will show a 404 page.
You can improve the developer experience on everything but function names changing
by using the `redirects` configuration. For example, if you changed the module `MyApp.MyModule`
to `MyApp.My.Module` and the extra `get-started.md` to `quickstart.md`, you can
setup the following redirects:
<!-- tabs-open -->
### Elixir
For this example, we've changed the module `MyApp.MyModule` to `MyApp.My.Module`, and the extra `get-started.md` to `quickstart.md`
```elixir
redirects: %{
"MyApp.MyModule" => "MyApp.My.Module",
"get-started" => "quickstart"
}
```
### Erlang
For this example, we've changed the module `:my_module` to `:my_module2`, and the extra `get-started.md` to `quickstart.md`
```erlang
{redirects, [
{"my_module", "my_module2"},
{"get-started", "quickstart"}
]}.
```
<!-- tabs-close -->
## Contributing
The easiest way to test changes to ExDoc is to locally rebuild the app and its own documentation:
Expand Down
2 changes: 2 additions & 0 deletions lib/ex_doc/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ defmodule ExDoc.Config do
package: nil,
proglang: :elixir,
project: nil,
redirects: %{},
retriever: ExDoc.Retriever,
skip_undefined_reference_warnings_on:
&__MODULE__.skip_undefined_reference_warnings_on/1,
Expand Down Expand Up @@ -78,6 +79,7 @@ defmodule ExDoc.Config do
output: nil | Path.t(),
package: :atom | nil,
project: nil | String.t(),
redirects: %{optional(String.t()) => String.t()} | [{String.t(), String.t()}],
retriever: atom(),
skip_undefined_reference_warnings_on: (String.t() -> boolean),
skip_code_autolink_to: (String.t() -> boolean),
Expand Down
29 changes: 21 additions & 8 deletions lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ defmodule ExDoc.Formatter.HTML do
generate_search(nodes_map, config) ++
generate_not_found(nodes_map, config) ++
generate_list(nodes_map.modules, nodes_map, config) ++
generate_list(nodes_map.tasks, nodes_map, config) ++ generate_index(config)
generate_list(nodes_map.tasks, nodes_map, config) ++
generate_redirects(config, ".html")

generate_build(Enum.sort(all_files), build)
config.output |> Path.join("index.html") |> Path.relative_to_cwd()
Expand Down Expand Up @@ -187,13 +188,6 @@ defmodule ExDoc.Formatter.HTML do
File.write!(build, entries)
end

defp generate_index(config) do
index_file = "index.html"
main_file = "#{config.main}.html"
generate_redirect(index_file, config, main_file)
[index_file]
end

defp generate_not_found(nodes_map, config) do
filename = "404.html"
config = set_canonical_url(config, filename)
Expand Down Expand Up @@ -390,6 +384,25 @@ defmodule ExDoc.Formatter.HTML do
|> Enum.sort_by(fn extra -> GroupMatcher.group_index(groups, extra.group) end)
end

def generate_redirects(config, ext) do
config.redirects
|> Map.new()
|> Map.put_new("index", config.main)
|> Enum.map(fn {from, to} ->
unless is_binary(from),
do: raise("expected a string for the source of a redirect, got: #{inspect(from)}")

unless is_binary(to),
do: raise("expected a string for the destination of a redirect, got: #{inspect(to)}")

source = from <> ext
destination = to <> ext
generate_redirect(source, config, destination)

source
end)
end

defp disambiguate_id(extra, discriminator) do
Map.put(extra, :id, "#{extra.id}-#{discriminator}")
end
Expand Down
50 changes: 50 additions & 0 deletions test/ex_doc/formatter/html_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,56 @@ defmodule ExDoc.Formatter.HTMLTest do
end
end

describe "generates redirects" do
test "redirects are generated based on the configuration", %{tmp_dir: tmp_dir} = context do
generate_docs(
doc_config(context,
extras: ["test/fixtures/LICENSE"],
redirects: %{
"/old-license" => "license"
}
)
)

assert File.read!(tmp_dir <> "/html/old-license.html") == """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Elixir v1.0.1 — Documentation</title>
<meta http-equiv="refresh" content="0; url=license.html">
<meta name="generator" content="ExDoc v0.34.2">
</head>
<body></body>
</html>
"""
end

test "redirects accept a list", %{tmp_dir: tmp_dir} = context do
generate_docs(
doc_config(context,
extras: ["test/fixtures/LICENSE"],
redirects: [
{"/old-license", "license"}
]
)
)

assert File.read!(tmp_dir <> "/html/old-license.html") == """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Elixir v1.0.1 — Documentation</title>
<meta http-equiv="refresh" content="0; url=license.html">
<meta name="generator" content="ExDoc v0.34.2">
</head>
<body></body>
</html>
"""
end
end

describe "generates extras" do
@extras [
"test/fixtures/LICENSE",
Expand Down

0 comments on commit 2c1a895

Please sign in to comment.