Skip to content

Commit c75e6bc

Browse files
committed
simplify @SpencerPark branch https://github.com/SpencerPark/gophernotes/tree/mime-rendering to implement rendering of HTML, JSON, JPEG, PNG, SVG, LaTeX...
New functions: display.PNG([]byte), display.JPEG([]byte), display.HTML(string), display.SVG(string) ...
1 parent 3c56c86 commit c75e6bc

File tree

4 files changed

+132
-3
lines changed

4 files changed

+132
-3
lines changed

display.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"image"
7+
r "reflect"
8+
"strings"
9+
10+
"github.com/cosmos72/gomacro/imports"
11+
)
12+
13+
// Support an interface similar - but not identical - to the IPython (canonical Jupyter kernel).
14+
// See http://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html#IPython.display.display
15+
// for a good overview of the support types. Note: This is missing _repr_markdown_ and _repr_javascript_.
16+
17+
var globalReceipt *msgReceipt // ugly global variable. any alternative?
18+
19+
const (
20+
mimeTypeHTML = "text/html"
21+
mimeTypeMarkdown = "text/markdown"
22+
mimeTypeLatex = "text/latex"
23+
mimeTypeSVG = "image/svg+xml"
24+
mimeTypePNG = "image/png"
25+
mimeTypeJPEG = "image/jpeg"
26+
mimeTypeJSON = "application/json"
27+
mimeTypeJavaScript = "application/javascript"
28+
)
29+
30+
// TODO handle the metadata
31+
32+
func render2(mimeType string, data interface{}) error {
33+
return render3(mimeType, fmt.Sprint(data), data)
34+
}
35+
36+
func render3(mimeType string, text string, data interface{}) error {
37+
receipt := globalReceipt
38+
if receipt == nil {
39+
return errors.New("msgReceipt is nil, cannot send display_data message")
40+
}
41+
return receipt.PublishDisplayData(
42+
bundledMIMEData{
43+
"text/plain": text,
44+
mimeType: data,
45+
}, make(bundledMIMEData))
46+
}
47+
48+
func HTML(html string) error {
49+
return render2(mimeTypeHTML, html)
50+
}
51+
52+
func Markdown(markdown string) error {
53+
return render2(mimeTypeMarkdown, markdown)
54+
}
55+
56+
func SVG(svg string) error {
57+
return render2(mimeTypeSVG, svg)
58+
}
59+
60+
func PNG(png []byte) error {
61+
return render3(mimeTypePNG, "{png-image}", png) // []byte are encoded as base64 by the marshaller
62+
}
63+
64+
func JPEG(jpeg []byte) error {
65+
return render3(mimeTypeJPEG, "{jpeg-image}", jpeg) // []byte are encoded as base64 by the marshaller
66+
}
67+
68+
func Image(img image.Image) error {
69+
return publishImage(img, globalReceipt)
70+
}
71+
72+
func Math(latex string) error {
73+
return render3(mimeTypeLatex, latex, "$$"+strings.Trim(latex, "$")+"$$")
74+
}
75+
76+
func Latex(latex string) error {
77+
return render3(mimeTypeLatex, latex, "$"+strings.Trim(latex, "$")+"$")
78+
}
79+
80+
func JSON(json map[string]interface{}) error {
81+
return render2(mimeTypeJSON, json)
82+
}
83+
84+
func JavaScript(javascript string) error {
85+
return render2(mimeTypeJavaScript, javascript)
86+
}
87+
88+
// MIME renders the data as a plain MIME bundle. The keys of the map are the MIME type of the
89+
// data (value) associated with that key. The data will be some JSON serializable object but the structure is
90+
// determined by what the frontend expects. Some easier-to-use formats supported by the Jupyter frontend
91+
// are provided by the various functions above.
92+
func MIME(data, metadata map[string]interface{}) error {
93+
return globalReceipt.PublishDisplayData(data, metadata)
94+
}
95+
96+
// prepare imports.Package for interpreted code
97+
var display = imports.Package{
98+
Binds: map[string]r.Value{
99+
"HTML": r.ValueOf(HTML),
100+
"JPEG": r.ValueOf(JPEG),
101+
"JSON": r.ValueOf(JSON),
102+
"JavaScript": r.ValueOf(JavaScript),
103+
"Latex": r.ValueOf(Latex),
104+
"Markdown": r.ValueOf(Markdown),
105+
"Math": r.ValueOf(Math),
106+
"MIME": r.ValueOf(MIME),
107+
"PNG": r.ValueOf(PNG),
108+
"SVG": r.ValueOf(SVG),
109+
},
110+
}
111+
112+
// allow import of "display" and "github.com/gopherdata/gophernotes" packages
113+
func init() {
114+
imports.Packages["display"] = display
115+
imports.Packages["github.com/gopherdata/gophernotes"] = display
116+
}

gophernotes

-34.2 MB
Binary file not shown.

image.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// then replaces it with a placeholder string equal to reflect.TypeOf(val).String()
1313
// to avoid overloading the front-end with huge amounts of output text:
1414
// fmt.Sprint(val) is often very large for an image
15-
func publishImages(vals []interface{}, receipt msgReceipt) []interface{} {
15+
func publishImages(vals []interface{}, receipt *msgReceipt) []interface{} {
1616
for i, val := range vals {
1717
if img, ok := val.(image.Image); ok {
1818
err := publishImage(img, receipt)
@@ -27,7 +27,7 @@ func publishImages(vals []interface{}, receipt msgReceipt) []interface{} {
2727
}
2828

2929
// publishImages sends a "display_data" broadcast message for given image.
30-
func publishImage(img image.Image, receipt msgReceipt) error {
30+
func publishImage(img image.Image, receipt *msgReceipt) error {
3131
bytes, mime, err := encodePng(img)
3232
if err != nil {
3333
return err

kernel.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ func runKernel(connectionFile string) {
9595
ir.Comp.Stdout = ioutil.Discard
9696
ir.Comp.Stderr = ioutil.Discard
9797

98+
// Inject the "display" package to render HTML, JSON, PNG, JPEG, SVG... from interpreted code
99+
_, err := ir.Comp.ImportPackageOrError("display", "display")
100+
if err != nil {
101+
log.Print(err)
102+
}
103+
98104
// Parse the connection info.
99105
var connInfo ConnectionInfo
100106

@@ -345,6 +351,13 @@ func handleExecuteRequest(ir *interp.Interp, receipt msgReceipt) error {
345351
io.Copy(&jupyterStdErr, rErr)
346352
}()
347353

354+
// set the globalReceipt variable used by rendering functions injected into the interpreter
355+
globalReceipt = &receipt
356+
defer func() {
357+
globalReceipt = nil
358+
}()
359+
360+
// eval
348361
vals, executionErr := doEval(ir, code)
349362

350363
// Close and restore the streams.
@@ -359,7 +372,7 @@ func handleExecuteRequest(ir *interp.Interp, receipt msgReceipt) error {
359372

360373
if executionErr == nil {
361374
// if one or more value is image.Image, display it instead
362-
vals = publishImages(vals, receipt)
375+
vals = publishImages(vals, &receipt)
363376

364377
content["status"] = "ok"
365378
content["user_expressions"] = make(map[string]string)

0 commit comments

Comments
 (0)