Skip to content

Instantly share code, notes, and snippets.

@ammmze
Created May 5, 2022 15:24
Show Gist options
  • Save ammmze/c1bf44265a4d4eb42c3985934e1f5085 to your computer and use it in GitHub Desktop.
Save ammmze/c1bf44265a4d4eb42c3985934e1f5085 to your computer and use it in GitHub Desktop.
Java Handlebars helpers matching mandrill
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.helper.IfHelper;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class ConditionalExpressionHelper extends IfHelper {
private enum ExpressionComparator {
EQUALS("=="),
NOT_EQUALS("!="),
LESS_THAN_OR_EQUALS("<="),
GREATER_THAN_OR_EQUALS(">="),
LESS_THAN("<"),
GREATER_THAN(">"),
;
private String comparator;
ExpressionComparator(String comparator) {
this.comparator = comparator;
}
public String getComparator() {
return comparator;
}
public static ExpressionComparator fromComparator(String comparator) {
for (ExpressionComparator exp : values()) {
if (exp.getComparator().equals(comparator)) {
return exp;
}
}
return null;
}
public static String[] comparators() {
List<String> comparators = new ArrayList<>();
for (ExpressionComparator exp : values()) {
comparators.add(exp.getComparator());
}
return comparators.toArray(new String[values().length]);
}
}
private final Pattern expressionPattern = Pattern.compile("(.*)(" + StringUtils.join(ExpressionComparator.comparators(), "|") + ")(.*)");
private Boolean comparison;
public ConditionalExpressionHelper() {
comparison = Boolean.TRUE;
}
public ConditionalExpressionHelper(Boolean comparison) {
this.comparison = comparison;
}
@Override
public Object apply(Object context, Options options) throws IOException {
Expression expression = getExpression(context, options);
if (expression == null){
return super.apply(context, options);
}
return super.apply(comparison.equals(expression.evaluate()), options);
}
private String unquote(String str) {
return str.replaceAll("^'(.*)'$", "$1")
.replaceAll("^\"(.*)\"$", "$1");
}
private Expression getExpression(Object context, Options options) {
if (!(context instanceof String)) {
return null;
}
Matcher matcher = expressionPattern.matcher((String) context);
if (!matcher.matches()) {
return null;
}
return new Expression(parseParameter(matcher.group(1).trim(), options),
ExpressionComparator.fromComparator(matcher.group(2).trim()),
parseParameter(matcher.group(3).trim(), options));
}
private Object parseParameter(String param, Options options) {
if (param.startsWith("\"") || param.startsWith("'")) {
return unquote(param);
}
try {
return NumberFormat.getInstance().parse(param);
} catch (ParseException e) {
return options.get(param);
}
}
public static class Expression {
private Object lh;
private ExpressionComparator comparator;
private Object rh;
public Expression(Object lh, ExpressionComparator comparator, Object rh) {
this.lh = lh;
this.comparator = comparator;
this.rh = rh;
}
public Object getLh() {
return lh;
}
public void setLh(Object lh) {
this.lh = lh;
}
public ExpressionComparator getComparator() {
return comparator;
}
public void setComparator(ExpressionComparator comparator) {
this.comparator = comparator;
}
public Object getRh() {
return rh;
}
public void setRh(Object rh) {
this.rh = rh;
}
public boolean evaluate() {
String exp = String.format("lh %s rh", comparator.getComparator());
ExpressionParser parser = new SpelExpressionParser();
return Boolean.TRUE.equals(parser.parseExpression(exp).getValue(this));
}
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
public class DateHelper implements Helper {
public static final String DEFAULT_FORMAT = "d/m/Y";
@Override
public Object apply(Object context, Options options) throws IOException {
String format = context instanceof String && StringUtils.isNotBlank((String) context) ? (String) context : DEFAULT_FORMAT;
return new SimpleDateFormat(convertPhpFormatToJavaFormat(format)).format(new Date());
}
private final static char ESC = '\\';
private final static Map<Character, String> FORMAT_MAP = new HashMap<Character, String>(){{
put('d', "dd"); // Day of the month, 2 digits with leading zeros. Example: 01 to 31
put('j', "d"); // Day of the month without leading zeros. Example: 1 to 31
put('D', "EEE"); // A textual representation of a day, three letters. Example: Mon through Sun
put('l', "EEEE"); // (lowercase 'L'). Example: A full textual representation of the day of the week. Example: Sunday through Saturday
put('N', "u"); // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0). Example: 1 (for Monday) through 7 (for Sunday)
put('W', "w"); // ISO-8601 week number of year, weeks starting on Monday. Example: Example: 42 (the 42nd week in the year)
put('F', "MMMM"); // A full textual representation of a month, such as January or March. Example: January through December
put('m', "MM"); // Numeric representation of a month, with leading zeros. Example: 01 through 12
put('M', "MMM"); // A short textual representation of a month, three letters. Example: Jan through Dec
put('n', "M"); // Numeric representation of a month, without leading zeros. Example: 1 through 12
put('o', "YYYY"); // ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0). Example: Examples: 1999 or 2003
put('y', "yy"); // A two digit representation of a year. Example: Examples: 99 or 03
put('Y', "yyyy"); // A full numeric representation of a year, 4 digits. Example: Examples: 1999 or 2003
put('A', "a"); // Uppercase Ante meridiem and Post meridiem. Example: AM or PM
put('g', "h"); // 12-hour format of an hour without leading zeros. Example: 1 through 12
put('h', "hh"); // 12-hour format of an hour with leading zeros. Example: 01 through 12
put('G', "H"); // 24-hour format of an hour without leading zeros. Example: 0 through 23
put('H', "HH"); // 24-hour format of an hour with leading zeros. Example: 00 through 23
put('i', "mm"); // Minutes with leading zeros. Example: 00 to 59
put('s', "ss"); // Seconds, with leading zeros. Example: 00 through 59
put('v', "SSS"); // Milliseconds (added in PHP 7.0.0). Same note applies as for u.. Example: Example: 654
put('e', "zzz"); // Timezone identifier (added in PHP 5.1.0). Example: Examples: UTC, GMT, Atlantic/Azores
put('O', "Z"); // Difference to Greenwich time (GMT) in hours. Example: Example: +0200
put('P', "XXX"); // Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3). Example: Example: +02:00
put('T', "zzz"); // Timezone abbreviation. Example: Examples: EST, MDT ...
put('c', "yyyy-MM-dd'T'HH:mm:ssXXX"); // ISO 8601 date (added in PHP 5). Example: 2004-02-12T15:19:21+00:00
put('r', "EEE, dd MMM yyyy HH:mm:ss Z"); // » RFC 2822 formatted date. Example: Example: Thu, 21 Dec 2000 16:01:07 +0200
// partially supported
put('a', "a"); // Lowercase Ante meridiem and Post meridiem. Example: am or pm ... EXCEPT: it ends up the same as A (upper case AM/PM instead of lower case am/pm)
put('z', "D"); // The day of the year (starting from 0). Example: 0 through 365 ... EXCEPT: in the java mapping, it starts with 1 instead of 0
// unsupported
put('S', ""); // English ordinal suffix for the day of the month, 2 characters. Example: st, nd, rd or th. Works well with j
put('w', ""); // Numeric representation of the day of the week. Example: 0 (for Sunday) through 6 (for Saturday)
put('t', ""); // Number of days in the given month. Example: 28 through 31
put('L', ""); // Whether it's a leap year. Example: 1 if it is a leap year, 0 otherwise.
put('B', ""); // Swatch Internet time. Example: 000 through 999
put('u', ""); // Microseconds (added in PHP 5.2.2). Note that date() will always generate 000000 since it takes an integer parameter, whereas DateTime::format() does support microseconds if DateTime was created with microseconds.. Example: Example: 654321
put('I', ""); // (capital i). Example: Whether or not the date is in daylight saving time. Example: 1 if Daylight Saving Time, 0 otherwise.
put('Z', ""); // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive.. Example: -43200 through 50400
put('U', ""); // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). Example: See also time()
}};
public static String convertPhpFormatToJavaFormat(String in) {
StringBuilder format = new StringBuilder();
boolean escaping = false;
for (int i = 0; i < in.length(); i++) {
boolean escaped = i > 0 && in.charAt(i - 1) == ESC;
char curr = in.charAt(i);
if (!escaping && escaped) {
format.append("'");
escaping = true;
}
if (escaping && !escaped && curr != ESC) {
format.append("'");
escaping = false;
}
if (!escaped && FORMAT_MAP.containsKey((curr))) {
format.append(FORMAT_MAP.get(curr));
} else if (escaped || curr != ESC) {
format.append(curr);
} else {
}
}
return format.toString();
}
}
package com.github.gist.ammmze.handlebars;
import com.github.jknack.handlebars.Handlebars;
import com.github.gist.ammmze.handlebars.helpers.ConditionalExpressionHelper;
import com.github.gist.ammmze.handlebars.helpers.DateHelper;
import com.github.gist.ammmze.handlebars.helpers.LowerHelper;
import com.github.gist.ammmze.handlebars.helpers.StripTagsHelper;
import com.github.gist.ammmze.handlebars.helpers.TitleHelper;
import com.github.gist.ammmze.handlebars.helpers.UnsubHelper;
import com.github.gist.ammmze.handlebars.helpers.UpperHelper;
import com.github.gist.ammmze.handlebars.helpers.UrlEncodeHelper;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class HandlebarsImpl extends Handlebars {
@PostConstruct
public void postConstruct() {
registerHelper("if", new ConditionalExpressionHelper());
registerHelper("else if", new ConditionalExpressionHelper());
registerHelper("unless", new ConditionalExpressionHelper(Boolean.FALSE));
registerHelper("upper", new UpperHelper());
registerHelper("lower", new LowerHelper());
registerHelper("title", new TitleHelper());
registerHelper("url", new UrlEncodeHelper());
registerHelper("date", new DateHelper());
registerHelper("striptags", new StripTagsHelper());
registerHelper("unsub", new UnsubHelper());
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
public class LowerHelper implements Helper<String> {
@Override
public Object apply(String context, Options options) throws IOException {
return context == null ? null : context.toLowerCase();
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
public class StripTagsHelper implements Helper<String> {
@Override
public Object apply(String context, Options options) throws IOException {
return context == null ? null : context.replaceAll("<[^>]*>", "");
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import com.ibm.icu.lang.UCharacter;
import java.io.IOException;
public class TitleHelper implements Helper<String> {
@Override
public Object apply(String context, Options options) throws IOException {
return UCharacter.toTitleCase(context, null);
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
public class UnsubHelper implements Helper {
public final static String DEFAULT_URL = "";
@Override
public Object apply(Object context, Options options) throws IOException {
return context instanceof String ? context : DEFAULT_URL;
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
public class UpperHelper implements Helper<String> {
@Override
public Object apply(String context, Options options) throws IOException {
return context == null ? null : context.toUpperCase();
}
}
package com.github.gist.ammmze.handlebars.helpers;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import java.io.IOException;
import java.net.URLEncoder;
public class UrlEncodeHelper implements Helper<String> {
@Override
public Object apply(String context, Options options) throws IOException {
return context == null ? null : URLEncoder.encode(context, "UTF-8");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment