Skip to content

Commit 6442686

Browse files
committed
use auto-rendering interfaces, as decided in #166
TODO: keep []xreflect.Type returned by eval(), to preserve interpreted methods
1 parent 44fda23 commit 6442686

File tree

5 files changed

+229
-99
lines changed

5 files changed

+229
-99
lines changed

display.go

Lines changed: 120 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,28 @@ const (
2828
MIMETypePNG = "image/png"
2929
MIMETypePDF = "application/pdf"
3030
MIMETypeSVG = "image/svg+xml"
31+
MIMETypeText = "text/plain"
3132
)
3233

3334
/**
34-
* general interface, allows libraries to fully control
35-
* how their data is displayed by gophernotes.
35+
* general interface, allows libraries to fully specify
36+
* how their data is displayed by Jupyter.
3637
* Supports multiple MIME formats.
3738
*
38-
* Note that Data is an alias:
39-
* type Data = struct { Data, Metadata, Transient map[string]interface{} }
40-
* thus libraries can implement Renderer without importing gophernotes
39+
* Note that Data defined above is an alias:
40+
* libraries can implement Renderer without importing gophernotes
4141
*/
4242
type Renderer = interface {
4343
Render() Data
4444
}
4545

4646
/**
47-
* simplified interface, allows libraries to control
48-
* how their data is displayed by gophernotes.
47+
* simplified interface, allows libraries to specify
48+
* how their data is displayed by Jupyter.
4949
* It only supports a single MIME format.
5050
*
51-
* Note that MIMEMap is an alias
52-
* type MIMEMap = map[string]interface{}
53-
* thus libraries can implement SimpleRenderer without importing gophernotes
51+
* Note that MIMEMap defined above is an alias:
52+
* libraries can implement SimpleRenderer without importing gophernotes
5453
*/
5554
type SimpleRenderer = interface {
5655
Render() MIMEMap
@@ -66,7 +65,7 @@ type SimpleRenderer = interface {
6665
type HTMLer = interface {
6766
HTML() string
6867
}
69-
type Javascripter = interface {
68+
type JavaScripter = interface {
7069
JavaScript() string
7170
}
7271
type JPEGer = interface {
@@ -97,57 +96,133 @@ func stubDisplay(Data) error {
9796
return errors.New("cannot display: connection with Jupyter not available")
9897
}
9998

100-
// TODO handle the metadata
99+
// return true if data type should be auto-rendered graphically
100+
func canAutoRender(data interface{}) bool {
101+
switch data.(type) {
102+
case Data, Renderer, SimpleRenderer, HTMLer, JavaScripter, JPEGer, JSONer,
103+
Latexer, Markdowner, PNGer, PDFer, SVGer, image.Image:
104+
return true
105+
default:
106+
return false
107+
}
108+
}
101109

102-
func read(data interface{}) ([]byte, string) {
110+
// detect and render data types that should be auto-rendered graphically
111+
func autoRender(mimeType string, data interface{}) Data {
112+
var s string
103113
var b []byte
114+
var err error
115+
var ret Data
116+
switch data := data.(type) {
117+
case Data:
118+
ret = data
119+
case Renderer:
120+
ret = data.Render()
121+
case SimpleRenderer:
122+
ret.Data = data.Render()
123+
case HTMLer:
124+
s = data.HTML()
125+
case JavaScripter:
126+
mimeType = MIMETypeJavaScript
127+
s = data.JavaScript()
128+
case JPEGer:
129+
mimeType = MIMETypeJPEG
130+
b = data.JPEG()
131+
case JSONer:
132+
ret.Data = MIMEMap{MIMETypeJSON: data.JSON()}
133+
case Latexer:
134+
mimeType = MIMETypeLatex
135+
s = data.Latex()
136+
case Markdowner:
137+
mimeType = MIMETypeMarkdown
138+
s = data.Markdown()
139+
case PNGer:
140+
mimeType = MIMETypePNG
141+
b = data.PNG()
142+
case PDFer:
143+
mimeType = MIMETypePDF
144+
b = data.PDF()
145+
case SVGer:
146+
mimeType = MIMETypeSVG
147+
s = data.SVG()
148+
case image.Image:
149+
b, mimeType, err = encodePng(data)
150+
if err == nil {
151+
ret.Metadata = imageMetadata(data)
152+
}
153+
default:
154+
panic(fmt.Errorf("internal error, autoRender invoked on unexpected type %T", data))
155+
}
156+
return fillDefaults(ret, data, s, b, mimeType, err)
157+
}
158+
159+
func fillDefaults(ret Data, data interface{}, s string, b []byte, mimeType string, err error) Data {
160+
if err != nil {
161+
return makeDataErr(err)
162+
}
163+
if ret.Data == nil {
164+
ret.Data = make(MIMEMap)
165+
}
166+
if ret.Data[MIMETypeText] == "" {
167+
if len(s) == 0 {
168+
s = fmt.Sprint(data)
169+
}
170+
ret.Data[MIMETypeText] = s
171+
}
172+
if len(b) != 0 {
173+
if len(mimeType) == 0 {
174+
mimeType = http.DetectContentType(b)
175+
}
176+
if len(mimeType) != 0 && mimeType != MIMETypeText {
177+
ret.Data[mimeType] = b
178+
}
179+
}
180+
return ret
181+
}
182+
183+
// do our best to render data graphically
184+
func render(mimeType string, data interface{}) Data {
185+
if canAutoRender(data) {
186+
return autoRender(mimeType, data)
187+
}
104188
var s string
105-
switch x := data.(type) {
189+
var b []byte
190+
var err error
191+
switch data := data.(type) {
106192
case string:
107-
s = x
193+
s = data
108194
case []byte:
109-
b = x
195+
b = data
110196
case io.Reader:
111-
bb, err := ioutil.ReadAll(x)
112-
if err != nil {
113-
panic(err)
114-
}
115-
b = bb
197+
b, err = ioutil.ReadAll(data)
116198
case io.WriterTo:
117199
var buf bytes.Buffer
118-
x.WriteTo(&buf)
200+
data.WriteTo(&buf)
119201
b = buf.Bytes()
120202
default:
121-
panic(errors.New(fmt.Sprintf("unsupported type, cannot display: expecting string, []byte, io.Reader or io.WriterTo, found %T", data)))
122-
}
123-
if len(s) == 0 {
124-
s = fmt.Sprint(data)
203+
panic(fmt.Errorf("unsupported type, cannot render: %T", data))
125204
}
126-
return b, s
205+
return fillDefaults(Data{}, data, s, b, mimeType, err)
127206
}
128207

129-
func Any(mimeType string, data interface{}) Data {
130-
if img, ok := data.(image.Image); ok {
131-
return Image(img)
132-
}
133-
b, s := read(data)
134-
if len(mimeType) == 0 {
135-
mimeType = http.DetectContentType(b)
136-
}
137-
d := Data{
208+
func makeDataErr(err error) Data {
209+
return Data{
138210
Data: MIMEMap{
139-
"text/plain": s,
211+
"ename": "ERROR",
212+
"evalue": err.Error(),
213+
"traceback": nil,
214+
"status": "error",
140215
},
141216
}
142-
if mimeType != "text/plain" {
143-
d.Data[mimeType] = b
144-
}
145-
return d
217+
}
218+
219+
func Any(mimeType string, data interface{}) Data {
220+
return render(mimeType, data)
146221
}
147222

148223
// same as Any("", data), autodetects MIME type
149224
func Auto(data interface{}) Data {
150-
return Any("", data)
225+
return render("", data)
151226
}
152227

153228
func MakeData(mimeType string, data interface{}) Data {
@@ -156,16 +231,16 @@ func MakeData(mimeType string, data interface{}) Data {
156231
mimeType: data,
157232
},
158233
}
159-
if mimeType != "text/plain" {
160-
d.Data["text/plain"] = fmt.Sprint(data)
234+
if mimeType != MIMETypeText {
235+
d.Data[MIMETypeText] = fmt.Sprint(data)
161236
}
162237
return d
163238
}
164239

165240
func MakeData3(mimeType string, plaintext string, data interface{}) Data {
166241
return Data{
167242
Data: MIMEMap{
168-
"text/plain": plaintext,
243+
MIMETypeText: plaintext,
169244
mimeType: data,
170245
},
171246
}

examples/Display.ipynb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,95 @@
456456
"func MakeData3(mimeType string, plaintext string, data interface{}) Data\n",
457457
"func MIME(data, metadata MIMEMap) Data"
458458
]
459+
},
460+
{
461+
"cell_type": "markdown",
462+
"metadata": {},
463+
"source": [
464+
"<h1 id=\"advanced\">Advanced usage</h1>\n",
465+
"<p>gophernotes also provides the following interfaces, mainly targeted at authors of Go libraries.</p>\n",
466+
"<p>If the only non-nil value returned at prompt has type <code>Data</code> or implements one of these interfaces, it will be automatically rendered graphically<p>\n",
467+
"<p>Note: implementing these interface in interpreted code will not work as expected, because of the following known limitation of the underlying Go interpreter: if an interpreted type is converted to <code>interface{}</code>, it forgets all its methods - and gophernotes automatically converts to <code>interface{}</code> all values returned at prompt.<p>"
468+
]
469+
},
470+
{
471+
"cell_type": "code",
472+
"execution_count": 2,
473+
"metadata": {},
474+
"outputs": [],
475+
"source": [
476+
"// MIMEMap holds data that can be presented in multiple formats. The keys are MIME types\n",
477+
"// and the values are the data formatted with respect to its MIME type.\n",
478+
"// All maps should contain at least a \"text/plain\" representation with a string value.\n",
479+
"type MIMEMap = map[string]interface{}\n",
480+
"\n",
481+
"// Data is the exact structure returned to Jupyter.\n",
482+
"// It allows to fully specify how a value should be displayed.\n",
483+
"type Data = struct {\n",
484+
"\tData MIMEMap\n",
485+
"\tMetadata MIMEMap\n",
486+
"\tTransient MIMEMap\n",
487+
"}\n",
488+
"\n",
489+
"/**\n",
490+
" * general interface, allows libraries to fully specify\n",
491+
" * how their data is displayed by Jupyter.\n",
492+
" * Supports multiple MIME formats.\n",
493+
" *\n",
494+
" * Note that Data defined above is an alias:\n",
495+
" * libraries can implement Renderer without importing gophernotes\n",
496+
" */\n",
497+
"type Renderer = interface {\n",
498+
"\tRender() Data\n",
499+
"}\n",
500+
"\n",
501+
"/**\n",
502+
" * simplified interface, allows libraries to specify\n",
503+
" * how their data is displayed by Jupyter.\n",
504+
" * It only supports a single MIME format.\n",
505+
" *\n",
506+
" * Note that MIMEMap defined above is an alias:\n",
507+
" * libraries can implement SimpleRenderer without importing gophernotes\n",
508+
" */\n",
509+
"type SimpleRenderer = interface {\n",
510+
"\tRender() MIMEMap\n",
511+
"}\n",
512+
"\n",
513+
"/**\n",
514+
" * specialized interfaces, each is dedicated to a specific MIME type.\n",
515+
" *\n",
516+
" * They are type aliases to emphasize that method signatures\n",
517+
" * are the only important thing, not the interface names.\n",
518+
" * Thus libraries can implement them without importing gophernotes\n",
519+
" */\n",
520+
"type HTMLer = interface {\n",
521+
"\tHTML() string\n",
522+
"}\n",
523+
"type JavaScripter = interface {\n",
524+
"\tJavaScript() string\n",
525+
"}\n",
526+
"type JPEGer = interface {\n",
527+
"\tJPEG() []byte\n",
528+
"}\n",
529+
"type JSONer = interface {\n",
530+
"\tJSON() map[string]interface{}\n",
531+
"}\n",
532+
"type Latexer = interface {\n",
533+
"\tLatex() string\n",
534+
"}\n",
535+
"type Markdowner = interface {\n",
536+
"\tMarkdown() string\n",
537+
"}\n",
538+
"type PNGer = interface {\n",
539+
"\tPNG() []byte\n",
540+
"}\n",
541+
"type PDFer = interface {\n",
542+
"\tPDF() []byte\n",
543+
"}\n",
544+
"type SVGer = interface {\n",
545+
"\tSVG() string\n",
546+
"}"
547+
]
459548
}
460549
],
461550
"metadata": {

0 commit comments

Comments
 (0)