Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add value name lookup for lambdas #176

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/main/java/com/samskivert/mustache/Mustache.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
19 changes: 18 additions & 1 deletion src/main/java/com/samskivert/mustache/Template.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
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;
}
}

}
Loading