Skip to content

Commit c6088a1

Browse files
committed
improvement: add redirects config, and documentation explaining it
1 parent b79ed25 commit c6088a1

File tree

4 files changed

+72
-7
lines changed

4 files changed

+72
-7
lines changed

Diff for: README.md

+29
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,35 @@ Similarly to the example above, if your Markdown includes Mermaid graph specific
448448
449449
For more details and configuration options, see the [Mermaid usage docs](https://mermaid-js.github.io/mermaid/#/usage).
450450
451+
## Changing documentation over time
452+
453+
As your project grows, your documentation may very likely change, even structurally. There are a few important things to consider in this regard:
454+
455+
- Links to your *extras* will break if you change or move file names.
456+
- Links to your *modules, and mix tasks* will change if you change your code.
457+
- Links to *functions* are actually links to modules with anchor links. If you change the function name, the link does
458+
not break but will leave users at the top of the module's documentation.
459+
460+
Because these docs are static files, when a user gets to a page that is not found, they will see a generic 404 page.
461+
They will not be redirected to your packages home page. This can potentially be jarring for users.
462+
463+
With this in mind, it is a good idea to preserve links to your old documentation. We do this with the `redirects` configuration.
464+
This can solve for everything but the function names changing.
465+
466+
For this example, we've changed the module `MyApp.MyModule` to `MyApp.My.Module`, and the extra `get-started.md` to `quickstart.md`
467+
468+
```elixir
469+
defp docs do
470+
[
471+
...,
472+
redirects: %{
473+
MyApp.MyModule => MyApp.My.Module,
474+
"get-started" => "quickstart"
475+
}
476+
]
477+
end
478+
```
479+
451480
## Contributing
452481
453482
The easiest way to test changes to ExDoc is to locally rebuild the app and its own documentation:

Diff for: lib/ex_doc/config.ex

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ defmodule ExDoc.Config do
3939
package: nil,
4040
proglang: :elixir,
4141
project: nil,
42+
redirects: %{},
4243
retriever: ExDoc.Retriever,
4344
skip_undefined_reference_warnings_on:
4445
&__MODULE__.skip_undefined_reference_warnings_on/1,
@@ -78,6 +79,7 @@ defmodule ExDoc.Config do
7879
output: nil | Path.t(),
7980
package: :atom | nil,
8081
project: nil | String.t(),
82+
redirects: %{optional(String.t()) => String.t()},
8183
retriever: atom(),
8284
skip_undefined_reference_warnings_on: (String.t() -> boolean),
8385
skip_code_autolink_to: (String.t() -> boolean),

Diff for: lib/ex_doc/formatter/html.ex

+31-7
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ defmodule ExDoc.Formatter.HTML do
4747
generate_search(nodes_map, config) ++
4848
generate_not_found(nodes_map, config) ++
4949
generate_list(nodes_map.modules, nodes_map, config) ++
50-
generate_list(nodes_map.tasks, nodes_map, config) ++ generate_index(config)
50+
generate_list(nodes_map.tasks, nodes_map, config) ++
51+
generate_redirects(config, ".html")
5152

5253
generate_build(Enum.sort(all_files), build)
5354
config.output |> Path.join("index.html") |> Path.relative_to_cwd()
@@ -187,12 +188,6 @@ defmodule ExDoc.Formatter.HTML do
187188
File.write!(build, entries)
188189
end
189190

190-
defp generate_index(config) do
191-
index_file = "index.html"
192-
main_file = "#{config.main}.html"
193-
generate_redirect(index_file, config, main_file)
194-
[index_file]
195-
end
196191

197192
defp generate_not_found(nodes_map, config) do
198193
filename = "404.html"
@@ -390,6 +385,35 @@ defmodule ExDoc.Formatter.HTML do
390385
|> Enum.sort_by(fn extra -> GroupMatcher.group_index(groups, extra.group) end)
391386
end
392387

388+
def generate_redirects(config, ext) do
389+
config.redirects
390+
|> Map.put_new("index", config.main)
391+
|> Enum.map(fn {from, to} ->
392+
source = stringify_redirect_item(from) <> ext
393+
destination = stringify_redirect_item(to) <> ext
394+
generate_redirect(source, config, destination)
395+
396+
source
397+
end)
398+
end
399+
400+
defp stringify_redirect_item(item) when is_binary(item) do
401+
item
402+
end
403+
404+
defp stringify_redirect_item(item) when is_atom(item) do
405+
inspected = inspect(item)
406+
407+
case to_string(item) do
408+
"Elixir." <> ^inspected -> inspected
409+
other -> other
410+
end
411+
end
412+
413+
defp stringify_redirect_item(item) do
414+
raise ArgumentError, "Redirect source and destination must be a string or an atom, got: #{inspect(item)}"
415+
end
416+
393417
defp disambiguate_id(extra, discriminator) do
394418
Map.put(extra, :id, "#{extra.id}-#{discriminator}")
395419
end

Diff for: mix.exs

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ defmodule ExDoc.Mixfile do
8787
"Cheatsheet.cheatmd",
8888
"CHANGELOG.md"
8989
] ++ test_dev_examples(Mix.env()),
90+
redirects: redirects(Mix.env()),
9091
source_ref: "v#{@version}",
9192
source_url: @source_url,
9293
groups_for_modules: [
@@ -107,6 +108,15 @@ defmodule ExDoc.Mixfile do
107108
defp test_dev_examples(:dev), do: Path.wildcard("test/examples/*")
108109
defp test_dev_examples(_), do: []
109110

111+
defp redirects(:dev) do
112+
%{
113+
"old-admonition" => "admonition",
114+
Exdoc.OldMarkdown => ExDoc.Markdown
115+
}
116+
end
117+
118+
defp redirects(_), do: %{}
119+
110120
defp clean_test_fixtures(_args) do
111121
File.rm_rf("test/tmp")
112122
end

0 commit comments

Comments
 (0)