Skip to content

Commit 816216f

Browse files
committed
Generate llms.txt instead of index.md
1 parent df248dd commit 816216f

6 files changed

Lines changed: 124 additions & 60 deletions

File tree

lib/ex_doc/formatter/markdown.ex

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ defmodule ExDoc.Formatter.MARKDOWN do
1212
|> Enum.split_with(&(&1.type != :task))
1313

1414
all_files =
15-
[generate_nav(config, modules, tasks, extras)] ++
15+
generate_nav(config, modules, tasks, extras) ++
16+
generate_api_reference(config, modules, tasks) ++
1617
generate_extras(extras, config) ++
1718
generate_list(config, modules) ++
1819
generate_list(config, tasks)
1920

20-
entrypoint = config.output |> Path.join("index.md") |> Path.relative_to_cwd()
21+
entrypoint = config.output |> Path.join("llms.txt") |> Path.relative_to_cwd()
2122
%{entrypoint: entrypoint, build: List.flatten(all_files)}
2223
end
2324

@@ -33,12 +34,29 @@ defmodule ExDoc.Formatter.MARKDOWN do
3334
extras = group_by_group(extras)
3435

3536
content =
36-
Templates.nav_template(config, modules, mix_tasks, extras)
37+
Templates.nav_template(config, modules, mix_tasks, extras, "Table of Contents")
3738
|> normalize_output()
3839

39-
filename = "index.md"
40-
File.write("#{config.output}/#{filename}", content)
41-
filename
40+
filename = "llms.txt"
41+
File.write(Path.join(config.output, filename), content)
42+
[filename]
43+
end
44+
45+
defp generate_api_reference(%{api_reference: false}, _modules, _tasks) do
46+
[]
47+
end
48+
49+
defp generate_api_reference(config, modules, tasks) do
50+
modules = group_by_group(modules)
51+
mix_tasks = group_by_group(tasks)
52+
53+
content =
54+
Templates.nav_template(config, modules, mix_tasks, [], "API Reference")
55+
|> normalize_output()
56+
57+
filename = "api-reference.md"
58+
File.write(Path.join(config.output, filename), content)
59+
[filename]
4260
end
4361

4462
defp group_by_group(nodes) do
@@ -50,7 +68,7 @@ defmodule ExDoc.Formatter.MARKDOWN do
5068
defp generate_extras(extras, config) do
5169
for %ExDoc.ExtraNode{id: id, source_doc: source_doc} <- extras do
5270
filename = "#{id}.md"
53-
output = "#{config.output}/#{filename}"
71+
output = Path.join(config.output, filename)
5472
File.write!(output, source_doc)
5573
filename
5674
end
@@ -70,7 +88,7 @@ defmodule ExDoc.Formatter.MARKDOWN do
7088
|> normalize_output()
7189

7290
filename = "#{module_node.id}.md"
73-
File.write("#{config.output}/#{filename}", content)
91+
File.write(Path.join(config.output, filename), content)
7492
filename
7593
end
7694
end

lib/ex_doc/formatter/markdown/templates.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ defmodule ExDoc.Formatter.MARKDOWN.Templates do
2727
:def,
2828
:module_template,
2929
Path.expand("templates/module_template.eex", __DIR__),
30-
[:_config, :module],
30+
[:config, :module],
3131
trim: true
3232
)
3333

3434
EEx.function_from_file(
3535
:def,
3636
:nav_template,
3737
Path.expand("templates/nav_template.eex", __DIR__),
38-
[:config, :modules, :mix_tasks, :extras],
38+
[:config, :modules, :mix_tasks, :extras, :title],
3939
trim: true
4040
)
4141

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@
88
<%= node_doc(module) %>
99
<%= for group <- module.docs_groups, node <- group.docs do %>
1010
<%= detail_template(node, module) %>
11+
<% end %><%= if config.api_reference do %>
12+
13+
---
14+
15+
*Consult [api-reference.md](api-reference.md) for complete listing*
1116
<% end %>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# <%= config.project %> v<%= config.version %> - Table of Contents
1+
# <%= config.project %> v<%= config.version %> - <%= title %>
22
<%= nav_group_template config.extra_section, extras %>
33
<%= nav_group_template "Modules", modules %>
44
<%= nav_group_template "Mix Tasks", mix_tasks %>

test/ex_doc/formatter/erlang_test.exs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ defmodule ExDoc.Formatter.ErlangTest do
3232
generate(c)
3333
html = c.tmp_dir |> Path.join("doc/foo.html") |> File.read!()
3434

