Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add value name lookup for lambdas
Browse files Browse the repository at this point in the history
agentgt committed Apr 18, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 00be427 commit 97e42a4
Showing 3 changed files with 160 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/main/java/com/samskivert/mustache/Mustache.java
Original file line number Diff line number Diff line change
@@ -1445,7 +1445,7 @@ protected SectionSegment (SectionSegment original, Template.Segment[] segs) {
}
} else if (value instanceof Lambda) {
try {
((Lambda)value).execute(tmpl.createFragment(_segs, ctx), out);
((Lambda)value).execute(tmpl.createFragment(_segs, ctx, _line), out);
} catch (IOException ioe) {
throw new MustacheException(ioe);
}
@@ -1556,7 +1556,7 @@ protected InvertedSegment (InvertedSegment original, Template.Segment[] segs) {
}
} else if (value instanceof InvertibleLambda) {
try {
((InvertibleLambda)value).executeInverse(tmpl.createFragment(_segs, ctx), out);
((InvertibleLambda)value).executeInverse(tmpl.createFragment(_segs, ctx, _line), out);
} catch (IOException ioe) {
throw new MustacheException(ioe);
}
19 changes: 18 additions & 1 deletion src/main/java/com/samskivert/mustache/Template.java
Original file line number Diff line number Diff line change
@@ -80,12 +80,20 @@ public String execute (Object context) {
* to inspect it (be that a {@code Map} or a POJO or something else). */
public abstract Object context ();

/** Like {@link #context()} btu returns the {@code n}th parent context object. {@code 0}
/** Like {@link #context()} but returns the {@code n}th parent context object. {@code 0}
* returns the same value as {@link #context()}, {@code 1} returns the parent context,
* {@code 2} returns the grandparent and so forth. Note that if you request a parent that
* does not exist an exception will be thrown. You should only use this method when you
* know your lambda is run consistently in a context with a particular lineage. */
public abstract Object context (int n);

/**
* Searches up the context stack for a matching name and returns the value associated.
* Names maybe dotted (also known as compound). Thisis equivalent to referencing a variable
* in a template like <code>{{name}}</code>. If no value is found or the value found is
* <code>null</code> then <code>null</code> is returned.
*/
public abstract /* @Nullable */ Object valueOrNull (String name);

/** Decompiles the template inside this lamdba and returns <em>an approximation</em> of
* the original template from which it was parsed. This is not the exact character for
@@ -197,7 +205,12 @@ protected void executeSegs (Context ctx, Writer out) throws MustacheException {
}
}

@Deprecated
protected Fragment createFragment (final Segment[] segs, final Context currentCtx) {
return createFragment(segs, currentCtx, 0);
}

protected Fragment createFragment (final Segment[] segs, final Context currentCtx, int line) {
return new Fragment() {
@Override public void execute (Writer out) {
execute(currentCtx, out);
@@ -214,6 +227,10 @@ protected Fragment createFragment (final Segment[] segs, final Context currentCt
@Override public Object context (int n) {
return context(currentCtx, n);
}
@Override
public /* @Nullable */ Object valueOrNull(String name) {
return Template.this.getValue(currentCtx, name, line, true);
}
@Override public StringBuilder decompile (StringBuilder into) {
for (Segment seg : segs) seg.decompile(_compiler.delims, into);
return into;
140 changes: 140 additions & 0 deletions src/test/java/com/samskivert/mustache/LocaleLambdaTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.samskivert.mustache;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.Test;

import com.samskivert.mustache.Template.Fragment;

public class LocaleLambdaTest {

@Test
public void testLocaleSpike() {
Map<String, Object> model = new HashMap<>();
Map<String, Object> user = new HashMap<>();
user.put("name", "Michael");
user.put("balance", new BigDecimal("5.50"));
model.put("user", user);

/*
* This is our pretend resource bundle.
*/
Map<String,String> messages = new HashMap<>();
messages.put("hello.welcome", "Hello {0}, & btw you owe {1,number,currency}!");

/*
* Here is our magic i18n
*/
MessageLambda ml = new MessageLambda(messages::get, Locale.US, Escapers.HTML);
model.put("@message", ml);
String template = "{{#@message}}hello.welcome(user.name,user.balance){{/@message}}";
String actual = Mustache.compiler().compile(template).execute(model);
String expected = "Hello Michael, &amp; btw you owe $5.50!";
assertEquals(expected, actual);
}
static class MessageLambda implements Mustache.Lambda {

private final Function<String,String> bundle;
private final Locale locale;
private final Mustache.Escaper escaper;

public MessageLambda(Function<String,String> bundle, Locale locale, Mustache.Escaper escaper) {
super();
this.bundle = bundle;
this.locale = locale;
this.escaper = escaper;
}

@Override
public void execute(Fragment frag, Writer out) throws IOException {
String body = frag.decompile();
MessageFunction function = parseDSL(body);
String key = function.getKey();
String message = bundle.apply(key);
if (message == null) {
throw new RuntimeException("Bundle missing key: " + key);
}
MessageFormat mf = new MessageFormat(message, locale);
/*
* Replace the args with values from the context.
*/
Object[] args = function.getParams().stream().map(k -> frag.valueOrNull(k)).toArray();
String response = mf.format(args);
escaper.escape(out, response);
}

}

/*
* Our format is
*
* key(param,...)
*
*/
public static MessageFunction parseDSL(String input) {
if (! input.contains("(")) {
return new MessageFunction(input, Collections.emptyList());
}
// Chat GPT wrote this garbage but it looks good to me... well after I edited.
// Regular expression pattern to match the DSL syntax
Pattern pattern = Pattern.compile("^([a-zA-Z0-9\\.\\-_]+)\\((.*?)\\)$");
Matcher matcher = pattern.matcher(input);

if (matcher.matches()) {
String key = matcher.group(1);
String paramsStr = matcher.group(2);

List<String> params = parseParameters(paramsStr);
return new MessageFunction(key, params);
}

return null; // Invalid DSL syntax
}

private static List<String> parseParameters(String paramsStr) {
List<String> params = new ArrayList<>();

// Split parameters by commas
String[] paramTokens = paramsStr.split(",");

// Add each parameter to the list
for (String param : paramTokens) {
params.add(param.trim()); // Remove any surrounding whitespace
}

return params;
}

static class MessageFunction {
private String key;
private List<String> params;

public MessageFunction(String key, List<String> params) {
this.key = key;
this.params = params;
}

public String getKey() {
return key;
}

public List<String> getParams() {
return params;
}
}

}

0 comments on commit 97e42a4

Please sign in to comment.