Skip to content

Commit 1c45645

Browse files
committed
Reduce the places we pass markdown globally and extend Erlang coverage
1 parent 8958323 commit 1c45645

16 files changed

Lines changed: 247 additions & 246 deletions

File tree

lib/ex_doc.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,10 +523,6 @@ defmodule ExDoc do
523523
retriever = Keyword.get(options, :retriever, ExDoc.Retriever)
524524
extras_input = Keyword.get(options, :extras, [])
525525

526-
if processor = options[:markdown_processor] do
527-
ExDoc.Markdown.put_markdown_processor(processor)
528-
end
529-
530526
# Build configs independently (build both upfront for validation)
531527
retriever_config = ExDoc.Config.build(options)
532528
formatter_config = ExDoc.Formatter.Config.build(project, version, options)

lib/ex_doc/cli.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ defmodule ExDoc.CLI do
228228
The file must either have ".exs" or ".config" extension.
229229
230230
The file with the ".exs" extension must be an Elixir script that returns
231-
a keyword list with the same options declares in `ExDoc.generate/3`.
231+
a keyword list with the same options declares in `ExDoc.generate/4`.
232232
Here is an example:
233233
234234
[

lib/ex_doc/config.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ defmodule ExDoc.Config do
1818
groups_for_extras: [],
1919
source_url_pattern: &__MODULE__.source_url_pattern/2,
2020
nest_modules_by_prefix: [],
21-
proglang: :elixir
21+
proglang: :elixir,
22+
markdown_processor: {ExDoc.Markdown.Earmark, []}
2223

2324
@type t :: %__MODULE__{
2425
filter_modules: (module, map -> boolean),
@@ -30,7 +31,8 @@ defmodule ExDoc.Config do
3031
groups_for_extras: [{binary(), term()}],
3132
source_url_pattern: (String.t(), integer() -> String.t() | nil),
3233
nest_modules_by_prefix: [String.t()],
33-
proglang: :elixir | :erlang
34+
proglang: :elixir | :erlang,
35+
markdown_processor: {module(), keyword()} | module()
3436
}
3537

3638
def build(options) do
@@ -80,8 +82,9 @@ defmodule ExDoc.Config do
8082

8183
retriever_options =
8284
Keyword.take(options, [
83-
:source_ref,
84-
:annotations_for_docs
85+
:annotations_for_docs,
86+
:markdown_processor,
87+
:source_ref
8588
])
8689

8790
struct!(preconfig, retriever_options)

lib/ex_doc/extras.ex

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,10 @@ defmodule ExDoc.Extras do
5454
"""
5555
def build(extras_input, config) do
5656
groups = config.groups_for_extras
57-
source_url_pattern = config.source_url_pattern
5857

5958
extras =
6059
extras_input
61-
|> Enum.map(&build_extra(&1, groups, source_url_pattern))
60+
|> Enum.map(&build_extra(&1, groups, config))
6261

6362
ids_count = Enum.reduce(extras, %{}, &Map.update(&2, &1.id, 1, fn c -> c + 1 end))
6463

@@ -74,15 +73,15 @@ defmodule ExDoc.Extras do
7473
Map.put(extra, :id, "#{extra.id}-#{discriminator}")
7574
end
7675

77-
defp build_extra(input, groups, source_url_pattern) when is_binary(input) do
78-
build_extra({input, %{}}, groups, source_url_pattern)
76+
defp build_extra(input, groups, config) when is_binary(input) do
77+
build_extra({input, %{}}, groups, config)
7978
end
8079

81-
defp build_extra({input, opts}, groups, source_url_pattern) when is_list(opts) do
82-
build_extra({input, Map.new(opts)}, groups, source_url_pattern)
80+
defp build_extra({input, opts}, groups, config) when is_list(opts) do
81+
build_extra({input, Map.new(opts)}, groups, config)
8382
end
8483

85-
defp build_extra({input, %{url: _} = input_options}, groups, _source_url_pattern) do
84+
defp build_extra({input, %{url: _} = input_options}, groups, _config) do
8685
input = to_string(input)
8786
title = validate_extra_string!(input_options, :title) || input
8887
url = validate_extra_string!(input_options, :url)
@@ -96,15 +95,15 @@ defmodule ExDoc.Extras do
9695
}
9796
end
9897

99-
defp build_extra({input, input_options}, groups, source_url_pattern) do
98+
defp build_extra({input, input_options}, groups, config) do
10099
input = to_string(input)
101100

102101
id =
103102
validate_extra_string!(input_options, :filename) ||
104103
input |> filename_to_title() |> Utils.text_to_id()
105104

106105
source_file = validate_extra_string!(input_options, :source) || input
107-
opts = [file: source_file, line: 1]
106+
opts = [file: source_file, line: 1, markdown_processor: config.markdown_processor]
108107

109108
{extension, source, ast} =
110109
case extension_name(input) do
@@ -139,7 +138,7 @@ defmodule ExDoc.Extras do
139138

140139
group = Config.match_extra(groups, input)
141140
source_path = source_file |> Path.relative_to(File.cwd!()) |> String.replace_leading("./", "")
142-
source_url = source_url_pattern.(source_path, 1)
141+
source_url = config.source_url_pattern.(source_path, 1)
143142
search_data = validate_search_data!(input_options[:search_data])
144143

145144
%ExDoc.Extras.Page{

lib/ex_doc/formatter/html/search_data.ex

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,20 @@ defmodule ExDoc.Formatter.HTML.SearchData do
8181
[intro | headers_sections] =
8282
Regex.split(~r/(?<!#)###? (?<header>\b.+)/, string, include_captures: true)
8383

84-
{headers, sections} =
84+
sections =
8585
headers_sections
8686
|> Enum.chunk_every(2)
87-
|> Enum.map(fn [header, section] -> {header, section} end)
88-
|> Enum.unzip()
89-
90-
# Now convert the headers into a single markdown document
91-
header_tags =
92-
headers
93-
|> Enum.join("\n\n")
94-
|> ExDoc.Markdown.to_ast()
87+
|> Enum.map(fn [header, section] ->
88+
# Hardcoded markdown processor because we use it only for header matching
89+
opts = [markdown_processor: ExDoc.Markdown.Earmark]
90+
[{tag, attrs, content, meta}] = ExDoc.Markdown.to_ast(header, opts)
91+
{tag, [section: section] ++ attrs, content, meta}
92+
end)
9593
|> ExDoc.DocAST.add_ids_to_headers([:h2, :h3], prefix)
96-
97-
sections =
98-
Enum.zip_with(header_tags, sections, fn {_, attrs, inner, _}, section ->
99-
{ExDoc.DocAST.text(inner), Keyword.fetch!(attrs, :id), clean_markdown(section)}
94+
|> Enum.map(fn {_, attrs, inner, _} ->
95+
id = Keyword.fetch!(attrs, :id)
96+
section = Keyword.fetch!(attrs, :section)
97+
{ExDoc.DocAST.text(inner), id, clean_markdown(section)}
10098
end)
10199

102100
{clean_markdown(intro), sections}

lib/ex_doc/language/erlang.ex

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,17 @@ defmodule ExDoc.Language.Erlang do
173173
## We try to parse the equiv in order to link to the target
174174
with {:ok, toks, _} <- :erl_scan.string(:unicode.characters_to_list(equiv <> ".")),
175175
{:ok, [{:call, _, {:atom, _, func}, args}]} <- :erl_parse.parse_exprs(toks) do
176-
"Equivalent to [`#{equiv}`](`#{prefix}#{func}/#{length(args)}`)."
176+
equivalent_to(
177+
{:a, [href: "`#{prefix}#{func}/#{length(args)}`"],
178+
[{:code, [class: "inline"], [equiv], %{}}], %{}}
179+
)
177180
else
178181
{:ok, [{:op, _, :/, {:atom, _, _}, {:integer, _, _}}]} ->
179-
"Equivalent to `#{prefix}#{equiv}`."
182+
equivalent_to({:code, [class: "inline"], ["#{prefix}#{equiv}"], %{}})
180183

181184
_ ->
182-
"Equivalent to `#{equiv}`."
185+
equivalent_to({:code, [class: "inline"], [equiv], %{}})
183186
end
184-
|> ExDoc.DocAST.parse!("text/markdown")
185187

186188
equiv ->
187189
ExDoc.warn("invalid equiv #{inspect(equiv)}",
@@ -194,6 +196,10 @@ defmodule ExDoc.Language.Erlang do
194196
end
195197
end
196198

199+
defp equivalent_to(node) do
200+
[{:p, [], ["Equivalent to ", node, "."], %{}}]
201+
end
202+
197203
@impl true
198204
def autolink_doc(ast, %Autolink{} = config) do
199205
true = config.language == __MODULE__

lib/ex_doc/markdown.ex

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,59 +23,32 @@ defmodule ExDoc.Markdown do
2323
"""
2424
@callback available?() :: boolean()
2525

26-
@markdown_processors [
27-
ExDoc.Markdown.Earmark
28-
]
29-
30-
@markdown_processor_key :markdown_processor
31-
3226
@doc """
3327
Converts the given markdown document to HTML AST.
34-
"""
35-
def to_ast(text, opts \\ []) when is_binary(text) do
36-
{processor, options} = get_markdown_processor()
37-
processor.to_ast(text, options |> Keyword.merge(opts))
38-
end
3928
40-
@doc """
41-
Gets the current markdown processor set globally.
42-
"""
43-
def get_markdown_processor do
44-
case Application.fetch_env(:ex_doc, @markdown_processor_key) do
45-
{:ok, {processor, options}} ->
46-
{processor, options}
29+
## Options
4730
48-
:error ->
49-
processor = find_markdown_processor() || raise_no_markdown_processor()
50-
put_markdown_processor({processor, []})
51-
{processor, []}
52-
end
53-
end
31+
* `:markdown_processor` - The markdown processor to use,
32+
either as a module or a `{module, keyword}` tuple
5433
55-
@doc """
56-
Changes the markdown processor globally.
34+
All other options are passed through to the markdown processor.
5735
"""
58-
def put_markdown_processor(processor) when is_atom(processor) do
59-
put_markdown_processor({processor, []})
60-
end
36+
def to_ast(text, opts \\ []) when is_binary(text) do
37+
{processor_pair, options} = Keyword.pop!(opts, :markdown_processor)
6138

62-
def put_markdown_processor({processor, options}) do
63-
Application.put_env(:ex_doc, @markdown_processor_key, {processor, options})
64-
end
39+
{processor, options} =
40+
case processor_pair do
41+
{processor, processor_options} when is_atom(processor) and is_list(processor_options) ->
42+
{processor, Keyword.merge(processor_options, options)}
6543

66-
defp find_markdown_processor do
67-
Enum.find(@markdown_processors, fn module ->
68-
Code.ensure_loaded?(module) && module.available?()
69-
end)
70-
end
44+
processor when is_atom(processor) and processor != nil ->
45+
{processor, options}
7146

72-
defp raise_no_markdown_processor do
73-
raise """
74-
Could not find a markdown processor to be used by ex_doc.
75-
You can either:
47+
_ ->
48+
raise ArgumentError,
49+
":markdown_processor must be either `Mod` or `{Mod, options}`, got: #{inspect(processor_pair)}"
50+
end
7651

77-
* Add {:earmark, ">= 0.0.0"} to your mix.exs deps
78-
to use an Elixir-based markdown processor
79-
"""
52+
processor.to_ast(text, options)
8053
end
8154
end

lib/ex_doc/retriever.ex

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,14 @@ defmodule ExDoc.Retriever do
130130
}
131131

