Remove untyped metadata (#484)

This commit is contained in:
Jonatan Kłosko 2021-07-30 13:04:07 +02:00 committed by GitHub
parent 42463c7cb1
commit 37b6a1aa40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 111 additions and 121 deletions

View file

@ -26,9 +26,15 @@ defmodule Livebook.LiveMarkdown.Export do
name = ["# ", notebook.name]
sections = Enum.map(notebook.sections, &render_section(&1, notebook, ctx))
metadata = notebook_metadata(notebook)
[name | sections]
|> Enum.intersperse("\n\n")
|> prepend_metadata(notebook.metadata)
|> prepend_metadata(metadata)
end
defp notebook_metadata(_notebook) do
%{}
end
defp render_section(section, notebook, ctx) do
@ -54,19 +60,21 @@ defmodule Livebook.LiveMarkdown.Export do
|> prepend_metadata(metadata)
end
defp section_metadata(%{parent_id: nil} = section, _notebook) do
section.metadata
defp section_metadata(%{parent_id: nil} = _section, _notebook) do
%{}
end
defp section_metadata(section, notebook) do
parent_idx = Notebook.section_index(notebook, section.parent_id)
Map.put(section.metadata, "branch_parent_index", parent_idx)
%{"branch_parent_index" => parent_idx}
end
defp render_cell(%Cell.Markdown{} = cell, _ctx) do
metadata = cell_metadata(cell)
cell.source
|> format_markdown_source()
|> prepend_metadata(cell.metadata)
|> prepend_metadata(metadata)
end
defp render_cell(%Cell.Elixir{} = cell, ctx) do
@ -74,9 +82,11 @@ defmodule Livebook.LiveMarkdown.Export do
code = get_elixir_cell_code(cell)
outputs = if ctx.include_outputs?, do: render_outputs(cell), else: []
metadata = cell_metadata(cell)
cell =
[delimiter, "elixir\n", code, "\n", delimiter]
|> prepend_metadata(cell.metadata)
|> prepend_metadata(metadata)
if outputs == [] do
cell
@ -98,10 +108,18 @@ defmodule Livebook.LiveMarkdown.Export do
|> put_unless_implicit(reactive: cell.reactive, props: cell.props)
|> Jason.encode!()
metadata = cell_metadata(cell)
"<!-- livebook:#{json} -->"
|> prepend_metadata(cell.metadata)
|> prepend_metadata(metadata)
end
defp cell_metadata(%Cell.Elixir{} = cell) do
put_unless_implicit(%{}, disable_formatting: cell.disable_formatting)
end
defp cell_metadata(_cell), do: %{}
defp render_outputs(cell) do
cell.outputs
|> Enum.reverse()
@ -125,7 +143,7 @@ defmodule Livebook.LiveMarkdown.Export do
defp render_output(_output), do: :ignored
defp get_elixir_cell_code(%{source: source, metadata: %{"disable_formatting" => true}}),
defp get_elixir_cell_code(%{source: source, disable_formatting: true}),
do: source
defp get_elixir_cell_code(%{source: source}), do: format_code(source)

View file

@ -200,29 +200,31 @@ defmodule Livebook.LiveMarkdown.Import do
defp build_notebook([{:cell, :elixir, source, outputs} | elems], cells, sections) do
{metadata, elems} = grab_metadata(elems)
attrs = cell_metadata_to_attrs(:elixir, metadata)
outputs = Enum.map(outputs, &{:text, &1})
cell = %{Notebook.Cell.new(:elixir) | source: source, metadata: metadata, outputs: outputs}
cell = %{Notebook.Cell.new(:elixir) | source: source, outputs: outputs} |> Map.merge(attrs)
build_notebook(elems, [cell | cells], sections)
end
defp build_notebook([{:cell, :markdown, md_ast} | elems], cells, sections) do
{metadata, elems} = grab_metadata(elems)
attrs = cell_metadata_to_attrs(:markdown, metadata)
source = md_ast |> Enum.reverse() |> MarkdownHelpers.markdown_from_ast()
cell = %{Notebook.Cell.new(:markdown) | source: source, metadata: metadata}
cell = %{Notebook.Cell.new(:markdown) | source: source} |> Map.merge(attrs)
build_notebook(elems, [cell | cells], sections)
end
defp build_notebook([{:cell, :input, data} | elems], cells, sections) do
{metadata, elems} = grab_metadata(elems)
attrs = parse_input_attrs(data)
cell = %{Notebook.Cell.new(:input) | metadata: metadata} |> Map.merge(attrs)
cell = Notebook.Cell.new(:input) |> Map.merge(attrs)
build_notebook(elems, [cell | cells], sections)
end
defp build_notebook([{:section_name, content} | elems], cells, sections) do
name = MarkdownHelpers.text_from_ast(content)
{metadata, elems} = grab_metadata(elems)
section = %{Notebook.Section.new() | name: name, cells: cells, metadata: metadata}
attrs = section_metadata_to_attrs(metadata)
section = %{Notebook.Section.new() | name: name, cells: cells} |> Map.merge(attrs)
build_notebook(elems, [], [section | sections])
end
@ -242,7 +244,8 @@ defmodule Livebook.LiveMarkdown.Import do
defp build_notebook([{:notebook_name, content} | elems], [], sections) do
name = MarkdownHelpers.text_from_ast(content)
{metadata, []} = grab_metadata(elems)
%{Notebook.new() | name: name, sections: sections, metadata: metadata}
attrs = notebook_metadata_to_attrs(metadata)
%{Notebook.new() | name: name, sections: sections} |> Map.merge(attrs)
end
# If there's no explicit notebook heading, use the defaults.
@ -279,13 +282,48 @@ defmodule Livebook.LiveMarkdown.Import do
end)
end
defp notebook_metadata_to_attrs(_metadata) do
%{}
end
defp section_metadata_to_attrs(metadata) do
Enum.reduce(metadata, %{}, fn
{"branch_parent_index", parent_idx}, attrs ->
# At this point we cannot extract other section id,
# so we temporarily keep the index
Map.put(attrs, :parent_id, {:idx, parent_idx})
_entry, attrs ->
attrs
end)
end
defp cell_metadata_to_attrs(:elixir, metadata) do
Enum.reduce(metadata, %{}, fn
{"disable_formatting", disable_formatting}, attrs ->
Map.put(attrs, :disable_formatting, disable_formatting)
_entry, attrs ->
attrs
end)
end
defp cell_metadata_to_attrs(_type, _metadata) do
%{}
end
defp postprocess_notebook(notebook) do
sections =
Enum.map(notebook.sections, fn section ->
# Set parent_id based on the persisted branch_parent_index if present
{parent_idx, metadata} = Map.pop(section.metadata, "branch_parent_index")
parent = parent_idx && Enum.at(notebook.sections, parent_idx)
%{section | metadata: metadata, parent_id: parent && parent.id}
case section.parent_id do
nil ->
section
{:idx, parent_idx} ->
parent = Enum.at(notebook.sections, parent_idx)
%{section | parent_id: parent.id}
end
end)
%{notebook | sections: sections}

