@@ -8,10 +8,13 @@ import (
88 "io"
99 "io/ioutil"
1010 "net/http"
11- r "reflect"
11+ "reflect"
12+ "sort"
1213 "strings"
1314
14- "github.com/cosmos72/gomacro/imports"
15+ "github.com/cosmos72/gomacro/base"
16+
17+ "github.com/cosmos72/gomacro/xreflect"
1518)
1619
1720// Support an interface similar - but not identical - to the IPython (canonical Jupyter kernel).
@@ -46,13 +49,13 @@ type Renderer = interface {
4649/**
4750 * simplified interface, allows libraries to specify
4851 * how their data is displayed by Jupyter.
49- * It only supports a single MIME format .
52+ * Supports multiple MIME formats .
5053 *
5154 * Note that MIMEMap defined above is an alias:
5255 * libraries can implement SimpleRenderer without importing gophernotes
5356 */
5457type SimpleRenderer = interface {
55- Render () MIMEMap
58+ SimpleRender () MIMEMap
5659}
5760
5861/**
@@ -96,31 +99,93 @@ func stubDisplay(Data) error {
9699 return errors .New ("cannot display: connection with Jupyter not available" )
97100}
98101
102+ // fill kernel.renderer map used to convert interpreted types
103+ // to known rendering interfaces
104+ func (kernel * Kernel ) initRenderers () {
105+ type Pair = struct {
106+ name string
107+ typ xreflect.Type
108+ }
109+ var pairs []Pair
110+ for name , typ := range kernel .display .Types {
111+ if typ .Kind () == reflect .Interface {
112+ pairs = append (pairs , Pair {name , typ })
113+ }
114+ }
115+ // for deterministic behaviour, sort alphabetically by name
116+ sort .Slice (pairs , func (i , j int ) bool {
117+ return pairs [i ].name < pairs [j ].name
118+ })
119+ kernel .render = make ([]xreflect.Type , len (pairs ))
120+ for i , pair := range pairs {
121+ kernel .render [i ] = pair .typ
122+ }
123+ }
124+
125+ // if vals[] contain a single non-nil value which is auto-renderable,
126+ // convert it to Data and return it.
127+ // otherwise return MakeData("text/plain", fmt.Sprint(vals...))
128+ func (kernel * Kernel ) autoRenderResults (vals []interface {}, types []xreflect.Type ) Data {
129+ var nilcount int
130+ var obj interface {}
131+ var typ xreflect.Type
132+ for i , val := range vals {
133+ if kernel .canAutoRender (val , types [i ]) {
134+ obj = val
135+ typ = types [i ]
136+ } else if val == nil {
137+ nilcount ++
138+ }
139+ }
140+ if obj != nil && nilcount == len (vals )- 1 {
141+ return kernel .autoRender ("" , obj , typ )
142+ }
143+ if nilcount == len (vals ) {
144+ // if all values are nil, return empty Data
145+ return Data {}
146+ }
147+ return MakeData (MIMETypeText , fmt .Sprint (vals ... ))
148+ }
149+
99150// return true if data type should be auto-rendered graphically
100- func canAutoRender (data interface {}) bool {
151+ func ( kernel * Kernel ) canAutoRender (data interface {}, typ xreflect. Type ) bool {
101152 switch data .(type ) {
102153 case Data , Renderer , SimpleRenderer , HTMLer , JavaScripter , JPEGer , JSONer ,
103154 Latexer , Markdowner , PNGer , PDFer , SVGer , image.Image :
104155 return true
105- default :
156+ }
157+ if kernel == nil || typ == nil {
106158 return false
107159 }
160+ // in gomacro, methods of interpreted types are emulated,
161+ // thus type-asserting them to interface types as done above cannot succeed.
162+ // Manually check if emulated type "pretends" to implement
163+ // one of the interfaces above
164+ for _ , xtyp := range kernel .render {
165+ if typ .Implements (xtyp ) {
166+ return true
167+ }
168+ }
169+ return false
108170}
109171
110172// detect and render data types that should be auto-rendered graphically
111- func autoRender (mimeType string , data interface {}) Data {
173+ func ( kernel * Kernel ) autoRender (mimeType string , arg interface {}, typ xreflect. Type ) Data {
112174 var s string
113175 var b []byte
114176 var err error
115177 var ret Data
116- switch data := data .(type ) {
178+ datain := arg
179+ again:
180+ switch data := datain .(type ) {
117181 case Data :
118182 ret = data
119183 case Renderer :
120184 ret = data .Render ()
121185 case SimpleRenderer :
122- ret .Data = data .Render ()
186+ ret .Data = data .SimpleRender ()
123187 case HTMLer :
188+ mimeType = MIMETypeHTML
124189 s = data .HTML ()
125190 case JavaScripter :
126191 mimeType = MIMETypeJavaScript
@@ -151,9 +216,29 @@ func autoRender(mimeType string, data interface{}) Data {
151216 ret .Metadata = imageMetadata (data )
152217 }
153218 default :
219+ if kernel != nil && typ != nil {
220+ // in gomacro, methods of interpreted types are emulated.
221+ // Thus type-asserting them to interface types as done above cannot succeed.
222+ // Manually check if emulated type "pretends" to implement one of the above interfaces
223+ // and, in case, tell the interpreter to convert to them
224+ for _ , xtyp := range kernel .render {
225+ if typ .Implements (xtyp ) {
226+ fun := kernel .ir .Comp .Converter (typ , xtyp )
227+ data = base .ValueInterface (fun (reflect .ValueOf (datain )))
228+ if data != nil {
229+ s = fmt .Sprint (data )
230+ datain = data
231+ // avoid infinite recursion
232+ kernel = nil
233+ typ = nil
234+ goto again
235+ }
236+ }
237+ }
238+ }
154239 panic (fmt .Errorf ("internal error, autoRender invoked on unexpected type %T" , data ))
155240 }
156- return fillDefaults (ret , data , s , b , mimeType , err )
241+ return fillDefaults (ret , arg , s , b , mimeType , err )
157242}
158243
159244func fillDefaults (ret Data , data interface {}, s string , b []byte , mimeType string , err error ) Data {
@@ -163,12 +248,18 @@ func fillDefaults(ret Data, data interface{}, s string, b []byte, mimeType strin
163248 if ret .Data == nil {
164249 ret .Data = make (MIMEMap )
165250 }
251+ // cannot autodetect the mime type of a string
252+ if len (s ) != 0 && len (mimeType ) != 0 {
253+ ret .Data [mimeType ] = s
254+ }
255+ // ensure plain text is set
166256 if ret .Data [MIMETypeText ] == "" {
167257 if len (s ) == 0 {
168258 s = fmt .Sprint (data )
169259 }
170260 ret .Data [MIMETypeText ] = s
171261 }
262+ // if []byte is available, use it
172263 if len (b ) != 0 {
173264 if len (mimeType ) == 0 {
174265 mimeType = http .DetectContentType (b )
@@ -182,8 +273,9 @@ func fillDefaults(ret Data, data interface{}, s string, b []byte, mimeType strin
182273
183274// do our best to render data graphically
184275func render (mimeType string , data interface {}) Data {
185- if canAutoRender (data ) {
186- return autoRender (mimeType , data )
276+ var kernel * Kernel // intentionally nil
277+ if kernel .canAutoRender (data , nil ) {
278+ return kernel .autoRender (mimeType , data , nil )
187279 }
188280 var s string
189281 var b []byte
@@ -304,45 +396,3 @@ func SVG(svg string) Data {
304396func MIME (data , metadata MIMEMap ) Data {
305397 return Data {data , metadata , nil }
306398}
307-
308- // prepare imports.Package for interpreted code
309- var display = imports.Package {
310- Binds : map [string ]r.Value {
311- "Any" : r .ValueOf (Any ),
312- "Auto" : r .ValueOf (Auto ),
313- "File" : r .ValueOf (File ),
314- "HTML" : r .ValueOf (HTML ),
315- "Image" : r .ValueOf (Image ),
316- "JPEG" : r .ValueOf (JPEG ),
317- "JSON" : r .ValueOf (JSON ),
318- "JavaScript" : r .ValueOf (JavaScript ),
319- "Latex" : r .ValueOf (Latex ),
320- "MakeData" : r .ValueOf (MakeData ),
321- "MakeData3" : r .ValueOf (MakeData3 ),
322- "Markdown" : r .ValueOf (Markdown ),
323- "Math" : r .ValueOf (Math ),
324- "MIME" : r .ValueOf (MIME ),
325- "MIMETypeHTML" : r .ValueOf (MIMETypeHTML ),
326- "MIMETypeJavaScript" : r .ValueOf (MIMETypeJavaScript ),
327- "MIMETypeJPEG" : r .ValueOf (MIMETypeJPEG ),
328- "MIMETypeJSON" : r .ValueOf (MIMETypeJSON ),
329- "MIMETypeLatex" : r .ValueOf (MIMETypeLatex ),
330- "MIMETypeMarkdown" : r .ValueOf (MIMETypeMarkdown ),
331- "MIMETypePDF" : r .ValueOf (MIMETypePDF ),
332- "MIMETypePNG" : r .ValueOf (MIMETypePNG ),
333- "MIMETypeSVG" : r .ValueOf (MIMETypeSVG ),
334- "PDF" : r .ValueOf (PDF ),
335- "PNG" : r .ValueOf (PNG ),
336- "SVG" : r .ValueOf (SVG ),
337- },
338- Types : map [string ]r.Type {
339- "Data" : r .TypeOf ((* Data )(nil )).Elem (),
340- "MIMEMap" : r .TypeOf ((* MIMEMap )(nil )).Elem (),
341- },
342- }
343-
344- // allow importing "display" and "github.com/gopherdata/gophernotes" packages
345- func init () {
346- imports .Packages ["display" ] = display
347- imports .Packages ["github.com/gopherdata/gophernotes" ] = display
348- }
0 commit comments