Skip to content

Commit

Permalink
Initial support for mime rendering. See #2.
Browse files Browse the repository at this point in the history
- Parse MIME types
- Register render functions for MIME types
- Displayable interface for owned classes
- Render with parameters and request rendering as certain types
  • Loading branch information
SpencerPark committed May 6, 2018
1 parent aebf87e commit ab43047
Show file tree
Hide file tree
Showing 26 changed files with 1,450 additions and 9 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.apache.tools.ant.filters.ReplaceTokens

group 'io.github.spencerpark'
version '2.0.0-SNAPSHOT'
version '2.1.0-SNAPSHOT'

apply plugin: 'java'
apply plugin: 'maven-publish'
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Sat Sep 30 21:00:10 EDT 2017
#Sat May 05 20:39:01 EDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import io.github.spencerpark.jupyter.kernel.comm.CommManager;
import io.github.spencerpark.jupyter.kernel.util.StringStyler;
import io.github.spencerpark.jupyter.kernel.util.TextColor;
import io.github.spencerpark.jupyter.messages.DisplayData;
import io.github.spencerpark.jupyter.kernel.display.DisplayData;
import io.github.spencerpark.jupyter.messages.Header;
import io.github.spencerpark.jupyter.messages.Message;
import io.github.spencerpark.jupyter.messages.MessageType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.spencerpark.jupyter.messages;
package io.github.spencerpark.jupyter.kernel.display;

import com.google.gson.annotations.SerializedName;
import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType;

import java.util.Collections;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -75,6 +76,22 @@ public void putTransientData(String key, Object value) {
this.transientData.put(key, value);
}

public void putData(MIMEType type, Object data) {
this.putData(type.toString(), data);
}

public void putMetaData(MIMEType type, Object data) {
this.putMetaData(type.toString(), data);
}

public void putData(MIMEType type, Object data, Object metadata) {
this.putData(type, data);
this.putMetaData(type, metadata);
}

public boolean hasDataForType(MIMEType type) {
return this.data.containsKey(type.toString());
}