View file

@ -13,19 +13,16 @@ defmodule Livebook.Notebook do
# A notebook is divided into a number of *sections*, each
# containing a number of *cells*.
defstruct [:name, :version, :sections, :metadata]
defstruct [:name, :version, :sections]
alias Livebook.Notebook.{Section, Cell}
alias Livebook.Utils.Graph
import Livebook.Utils, only: [access_by_id: 1]
@type metadata :: %{String.t() => term()}
@type t :: %__MODULE__{
name: String.t(),
version: String.t(),
sections: list(Section.t()),
metadata: metadata()
sections: list(Section.t())
}
@version "1.0"
@ -38,8 +35,7 @@ defmodule Livebook.Notebook do
%__MODULE__{
name: "Untitled notebook",
version: @version,
sections: [],
metadata: %{}
sections: []
}
end

View file

@ -12,16 +12,6 @@ defmodule Livebook.Notebook.Cell do
@type id :: Utils.id()
@typedoc """
Arbitrary cell information persisted as part of the notebook.
## Recognised entries
* `disable_formatting` - whether this particular cell should
not be automatically formatted. Relevant for Elixir cells only.
"""
@type metadata :: %{String.t() => term()}
@type t :: Cell.Elixir.t() | Cell.Markdown.t() | Cell.Input.t()
@type type :: :markdown | :elixir | :input

