From 15f49a4edca3e1702f35b292eb85457519c96f66 Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Fri, 27 Jan 2023 12:02:51 -0600 Subject: [PATCH 1/6] Create format_number filter --- .../jinjava/lib/filter/NumberFilter.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java new file mode 100644 index 000000000..051d45e04 --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java @@ -0,0 +1,110 @@ +package com.hubspot.jinjava.lib.filter; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +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.JinjavaInterpreter; +import com.hubspot.jinjava.interpret.TemplateError; +import com.hubspot.jinjava.interpret.TemplateError.ErrorItem; +import com.hubspot.jinjava.interpret.TemplateError.ErrorReason; +import com.hubspot.jinjava.interpret.TemplateError.ErrorType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; + +@JinjavaDoc( + value = "Formats a given number based on the locale passed in as a parameter.", + input = @JinjavaParam( + value = "value", + desc = "The number to be formatted based on locale", + required = true + ), + params = { + @JinjavaParam( + value = "locale", + desc = "Locale in which to format the number. The default is the page's locale." + ), + @JinjavaParam( + value = "decimal precision number", + type = "number", + desc = "A number input that determines the decimal precision of the formatted value. If the number of decimal digits from the input value is less than the decimal precision number, use the number of decimal digits from the input value. Otherwise, use the decimal precision number. The default is the number of decimal digits from the input value." + ) + }, + snippets = { + @JinjavaSnippet(code = "{{ number|format_number }}"), + @JinjavaSnippet(code = "{{ number|format_number(\"en-US\") }}"), + @JinjavaSnippet(code = "{{ number|format_number(\"en-US\", 3) }}") + } +) +public class NumberFilter implements Filter { + private static final String FORMAT_NUMBER_FILTER_NAME = "format_number"; + + @Override + public String getName() { + return FORMAT_NUMBER_FILTER_NAME; + } + + @Override + public Object filter(Object var, JinjavaInterpreter interpreter, String... args) { + Locale locale = args.length > 0 && !Strings.isNullOrEmpty(args[0]) + ? Locale.forLanguageTag(args[0]) + : interpreter.getConfig().getLocale(); + + BigDecimal number; + try { + number = parseInput(var); + } catch (Exception e) { + if (interpreter.getContext().isValidationMode()) { + return ""; + } + interpreter.addError( + new TemplateError( + ErrorType.WARNING, + ErrorReason.INVALID_INPUT, + ErrorItem.FILTER, + "Input value '" + var + "' could not be parsed.", + null, + interpreter.getLineNumber(), + e, + null, + ImmutableMap.of("value", Objects.toString(var)) + ) + ); + return var; + } + + int noOfDecimalPlacesInInput = Math.max(0, number.scale()); + int decimalPrecisionNumber = args.length > 1 + ? Integer.parseInt(args[1]) + : noOfDecimalPlacesInInput; + + return formatNumber(locale, number, noOfDecimalPlacesInInput, decimalPrecisionNumber); + } + + private BigDecimal parseInput(Object input) throws Exception { + DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(); + df.setParseBigDecimal(true); + + return (BigDecimal) df.parseObject(Objects.toString(input)); + } + + private String formatNumber( + Locale locale, + BigDecimal number, + int noOfDecimalPlacesInInput, + int decimalPrecisionNumber + ) { + NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); + + numberFormat.setMinimumFractionDigits(noOfDecimalPlacesInInput); + numberFormat.setMaximumFractionDigits( + Math.min(noOfDecimalPlacesInInput, decimalPrecisionNumber) + ); + + return numberFormat.format(number); + } +} From f3ef0f1ec68ddccc564648249c733d3e7b8cd86c Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Fri, 27 Jan 2023 13:43:37 -0600 Subject: [PATCH 2/6] Rename to NumberFormatFilter --- .../lib/filter/{NumberFilter.java => NumberFormatFilter.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/com/hubspot/jinjava/lib/filter/{NumberFilter.java => NumberFormatFilter.java} (98%) diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java similarity index 98% rename from src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java rename to src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java index 051d45e04..0b50f4593 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/NumberFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java @@ -40,7 +40,7 @@ @JinjavaSnippet(code = "{{ number|format_number(\"en-US\", 3) }}") } ) -public class NumberFilter implements Filter { +public class NumberFormatFilter implements Filter { private static final String FORMAT_NUMBER_FILTER_NAME = "format_number"; @Override From 834fcd4a0a8af03fb5150ff9303938188c31124e Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Fri, 27 Jan 2023 13:54:19 -0600 Subject: [PATCH 3/6] Add filter to FilterLibrary --- src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java index 544230d9c..2bcb27080 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java @@ -82,6 +82,7 @@ protected void registerDefaults() { Md5Filter.class, MinusTimeFilter.class, MultiplyFilter.class, + NumberFormatFilter.class, PlusTimeFilter.class, PrettyPrintFilter.class, RandomFilter.class, From 40d44c34cf153289cad80a127496b5b0728744d8 Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Fri, 27 Jan 2023 13:55:01 -0600 Subject: [PATCH 4/6] Add tests for filter using different locales --- .../lib/filter/NumberFormatFilterTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java new file mode 100644 index 000000000..7b1880b39 --- /dev/null +++ b/src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java @@ -0,0 +1,74 @@ +package com.hubspot.jinjava.lib.filter; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hubspot.jinjava.BaseJinjavaTest; +import java.util.HashMap; +import org.junit.Before; +import org.junit.Test; + +public class NumberFormatFilterTest extends BaseJinjavaTest { + + @Before + public void setup() {} + + @Test + public void testNumberFormatFilter() { + assertThat( + jinjava.render("{{1000|format_number('en-US')}}", new HashMap()) + ) + .isEqualTo("1,000"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('en-US') }}", + new HashMap() + ) + ) + .isEqualTo("1,000.333"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('en-US', 2) }}", + new HashMap() + ) + ) + .isEqualTo("1,000.33"); + + assertThat( + jinjava.render("{{ 1000|format_number('fr') }}", new HashMap()) + ) + .isEqualTo("1\u00a0000"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('fr') }}", + new HashMap() + ) + ) + .isEqualTo("1\u00a0000,333"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('fr', 2) }}", + new HashMap() + ) + ) + .isEqualTo("1\u00a0000,33"); + + assertThat( + jinjava.render("{{ 1000|format_number('es') }}", new HashMap()) + ) + .isEqualTo("1.000"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('es') }}", + new HashMap() + ) + ) + .isEqualTo("1.000,333"); + assertThat( + jinjava.render( + "{{ 1000.333|format_number('es', 2) }}", + new HashMap() + ) + ) + .isEqualTo("1.000,33"); + } +} From faa31612fe456412597a82fad6c310a2cf102fcc Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Mon, 6 Feb 2023 14:23:33 -0600 Subject: [PATCH 5/6] Update variable names, class name and method parameter --- .../jinjava/lib/filter/FilterLibrary.java | 2 +- ...matFilter.java => FormatNumberFilter.java} | 26 +++++++++++-------- ...rTest.java => FormatNumberFilterTest.java} | 4 +-- 3 files changed, 18 insertions(+), 14 deletions(-) rename src/main/java/com/hubspot/jinjava/lib/filter/{NumberFormatFilter.java => FormatNumberFilter.java} (84%) rename src/test/java/com/hubspot/jinjava/lib/filter/{NumberFormatFilterTest.java => FormatNumberFilterTest.java} (94%) diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java index 2bcb27080..57990d84a 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java @@ -82,7 +82,7 @@ protected void registerDefaults() { Md5Filter.class, MinusTimeFilter.class, MultiplyFilter.class, - NumberFormatFilter.class, + FormatNumberFilter.class, PlusTimeFilter.class, PrettyPrintFilter.class, RandomFilter.class, diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/FormatNumberFilter.java similarity index 84% rename from src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java rename to src/main/java/com/hubspot/jinjava/lib/filter/FormatNumberFilter.java index 0b50f4593..924e8e325 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/NumberFormatFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FormatNumberFilter.java @@ -15,6 +15,7 @@ import java.text.NumberFormat; import java.util.Locale; import java.util.Objects; +import java.util.Optional; @JinjavaDoc( value = "Formats a given number based on the locale passed in as a parameter.", @@ -29,7 +30,7 @@ desc = "Locale in which to format the number. The default is the page's locale." ), @JinjavaParam( - value = "decimal precision number", + value = "max decimal precision", type = "number", desc = "A number input that determines the decimal precision of the formatted value. If the number of decimal digits from the input value is less than the decimal precision number, use the number of decimal digits from the input value. Otherwise, use the decimal precision number. The default is the number of decimal digits from the input value." ) @@ -40,7 +41,7 @@ @JinjavaSnippet(code = "{{ number|format_number(\"en-US\", 3) }}") } ) -public class NumberFormatFilter implements Filter { +public class FormatNumberFilter implements Filter { private static final String FORMAT_NUMBER_FILTER_NAME = "format_number"; @Override @@ -77,12 +78,11 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args) return var; } - int noOfDecimalPlacesInInput = Math.max(0, number.scale()); - int decimalPrecisionNumber = args.length > 1 - ? Integer.parseInt(args[1]) - : noOfDecimalPlacesInInput; + Optional maxDecimalPrecision = args.length > 1 + ? Optional.of(Integer.parseInt(args[1])) + : Optional.empty(); - return formatNumber(locale, number, noOfDecimalPlacesInInput, decimalPrecisionNumber); + return formatNumber(locale, number, maxDecimalPrecision); } private BigDecimal parseInput(Object input) throws Exception { @@ -95,14 +95,18 @@ private BigDecimal parseInput(Object input) throws Exception { private String formatNumber( Locale locale, BigDecimal number, - int noOfDecimalPlacesInInput, - int decimalPrecisionNumber + Optional maxDecimalPrecision ) { NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); + int numDecimalPlacesInInput = Math.max(0, number.scale()); - numberFormat.setMinimumFractionDigits(noOfDecimalPlacesInInput); numberFormat.setMaximumFractionDigits( - Math.min(noOfDecimalPlacesInInput, decimalPrecisionNumber) + Math.min( + numDecimalPlacesInInput, + maxDecimalPrecision.isPresent() + ? maxDecimalPrecision.get() + : numDecimalPlacesInInput + ) ); return numberFormat.format(number); diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/FormatNumberFilterTest.java similarity index 94% rename from src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java rename to src/test/java/com/hubspot/jinjava/lib/filter/FormatNumberFilterTest.java index 7b1880b39..25b715a0a 100644 --- a/src/test/java/com/hubspot/jinjava/lib/filter/NumberFormatFilterTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/filter/FormatNumberFilterTest.java @@ -7,13 +7,13 @@ import org.junit.Before; import org.junit.Test; -public class NumberFormatFilterTest extends BaseJinjavaTest { +public class FormatNumberFilterTest extends BaseJinjavaTest { @Before public void setup() {} @Test - public void testNumberFormatFilter() { + public void testFormatNumberFilter() { assertThat( jinjava.render("{{1000|format_number('en-US')}}", new HashMap()) ) From a80a4ed011d28bfff3fa5f593afd6d643290a1d9 Mon Sep 17 00:00:00 2001 From: Julia Uy Date: Mon, 6 Feb 2023 14:55:58 -0600 Subject: [PATCH 6/6] Move class in library for alphabetical order --- src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java index 57990d84a..216a1d2bd 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/FilterLibrary.java @@ -62,6 +62,7 @@ protected void registerDefaults() { FormatFilter.class, FormatDateFilter.class, FormatDatetimeFilter.class, + FormatNumberFilter.class, FormatTimeFilter.class, FromJsonFilter.class, FromYamlFilter.class, @@ -82,7 +83,6 @@ protected void registerDefaults() { Md5Filter.class, MinusTimeFilter.class, MultiplyFilter.class, - FormatNumberFilter.class, PlusTimeFilter.class, PrettyPrintFilter.class, RandomFilter.class,