Fix output ambiguity in livemd import/export (#892)

* Fix output ambiguity in livemd import/export

* Update test/livebook/live_markdown/import_test.exs

Co-authored-by: José Valim <jose.valim@dashbit.co>

Co-authored-by: José Valim <jose.valim@dashbit.co>
This commit is contained in:
Jonatan Kłosko 2022-01-19 18:27:56 +01:00 committed by GitHub
parent 82f70e3c96
commit 6aaf1d5b58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 166 additions and 10 deletions

View file

@ -36,6 +36,8 @@ defmodule Livebook.LiveMarkdown do
# - `{"break_markdown":true}` - an annotation splitting the markdown content
# into separate Markdown cells
#
# - `{"output":true}` - an annotation marking a code snippet as cell output
#
# - section metadata, recognised keys `branch_parent_index`
#
# - cell metadata, recognised keys: `disable_formatting`

View file

@ -161,13 +161,17 @@ defmodule Livebook.LiveMarkdown.Export do
text = String.replace_suffix(text, "\n", "")
delimiter = MarkdownHelpers.code_block_delimiter(text)
text = strip_ansi(text)
[delimiter, "output\n", text, "\n", delimiter]
[delimiter, "\n", text, "\n", delimiter]
|> prepend_metadata(%{output: true})
end
defp render_output({:text, text}, _ctx) do
delimiter = MarkdownHelpers.code_block_delimiter(text)
text = strip_ansi(text)
[delimiter, "output\n", text, "\n", delimiter]
[delimiter, "\n", text, "\n", delimiter]
|> prepend_metadata(%{output: true})
end
defp render_output(
@ -180,7 +184,10 @@ defmodule Livebook.LiveMarkdown.Export do
case encode_js_data(payload) do
{:ok, binary} ->
["```", info_string, "\n", binary, "\n", "```"]
delimiter = MarkdownHelpers.code_block_delimiter(binary)
[delimiter, info_string, "\n", binary, "\n", delimiter]
|> prepend_metadata(%{output: true})
_ ->
:ignored

View file

@ -180,6 +180,7 @@ defmodule Livebook.LiveMarkdown.Import do
end
end
# Import ```output snippets for backward compatibility
defp take_outputs(
[{"pre", _, [{"code", [{"class", "output"}], [output], %{}}], %{}} | ast],
outputs
@ -187,6 +188,29 @@ defmodule Livebook.LiveMarkdown.Import do
take_outputs(ast, [{:text, output} | outputs])
end
defp take_outputs(
[
{:comment, _, [~s/livebook:{"output":true}/], %{comment: true}},
{"pre", _, [{"code", [], [output], %{}}], %{}}
| ast
],
outputs
) do
take_outputs(ast, [{:text, output} | outputs])
end
# Ignore other exported outputs
defp take_outputs(
[
{:comment, _, [~s/livebook:{"output":true}/], %{comment: true}},
{"pre", _, [{"code", [{"class", _info_string}], [_output], %{}}], %{}}
| ast
],
outputs
) do
take_outputs(ast, outputs)
end
defp take_outputs(ast, outputs), do: {outputs, ast}
# Builds a notebook from the list of elements obtained in the previous step.

View file

@ -584,7 +584,9 @@ defmodule Livebook.LiveMarkdown.ExportTest do
IO.puts("hey")
```
```output
<!-- livebook:{"output":true} -->
```
hey
```
"""
@ -624,11 +626,15 @@ defmodule Livebook.LiveMarkdown.ExportTest do
IO.puts("hey")
```
```output
<!-- livebook:{"output":true} -->
```
hey
```
```output
<!-- livebook:{"output":true} -->
```
:ok
```
"""
@ -754,6 +760,8 @@ defmodule Livebook.LiveMarkdown.ExportTest do
:ok
```
<!-- livebook:{"output":true} -->
```mermaid
graph TD;
A-->B;
@ -802,6 +810,8 @@ defmodule Livebook.LiveMarkdown.ExportTest do
:ok
```
<!-- livebook:{"output":true} -->
```box
{"height":50,"width":50}
```
@ -853,6 +863,8 @@ defmodule Livebook.LiveMarkdown.ExportTest do
:ok
```
<!-- livebook:{"output":true} -->
```vega-lite
{"height":50,"width":50}
```
@ -897,7 +909,9 @@ defmodule Livebook.LiveMarkdown.ExportTest do
IO.puts("hey")
```
```output
<!-- livebook:{"output":true} -->
```
hey
```
"""

View file

@ -340,6 +340,8 @@ defmodule Livebook.LiveMarkdown.ImportTest do
Enum.to_list(1..10)
```
<!-- livebook:{"force_markdown":true} -->
```erlang
spawn_link(fun() -> io:format("Hiya") end).
```
@ -566,11 +568,15 @@ defmodule Livebook.LiveMarkdown.ImportTest do
IO.puts("hey")
```
```output
<!-- livebook:{"output":true} -->
```
hey
```
```output
<!-- livebook:{"output":true} -->
```
:ok
```
"""
@ -596,6 +602,63 @@ defmodule Livebook.LiveMarkdown.ImportTest do
} = notebook
end
test "discards other output snippets" do
markdown = """
# My Notebook
## Section 1
```elixir
IO.puts("hey")
```
```elixir
plot()
```
<!-- livebook:{"output":true} -->
```vega-lite
{}
```
```elixir
:ok
```
"""
{notebook, []} = Import.notebook_from_markdown(markdown)
assert %Notebook{
name: "My Notebook",
sections: [
%Notebook.Section{
name: "Section 1",
cells: [
%Cell.Elixir{
source: """
IO.puts("hey")\
""",
outputs: []
},
%Cell.Elixir{
source: """
plot()\
""",
outputs: []
},
%Cell.Elixir{
source: """
:ok\
""",
outputs: []
}
]
}
]
} = notebook
end
test "imports notebook :persist_outputs attribute" do
markdown = """
<!-- livebook:{"persist_outputs":true} -->
@ -654,5 +717,49 @@ defmodule Livebook.LiveMarkdown.ImportTest do
" Also, to make the input reactive you can use an automatically reevaluating cell"
] == messages
end
test "imports snippets with output info string" do
# We now explicitly mark every output sinppet with <!-- livebook:{"output":true} -->
# and use empty snippets for textual outputs, however previously
# we supported ```output too, so let's ensure they still work
markdown = """
# My Notebook
## Section 1
```elixir
IO.puts("hey")
```
```output
hey
```
```output
:ok
```
"""
{notebook, []} = Import.notebook_from_markdown(markdown)
assert %Notebook{
name: "My Notebook",
sections: [
%Notebook.Section{
name: "Section 1",
cells: [
%Cell.Elixir{
source: """
IO.puts("hey")\
""",
outputs: [{0, {:text, ":ok"}}, {1, {:text, "hey"}}]
}
]
}
],
output_counter: 2
} = notebook
end
end
end

View file

@ -114,7 +114,9 @@ defmodule LivebookWeb.SessionControllerTest do
IO.puts("hey")
```
```output
<!-- livebook:{"output":true} -->
```
hey
```
"""