View file

@ -6,16 +6,16 @@ defmodule Livebook.Notebook.Cell.Elixir do
# It consists of text content that the user can edit
# and produces some output once evaluated.
defstruct [:id, :metadata, :source, :outputs]
defstruct [:id, :source, :outputs, :disable_formatting]
alias Livebook.Utils
alias Livebook.Notebook.Cell
@type t :: %__MODULE__{
id: Cell.id(),
metadata: Cell.metadata(),
source: String.t(),
outputs: list(output())
outputs: list(output()),
disable_formatting: boolean()
}
@typedoc """
@ -47,9 +47,9 @@ defmodule Livebook.Notebook.Cell.Elixir do
def new() do
%__MODULE__{
id: Utils.random_id(),
metadata: %{},
source: "",
outputs: []
outputs: [],
disable_formatting: false
}
end
end

View file

@ -6,14 +6,13 @@ defmodule Livebook.Notebook.Cell.Input do
# It consists of an input that the user may fill
# and then read during code evaluation.
defstruct [:id, :metadata, :type, :name, :value, :reactive, :props]
defstruct [:id, :type, :name, :value, :reactive, :props]
alias Livebook.Utils
alias Livebook.Notebook.Cell
@type t :: %__MODULE__{
id: Cell.id(),
metadata: Cell.metadata(),
type: type(),
name: String.t(),
value: String.t(),
@ -36,7 +35,6 @@ defmodule Livebook.Notebook.Cell.Input do
def new() do
%__MODULE__{
id: Utils.random_id(),
metadata: %{},
type: :text,
name: "input",
value: "",

View file

@ -6,14 +6,13 @@ defmodule Livebook.Notebook.Cell.Markdown do
# It consists of Markdown content that the user can edit
# and which is then rendered on the page.
defstruct [:id, :metadata, :source]
defstruct [:id, :source]
alias Livebook.Utils
alias Livebook.Notebook.Cell
@type t :: %__MODULE__{
id: Cell.id(),
metadata: Cell.metadata(),
source: String.t()
}
@ -24,7 +23,6 @@ defmodule Livebook.Notebook.Cell.Markdown do
def new() do
%__MODULE__{
id: Utils.random_id(),
metadata: %{},
source: ""
}
end

View file

@ -65,7 +65,7 @@ defmodule Livebook.Notebook.Export.Elixir do
defp comment_out(""), do: ""
defp comment_out(line), do: ["# ", line]
defp get_elixir_cell_code(%{source: source, metadata: %{"disable_formatting" => true}}),
defp get_elixir_cell_code(%{source: source, disable_formatting: true}),
do: source
defp get_elixir_cell_code(%{source: source}), do: format_code(source)

View file

@ -10,20 +10,18 @@ defmodule Livebook.Notebook.Section do
# a branching section. Such section logically follows its
# parent section and has no impact on any further sections.
defstruct [:id, :name, :cells, :parent_id, :metadata]
defstruct [:id, :name, :cells, :parent_id]
alias Livebook.Notebook.Cell
alias Livebook.Utils
@type id :: Utils.id()
@type metadata :: %{String.t() => term()}
@type t :: %__MODULE__{
id: id(),
name: String.t(),
cells: list(Cell.t()),
parent_id: id() | nil,
metadata: metadata()
parent_id: id() | nil
}
@doc """
@ -35,8 +33,7 @@ defmodule Livebook.Notebook.Section do
id: Utils.random_id(),
name: "Section",
cells: [],
parent_id: nil,
metadata: %{}
parent_id: nil
}
end
end

View file