35-
# Check moduledoc is rendered in HTML
36-
assert html =~ "<p>foo module.</p>"
37-
38-
# Check function doc is rendered in HTML
39-
assert html =~ "<p>f/0 function.</p>"
40-
4135
# Check specs are rendered with proper links in HTML
4236
assert html =~
4337
~s|-spec</span> foo(<a href="#t:t/0">t</a>()) -> <a href="#t:t/0">t</a>().|
@@ -52,9 +46,6 @@ defmodule ExDoc.Formatter.ErlangTest do
5246
assert html =~
5347
~s|-type</span> t2() :: #rec{k1 :: <a href="https://www.erlang.org/doc/apps/stdlib/uri_string.html#t:uri_string/0">uri_string:uri_string</a>(), k2 :: <a href="https://www.erlang.org/doc/apps/stdlib/uri_string.html#t:uri_string/0">uri_string:uri_string</a>() \| undefined}.|
5448

55-
# Check type doc is rendered in HTML
56-
assert html =~ "<p>t/0 type.</p>"
57-
5849
# EEP 48 uses erlang+html format, so no markdown should be generated
5950
refute File.exists?(Path.join(c.tmp_dir, "doc/foo.md"))
6051
end

test/ex_doc/formatter/markdown_test.exs

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ defmodule ExDoc.Formatter.MarkdownTest do
2525

2626
test "generates Markdown files in the default directory", %{tmp_dir: tmp_dir} = context do
2727
generate(config(context))
28-
assert File.regular?(tmp_dir <> "/index.md")
28+
assert File.regular?(tmp_dir <> "/llms.txt")
29+
assert File.regular?(tmp_dir <> "/api-reference.md")
2930

3031
assert File.regular?(tmp_dir <> "/CompiledWithDocs.md")
3132
assert File.regular?(tmp_dir <> "/CompiledWithDocs.Nested.md")
@@ -42,31 +43,34 @@ defmodule ExDoc.Formatter.MarkdownTest do
4243
content = File.read!(tmp_dir <> "/CompiledWithDocs.md")
4344

