package com.hubspot.jinjava.lib.fn; import static com.hubspot.jinjava.util.Logging.ENGINE_LOG; import java.time.Instant; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.math.NumberUtils; import com.google.common.collect.Lists; import com.hubspot.jinjava.JinjavaConfig; import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; import com.hubspot.jinjava.interpret.InterpretException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.objects.date.PyishDate; import com.hubspot.jinjava.objects.date.StrftimeFormatter; import com.hubspot.jinjava.tree.Node; public class Functions { public static final int RANGE_LIMIT = 1000; @JinjavaDoc(value = "Only usable within blocks, will render the contents of the parent block by calling super.", snippets = { @JinjavaSnippet(desc = "This gives back the results of the parent block", code = "{% block sidebar %}\n" + " <h3>Table Of Contents</h3>\n\n" + " ...\n" + " {{ super() }}\n" + "{% endblock %}") }) public static String renderSuperBlock() { JinjavaInterpreter interpreter = JinjavaInterpreter.getCurrent(); StringBuilder result = new StringBuilder(); List<? extends Node> superBlock = interpreter.getContext().getSuperBlock(); if (superBlock != null) { for (Node n : superBlock) { result.append(n.render(interpreter)); } } return result.toString(); } public static List<Object> immutableListOf(Object... items) { return Collections.unmodifiableList(Lists.newArrayList(items)); } @JinjavaDoc(value = "formats a date to a string", params = { @JinjavaParam(value = "var", type = "date", defaultValue = "current time"), @JinjavaParam(value = "format", defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT) }) public static String dateTimeFormat(Object var, String... format) { ZonedDateTime d = getDateTimeArg(var); if (d == null) { return ""; } Locale locale = JinjavaInterpreter.getCurrentMaybe() .map(JinjavaInterpreter::getConfig) .map(JinjavaConfig::getLocale) .orElse(Locale.ENGLISH); if (format.length > 0) { return StrftimeFormatter.format(d, format[0], locale); } else { return StrftimeFormatter.format(d, locale); } } private static ZonedDateTime getDateTimeArg(Object var) { ZonedDateTime d = null; if (var == null) { d = ZonedDateTime.now(ZoneOffset.UTC); } else if (var instanceof Number) { d = ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) var).longValue()), ZoneOffset.UTC); } else if (var instanceof PyishDate) { d = ((PyishDate) var).toDateTime(); } else if (var instanceof ZonedDateTime) { d = (ZonedDateTime) var; } else if (!ZonedDateTime.class.isAssignableFrom(var.getClass())) { throw new InterpretException("Input to function must be a date object, was: " + var.getClass()); } return d; } @JinjavaDoc(value = "gets the unix timestamp milliseconds value of a datetime", params = { @JinjavaParam(value = "var", type = "date", defaultValue = "current time"), }) public static long unixtimestamp(Object var) { ZonedDateTime d = getDateTimeArg(var); if (d == null) { return 0; } return d.toEpochSecond() * 1000; } private static final int DEFAULT_TRUNCATE_LENGTH = 255; private static final String DEFAULT_END = "..."; @JinjavaDoc(value = "truncates a given string to a specified length", params = { @JinjavaParam("s"), @JinjavaParam(value = "length", type = "number", defaultValue = "255"), @JinjavaParam(value = "end", defaultValue = "...") }) public static Object truncate(Object var, Object... arg) { if (var instanceof String) { int length = DEFAULT_TRUNCATE_LENGTH; boolean killwords = false; String ends = DEFAULT_END; if (arg.length > 0) { try { length = Integer.parseInt(Objects.toString(arg[0])); } catch (Exception e) { ENGINE_LOG.warn("truncate(): error setting length for {}, using default {}", arg[0], DEFAULT_TRUNCATE_LENGTH); } } if (arg.length > 1) { try { killwords = BooleanUtils.toBoolean(Objects.toString(arg[1])); } catch (Exception e) { ENGINE_LOG.warn("truncate(); error setting killwords for {}", arg[1]); } } if (arg.length > 2) { ends = Objects.toString(arg[2]); } String string = (String) var; if (string.length() > length) { if (!killwords) { length = movePointerToJustBeforeLastWord(length, string); } return string.substring(0, length) + ends; } else { return string; } } return var; } public static int movePointerToJustBeforeLastWord(int pointer, String s) { while (pointer > 0 && pointer < s.length()) { if (Character.isWhitespace(s.charAt(--pointer))) { break; } } return pointer + 1; } @JinjavaDoc(value = "<p>Return a list containing an arithmetic progression of integers.</p>" + "<p>With one parameter, range will return a list from 0 up to (but not including) the value. " + " With two parameters, the range will start at the first value and increment by 1 up to (but not including) the second value. " + " The third parameter specifies the step increment.</p> <p>All values can be negative. Impossible ranges will return an empty list.</p>" + "<p>Ranges can generate a maximum of " + RANGE_LIMIT + " values.</p>", params = { @JinjavaParam(value = "start", type = "number", defaultValue = "0"), @JinjavaParam(value = "end", type = "number"), @JinjavaParam(value = "step", type = "number", defaultValue = "1")}) public static List<Integer> range(Object arg1, Object... args) { List<Integer> result = new ArrayList<>(); int start = 0; int end; int step = 1; switch (args.length) { case 0: end = NumberUtils.toInt(arg1.toString()); break; case 1: start = NumberUtils.toInt(arg1.toString()); end = NumberUtils.toInt(args[0].toString()); break; default: start = NumberUtils.toInt(arg1.toString()); end = NumberUtils.toInt(args[0].toString()); step = NumberUtils.toInt(args[1].toString(), 1); } if (step == 0) { return result; } if (start < end) { if (step < 0) { return result; } for (int i = start; i < end; i += step) { if (result.size() >= RANGE_LIMIT) { break; } result.add(i); } } else { if (step > 0) { return result; } for (int i = start; i > end; i += step) { if (result.size() >= RANGE_LIMIT) { break; } result.add(i); } } return result; } }