Skip to content

Commit ea173a6

Browse files
committed
refactor: update Markdown formatting for module and function headers
1 parent 495025f commit ea173a6

File tree

8 files changed

+201
-335
lines changed

8 files changed

+201
-335
lines changed

lib/ex_doc/formatter/markdown.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ defmodule ExDoc.Formatter.Markdown do
157157
modules_info =
158158
nodes_map.modules
159159
|> Enum.map(fn module_node ->
160-
"- **#{module_node.title}** (#{module_node.id}.md): #{module_node.doc |> ExDoc.DocAST.synopsis() |> extract_plain_text()}"
160+
"- [#{module_node.title}](#{module_node.id}.md): #{module_node.doc |> ExDoc.DocAST.synopsis() |> extract_plain_text()}"
161161
end)
162162
|> Enum.join("\n")
163163

@@ -166,7 +166,7 @@ defmodule ExDoc.Formatter.Markdown do
166166
tasks_list =
167167
nodes_map.tasks
168168
|> Enum.map(fn task_node ->
169-
"- **#{task_node.title}** (#{task_node.id}.md): #{task_node.doc |> ExDoc.DocAST.synopsis() |> extract_plain_text()}"
169+
"- [#{task_node.title}](#{task_node.id}.md): #{task_node.doc |> ExDoc.DocAST.synopsis() |> extract_plain_text()}"
170170
end)
171171
|> Enum.join("\n")
172172

@@ -184,7 +184,7 @@ defmodule ExDoc.Formatter.Markdown do
184184
_ -> []
185185
end)
186186
|> Enum.map(fn extra ->
187-
"- **#{extra.title}** (#{extra.id}.md): #{extra.title}"
187+
"- [#{extra.title}](#{extra.id}.md)"
188188
end)
189189
|> Enum.join("\n")
190190

lib/ex_doc/formatter/markdown/templates.ex

Lines changed: 13 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,20 @@ defmodule ExDoc.Formatter.Markdown.Templates do
44
require EEx
55

66
import ExDoc.Utils,
7-
only: [before_closing_body_tag: 2, h: 1, text_to_id: 1]
7+
only: [before_closing_body_tag: 2, h: 1]
88

99
@doc """
1010
Generate content from the module template for a given `node`.
1111
"""
1212
def module_page(config, module_node) do
13-
summary =
13+
docs =
1414
for group <- module_node.docs_groups do
1515
{group.title, group.docs}
1616
end
1717

18-
module_template(config, module_node, summary)
18+
module_template(config, module_node, docs)
1919
end
2020

21-
@doc """
22-
Returns the formatted title for the module page.
23-
"""
24-
def module_type(%{type: :task} = _node), do: ""
25-
def module_type(%{type: :module} = _node), do: ""
26-
def module_type(%{type: type} = _node), do: "(#{type})"
27-
2821
@doc """
2922
Formats the attribute type used to define the spec of the given `node`.
3023
"""
@@ -33,161 +26,19 @@ defmodule ExDoc.Formatter.Markdown.Templates do
3326
end
3427

3528
@doc """
36-
Generates an ID for a static file.
29+
Returns the original markdown documentation from source_doc.
3730
"""
38-
def static_file_to_id(static_file) do
39-
static_file |> Path.basename() |> text_to_id()
40-
end
41-
42-
def node_doc(%{doc: doc}) when is_list(doc) do
43-
# Handle DocAST by converting to Markdown.
44-
ExDoc.DocAST.to_markdown(doc)
45-
end
46-
47-
def node_doc(%{doc: doc}) when is_binary(doc), do: doc
4831
def node_doc(%{source_doc: %{"en" => source}}) when is_binary(source), do: source
49-
def node_doc(%{rendered_doc: source}) when is_binary(source), do: source
50-
51-
def node_doc(%{source_doc: %{"en" => source}}) when is_list(source) do
52-
# Handle DocAST by converting to Markdown.
53-
# For Erlang docs, we can extract the text content.
54-
extract_text_from_doc_ast(source)
55-
end
56-
5732
def node_doc(_), do: nil
5833

