Packages
ex_doc
0.8.3
0.40.3
0.40.2
0.40.1
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.3
0.37.2
0.37.1
0.37.0
0.37.0-rc.2
0.37.0-rc.1
0.37.0-rc.0
0.36.1
0.36.0
0.35.1
0.35.0
0.34.2
0.34.1
0.34.0
0.33.0
0.32.2
0.32.1
0.32.0
0.31.2
0.31.1
0.31.0
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.6
0.28.5
0.28.4
0.28.3
0.28.2
0.28.1
0.28.0
0.27.3
0.27.2
0.27.1
0.27.0
0.26.0
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.6
0.22.5
0.22.4
0.22.2
0.22.1
0.22.0
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.19.0-rc
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
retired
0.17.1
0.17.0
retired
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
ExDoc is a documentation generation tool for Elixir
Current section
Files
Jump to
Current section
Files
lib/ex_doc/formatter/html.ex
defmodule ExDoc.Formatter.HTML do
@moduledoc """
Provide HTML-formatted documentation
"""
alias ExDoc.Formatter.HTML.Templates
alias ExDoc.Formatter.HTML.Autolink
# default values
@main "overview"
@doc """
Generate HTML documentation for the given modules
"""
@spec run(list, %ExDoc.Config{}) :: String.t
def run(module_nodes, config) when is_map(config) do
config = normalize_config(config)
output = Path.expand(config.output)
File.rm_rf! output
:ok = File.mkdir_p output
generate_assets(output, config)
all = Autolink.all(module_nodes)
modules = filter_list(:modules, all)
exceptions = filter_list(:exceptions, all)
protocols = filter_list(:protocols, all)
has_readme = config.readme && generate_readme(output, module_nodes, config, modules, exceptions, protocols)
generate_index(output, config)
generate_overview(modules, exceptions, protocols, output, config, has_readme)
generate_sidebar_items(modules, exceptions, protocols, output)
generate_list(modules, all, output, config, has_readme)
generate_list(exceptions, all, output, config, has_readme)
generate_list(protocols, all, output, config, has_readme)
Path.join(config.output, "index.html")
end
# Builds `config` by setting default values and checking for non-valid ones.
@spec normalize_config(%ExDoc.Config{}) :: %ExDoc.Config{}
defp normalize_config(config) when is_map(config) do
if config.main == "index" do
raise ArgumentError, message: "\"main\" cannot be set to \"index\", otherwise it will recursively link to itself"
end
Map.put(config, :main, config.main || @main)
end
defp generate_index(output, config) do
generate_redirect(output, "index.html", config, "#{config.main}.html")
end
defp generate_overview(modules, exceptions, protocols, output, config, has_readme) do
content = Templates.overview_template(config, modules, exceptions, protocols, has_readme)
:ok = File.write("#{output}/overview.html", content)
end
defp generate_sidebar_items(modules, exceptions, protocols, output) do
input = for node <- [%{id: "modules", value: modules}, %{id: "exceptions", value: exceptions}, %{id: "protocols", value: protocols}], !Enum.empty?(node.value), do: node
content = Templates.sidebar_items_template(input)
:ok = File.write("#{output}/dist/sidebar_items.js", content)
end
defp assets do
[{ templates_path("dist/*.{css,js}"), "dist" },
{ templates_path("fonts/*.{eot,svg,ttf,woff,woff2}"), "fonts" }]
end
defp generate_assets(output, _config) do
Enum.each assets, fn({ pattern, dir }) ->
output = "#{output}/#{dir}"
File.mkdir output
Enum.map Path.wildcard(pattern), fn(file) ->
base = Path.basename(file)
File.copy file, "#{output}/#{base}"
end
end
end
defp generate_readme(output, module_nodes, config, modules, exceptions, protocols) do
readme_path = Path.expand(config.readme)
write_readme(output, File.read(readme_path), module_nodes, config, modules, exceptions, protocols)
end
defp write_readme(output, {:ok, content}, module_nodes, config, modules, exceptions, protocols) do
content = Autolink.project_doc(content, module_nodes)
readme_html = Templates.readme_template(config, modules, exceptions, protocols, content) |> pretty_codeblocks
:ok = File.write("#{output}/readme.html", readme_html)
true
end
defp write_readme(_, _, _, _, _, _, _) do
false
end
defp generate_redirect(output, file_name, config, redirect_to) do
content = Templates.redirect_template(config, redirect_to)
:ok = File.write("#{output}/#{file_name}", content)
end
@doc false
# Helper to handle plain code blocks (```...```) with and without
# language specification and indentation code blocks
def pretty_codeblocks(bin) do
bin = Regex.replace(~r/<pre><code(\s+class=\"\")?>\s*iex>/,
# Add "elixir" class for now, until we have support for
# "iex" in highlight.js
bin, "<pre><code class=\"iex elixir\">iex>")
bin = Regex.replace(~r/<pre><code(\s+class=\"\")?>/,
bin, "<pre><code class=\"elixir\">")
bin
end
@doc false
# Helper to split modules into different categories.
#
# Public so that code in Template can use it.
def categorize_modules(nodes) do
[modules: filter_list(:modules, nodes),
exceptions: filter_list(:exceptions, nodes),
protocols: filter_list(:protocols, nodes)]
end
def filter_list(:modules, nodes) do
Enum.filter nodes, &match?(%ExDoc.ModuleNode{type: x} when not x in [:exception, :protocol, :impl], &1)
end
def filter_list(:exceptions, nodes) do
Enum.filter nodes, &match?(%ExDoc.ModuleNode{type: x} when x in [:exception], &1)
end
def filter_list(:protocols, nodes) do
Enum.filter nodes, &match?(%ExDoc.ModuleNode{type: x} when x in [:protocol], &1)
end
defp generate_list(nodes, all, output, config, has_readme) do
nodes
|> Enum.map(&Task.async(fn -> generate_module_page(&1, all, output, config, has_readme) end))
|> Enum.map(&Task.await/1)
end
defp generate_module_page(node, modules, output, config, has_readme) do
content = Templates.module_page(node, config, modules, has_readme)
File.write("#{output}/#{node.id}.html", content)
end
defp templates_path(other) do
Path.expand("html/templates/#{other}", __DIR__)
end
end