Earnest beginnings.

This commit is contained in:
Michael Bayne
2010-10-21 06:32:37 +00:00
parent 12c7860e91
commit 5640791b4b
4 changed files with 355 additions and 0 deletions
@@ -0,0 +1,110 @@
//
// $Id$
package com.samskivert.mustache;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
/**
* Provides <a href="http://mustache.github.com/">Mustache</a> templating services.
*/
public class Mustache
{
/**
* Compiles the supplied template into a repeatedly executable intermediate form.
*/
public static Template compile (String template)
{
return compile(new StringReader(template));
}
/**
* Compiles the supplied template into a repeatedly executable intermediate form.
*/
public static Template compile (Reader template)
{
return null; // TODO
}
private Mustache () {} // no instantiateski
/** A simple segment that reproduces a string. */
protected static class StringSegment extends Template.Segment {
public StringSegment (String text) {
_text = text;
}
@Override public void execute (Object ctx, Writer out) {
write(out, _text);
}
protected final String _text;
}
/** A segment that substitutes the contents of a variable. */
protected static class VariableSegment extends Template.Segment {
public VariableSegment (String name) {
_name = name;
}
@Override public void execute (Object ctx, Writer out) {
Object value = getValue(ctx, _name);
// TODO: configurable behavior on missing values
if (value != null) {
write(out, String.valueOf(value));
}
}
protected final String _name;
}
/** A segment that represents a section. */
protected static class SectionSegment extends Template.Segment {
public SectionSegment (String name, Template.Segment[] segs) {
_name = name;
_segs = segs;
}
@Override public void execute (Object ctx, Writer out) {
Object value = getValue(ctx, _name);
if (value == null) {
return; // TODO: configurable behavior on missing values
}
if (value instanceof Iterable<?>) {
Iterable<?> iable = (Iterable<?>)value;
for (Object elem : iable) {
executeSegs(elem, out);
}
} else if (value instanceof Boolean) {
if ((Boolean)value) {
executeSegs(ctx, out);
}
} else if (value.getClass().isArray()) {
for (int ii = 0, ll = Array.getLength(value); ii < ll; ii++) {
executeSegs(Array.get(value, ii), out);
}
} else if (value instanceof Iterator<?>) {
Iterator<?> iter = (Iterator<?>)value;
while (iter.hasNext()) {
executeSegs(iter.next(), out);
}
} else {
executeSegs(value, out);
}
}
protected void executeSegs (Object ctx, Writer out) {
for (Template.Segment seg : _segs) {
seg.execute(ctx, out);
}
}
protected String _name;
protected Template.Segment[] _segs;
}
}
@@ -0,0 +1,22 @@
//
// $Id$
package com.samskivert.mustache;
/**
* An exception thrown when an error occurs parsing or executing a Mustache template.
*/
public class MustacheException extends RuntimeException
{
public MustacheException (String message) {
super(message);
}
public MustacheException (Throwable cause) {
super(cause);
}
public MustacheException (String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,87 @@
//
// $Id$
package com.samskivert.mustache;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
/**
* Represents a compiled template.
*/
public class Template
{
/**
* Executes this template with the supplied data, writing the results to the supplied writer.
*
* <p>The data can be any tree of objects. Given a name <code>foo</code>, the following
* mechanisms are supported for resolving its value (and are sought in this order):
* <ul>
* <li>If the object is a {@link Map}, {@link Map#get} will be called with the string
* <code>foo</code> as the key.
* <li>A method named <code>foo</code> in the supplied object (with non-void return value).
* <li>A method named <code>getFoo</code> in the supplied object (with non-void return value).
* <li>A field named <code>foo</code> in the supplied object.
* </ul>
* </p><p> The field type, method return type, or map value type should correspond to the
* desired behavior if the resolved name corresponds to a section. {@link Boolean} is used for
* showing or hiding sections without binding a sub-context. Arrays, {@link Iterator} and
* {@link Iterable} implementations are used for sections that repeat, with the context bound
* to the elements of the array, iterator or iterable. Lambdas are current unsupported, though
* they would be easy enough to add if desire exists. See the <a
* href="http://mustache.github.com/mustache.5.html">Mustache documentation</a> for more
* details on section behavior. </p>
*
* @throws MustacheException if an error occurs while writing the template.
*/
public void execute (Object data, Writer out) throws MustacheException
{
for (Segment seg : _segs) {
seg.execute(data, out);
}
}
/**
* Executes this template with the supplied data, returning the results as a string. See {@link
* #execute(Object, Writer).
*
* @throws MustacheException if an error occurs while writing the template.
*/
public String execute (Object data) throws MustacheException
{
StringWriter out = new StringWriter();
execute(data, out);
return out.toString();
}
protected Template (Segment[] segs)
{
_segs = segs;
}
/** A template is broken into segments. */
protected static abstract class Segment
{
abstract void execute (Object ctx, Writer out);
protected Object getValue (Object ctx, String name) {
// TODO: support things other than values
return ((Map<?,?>)ctx).get(name);
}
protected static void write (Writer out, String data) {
try {
out.write(data);
} catch (IOException ioe) {
throw new MustacheException(ioe);
}
}
}
protected final Segment[] _segs;
}