132132
{doc_line, doc_file, format, source_doc, doc_ast, metadata} =
133-
get_module_docs(module_data, source)
133+
get_module_docs(module_data, source, config)
134134

135135
group_for_doc = config.group_for_doc
136136
annotations_for_docs = config.annotations_for_docs
137137

138-
{docs, nodes_groups} = get_docs(module_data, source, group_for_doc, annotations_for_docs)
138+
{docs, nodes_groups} =
139+
get_docs(module_data, source, group_for_doc, annotations_for_docs, config)
140+
139141
docs = ExDoc.Utils.natural_sort_by(docs, &"#{&1.name}/#{&1.arity}")
140142

141143
moduledoc_groups = Map.get(metadata, :groups, [])
@@ -144,7 +146,8 @@ defmodule ExDoc.Retriever do
144146
get_docs_groups(
145147
moduledoc_groups ++ config.docs_groups ++ module_data.default_groups,
146148
nodes_groups,
147-
docs
149+
docs,
150+
config
148151
)
149152

150153
metadata = Map.put(metadata, :kind, module_data.type)
@@ -188,29 +191,29 @@ defmodule ExDoc.Retriever do
188191

189192
# Helpers
190193

191-
defp get_module_docs(module_data, source) do
194+
defp get_module_docs(module_data, source, config) do
192195
{:docs_v1, anno, _, format, moduledoc, metadata, _} = module_data.docs
193196
doc_file = anno_file(anno, source)
194197
doc_line = anno_line(anno)
195-
options = [file: doc_file, line: doc_line + 1]
198+
options = [file: doc_file, line: doc_line + 1, markdown_processor: config.markdown_processor]
196199
{doc_line, doc_file, format, moduledoc, doc_ast(format, moduledoc, options), metadata}
197200
end
198201

