diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 0f1af54cf65..cda3bdd9f2b 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -118,10 +118,196 @@ defmodule IO.ANSI.Docs do print_markdown(doc, options) end + def print(doc, "application/erlang+html", options) when is_list(options) do + print_erlang_html(doc, options) + end + def print(_doc, format, options) when is_binary(format) and is_list(options) do IO.puts("\nUnknown documentation format #{inspect(format)}\n") end + ## Erlang+html + + def print_erlang_html(doc, options) do + options = Keyword.merge(default_options(), options) + IO.write(traverse_erlang_html(doc, "", options)) + end + + defp traverse_erlang_html(text, _indent, _options) when is_binary(text) do + text + end + + defp traverse_erlang_html(nodes, indent, options) when is_list(nodes) do + for node <- nodes do + traverse_erlang_html(node, indent, options) + end + end + + defp traverse_erlang_html({:div, [class: class] ++ _, entries}, indent, options) do + prefix = indent <> quote_prefix(options) + + content = + entries + |> traverse_erlang_html(indent, options) + |> IO.iodata_to_binary() + |> String.trim_trailing() + + [ + prefix, + class |> to_string() |> String.upcase(), + "\n#{prefix}\n#{prefix}" | String.replace(content, "\n", "\n#{prefix}") + ] + |> newline_cons() + end + + defp traverse_erlang_html({:p, _, entries}, indent, options) do + [indent | handle_erlang_html_text(entries, indent, options)] + end + + defp traverse_erlang_html({:h1, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(1, options) |> newline_cons() + end + + defp traverse_erlang_html({:h2, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(2, options) |> newline_cons() + end + + defp traverse_erlang_html({:h3, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(3, options) |> newline_cons() + end + + defp traverse_erlang_html({:h4, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(4, options) |> newline_cons() + end + + defp traverse_erlang_html({:h5, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(5, options) |> newline_cons() + end + + defp traverse_erlang_html({:h6, _, entries}, indent, options) do + entries |> traverse_erlang_html(indent, options) |> heading(6, options) |> newline_cons() + end + + defp traverse_erlang_html({:br, _, []}, _indent, _options) do + [] + end + + defp traverse_erlang_html({:i, _, entries}, indent, options) do + inline_text("_", traverse_erlang_html(entries, indent, options), options) + end + + defp traverse_erlang_html({:em, _, entries}, indent, options) do + inline_text("*", traverse_erlang_html(entries, indent, options), options) + end + + defp traverse_erlang_html({tag, _, entries}, indent, options) when tag in [:strong, :b] do + inline_text("**", traverse_erlang_html(entries, indent, options), options) + end + + defp traverse_erlang_html({:code, _, entries}, indent, options) do + inline_text("`", traverse_erlang_html(entries, indent, options), options) + end + + defp traverse_erlang_html({:pre, _, [{:code, _, entries}]}, indent, options) do + string = + entries + |> traverse_erlang_html(indent, options) + |> IO.iodata_to_binary() + + ["#{indent} ", String.replace(string, "\n", "\n#{indent} ")] |> newline_cons() + end + + defp traverse_erlang_html({:a, attributes, entries}, indent, options) do + if href = attributes[:href] do + [traverse_erlang_html(entries, indent, options), ?\s, ?(, href, ?)] + else + traverse_erlang_html(entries, indent, options) + end + end + + defp traverse_erlang_html({:dl, _, entries}, indent, options) do + traverse_erlang_html(entries, indent, options) + end + + defp traverse_erlang_html({:dt, _, entries}, indent, options) do + [ + "#{indent} ", + bullet_text(options) | handle_erlang_html_text(entries, indent <> " ", options) + ] + end + + defp traverse_erlang_html({:dd, _, entries}, indent, options) do + ["#{indent} " | handle_erlang_html_text(entries, indent <> " ", options)] + end + + defp traverse_erlang_html({:ul, attributes, entries}, indent, options) do + if attributes[:class] == "types" do + types = + for {:li, _, lines} <- entries, + line <- lines, + do: ["#{indent} ", traverse_erlang_html(line, indent <> " ", options), ?\n] + + if types != [] do + ["#{indent}Typespecs:\n\n", types, ?\n] + else + [] + end + else + for {:li, _, lines} <- entries do + [ + "#{indent} ", + bullet_text(options) | handle_erlang_html_text(lines, indent <> " ", options) + ] + end + end + end + + defp traverse_erlang_html({:ol, _, entries}, indent, options) do + for {{:li, _, lines}, i} <- Enum.with_index(entries, 1) do + [ + "#{indent} ", + Integer.to_string(i), + ". " | handle_erlang_html_text(lines, indent <> " ", options) + ] + end + end + + defp traverse_erlang_html({tag, _, entries}, indent, options) do + [ + indent <> "<#{tag}>\n", + traverse_erlang_html(entries, indent <> " ", options) + |> IO.iodata_to_binary() + |> String.trim_trailing(), + "\n" <> indent <> "" + ] + |> newline_cons() + end + + defp newline_cons(text) do + [text | "\n\n"] + end + + defp handle_erlang_html_text(entries, indent, options) do + if Enum.all?(entries, &inline_html?/1) do + entries + |> traverse_erlang_html(indent, options) + |> IO.iodata_to_binary() + |> String.split(@spaces) + |> wrap_text(options[:width], indent, true, "", []) + |> tl() + |> newline_cons() + else + entries + |> traverse_erlang_html(indent, options) + |> IO.iodata_to_binary() + |> String.trim_leading() + end + end + + defp inline_html?(binary) when is_binary(binary), do: true + defp inline_html?({tag, _, _}) when tag in [:a, :code, :em, :i, :strong, :b, :br], do: true + defp inline_html?(_), do: false + ## Markdown def print_markdown(doc, options) do @@ -176,19 +362,14 @@ defmodule IO.ANSI.Docs do process_code(rest, [line], indent, options) end - defp process(["```mermaid" <> _line | rest], text, indent, options) do - write_text(text, indent, options) - - rest - |> Enum.drop_while(&(&1 != "```")) - |> Enum.drop(1) - |> process([], indent, options) - end - defp process(["```" <> _line | rest], text, indent, options) do process_fenced_code_block(rest, text, indent, options, _delimiter = "```") end + defp process(["~~~" <> _line | rest], text, indent, options) do + process_fenced_code_block(rest, text, indent, options, _delimiter = "~~~") + end + defp process(["