@ -5,12 +5,14 @@ defmodule LivebookWeb.SessionLive.ElixirCellSettingsComponent do
@impl true
def update(assigns, socket) do
metadata = assigns.cell.metadata
cell = assigns.cell
assigns =
Map.merge(assigns, %{disable_formatting: Map.get(metadata, "disable_formatting", false)})
socket =
socket
|> assign(assigns)
|> assign_new(:disable_formatting, fn -> cell.disable_formatting end)
{:ok, assign(socket, assigns)}
{:ok, socket}
end
@impl true
@ -39,21 +41,13 @@ defmodule LivebookWeb.SessionLive.ElixirCellSettingsComponent do
end
@impl true
def handle_event("save", params, socket) do
metadata = update_metadata(socket.assigns.cell.metadata, params)
def handle_event("save", %{"disable_formatting" => disable_formatting}, socket) do
disable_formatting = disable_formatting == "true"
Session.set_cell_attributes(socket.assigns.session_id, socket.assigns.cell.id, %{
metadata: metadata
disable_formatting: disable_formatting
})
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
end
defp update_metadata(metadata, form_data) do
if form_data["disable_formatting"] == "true" do
Map.put(metadata, "disable_formatting", true)
else
Map.delete(metadata, "disable_formatting")
end
end
end

View file