199-
defp get_docs(module_data, source, group_for_doc, annotations_for_docs) do
202+
defp get_docs(module_data, source, group_for_doc, annotations_for_docs, config) do
200203
{:docs_v1, _, _, _, _, _, docs} = module_data.docs
201204

202205
{nodes, groups} =
203206
for doc <- docs,
204207
doc_data = module_data.language.doc_data(doc, module_data) do
205208
{_node, _group} =
206-
get_doc(doc, doc_data, module_data, source, group_for_doc, annotations_for_docs)
209+
get_doc(doc, doc_data, module_data, source, group_for_doc, annotations_for_docs, config)
207210
end
208211
|> Enum.unzip()
209212

210213
{filter_defaults(nodes), groups}
211214
end
212215

213-
defp get_doc(doc, doc_data, module_data, source, group_for_doc, annotations_for_docs) do
216+
defp get_doc(doc, doc_data, module_data, source, group_for_doc, annotations_for_docs, config) do
214217
{:docs_v1, _, _, content_type, _, module_metadata, _} = module_data.docs
215218
{{type, name, arity}, anno, _signature, source_doc, metadata} = doc
216219
doc_file = anno_file(anno, source)
@@ -231,7 +234,11 @@ defmodule ExDoc.Retriever do
231234
defaults = get_defaults(name, arity, Map.get(metadata, :defaults, 0))
232235