59-
@doc """
60-
Get synopsis for a node, handling both DocAST and string documentation.
61-
"""
62-
def node_synopsis(%{doc: doc}) when is_list(doc) do
63-
# For DocAST, extract synopsis DocAST and convert to Markdown.
64-
case extract_synopsis_ast(doc) do
65-
nil -> nil
66-
synopsis_ast -> ExDoc.DocAST.to_markdown(synopsis_ast)
67-
end
68-
end
69-
70-
def node_synopsis(%{doc: doc}) when is_binary(doc) do
71-
synopsis(doc)
72-
end
73-
74-
def node_synopsis(%{source_doc: %{"en" => source}}) when is_binary(source) do
75-
synopsis(source)
76-
end
77-
78-
def node_synopsis(%{rendered_doc: source}) when is_binary(source) do
79-
synopsis(source)
80-
end
81-
82-
def node_synopsis(%{source_doc: %{"en" => source}}) when is_list(source) do
83-
case extract_synopsis_ast(source) do
84-
nil -> nil
85-
synopsis_ast -> synopsis_ast |> ExDoc.DocAST.to_markdown() |> extract_plain_text()
86-
end
87-
end
88-
89-
def node_synopsis(_), do: nil
90-
91-
# Extract synopsis as DocAST (similar to ExDoc.DocAST.synopsis but returns an AST instead of an HTML string).
92-
defp extract_synopsis_ast({:p, _attrs, [_ | _] = inner, _meta}) do
93-
inner =
94-
case Enum.split(inner, -1) do
95-
{pre, [post]} when is_binary(post) ->
96-
pre ++ [String.trim_trailing(post, ":")]
97-
98-
_ ->
99-
inner
100-
end
101-
102-
{:p, [], ExDoc.DocAST.remove_ids(inner), %{}}
103-
end
104-
105-
defp extract_synopsis_ast([head | _]), do: extract_synopsis_ast(head)
106-
defp extract_synopsis_ast(_other), do: nil
107-
108-
defp extract_text_from_doc_ast(ast) when is_list(ast) do
109-
Enum.map_join(ast, "\n\n", &extract_text_from_doc_ast/1)
110-
end
111-
112-
defp extract_text_from_doc_ast({_tag, _attrs, content}) when is_list(content) do
113-
Enum.map_join(content, "", &extract_text_from_doc_ast/1)
114-
end
115-
116-
defp extract_text_from_doc_ast({_tag, _attrs, content, _meta}) when is_list(content) do
117-
Enum.map_join(content, "", &extract_text_from_doc_ast/1)
118-
end
119-
120-
defp extract_text_from_doc_ast(text) when is_binary(text), do: text
121-
defp extract_text_from_doc_ast(_), do: ""
122-
123-
# Extract plain text from markdown (similar to the one in markdown.ex but here for templates)
124-
defp extract_plain_text(markdown) when is_binary(markdown) do
125-
markdown
126-
|> String.replace(~r/<[^>]*>/, "")
127-
|> String.replace(~r/\s+/, " ")
128-
|> String.trim()
129-
|> case do
130-
"" ->
131-
nil
132-
133-
text ->
134-
text
135-
|> String.slice(0, 150)
136-
|> then(fn s -> if String.length(s) == 150, do: s <> "...", else: s end)
137-
end
138-
end
139-
140-
defp extract_plain_text(_), do: nil
141-
142-
@doc """
143-
Gets the first paragraph of the documentation of a node. It strips
144-
surrounding white-spaces and trailing `:`.
145-
146-
If `doc` is `nil`, it returns `nil`.
147-
"""
148-
@spec synopsis(String.t()) :: String.t()
149-
@spec synopsis(nil) :: nil
150-
def synopsis(doc) when is_binary(doc) do
151-
case :binary.split(doc, "\n\n") do
152-
[left, _] -> (String.trim_trailing(left) |> String.trim_trailing(":")) <> "\n\n"
153-
[all] -> all
154-
end
155-
end
156-
157-
def synopsis(_), do: nil
158-
159-
defp rewrite_headings(content) when is_binary(content) do
160-
~r/^(\#{1,6})\s+(.*)/m
161-
|> Regex.scan(content)
162-
|> Enum.reduce(content, fn [match, level, title], content ->
163-
replacement = rewrite_heading(level, title)
164-
String.replace(content, match, replacement, global: false)
165-
end)
166-
end
167-
168-
defp rewrite_headings(_), do: nil
169-
170-
defp rewrite_heading("#", title), do: do_rewrite_heading("#####", title)
171-
defp rewrite_heading(_, title), do: do_rewrite_heading("######", title)
172-
173-
defp do_rewrite_heading(level, title) do
174-
"""
175-
#{level} #{title}
176-
"""
177-
end
178-
179-
defp enc(binary), do: URI.encode(binary) |> String.replace("/", "-")
180-
18134
@doc """
18235
Creates a chapter which contains all the details about an individual module.
183-
184-
This chapter can include the following sections: *functions*, *types*, *callbacks*.
18536
"""
18637
EEx.function_from_file(
18738
:def,
18839
:module_template,
18940
Path.expand("templates/module_template.eex", __DIR__),
190-
[:config, :module, :summary],
41+
[:config, :module, :docs],
19142
trim: true
19243
)
19344

@@ -218,24 +69,12 @@ defmodule ExDoc.Formatter.Markdown.Templates do
21869
trim: true
21970
)
22071