public void putText(String text) {
this.putData("text/plain", text);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.github.spencerpark.jupyter.kernel.display;

import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType;

import java.util.Set;
import java.util.function.BiConsumer;

public interface DisplayDataRenderable {
/**
* Specifies a set of {@link MIMEType}s that this class may be rendered as.
* <p>
* NOTE: Specifying the supported render types does not prevent {@link #render(RenderContext)}
* from being invoked with other types. Implementations should handle these cases gracefully
* with a no-op.
* <p>
* When used in conjunction with {@link Renderer} this annotation provides information to the
* routing algorithm.
* <p>
* In particular {@link Renderer#render(Object)} will request that the object
* is rendered as the {@link #getPreferredRenderTypes()} types.
*/
public Set<MIMEType> getSupportedRenderTypes();

/**
* Species a subset of {@link #getSupportedRenderTypes()} in which this class
* prefers to be rendered as.
* <p>
* For example a class may support rendering as {@code application/json} and
* {@code application/xml} but when given a choice should only be rendered as
* {@code application/json}. In this case {@code getPreferredRenderTypes()} should
* be {@code "application/json"}.
*
* @return a set of {@link MIMEType}s that this class
* prefers to be rendered as.
*/
public default Set<MIMEType> getPreferredRenderTypes() {
return this.getSupportedRenderTypes();
}

/**
* Render this object into the {@link RenderContext#getOutputContainer()} based on the requested types
* from the {@code context}. Implementations may also use the {@code context}
* to delegate rendering.
* <p>
* Implementations should test if the {@link RenderContext#wantsDataRenderedAs(MIMEType)}
* for all of the supported types and if true store the rendered data in the container
* <strong>at the resolved MIME type, not the supported one.</strong> Use the type returned
* by {@link RenderContext#resolveRequestedType(MIMEType)}.
* <p>
* For convenience implementations may use {@link RenderContext#renderIfRequested(MIMEType, BiConsumer)}
* which streamlines these operations:
* <pre>
* {@code private static MIMEType PNG = MIMEType.parse("image/png");
* private String renderAsPNG() {...}
* public void render(RenderContext context) {
* context.renderIfRequested(PNG, (type, out) -> {
* out.putData(type, this.renderAsPNG());
* });
* // or to store the return value of renderAsPNG at the correct
* // type use
* context.renderIfRequested(PNG, this::renderAsPNG);
* // or if you need the type to make a rendering decision and then store
* // the return value at the correct type
* context.renderIfRequested(PNG, type -> this.renderAsPNG());
* }
* }
* </pre>
*
* @param context the context that the render is taking place in.
*/
public void render(RenderContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.spencerpark.jupyter.kernel.display;

import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType;

@FunctionalInterface
public interface MIMESuffixAssociation {
static final MIMESuffixAssociation NONE = s -> null;

/**
* Returns the delegate MIME type associated with a suffix. For example the
* suffix {@code json} is associated with the {@code application/json} type.
*
* @param suffix the suffix to resolve
*
* @return the delegate {@link MIMEType}
*/
MIMEType resolveSuffix(String suffix);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.github.spencerpark.jupyter.kernel.display;

import io.github.spencerpark.jupyter.kernel.display.mime.MIMEType;

import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class RenderContext {
private final RenderRequestTypes requestedTypes;
private final Renderer renderer;
private final Map<String, Object> params;
private final DisplayData out;

public RenderContext(RenderRequestTypes requestedTypes, Renderer renderer, Map<String, Object> params, DisplayData out) {
this.requestedTypes = requestedTypes;
this.renderer = renderer;
this.params = params;
this.out = out;
}

public Renderer getRenderer() {
return this.renderer;
}

public DisplayData getOutputContainer() {
return this.out;
}

public Object getParameter(String key) {
return this.params.get(key);
}

public Object getParameter(String key, Object defaultValue) {
return this.params.getOrDefault(key, defaultValue);
}

public String getParameterAsString(String key) {
Object value = this.getParameter(key);
return value == null ? null : String.valueOf(value);
}

public String getParameterAsString(String key, String defaultValue) {
String value = this.getParameterAsString(key);
return value == null ? defaultValue : value;
}

public Integer getParameterAsInt(String key) {
Object value = this.getParameter(key);
return value == null
? null
: value instanceof Number
? ((Number) value).intValue()
: Integer.parseInt(String.valueOf(value));
}

public Integer getParameterAsInt(String key, Integer defaultValue) {
Integer value = this.getParameterAsInt(key);
return value == null ? defaultValue : value;
}

public Double getParameterAsDouble(String key) {
Object value = this.getParameter(key);
return value == null
? null
: value instanceof Number
? ((Number) value).doubleValue()
: Double.parseDouble(String.valueOf(value));
}

public Double getParameterAsDouble(String key, Double defaultValue) {
Double value = this.getParameterAsDouble(key);
return value == null ? defaultValue : value;
}

public Boolean getParameterAsBoolean(String key) {
Object value = this.getParameter(key);
return value == null
? null
: value instanceof Boolean
? (Boolean) value
: Boolean.parseBoolean(String.valueOf(value));
}

public Boolean getParameterAsBoolean(String key, Boolean defaultValue) {
Boolean value = this.getParameterAsBoolean(key);
return value == null ? defaultValue : value;
}

public boolean wantsDataRenderedAs(MIMEType type) {
return this.requestedTypes.resolveSupportedType(type) != null;
}

public MIMEType resolveRequestedType(MIMEType supported) {
return this.requestedTypes.resolveSupportedType(supported);
}

public void renderIfRequested(MIMEType supportedType, BiConsumer<MIMEType, DisplayData> renderFunction) {
MIMEType resolvedType = this.requestedTypes.resolveSupportedType(supportedType);
if (resolvedType != null)
renderFunction.accept(resolvedType, this.getOutputContainer());
}

public void renderIfRequested(MIMEType supportedType, Function<MIMEType, Object> renderFunction) {
MIMEType resolvedType = this.requestedTypes.resolveSupportedType(supportedType);
if (resolvedType != null)
this.getOutputContainer().putData(resolvedType, renderFunction.apply(resolvedType));
}

public void renderIfRequested(MIMEType supportedType, Supplier<Object> render) {
MIMEType resolvedType = this.requestedTypes.resolveSupportedType(supportedType);
if (resolvedType != null)
this.getOutputContainer().putData(resolvedType, render.get());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.spencerpark.jupyter.kernel.display;

@FunctionalInterface
public interface RenderFunction<T> {
void render(T data, RenderContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.github.spencerpark.jupyter.kernel.display;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* A utility class for inline map construction for use in the context of rendering.
*
* See: {@link Renderer#render(Object, Map)} and {@link Renderer#renderAs(Object, Map, String...)}
* which take a parameter map.
*/
public class RenderParams extends LinkedHashMap<String, Object> {
//TODO use the path map from MellowD to support a getAll query or one with wildcard patterns
public static class Param<T> {
public final String key;
public final T value;

public Param(String key, T value) {
this.key = key;
this.value = value;
}
}

public static <T> Param<T> param(String key, T value) {
return new Param<>(key, value);
}

public static RenderParams paramsOf(Param... params) {
RenderParams renderParams = new RenderParams();
for (Param p : params)
renderParams.put(p.key, p.value);
return renderParams;
}

public static RenderParams paramsOf(String key, Object value) {
RenderParams renderParams = new RenderParams();
renderParams.put(key, value);
return renderParams;
}

public RenderParams with(String key, Object value) {
this.put(key, value);
return this;
}

public RenderParams with(Param param) {
this.put(param.key, param.value);
return this;
}

public RenderParams and(String key, Object value) {
return with(key, value);
}

public RenderParams and(Param param) {
return with(param);
}
}
Loading

0 comments on commit ab43047

Please sign in to comment.