Packages
livebook
0.1.2
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/live_markdown/export.ex
defmodule Livebook.LiveMarkdown.Export do
alias Livebook.Notebook
alias Livebook.LiveMarkdown.MarkdownHelpers
@doc """
Converts the given notebook into a Markdown document.
"""
@spec notebook_to_markdown(Notebook.t()) :: String.t()
def notebook_to_markdown(notebook) do
iodata = render_notebook(notebook)
# Add trailing newline
IO.iodata_to_binary([iodata, "\n"])
end
defp render_notebook(notebook) do
name = "# #{notebook.name}"
sections = Enum.map(notebook.sections, &render_section/1)
[name | sections]
|> Enum.intersperse("\n\n")
|> prepend_metadata(notebook.metadata)
end
defp render_section(section) do
name = "## #{section.name}"
cells = Enum.map(section.cells, &render_cell/1)
[name | cells]
|> Enum.intersperse("\n\n")
|> prepend_metadata(section.metadata)
end
defp render_cell(%{type: :markdown} = cell) do
cell.source
|> format_markdown_source()
|> prepend_metadata(cell.metadata)
end
defp render_cell(%{type: :elixir} = cell) do
code = get_elixir_cell_code(cell)
"""
```elixir
#{code}
```\
"""
|> prepend_metadata(cell.metadata)
end
defp get_elixir_cell_code(%{source: source, metadata: %{"disable_formatting" => true}}),
do: source
defp get_elixir_cell_code(%{source: source}), do: format_code(source)
defp render_metadata(metadata) do
metadata_json = Jason.encode!(metadata)
"<!-- livebook:#{metadata_json} -->"
end
defp prepend_metadata(iodata, metadata) when metadata == %{}, do: iodata
defp prepend_metadata(iodata, metadata) do
content = render_metadata(metadata)
[content, "\n\n", iodata]
end
defp format_markdown_source(markdown) do
markdown
|> EarmarkParser.as_ast()
|> elem(1)
|> rewrite_ast()
|> MarkdownHelpers.markdown_from_ast()
end
# Alters AST of the user-entered markdown.
defp rewrite_ast(ast) do
ast
|> remove_reserved_headings()
|> add_markdown_annotation_before_elixir_block()
end
defp remove_reserved_headings(ast) do
Enum.filter(ast, fn
{"h1", _, _, _} -> false
{"h2", _, _, _} -> false
_ast_node -> true
end)
end
defp add_markdown_annotation_before_elixir_block(ast) do
Enum.flat_map(ast, fn
{"pre", _, [{"code", [{"class", "elixir"}], [_source], %{}}], %{}} = ast_node ->
[{:comment, [], [~s/livebook:{"force_markdown":true}/], %{comment: true}}, ast_node]
ast_node ->
[ast_node]
end)
end
defp format_code(code) do
try do
Code.format_string!(code)
rescue
_ -> code
end
end
end