Skip to content

Commit

Permalink
feat: decode cell attachments in raw and markdown cells
Browse files Browse the repository at this point in the history
The testing package got the corresponding fixture and is now made public
(internal/test -> pkg/test) so that it can be used in external packages
providing nb extensions.
  • Loading branch information
bevzzz committed Jan 25, 2024
1 parent 782a14d commit e090cd6
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 117 deletions.
98 changes: 84 additions & 14 deletions decode/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ type Cell struct {
Text []byte
}

type WithAttachments struct {
Cell
Filename string
MimeType string
Data []byte
}

func (w WithAttachments) HasAttachments() bool {
return w.Filename != ""
}

func TestDecodeBytes(t *testing.T) {
t.Run("notebook", func(t *testing.T) {
for _, tt := range []struct {
Expand Down Expand Up @@ -54,19 +65,30 @@ func TestDecodeBytes(t *testing.T) {
for _, tt := range []struct {
name string
json string
want Cell
want WithAttachments
}{
{
name: "v4.4",
json: `{
"nbformat": 4, "nbformat_minor": 4, "metadata": {}, "cells": [
{"cell_type": "markdown", "metadata": {}, "source": ["Join", " ", "me"]}
{"cell_type": "markdown", "metadata": {}, "source": [
"Look", " at ", "me: ![alt](attachment:photo.png)"
], "attachments": {
"photo.png": {
"image/png": "base64-encoded-image-data"
}
}}
]
}`,
want: Cell{
Type: schema.Markdown,
MimeType: common.MarkdownText,
Text: []byte("Join me"),
want: WithAttachments{
Cell: Cell{
Type: schema.Markdown,
MimeType: common.MarkdownText,
Text: []byte("Look at me: ![alt](attachment:photo.png)"),
},
Filename: "photo.png",
MimeType: "image/png",
Data: []byte("base64-encoded-image-data"),
},
},
} {
Expand All @@ -77,7 +99,7 @@ func TestDecodeBytes(t *testing.T) {
got := nb.Cells()
require.Len(t, got, 1, "expected 1 cell")

checkCell(t, got[0], tt.want)
checkCellWithAttachments(t, got[0], tt.want)
})
}
})
Expand All @@ -86,7 +108,7 @@ func TestDecodeBytes(t *testing.T) {
for _, tt := range []struct {
name string
json string
want Cell
want WithAttachments
}{
{
name: "v4.4: no explicit mime-type",
Expand All @@ -95,11 +117,11 @@ func TestDecodeBytes(t *testing.T) {
{"cell_type": "raw", "source": ["Plain as the nose on your face"]}
]
}`,
want: Cell{
want: WithAttachments{Cell: Cell{
Type: schema.Raw,
MimeType: common.PlainText,
Text: []byte("Plain as the nose on your face"),
},
}},
},
{
name: "v4.4: metadata.format has specific mime-type",
Expand All @@ -108,11 +130,11 @@ func TestDecodeBytes(t *testing.T) {
{"cell_type": "raw", "metadata": {"format": "text/html"}, "source": ["<p>Hi, mom!</p>"]}
]
}`,
want: Cell{
want: WithAttachments{Cell: Cell{
Type: schema.Raw,
MimeType: "text/html",
Text: []byte("<p>Hi, mom!</p>"),
},
}},
},
{
name: "v4.4: metadata.raw_mimetype has specific mime-type",
Expand All @@ -121,10 +143,35 @@ func TestDecodeBytes(t *testing.T) {
{"cell_type": "raw", "metadata": {"raw_mimetype": "application/x-latex"}, "source": ["$$"]}
]
}`,
want: Cell{
want: WithAttachments{Cell: Cell{
Type: schema.Raw,
MimeType: "application/x-latex",
Text: []byte("$$"),
}},
},
{
name: "v4.4: with attachments",
json: `{
"nbformat": 4, "nbformat_minor": 4, "metadata": {}, "cells": [
{
"cell_type": "raw", "metadata": {},
"source": ["![alt](attachment:photo.png)"], "attachments": {
"photo.png": {
"image/png": "base64-encoded-image-data"
}
}
}
]
}`,
want: WithAttachments{
Cell: Cell{
Type: schema.Raw,
MimeType: common.PlainText,
Text: []byte("![alt](attachment:photo.png)"),
},
Filename: "photo.png",
MimeType: "image/png",
Data: []byte("base64-encoded-image-data"),
},
},
} {
Expand All @@ -135,7 +182,7 @@ func TestDecodeBytes(t *testing.T) {
got := nb.Cells()
require.Len(t, got, 1, "expected 1 cell")

checkCell(t, got[0], tt.want)
checkCellWithAttachments(t, got[0], tt.want)
})
}
})
Expand Down Expand Up @@ -398,6 +445,29 @@ func checkCell(tb testing.TB, got schema.Cell, want Cell) {
}
}

// checkCellWithAttachments compares the cell's type, content, and attachments to expected.
func checkCellWithAttachments(tb testing.TB, got schema.Cell, want WithAttachments) {
tb.Helper()
checkCell(tb, got, want.Cell)
if !want.HasAttachments() {
return
}

cell, ok := got.(schema.HasAttachments)
if !ok {
tb.Fatal("cell has no attachments (does not implement schema.HasAttachments)")
}

var mb schema.MimeBundle
att := cell.Attachments()
if mb = att.MimeBundle(want.Filename); mb == nil {
tb.Fatalf("no data for %s, want %q", want.Filename, want.Data)
}

require.Equal(tb, want.MimeType, mb.MimeType(), "reported mime-type")
require.Equal(tb, want.Data, mb.Text(), "attachment data")
}

// toCodeCell fails the test if the cell does not implement schema.CodeCell.
func toCodeCell(tb testing.TB, cell schema.Cell) schema.CodeCell {
tb.Helper()
Expand Down
2 changes: 1 addition & 1 deletion extension/adapter/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"testing"

"github.com/bevzzz/nb/extension/adapter"
"github.com/bevzzz/nb/internal/test"
"github.com/bevzzz/nb/pkg/test"
"github.com/bevzzz/nb/render"
"github.com/bevzzz/nb/schema"
)
Expand Down
2 changes: 1 addition & 1 deletion extension/extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/bevzzz/nb"
"github.com/bevzzz/nb/extension"
"github.com/bevzzz/nb/internal/test"
"github.com/bevzzz/nb/pkg/test"

Check failure on line 10 in extension/extension_test.go

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

no required module provides package github.com/bevzzz/nb/pkg/test; to add it:

Check failure on line 10 in extension/extension_test.go

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

no required module provides package github.com/bevzzz/nb/pkg/test; to add it:

Check failure on line 10 in extension/extension_test.go

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

no required module provides package github.com/bevzzz/nb/pkg/test; to add it:

Check failure on line 10 in extension/extension_test.go

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

no required module provides package github.com/bevzzz/nb/pkg/test; to add it:
"github.com/bevzzz/nb/render"
"github.com/bevzzz/nb/schema"
"github.com/stretchr/testify/require"
Expand Down
97 changes: 0 additions & 97 deletions internal/test/cell.go

This file was deleted.

2 changes: 1 addition & 1 deletion render/html/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"

"github.com/bevzzz/nb/internal/test"
"github.com/bevzzz/nb/pkg/test"
"github.com/bevzzz/nb/render"
"github.com/bevzzz/nb/render/html"
"github.com/bevzzz/nb/schema"
Expand Down
2 changes: 1 addition & 1 deletion render/html/wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/bevzzz/nb/internal/test"
"github.com/bevzzz/nb/pkg/test"
"github.com/bevzzz/nb/render/html"
"github.com/bevzzz/nb/schema"
"github.com/bevzzz/nb/schema/common"
Expand Down
2 changes: 1 addition & 1 deletion render/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"testing"

"github.com/bevzzz/nb/internal/test"
"github.com/bevzzz/nb/pkg/test"
"github.com/bevzzz/nb/render"
"github.com/bevzzz/nb/schema"
"github.com/bevzzz/nb/schema/common"
Expand Down
2 changes: 1 addition & 1 deletion schema/common/notebook.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
type Notebook struct {
VersionMajor int `json:"nbformat"`
VersionMinor int `json:"nbformat_minor"`
Metadata json.RawMessage `json:"metadata"`
Metadata json.RawMessage `json:"metadata"` // TODO: omitempty
Cells []json.RawMessage `json:"cells"`
}

Expand Down
13 changes: 13 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ type Cell interface {
Text() []byte
}

type HasAttachments interface {
// Attachments are only defined for v4.0 and above for markdown and raw cells
// and may be omitted in the JSON. Cells without attachments should return nil.
Attachments() Attachments
}

// CellType reports the intended cell type to the components that work
// with notebook cells through the Cell interface.
//
Expand Down Expand Up @@ -123,3 +129,10 @@ type MimeBundle interface {
// A renderer may want to fallback to this option if it is not able to render the richer mime-type.
PlainText() []byte
}

// Attachments are data for inline images stored as a mime-bundle keyed by filename.
type Attachments interface {
// MimeBundle returns a mime-bundle associated with the filename.
// If no data is present for the file, implementations should return nil.
MimeBundle(filename string) MimeBundle
}
Loading

0 comments on commit e090cd6

Please sign in to comment.