233236
doc_ast =
234-
doc_ast(content_type, source_doc, file: doc_file, line: doc_line + 1) ||
237+
doc_ast(content_type, source_doc,
238+
file: doc_file,
239+
line: doc_line + 1,
240+
markdown_processor: config.markdown_processor
241+
) ||
235242
doc_data.doc_fallback.()
236243

237244
group = normalize_group(group_for_doc.(metadata) || doc_data.default_group)
@@ -276,7 +283,7 @@ defmodule ExDoc.Retriever do
276283
end)
277284
end
278285

279-
defp get_docs_groups(module_groups, nodes_groups, doc_nodes) do
286+
defp get_docs_groups(module_groups, nodes_groups, doc_nodes, config) do
280287
module_groups = Enum.map(module_groups, &normalize_group/1)
281288

282289
nodes_groups_descriptions = Map.new(nodes_groups, &{&1.title, &1.description})
@@ -299,15 +306,15 @@ defmodule ExDoc.Retriever do
299306
{[], seen}
300307

301308
child_nodes ->
302-
group = finalize_group(group, child_nodes, nodes_groups_descriptions)
309+
group = finalize_group(group, child_nodes, nodes_groups_descriptions, config)
303310
{[group], seen}
304311
end
305312
end)
306313

307314
docs_groups
308315
end
309316

310-
defp finalize_group(group, doc_nodes, description_fallbacks) do
317+
defp finalize_group(group, doc_nodes, description_fallbacks, config) do
311318
description =
312319
case group.description do
313320
nil -> Map.get(description_fallbacks, group.title)
@@ -320,7 +327,11 @@ defmodule ExDoc.Retriever do
320327
nil
321328

322329
text ->
323-
doc_ast = doc_ast("text/markdown", %{"en" => text}, [])
330+
doc_ast =
331+
doc_ast("text/markdown", %{"en" => text},
332+
markdown_processor: config.markdown_processor
333+
)
334+
324335
sub_id = ExDoc.Utils.text_to_id(group.title)
325336
normalize_doc_ast(doc_ast, "group-#{sub_id}-")
326337
end

0 commit comments

Comments
 (0)