-
Notifications
You must be signed in to change notification settings - Fork 354
feat: add Markdown formatter #2148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,6 +65,67 @@ defmodule ExDoc.DocAST do | |
| Enum.map(attrs, fn {key, val} -> " #{key}=\"#{ExDoc.Utils.h(val)}\"" end) | ||
| end | ||
|
|
||
| @doc """ | ||
| Transform AST into markdown string. | ||
| """ | ||
| def to_markdown(ast) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of converting it to markdown... I wonder if we should keep the original document and use it instead? That's because converting to markdown can come with many pitfalls. For example, |
||
|
|
||
| def to_markdown(binary) when is_binary(binary) do | ||
| ExDoc.Utils.h(binary) | ||
| end | ||
|
|
||
| def to_markdown(list) when is_list(list) do | ||
| Enum.map_join(list, "", &to_markdown/1) | ||
| end | ||
|
|
||
| def to_markdown({:comment, _attrs, inner, _meta}) do | ||
| "<!--#{inner}-->" | ||
| end | ||
|
|
||
| def to_markdown({:code, attrs, inner, _meta}) do | ||
| lang = attrs[:class] || "" | ||
|
|
||
| """ | ||
| ```#{lang} | ||
| #{inner} | ||
| ``` | ||
| """ | ||
| end | ||
|
|
||
| def to_markdown({:a, attrs, inner, _meta}) do | ||
| "[#{to_markdown(inner)}](#{attrs[:href]})" | ||
| end | ||
|
|
||
| def to_markdown({:hr, _attrs, _inner, _meta}) do | ||
| "\n\n---\n\n" | ||
| end | ||
|
|
||
| def to_markdown({:p, _attrs, inner, _meta}) do | ||
| to_markdown(inner) <> "\n\n" | ||
| end | ||
|
|
||
| def to_markdown({:br, _attrs, _inner, _meta}) do | ||
| "\n\n" | ||
| end | ||
|
|
||
| def to_markdown({:img, attrs, _inner, _meta}) do | ||
| alt = attrs[:alt] || "" | ||
| title = attrs[:title] || "" | ||
| "" | ||
| end | ||
|
|
||
| def to_markdown({tag, _attrs, _inner, _meta}) when tag in @void_elements do | ||
| "" | ||
| end | ||
|
|
||
| def to_markdown({_tag, _attrs, inner, %{verbatim: true}}) do | ||
| Enum.join(inner, "") | ||
| end | ||
|
|
||
| def to_markdown({_tag, _attrs, inner, _meta}) do | ||
| to_markdown(inner) | ||
| end | ||
|
|
||
| ## parse markdown | ||
|
|
||
| defp parse_markdown(markdown, opts) do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,8 +11,12 @@ defmodule ExDoc.Formatter.EPUB do | |
| """ | ||
| @spec run([ExDoc.ModuleNode.t()], [ExDoc.ModuleNode.t()], ExDoc.Config.t()) :: String.t() | ||
| def run(project_nodes, filtered_modules, config) when is_map(config) do | ||
| # Store original output for build file before normalize_config creates temp path | ||
| original_output = config.output | ||
| config = normalize_config(config) | ||
| File.rm_rf!(config.output) | ||
|
|
||
| build = Path.join(original_output, ".build") | ||
| output_setup(build, config) | ||
| File.mkdir_p!(Path.join(config.output, "OEBPS")) | ||
|
|
||
| project_nodes = | ||
|
|
@@ -40,12 +44,15 @@ defmodule ExDoc.Formatter.EPUB do | |
| uuid = "urn:uuid:#{uuid4()}" | ||
| datetime = format_datetime() | ||
|
|
||
| generate_content(config, nodes_map, uuid, datetime, static_files) | ||
| generate_nav(config, nodes_map) | ||
| generate_title(config) | ||
| generate_extras(config) | ||
| generate_list(config, nodes_map.modules) | ||
| generate_list(config, nodes_map.tasks) | ||
| content_files = generate_content(config, nodes_map, uuid, datetime, static_files) | ||
| nav_files = generate_nav(config, nodes_map) | ||
| title_files = generate_title(config) | ||
| extra_files = generate_extras(config) | ||
| module_files = generate_list(config, nodes_map.modules) | ||
| task_files = generate_list(config, nodes_map.tasks) | ||
|
|
||
| all_files = List.flatten([content_files, nav_files, title_files, extra_files, module_files, task_files]) | ||
| generate_build(all_files, build) | ||
|
|
||
| {:ok, epub} = generate_epub(config.output) | ||
| File.rm_rf!(config.output) | ||
|
|
@@ -65,14 +72,16 @@ defmodule ExDoc.Formatter.EPUB do | |
| for {_title, extras} <- config.extras, | ||
| node <- extras, | ||
| not is_map_key(node, :url) do | ||
| output = "#{config.output}/OEBPS/#{node.id}.xhtml" | ||
| filename = "OEBPS/#{node.id}.xhtml" | ||
| output = "#{config.output}/#{filename}" | ||
| html = Templates.extra_template(config, node) | ||
|
|
||
| if File.regular?(output) do | ||
| Utils.warn("file #{Path.relative_to_cwd(output)} already exists", []) | ||
| end | ||
|
|
||
| File.write!(output, html) | ||
| filename | ||
| end | ||
| end | ||
|
|
||
|
|
@@ -84,7 +93,9 @@ defmodule ExDoc.Formatter.EPUB do | |
| do: {Path.relative_to(name, "OEBPS"), media_type} | ||
|
|
||
| content = Templates.content_template(config, nodes, uuid, datetime, static_files) | ||
| File.write("#{config.output}/OEBPS/content.opf", content) | ||
| filename = "OEBPS/content.opf" | ||
| File.write("#{config.output}/#{filename}", content) | ||
| [filename] | ||
| end | ||
|
|
||
| defp generate_nav(config, nodes) do | ||
|
|
@@ -94,12 +105,16 @@ defmodule ExDoc.Formatter.EPUB do | |
| end) | ||
|
|
||
| content = Templates.nav_template(config, nodes) | ||
| File.write("#{config.output}/OEBPS/nav.xhtml", content) | ||
| filename = "OEBPS/nav.xhtml" | ||
| File.write("#{config.output}/#{filename}", content) | ||
| [filename] | ||
| end | ||
|
|
||
| defp generate_title(config) do | ||
| content = Templates.title_template(config) | ||
| File.write("#{config.output}/OEBPS/title.xhtml", content) | ||
| filename = "OEBPS/title.xhtml" | ||
| File.write("#{config.output}/#{filename}", content) | ||
| [filename] | ||
| end | ||
|
|
||
| defp generate_list(config, nodes) do | ||
|
|
@@ -126,6 +141,31 @@ defmodule ExDoc.Formatter.EPUB do | |
| ) | ||
| end | ||
|
|
||
| defp output_setup(build, config) do | ||
| if File.exists?(build) do | ||
| build | ||
| |> File.read!() | ||
| |> String.split("\n", trim: true) | ||
| |> Enum.map(&Path.join(config.output, &1)) | ||
| |> Enum.each(&File.rm/1) | ||
|
|
||
| File.rm(build) | ||
| else | ||
| File.rm_rf!(config.output) | ||
| end | ||
| end | ||
|
|
||
| defp generate_build(files, build) do | ||
| entries = | ||
| files | ||
| |> Enum.uniq() | ||
| |> Enum.sort() | ||
| |> Enum.map(&[&1, "\n"]) | ||
|
|
||
| File.mkdir_p!(Path.dirname(build)) | ||
| File.write!(build, entries) | ||
| end | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The changes to this file are unrelated no? Should they be moved to a separate PR? |
||
|
|
||
| ## Helpers | ||
|
|
||
| defp default_assets(config) do | ||
|
|
@@ -159,7 +199,9 @@ defmodule ExDoc.Formatter.EPUB do | |
|
|
||
| defp generate_module_page(module_node, config) do | ||
| content = Templates.module_page(config, module_node) | ||
| File.write("#{config.output}/OEBPS/#{module_node.id}.xhtml", content) | ||
| filename = "OEBPS/#{module_node.id}.xhtml" | ||
| File.write("#{config.output}/#{filename}", content) | ||
| filename | ||
| end | ||
|
|
||
| @two_power_16 65536 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's please keep the previous convension of upcase, we can revisit it later.