Packages
ex_doc
0.14.1
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/epub.ex
defmodule ExDoc.Formatter.EPUB do
@moduledoc """
Provide EPUB documentation
"""
alias ExDoc.Formatter.HTML
alias ExDoc.Formatter.EPUB.Templates
@doc """
Generate EPUB documentation for the given modules
"""
@spec run(list, ExDoc.Config.t) :: String.t
def run(module_nodes, config) when is_map(config) do
output = Path.expand(config.output)
File.rm_rf!(output)
File.mkdir_p!("#{output}/OEBPS")
assets() |> HTML.assets_path("epub") |> HTML.generate_assets(output)
all = HTML.Autolink.all(module_nodes, ".xhtml")
modules = HTML.filter_list(:modules, all)
exceptions = HTML.filter_list(:exceptions, all)
protocols = HTML.filter_list(:protocols, all)
config =
if config.logo do
HTML.process_logo_metadata(config, "#{config.output}/OEBPS/assets")
else
config
end
generate_mimetype(output)
generate_extras(output, config, module_nodes)
uuid = "urn:uuid:#{uuid4()}"
datetime = format_datetime()
nodes = modules ++ exceptions ++ protocols
generate_content(output, config, nodes, uuid, datetime)
generate_toc(output, config, nodes, uuid)
generate_nav(output, config, nodes)
generate_title(output, config)
generate_list(output, config, modules)
generate_list(output, config, exceptions)
generate_list(output, config, protocols)
{:ok, epub_file} = generate_epub(output, config)
delete_extras(output)
epub_file
end
defp generate_mimetype(output) do
content = "application/epub+zip"
File.write("#{output}/mimetype", content)
end
defp generate_extras(output, config, module_nodes) do
config.extras
|> Enum.map(&Task.async(fn ->
create_extra_files(&1, output, config, module_nodes)
end))
|> Enum.map(&Task.await(&1, :infinity))
end
defp create_extra_files(input, output, config, module_nodes) do
if HTML.valid_extension_name?(input) do
content =
input
|> File.read!()
|> HTML.Autolink.project_doc(module_nodes, nil, ".xhtml")
file_name =
input
|> Path.basename(".md")
|> String.upcase()
config = Map.put(config, :title, file_name)
extra_html =
config
|> Templates.extra_template(content)
File.write!("#{output}/OEBPS/#{file_name}.xhtml", extra_html)
else
raise ArgumentError, "file format not recognized, allowed format is: .md"
end
end
defp generate_content(output, config, nodes, uuid, datetime) do
content = Templates.content_template(config, nodes, uuid, datetime)
File.write("#{output}/OEBPS/content.opf", content)
end
defp generate_toc(output, config, nodes, uuid) do
content = Templates.toc_template(config, nodes, uuid)
File.write("#{output}/OEBPS/toc.ncx", content)
end
defp generate_nav(output, config, nodes) do
content = Templates.nav_template(config, nodes)
File.write("#{output}/OEBPS/nav.xhtml", content)
end
defp generate_title(output, config) do
content = Templates.title_template(config)
File.write("#{output}/OEBPS/title.xhtml", content)
end
defp generate_list(output, config, nodes) do
nodes
|> Enum.map(&Task.async(fn ->
generate_module_page(output, config, &1)
end))
|> Enum.map(&Task.await(&1, :infinity))
end
defp generate_epub(output, config) do
output = Path.expand(output)
target_path =
String.to_char_list("#{output}/#{config.project}-v#{config.version}.epub")
:zip.create(target_path,
files_to_add(output),
compress: ['.css', '.xhtml', '.html', '.ncx',
'.opf', '.jpg', '.png', '.xml'])
end
defp delete_extras(output) do
for target <- ["META-INF", "mimetype", "OEBPS"] do
File.rm_rf! "#{output}/#{target}"
end
:ok
end
## Helpers
defp assets do
[
{"dist/*.{css,js}", "OEBPS/dist" },
{"assets/*.xml", "META-INF" },
{"assets/mimetype", "." }
]
end
defp files_to_add(path) do
File.cd! path, fn ->
meta = Path.wildcard("META-INF/*")
oebps = Path.wildcard("OEBPS/**/*")
Enum.reduce meta ++ oebps ++ ["mimetype"], [], fn(f, acc) ->
case File.read(f) do
{:ok, bin} ->
[{f |> String.to_char_list, bin}|acc]
{:error, _} ->
acc
end
end
end
end
# Helper to format Erlang datetime tuple
defp format_datetime do
{{year, month, day}, {hour, min, sec}} = :calendar.universal_time()
list = [year, month, day, hour, min, sec]
"~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ"
|> :io_lib.format(list)
|> IO.iodata_to_binary()
end
defp generate_module_page(output, config, node) do
content = Templates.module_page(config, node)
File.write("#{output}/OEBPS/#{node.id}.xhtml", content)
end
# Helper to generate an UUID v4. This version uses pseudo-random bytes generated by
# the `crypto` module.
defp uuid4 do
<<u0::48, _::4, u1::12, _::2, u2::62>> = :crypto.strong_rand_bytes(16)
bin = <<u0::48, 4::4, u1::12, 2::2, u2::62>>
<<u0::32, u1::16, u2::16, u3::16, u4::48>> = bin
Enum.map_join([<<u0::32>>, <<u1::16>>, <<u2::16>>, <<u3::16>>, <<u4::48>>], <<45>>,
&(Base.encode16(&1, case: :lower)))
end
end