„Glücklich – wer darauf bedacht, wie man Ander’n Freude macht.“
Wilhelm Busch
The last article described how the .version
variable in FreshMarker can always be kept up to date. In this article, we want to simplify the use of the variable.
As the .version
variable was previously of the string type, it was only possible to work with the string comparison operators and built-ins.
<#if .version == '1.0.2'> current <#elseif .version?startsWith('1.0.')> 1.0 <#else> other </#if>
In this example, the major and minor versions are first compared with a constant and then with the build-ins startsWith
. To use complex comparisons of the major, minor and patch versions, the string representation is unfavourable.
In the future the .version
variable is given its own type which has some helpful built-ins. The built-ins is_after
and patch
are shown in the following example.
<#if .version?is_after('1.1.0')> 1.1 <#elseif .version?is_after('1.0.1')> 1.0 </#if> patch: ${.version?patch}
The built-in is_after
is used to check whether the current version is after the version in the built-in parameter. The parameter can be specified as a string or version. In addition to is_after
, there is also is_equal
and is_before
. The built-in patch
returns the patch part of the current version as an integer. In addition, the major and minor part of the version can be returned as integers via the built-ins major
and minor
.
In order to be able to use a separate type for the versions, we first need a TemplatePrimitive
implementation.
public class TemplateVersion extends TemplatePrimitive<Version> { public TemplateVersion(String value) { super(Version.byString(value)); } public TemplateNumber major() { return new TemplateNumber(getValue().major()); } public TemplateBoolean isBefore(TemplateVersion value) { return TemplateBoolean.from(getValue().isBefore(value.getValue())); } // ... }
The TemplateVersion
class uses a record with the attributes major
, minor
and patch
. There are corresponding methods in the class for all built-ins.
The built-in methods still need to be registered. The PluginProvider
implementations exist in FreshMarker for this purpose. The following SystemPluginProvider
registers all version build-ins.
public class SystemPluginProvider implements PluginProvider { private static final BuiltInKeyBuilder<TemplateVersion> VERSION = new BuiltInKeyBuilder<>(TemplateVersion.class); private static final BuiltInKeyBuilder<TemplateString> STRING = new BuiltInKeyBuilder<>(TemplateString.class); @Override public void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) { register(builtIns, VERSION.of("is_before"), (x, y, e) -> version(x, e).isBefore(parameter(y, e))); register(builtIns, VERSION.of("is_after"), (x, y, e) -> version(x, e).isAfter(parameter(y, e))); register(builtIns, VERSION.of("is_equal"), (x, y, e) -> version(x, e).isEqual(parameter(y, e))); register(builtIns, VERSION.of("major"), (x, y, e) -> version(x, e).major()); register(builtIns, VERSION.of("minor"), (x, y, e) -> version(x, e).minor()); register(builtIns, VERSION.of("patch"), (x, y, e) -> version(x, e).patch()); register(builtIns, STRING.of("version"), (x, y, e) -> x.evaluateToObject(e).asString().map(TemplatePrimitive::toString) .map(TemplateVersion::new).orElseThrow()); } private static TemplateVersion version(TemplateObject x, ProcessContext e) { return x.evaluate(e, TemplateVersion.class); } private static TemplateVersion parameter(List<TemplateObject> objects, ProcessContext context) { TemplateObject value = objects.getFirst().evaluateToObject(context); return switch (value) { case TemplateVersion version -> version; case TemplateString string -> new TemplateVersion(string.toString()); default -> throw new IllegalStateException("invalid type: " + value.getModelType()); }; } private void register(Map<BuiltInKey, BuiltIn> buildIns, BuiltInKey builtInKey, BuiltInFunction function) { buildIns.put(builtInKey, new FunctionalBuiltIn(function)); } }
In addition to the built-ins presented above, a further built-in is added for the string type. With version
, a version is created from a string. Finally, the .version
variable only needs to be created as a TemplateVersion
variable instead of a TemplateString
variable.
public record TemplateBuiltInVariable(String name) implements TemplateExpression { @Override public TemplateObject evaluateToObject(ProcessContext context) { return switch (name) { case "now" -> new TemplateLocalDateTime(LocalDateTime.now()).at(context); case "locale" -> new TemplateString(context.getEnvironment().getLocale().toString()); case "country" -> new TemplateString(context.getEnvironment().getLocale().getCountry()); case "lang" -> new TemplateString(context.getEnvironment().getLocale().getLanguage()); case "version" -> new TemplateVersion(getClass().getPackage().getImplementationVersion()); default -> throw new IllegalStateException("Unexpected value: " + name); }; } }
This is all that is required to implement the new features for the .version
variable. They can then be used from version 1.1.0.