4445
# Header
45-
assert content =~ ~r{^# `CompiledWithDocs`}m
46-
assert content =~ ~r{\*example_module_tag\*}
46+
assert content =~ "# `CompiledWithDocs`"
47+
assert content =~ "*example_module_tag*"
4748

4849
# Moduledoc
49-
assert content =~ ~r{moduledoc}
50+
assert content =~ "moduledoc"
5051

5152
# Function header
52-
assert content =~ ~r{^# `example`$}m
53+
assert content =~ "# `example`"
5354

5455
# Function documentation
55-
assert content =~ ~r{Some example}
56+
assert content =~ "Some example"
5657

5758
# Deprecated notice
58-
assert content =~ ~r{> This function is deprecated\. Use something else instead\.}
59+
assert content =~ "> This function is deprecated. Use something else instead."
5960

6061
# Struct
61-
assert content =~ ~r{^# `__struct__`$}m
62-
assert content =~ ~r{Some struct}
62+
assert content =~ "# `__struct__`"
63+
assert content =~ "Some struct"
6364

6465
# Since annotation
65-
assert content =~ ~r{^# `example_1`$}m
66-
assert content =~ ~r{\*since 1\.3\.0\*}
66+
assert content =~ "# `example_1`"
67+
assert content =~ "*since 1.3.0*"
6768

6869
# Macro annotation
69-
assert content =~ ~r{\*macro\*}
70+
assert content =~ "*macro*"
71+
72+
# Footer should be present when api_reference defaults to true
73+
assert content =~ "Consult [api-reference.md]"
7074
end
7175

7276
test "renders types and specs properly", %{tmp_dir: tmp_dir} = context do
@@ -131,34 +135,48 @@ defmodule ExDoc.Formatter.MarkdownTest do
131135
config = config(context, output: custom_output)
132136
generate(config)
133137

134-
assert File.regular?(custom_output <> "/index.md")
138+
assert File.regular?(custom_output <> "/llms.txt")
135139
assert File.regular?(custom_output <> "/CompiledWithDocs.md")
136-
refute File.exists?(tmp_dir <> "/index.md")
140+
refute File.exists?(tmp_dir <> "/llms.txt")
137141
end
138142

139143
test "handles custom project name and version", %{tmp_dir: tmp_dir} = context do
140144
config = config(context, project: "MyProject", version: "2.0.0")
141145
generate(config)
142146

143-
content = File.read!(tmp_dir <> "/index.md")
147+
content = File.read!(tmp_dir <> "/llms.txt")
144148
assert content =~ "# MyProject v2.0.0 - Table of Contents"
145149
end
150+
end
146151

147-
test "processes source_url configuration", %{tmp_dir: tmp_dir} = context do
148-
config = config(context, source_url: "https://github.com/example/project")
149-
generate(config)
152+
test "stores generated content in .build.markdown", %{tmp_dir: tmp_dir} = context do
153+
config = config(context, extras: ["test/fixtures/README.md"])
154+
generate(config)
155+
156+
# Verify necessary files in .build.markdown
157+
content = File.read!(tmp_dir <> "/.build.markdown")
158+
assert content =~ "readme.md"
159+
assert content =~ "llms.txt"
160+
assert content =~ "CompiledWithDocs.md"
161+
assert content =~ "Mix.Tasks.TaskWithDocs.md"
162+
163+
# Verify the files listed in .build.markdown actually exist
164+
files =
165+
content
166+
|> String.split("\n", trim: true)
167+
|> Enum.map(&Path.join(tmp_dir, &1))
150168

151-
assert File.regular?(tmp_dir <> "/CompiledWithDocs.md")
152-
assert File.regular?(tmp_dir <> "/index.md")
169+
for file <- files do
170+
assert File.exists?(file)
153171
end
154172
end
155173

156-
describe "index file" do
174+
describe "llms.txt" do
157175
test "generates index", %{tmp_dir: tmp_dir} = context do
158176
config = config(context, extras: ["test/fixtures/README.md"], extra_section: "Guides")
159177
generate(config)
160178

161-
content = File.read!(tmp_dir <> "/index.md")
179+
content = File.read!(tmp_dir <> "/llms.txt")
162180

163181
assert content =~ "# Elixir v1.0.1 - Table of Contents"
164182

@@ -193,30 +211,62 @@ defmodule ExDoc.Formatter.MarkdownTest do
193211
test "when no extras exist", %{tmp_dir: tmp_dir} = context do
194212
config = config(context)
195213
generate(config)
196-
content = File.read!(tmp_dir <> "/index.md")
197-
refute content =~ ~r/## (Pages|Guides)/
214+
content = File.read!(tmp_dir <> "/llms.txt")
215+
refute content =~ "## Pages"
216+
refute content =~ "## Guides"
198217
end
199218
end
200219

201-
test "stores generated content in .build.markdown", %{tmp_dir: tmp_dir} = context do
202-
config = config(context, extras: ["test/fixtures/README.md"])
203-
generate(config)
220+
describe "api_reference" do
221+
test "when false, does not generate api-reference.md", %{tmp_dir: tmp_dir} = context do
222+
config = config(context, api_reference: false)
223+
generate(config)
224+
refute File.exists?(tmp_dir <> "/api-reference.md")
204225

205-
# Verify necessary files in .build.markdown
206-
content = File.read!(tmp_dir <> "/.build.markdown")
207-
assert content =~ ~r(^readme\.md$)m
208-
assert content =~ ~r(^index\.md$)m
209-
assert content =~ ~r(^CompiledWithDocs\.md$)m
210-
assert content =~ ~r(^Mix\.Tasks\.TaskWithDocs\.md$)m
226+
# Modules should not have the footer
227+
content = File.read!(tmp_dir <> "/CompiledWithDocs.md")
228+
refute content =~ "Consult [api-reference.md]"
229+
end
211230

212-
# Verify the files listed in .build.markdown actually exist
213-
files =
214-
content
215-
|> String.split("\n", trim: true)
216-
|> Enum.map(&Path.join(tmp_dir, &1))
231+
test "when true, generates api-reference.md", %{tmp_dir: tmp_dir} = context do
232+
config =
233+
config(context,
234+
extras: ["test/fixtures/README.md"],
235+
extra_section: "Guides",
236+
api_reference: true
237+
)
217238

218-
for file <- files do
219-
assert File.exists?(file)
239+
generate(config)
240+
api_content = File.read!(tmp_dir <> "/api-reference.md")
241+
242+
# Should have API Reference title
243+
assert api_content =~ "# Elixir v1.0.1 - API Reference"
244+
245+
# Should NOT include extras section
246+
refute api_content =~ "## Guides"
247+
refute api_content =~ "README"
248+
249+
# Should include modules section
250+
assert api_content =~ """
251+
## Modules
252+
253+
- [CallbacksNoDocs](CallbacksNoDocs.md)
254+
- [Common.Nesting.Prefix.B.A](Common.Nesting.Prefix.B.A.md): moduledoc
255+
"""
256+
257+
# Should include mix tasks
258+
assert api_content =~ """
259+
## Mix Tasks
260+
261+
- [mix task_with_docs](Mix.Tasks.TaskWithDocs.md): Very useful task
262+
"""
263+
264+
# Modules should have the footer
265+
module_content = File.read!(tmp_dir <> "/CompiledWithDocs.md")
266+
assert module_content =~ "---"
267+
268+
assert module_content =~
269+
"*Consult [api-reference.md](api-reference.md) for complete listing*"
220270
end
221271
end
222272
end

0 commit comments

Comments
 (0)