@ -8,17 +8,14 @@ defmodule Livebook.LiveMarkdown.ExportTest do
notebook = %{
Notebook.new()
| name: "My Notebook",
metadata: %{"author" => "Sherlock Holmes"},
sections: [
%{
Notebook.Section.new()
| name: "Section 1",
metadata: %{"created_at" => "2021-02-15"},
cells: [
%{
Notebook.Cell.new(:markdown)
| metadata: %{"updated_at" => "2021-02-15"},
source: """
| source: """
Make sure to install:
* Erlang
@ -28,7 +25,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
},
%{
Notebook.Cell.new(:elixir)
| metadata: %{"readonly" => true},
| disable_formatting: true,
source: """
Enum.to_list(1..10)\
"""
@ -85,23 +82,17 @@ defmodule Livebook.LiveMarkdown.ExportTest do
}
expected_document = """
<!-- livebook:{"author":"Sherlock Holmes"} -->
# My Notebook
<!-- livebook:{"created_at":"2021-02-15"} -->
## Section 1
<!-- livebook:{"updated_at":"2021-02-15"} -->
Make sure to install:
* Erlang
* Elixir
* PostgreSQL
<!-- livebook:{"readonly":true} -->
<!-- livebook:{"disable_formatting":true} -->
```elixir
Enum.to_list(1..10)
@ -355,7 +346,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
assert expected_document == document
end
test "does not format code in Elixir cells which explicitly state so in metadata" do
test "does not format code in Elixir cells which have formatting disabled" do
notebook = %{
Notebook.new()
| name: "My Notebook",
@ -366,7 +357,7 @@ defmodule Livebook.LiveMarkdown.ExportTest do
cells: [
%{
Notebook.Cell.new(:elixir)
| metadata: %{"disable_formatting" => true},
| disable_formatting: true,
source: """
[1,2,3] # Comment\
"""

View file

@ -7,23 +7,17 @@ defmodule Livebook.LiveMarkdown.ImportTest do
test "acceptance" do
markdown = """
<!-- livebook:{"author":"Sherlock Holmes"} -->
# My Notebook
<!-- livebook:{"created_at":"2021-02-15"} -->
## Section 1
<!-- livebook:{"updated_at":"2021-02-15"} -->
Make sure to install:
* Erlang
* Elixir
* PostgreSQL
<!-- livebook:{"readonly":true} -->
<!-- livebook:{"disable_formatting": true} -->
```elixir
Enum.to_list(1..10)
@ -56,14 +50,11 @@ defmodule Livebook.LiveMarkdown.ImportTest do
assert %Notebook{
name: "My Notebook",
metadata: %{"author" => "Sherlock Holmes"},
sections: [
%Notebook.Section{
name: "Section 1",
metadata: %{"created_at" => "2021-02-15"},
cells: [
%Cell.Markdown{
metadata: %{"updated_at" => "2021-02-15"},
source: """
Make sure to install:
@ -73,13 +64,12 @@ defmodule Livebook.LiveMarkdown.ImportTest do
"""
},
%Cell.Elixir{
metadata: %{"readonly" => true},
disable_formatting: true,
source: """
Enum.to_list(1..10)\
"""
},
%Cell.Markdown{
metadata: %{},
source: """
This is it for this section.\
"""
@ -89,23 +79,19 @@ defmodule Livebook.LiveMarkdown.ImportTest do
%Notebook.Section{
id: section2_id,
name: "Section 2",
metadata: %{},
cells: [
%Cell.Input{
metadata: %{},
type: :text,
name: "length",
value: "100",
reactive: true
},
%Cell.Elixir{
metadata: %{},
source: """
IO.gets("length: ")\
"""
},
%Cell.Input{
metadata: %{},
type: :range,
name: "length",
value: "100",
@ -115,11 +101,9 @@ defmodule Livebook.LiveMarkdown.ImportTest do
},
%Notebook.Section{
name: "Section 3",
metadata: %{},
parent_id: section2_id,
cells: [
%Cell.Elixir{
metadata: %{},
source: """
Process.info()\
"""
@ -152,10 +136,8 @@ defmodule Livebook.LiveMarkdown.ImportTest do
sections: [
%Notebook.Section{
name: "Section 1",
metadata: %{},
cells: [
%Cell.Markdown{
metadata: %{},
source: """
Line 1.\\
Line 2.
@ -213,7 +195,6 @@ defmodule Livebook.LiveMarkdown.ImportTest do
name: "Probably section 1",
cells: [
%Cell.Markdown{
metadata: %{},
source: """
### Heading
@ -226,7 +207,6 @@ defmodule Livebook.LiveMarkdown.ImportTest do
name: "Probably section 2",
cells: [
%Cell.Markdown{
metadata: %{},
source: """
**Tiny heading**\
"""
@ -330,7 +310,6 @@ defmodule Livebook.LiveMarkdown.ImportTest do
assert %Notebook{
name: "My Notebook",
metadata: %{"author" => "Sherlock Holmes"},
sections: [
%Notebook.Section{
name: "Section",

View file

@ -8,17 +8,14 @@ defmodule Livebook.Notebook.Export.ElixirTest do
notebook = %{
Notebook.new()
| name: "My Notebook",
metadata: %{"author" => "Sherlock Holmes"},
sections: [
%{
Notebook.Section.new()
| name: "Section 1",
metadata: %{"created_at" => "2021-02-15"},
cells: [
%{
Notebook.Cell.new(:markdown)
| metadata: %{"updated_at" => "2021-02-15"},
source: """
| source: """
Make sure to install:
* Erlang
@ -28,15 +25,14 @@ defmodule Livebook.Notebook.Export.ElixirTest do
},
%{
Notebook.Cell.new(:elixir)
| metadata: %{"readonly" => true},
| disable_formatting: true,
source: """
Enum.to_list(1..10)\
"""
},
%{
Notebook.Cell.new(:markdown)
| metadata: %{},
source: """
| source: """
This is it for this section.\
"""
}
@ -46,7 +42,6 @@ defmodule Livebook.Notebook.Export.ElixirTest do
Notebook.Section.new()
| id: "s2",
name: "Section 2",
metadata: %{},
cells: [
%{
Notebook.Cell.new(:input)
@ -57,8 +52,7 @@ defmodule Livebook.Notebook.Export.ElixirTest do
},
%{
Notebook.Cell.new(:elixir)
| metadata: %{},
source: """
| source: """
IO.gets("length: ")\
"""
},
@ -74,13 +68,11 @@ defmodule Livebook.Notebook.Export.ElixirTest do
%{
Notebook.Section.new()
| name: "Section 3",
metadata: %{},
parent_id: "s2",
cells: [
%{
Notebook.Cell.new(:elixir)
| metadata: %{},
source: """
| source: """
Process.info()\
"""
}

View file

@ -2782,14 +2782,13 @@ defmodule Livebook.Session.DataTest do
{:insert_cell, self(), "s1", 0, :elixir, "c1"}
])
metadata = %{"disable_formatting" => true}
attrs = %{metadata: metadata}
attrs = %{disable_formatting: true}
operation = {:set_cell_attributes, self(), "c1", attrs}
assert {:ok,
%{
notebook: %{
sections: [%{cells: [%{metadata: ^metadata}]}]
sections: [%{cells: [%{disable_formatting: true}]}]
}
}, _} = Data.apply_operation(data, operation)
end

View file

@ -173,7 +173,7 @@ defmodule Livebook.SessionTest do
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id)
attrs = %{metadata: %{"disable_formatting" => true}}
attrs = %{disable_formatting: true}
Session.set_cell_attributes(session_id, cell_id, attrs)
assert_receive {:operation, {:set_cell_attributes, ^pid, ^cell_id, ^attrs}}