Skip to content

Commit

Permalink
feat: functions provider SPI (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
erdos authored Jul 30, 2024
1 parent 0816a6c commit ad00799
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 12 deletions.
11 changes: 10 additions & 1 deletion build.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(ns build
(:require [clojure.tools.build.api :as b]
(:require [clojure.java.io :as io]
[clojure.tools.build.api :as b]
[clojure.tools.build.util.file :as file]))

(def build-folder "target")
Expand Down Expand Up @@ -29,6 +30,14 @@
:javac-opts ["-source" "8" "-target" "8"]})
(b/copy-file {:src "java-src/io/github/erdos/stencil/standalone/help.txt"
:target "target/classes/io/github/erdos/stencil/standalone/help.txt"})

;; generate service provider config for function providers
(let [services (io/file "target/classes/META-INF/services/io.github.erdos.stencil.functions.FunctionsProvider")]
(io/make-parents services)
(doseq [f (file-seq (clojure.java.io/file jar-content))
[_ a] (re-seq #"^target/classes/(.*\$Provider).class$" (str f))
:let [clazz (str (.replace a "/" ".") "\n")]]
(spit services, clazz :append true)))
(spit (str jar-content "/stencil-version") version)
opts)

Expand Down
11 changes: 10 additions & 1 deletion docs/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ Expects one number argument containing a list with numbers. Sums up the numbers

## Custom functions

You can register custom implementations of `io.github.erdos.stencil.functions.Function` or the `stencil.functions/call-fn` multimethod.
You can register custom implementations of `io.github.erdos.stencil.functions.Function` or the `stencil.functions/call-fn` multimethod.
If you implement the `call-fn` multimethod, the namespace containing these implementations should be loaded before rendering a document.
(Keep in mind, that `call-fn` implementations always have priority over `io.github.erdos.stencil.functions.Function` implementations)

Clojure example:

Expand Down Expand Up @@ -271,3 +273,10 @@ public class FirstFuncion implements Function {

API.render(preparedTemplate, fragments, data, Arrays.asList(new FirstFunction()));
```


### Automatic registration of custom functions

Stencil uses the JVM's [ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) facility to load function provider implementations.
If you want to register your custom functions automatically, implement the `io.github.erdos.stencil.functions.FunctionsProvider` interface,
and add these implementations to your extension library's `META-INF/services/io.github.erdos.stencil.functions.FunctionsProvider` file.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.erdos.stencil.functions;

import java.util.Arrays;
import java.util.Collection;

/**
Expand Down Expand Up @@ -72,4 +73,11 @@ public Object call(Object... arguments) {
public String getName() {
return name().toLowerCase();
}

public static class Provider implements FunctionsProvider {
@Override
public Iterable<Function> functions() {
return Arrays.asList(values());
}
}
}
7 changes: 7 additions & 0 deletions java-src/io/github/erdos/stencil/functions/DateFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,11 @@ private static Optional<LocalDateTime> maybeLocalDateTime(Object obj) {
public String getName() {
return name().toLowerCase();
}

public static class Provider implements FunctionsProvider {
@Override
public Iterable<Function> functions() {
return asList(values());
}
}
}
31 changes: 21 additions & 10 deletions java-src/io/github/erdos/stencil/functions/FunctionEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,49 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;

public final class FunctionEvaluator {
private final static Map<String, Function> preloaded;

private final Map<String, Function> functions = new HashMap<>();
static {
preloaded = new HashMap<>();
for (FunctionsProvider provider : ServiceLoader.load(FunctionsProvider.class)) {
for (Function fn : provider.functions()) {
registerFunction(preloaded, fn);
}
}
}

private final Map<String, Function> functions;

{
registerFunctions(BasicFunctions.values());
registerFunctions(StringFunctions.values());
registerFunctions(NumberFunctions.values());
registerFunctions(DateFunctions.values());
registerFunctions(LocaleFunctions.values());
this.functions = new HashMap<>(preloaded);
}

private void registerFunction(Function function) {
private static void registerFunction(Map<String, Function> map, Function function) {
if (function == null)
throw new IllegalArgumentException("Registered function must not be null.");
functions.put(function.getName().toLowerCase(), function);
Function present = map.put(function.getName().toLowerCase(), function);
if (present != null)
throw new IllegalArgumentException("Function with name has already been registered.");
}

/**
* Registers a function to this evaluator engine.
* Registered functions can be invoked from inside template files.
*
* @param functions any number of function instances.
* @param functions list of functions to register
*/

@SuppressWarnings("WeakerAccess")
public void registerFunctions(Function... functions) {
for (Function function : functions) {
registerFunction(function);
registerFunction(this.functions, function);
}
}


/**
* Calls a function by name.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.erdos.stencil.functions;

public interface FunctionsProvider {
Iterable<Function> functions();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.erdos.stencil.functions;

import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Locale;

import static java.util.Locale.forLanguageTag;
Expand Down Expand Up @@ -53,4 +54,11 @@ private static String formatting(Function function, java.util.function.Function<
public String getName() {
return name().toLowerCase();
}

public static class Provider implements FunctionsProvider {
@Override
public Iterable<Function> functions() {
return Arrays.asList(values());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.erdos.stencil.functions;

import java.util.Arrays;

/**
* Common numeric functions.
*/
Expand Down Expand Up @@ -73,4 +75,11 @@ private static Number maybeNumber(Object... arguments) {
return (Number) arguments[0];
}
}

public static class Provider implements FunctionsProvider {
@Override
public Iterable<Function> functions() {
return Arrays.asList(values());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,11 @@ public Object call(Object... arguments) throws IllegalArgumentException {
public String getName() {
return name().toLowerCase();
}

public static class Provider implements FunctionsProvider {
@Override
public Iterable<Function> functions() {
return Arrays.asList(values());
}
}
}

0 comments on commit ad00799

Please sign in to comment.