Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/ex_doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ defmodule ExDoc do
* `:title` - The title of the extra page. If not provided, the title will be inferred from the extra name.
* `:url` - The external url to link to from the sidebar.

Bare filenames such as `[Intro](intro.md)` use the legacy filename-based lookup against the flattened output.
Links with a directory component, such as `[Intro](guides/intro.md)`, `[Intro](../guides/intro.md)`, or
`[Intro](/guides/intro.md)`, are resolved against the extra source path (or project root for `/`).

### Customizing search data

It is possible to fully customize the way a given extra is indexed, both in autocomplete and in search.
Expand Down
24 changes: 22 additions & 2 deletions lib/ex_doc/autolink.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ defmodule ExDoc.Autolink do
:language,
file: "nofile",
apps: [],
extras: [],
extras: %{},
deps: [],
ext: ".html",
current_kfa: nil,
Expand Down Expand Up @@ -217,7 +217,7 @@ defmodule ExDoc.Autolink do
with %{scheme: nil, host: nil, path: path} = uri <- URI.parse(link),
true <- is_binary(path) and path != "" and not (path =~ ref_regex()),
true <- Path.extname(path) in @builtin_ext do
if file = config.extras[Path.basename(path)] do
if file = resolve_extra_target(path, config) do
append_fragment(file <> config.ext, uri.fragment)
else
maybe_warn(config, nil, nil, %{file_path: path, original_text: link})
Expand All @@ -228,6 +228,26 @@ defmodule ExDoc.Autolink do
end
end

defp resolve_extra_target(path, config) do
filename = Path.basename(path)

case path do
"/" <> absolute_path ->
config.extras[absolute_path]

^filename ->
config.extras[filename]

relative_path ->
path =
relative_path
|> Path.expand(Path.dirname(config.file))
|> Path.relative_to_cwd()

config.extras[path]
end
end

defp maybe_remove_link(nil, :custom_link) do
:remove_link
end
Expand Down
5 changes: 4 additions & 1 deletion lib/ex_doc/formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,10 @@ defmodule ExDoc.Formatter do

%ExDoc.ExtraNode{source_path: source_path, id: id}, acc when is_binary(source_path) ->
base = Path.basename(source_path)
Map.put(acc, base, id)

acc
|> Map.put(source_path, id)
|> Map.put(base, id)

_extra, acc ->
acc
Expand Down
41 changes: 41 additions & 0 deletions test/ex_doc/language/elixir_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ defmodule ExDoc.Language.ElixirTest do

test "extras" do
opts = [
file: "guides/current.md",
extras: %{
"guide/Foo Bar.md" => "foo-bar",
"Foo Bar.md" => "foo-bar",
"Bar Baz.livemd" => "bar-baz",
"Bar Baz.cheatmd" => "bar-baz"
Expand All @@ -286,6 +288,45 @@ defmodule ExDoc.Language.ElixirTest do
assert autolink_doc("[Foo](#baz)", opts) == ~s|<a href="#baz">Foo</a>|
end

test "path-qualified extra links use the extra source path" do
opts = [
file: "guides/current.md",
extras: %{"guides/Foo Bar.md" => "foo-bar", "Foo Bar.md" => "legacy-foo"}
]

assert autolink_doc("[Foo](./Foo Bar.md)", opts) ==
~s|<a href="foo-bar.html">Foo</a>|

assert autolink_doc("[Foo](../guides/Foo Bar.md)", opts) ==
~s|<a href="foo-bar.html">Foo</a>|

assert autolink_doc("[Foo](/guides/Foo Bar.md)", opts) ==
~s|<a href="foo-bar.html">Foo</a>|
end

test "bare filename extra links use legacy lookup" do
opts = [
file: "guides/current.md",
extras: %{"guides/Foo Bar.md" => "relative-foo", "Foo Bar.md" => "legacy-foo"}
]

assert autolink_doc("[Foo](Foo Bar.md)", opts) ==
~s|<a href="legacy-foo.html">Foo</a>|
end

test "extras with bad directories warn instead of silently matching by basename" do
opts = [
warnings: :send,
file: "guides/current.md",
extras: %{"guide/Foo Bar.md" => "foo-bar", "Foo Bar.md" => "foo-bar"}
]

assert warn(fn ->
assert autolink_doc("[Foo](/bad_dir/Foo Bar.md)", opts) ==
~s|<a href="/bad_dir/Foo Bar.md">Foo</a>|
end) =~ ~s|documentation references file "/bad_dir/Foo Bar.md" but it does not exist|
end

test "special case links" do
assert autolink_doc("`//2`") ==
~s|<a href="https://hexdocs.pm/elixir/Kernel.html#//2"><code class="inline">//2</code></a>|
Expand Down
12 changes: 11 additions & 1 deletion test/ex_doc/language/erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,16 @@ defmodule ExDoc.Language.ErlangTest do
extras: %{"Foo Bar.md" => "foo-bar", "Bar Baz.livemd" => "bar-baz"}
]

@relative_opts [
file: "guides/current.md",
extras: %{
"guide/Foo Bar.md" => "foo-bar",
"guide/Bar Baz.livemd" => "bar-baz",
"Foo Bar.md" => "foo-bar",
"Bar Baz.livemd" => "bar-baz"
}
]

test "extras", c do
assert autolink_doc("[Foo](Foo Bar.md)", c, @opts) ==
~s|<a href="foo-bar.html">Foo</a>|
Expand All @@ -690,7 +700,7 @@ defmodule ExDoc.Language.ErlangTest do
end

test "extras relative", c do
assert autolink_doc("[Foo](../guide/Foo Bar.md)", c, @opts) ==
assert autolink_doc("[Foo](../guide/Foo Bar.md)", c, @relative_opts) ==
~s|<a href="foo-bar.html">Foo</a>|
end
end
Expand Down
Loading