221-
# EEx.function_from_file(
222-
# :defp,
223-
# :toc_item_template,
224-
# Path.expand("templates/toc_item_template.eex", __DIR__),
225-
# [:nodes],
226-
# trim: true
227-
# )
228-
229-
# def media_type(_arg), do: nil
230-
231-
templates = [
232-
detail_template: [:node, :module],
233-
summary_template: [:name, :nodes]
234-
]
235-
236-
Enum.each(templates, fn {name, args} ->
237-
filename = Path.expand("templates/#{name}.eex", __DIR__)
238-
@doc false
239-
EEx.function_from_file(:def, name, filename, args, trim: true)
240-
end)
72+
@doc false
73+
EEx.function_from_file(
74+
:def,
75+
:detail_template,
76+
Path.expand("templates/detail_template.eex", __DIR__),
77+
[:node, :module],
78+
trim: true
79+
)
24180
end
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
<a id="<%= enc node.id %>"></a>
2-
#### `<%=h node.signature %>` <%= if node.source_url do %>[🔗](<%= node.source_url %>)<% end %> <%= for annotation <- node.annotations do %>(<%= annotation %>) <% end %>
1+
# `<%= node.name %>`
2+
<%= if node.source_url do %>[🔗](<%= node.source_url %>)<% end %>
33

4+
<%= for annotation <- node.annotations do %>*<%= annotation %>* <% end %>
45
<%= if deprecated = node.deprecated do %>
56
> This <%= node.type %> is deprecated. <%= h(deprecated) %>.
6-
77
<% end %>
88
<%= if node.specs != [] do %>
99
<%= for spec <- node.specs do %>
@@ -12,6 +12,4 @@
1212
```
1313
<% end %>
1414
<% end %>
15-
16-
<%= rewrite_headings(node_doc(node)) %>
17-
15+
<%= node_doc(node) %>
Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,14 @@
1-
# <%= module.title %> <%= module_type(module) %> (<%= config.project %> v<%= config.version %>)
2-
3-
<%= for annotation <- module.annotations do %>*(<%= annotation %>)* <% end %>
1+
# `<%=h module.title %>`
2+
<%= if module.source_url do %>[🔗](<%= module.source_url %>)<% end %>
43

4+
<%= for annotation <- module.annotations do %>*<%= annotation %>* <% end %>
55
<%= if deprecated = module.deprecated do %>
66
> This <%= module.type %> is deprecated. <%=h deprecated %>.
7-
87
<% end %>
9-
<%= if doc = node_doc(module) do %>
10-
<%= doc %>
11-
<% end %>
12-
13-
<%= if summary != [] do %>
14-
## Table of Contents
15-
<%= for {name, nodes} <- summary, do: summary_template(name, nodes) %>
16-
<% end %>
17-
18-
## Contents
19-
20-
<%= for {name, nodes} <- summary, _key = text_to_id(name) do %>
21-
### <%=h to_string(name) %>
22-
8+
<%= node_doc(module) %>
9+
<%= for {_name, nodes} <- docs do %>
2310
<%= for node <- nodes do %>
2411
<%= detail_template(node, module) %>
2512
<% end %>
26-
27-
<% end %>
28-
29-
---
30-
31-
<%= if module.source_url do %>
32-
[<%= String.capitalize(to_string(module.type)) %> Source Code](<%= module.source_url %>)
3313
<% end %>
34-
3514
<%= before_closing_body_tag(config, :markdown) %>

lib/ex_doc/formatter/markdown/templates/summary_template.eex

Lines changed: 0 additions & 15 deletions
This file was deleted.

test/ex_doc/doc_ast_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,18 +334,21 @@ defmodule ExDoc.DocASTTest do
334334

335335
describe "highlight" do
336336
test "with default class" do
337+
# Four spaces
337338
assert highlight("""
338339
mix run --no-halt path/to/file.exs
339340
""") =~
340341
~r{<pre><code class=\"makeup elixir\" translate="no">.*}
341342

343+
# Code block without language
342344
assert highlight("""
343345
```
344346
mix run --no-halt path/to/file.exs</code></pre>
345347
```
346348
""") =~
347349
~r{<pre><code class=\"makeup elixir\" translate="no">.*}
348350

351+
# Pre IAL
349352
assert highlight("""
350353
```
351354
mix run --no-halt path/to/file.exs</code></pre>
@@ -354,20 +357,23 @@ defmodule ExDoc.DocASTTest do
354357
""") =~
355358
~r{<pre class="wrap"><code class=\"makeup elixir\" translate="no">.*}
356359

360+
# Code with language
357361
assert highlight("""
358362
```html
359363
<foo />
360364
```
361365
""") =~
362366
~r{<pre><code class=\"makeup html\" translate="no">.*}
363367

368+
# Code with shell detection
364369
assert highlight("""
365370
```
366371
$ hello
367372
```
368373
""") =~
369374
~r{<pre><code class=\"makeup shell\" translate="no"><span class="gp unselectable">\$.*}
370375

376+
# Nested in another element
371377
assert highlight("""
372378
> ```elixir
373379
> hello

0 commit comments

Comments
 (0)