improved renderTemplate
method
This commit is contained in:
@@ -34,7 +34,6 @@ import java.nio.file.attribute.FileAttribute;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
@@ -46,6 +45,8 @@ import java.util.Map;
|
||||
import java.util.MissingFormatArgumentException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
@@ -293,27 +294,96 @@ public class JWO {
|
||||
* "This template was created by John Doe."
|
||||
*/
|
||||
public static String renderTemplate(String template, Map<String, Object> valuesMap) {
|
||||
return renderTemplate(template, valuesMap, null);
|
||||
}
|
||||
|
||||
public static int indexOfWithEscape(String haystack, char needle, char escape, int begin, int end) {
|
||||
int result = -1;
|
||||
int cursor = begin;
|
||||
if(end == 0) {
|
||||
end = haystack.length();
|
||||
}
|
||||
int escapeCount = 0;
|
||||
while(cursor < end) {
|
||||
char c = haystack.charAt(cursor);
|
||||
if(escapeCount > 0) {
|
||||
--escapeCount;
|
||||
if(c == escape) {
|
||||
result = -1;
|
||||
}
|
||||
} else if(escapeCount == 0) {
|
||||
if (c == escape) {
|
||||
++escapeCount;
|
||||
}
|
||||
if (c == needle) {
|
||||
result = cursor;
|
||||
}
|
||||
}
|
||||
if(result >= 0 && escapeCount == 0) {
|
||||
break;
|
||||
}
|
||||
++cursor;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String renderTemplate(
|
||||
String template,
|
||||
Map<String, Object> valuesMap,
|
||||
Map<String, Map<String, Object>> dictMap) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Object absent = new Object();
|
||||
|
||||
int cursor = 0;
|
||||
while (cursor < template.length()) {
|
||||
String key;
|
||||
char ch = template.charAt(cursor);
|
||||
if (ch != '$' || (cursor > 0 && template.charAt(cursor - 1) == '\\')) {
|
||||
sb.append(template.charAt(cursor++));
|
||||
} else if (cursor + 1 < template.length() && template.charAt(cursor + 1) == '{') {
|
||||
while(cursor < template.length()) {
|
||||
int nextPlaceHolder = indexOfWithEscape(template, '$', '$', cursor, template.length());
|
||||
if (nextPlaceHolder < 0) {
|
||||
nextPlaceHolder = template.length();
|
||||
}
|
||||
while (cursor < nextPlaceHolder) {
|
||||
char ch = template.charAt(cursor++);
|
||||
sb.append(ch);
|
||||
}
|
||||
if (cursor + 1 < template.length() && template.charAt(cursor + 1) == '{') {
|
||||
String key;
|
||||
String context = null;
|
||||
String defaultValue = null;
|
||||
Object value;
|
||||
int end = template.indexOf('}', cursor + 1);
|
||||
key = template.substring(cursor + 2, end);
|
||||
Object value = valuesMap.getOrDefault(key, absent);
|
||||
int colon;
|
||||
if (dictMap == null)
|
||||
colon = -1;
|
||||
else {
|
||||
colon = indexOfWithEscape(template, ':', '\\', cursor + 1, template.length());
|
||||
if (colon >= end) colon = -1;
|
||||
}
|
||||
if (colon < 0) {
|
||||
key = template.substring(cursor + 2, end);
|
||||
value = valuesMap.getOrDefault(key, absent);
|
||||
} else {
|
||||
context = template.substring(cursor + 2, colon);
|
||||
int secondColon = indexOfWithEscape(template, ':', '\\', colon + 1, end);
|
||||
if(secondColon < 0) {
|
||||
key = template.substring(colon + 1, end);
|
||||
} else {
|
||||
key = template.substring(colon + 1, secondColon);
|
||||
defaultValue = template.substring(secondColon + 1, end);
|
||||
}
|
||||
value = Optional.ofNullable(dictMap.get(context))
|
||||
.map(m -> m.get(key))
|
||||
.orElse(absent);
|
||||
}
|
||||
if (value != absent) {
|
||||
sb.append(value.toString());
|
||||
} else {
|
||||
raise(MissingFormatArgumentException.class, "Missing value for placeholder '%s'", key);
|
||||
if (defaultValue != null) {
|
||||
sb.append(defaultValue);
|
||||
} else {
|
||||
raise(MissingFormatArgumentException.class, "Missing value for placeholder '%s'",
|
||||
context == null ? key : context + ':' + key);
|
||||
}
|
||||
}
|
||||
cursor = end + 1;
|
||||
} else {
|
||||
sb.append(template.charAt(cursor++));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
@@ -329,14 +399,20 @@ public class JWO {
|
||||
* "This template was created by John Doe."
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String renderTemplate(Reader reader, Map<String, Object> valuesMap) {
|
||||
public static String renderTemplate(Reader reader,
|
||||
Map<String, Object> valuesMap,
|
||||
Map<String, Map<String, Object>> dictMap) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buf = new char[1024];
|
||||
int read;
|
||||
while (!((read = reader.read(buf)) < 0)) {
|
||||
sb.append(buf, 0, read);
|
||||
}
|
||||
return renderTemplate(sb.toString(), valuesMap);
|
||||
return renderTemplate(sb.toString(), valuesMap, dictMap);
|
||||
}
|
||||
public static String renderTemplate(Reader reader,
|
||||
Map<String, Object> valuesMap) {
|
||||
return renderTemplate(reader, valuesMap, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -871,4 +947,19 @@ public class JWO {
|
||||
public static <T> Runnable compose(Supplier<T> sup, Consumer<T> con) {
|
||||
return () -> con.accept(sup.get());
|
||||
}
|
||||
|
||||
public static <T> T loadService(Class<T> serviceClass) {
|
||||
return StreamSupport.stream(ServiceLoader.load(serviceClass).spliterator(), false)
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() -> newThrowable(
|
||||
ServiceConfigurationError.class,
|
||||
"Unable to find a valid implementation of '%s'",
|
||||
serviceClass.getName()));
|
||||
}
|
||||
|
||||
public static <T, U, R> Optional<R> zip(Optional<T> opt1, Optional<U> opt2, BiFun<T, U, R> cb) {
|
||||
if (!opt1.isPresent() || !opt2.isPresent()) return Optional.empty();
|
||||
else return Optional.ofNullable(cb.apply(opt1.get(), opt2.get()));
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package net.woggioni.jwo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@@ -7,6 +8,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -20,6 +23,9 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.woggioni.jwo.CollectionUtils.immutableList;
|
||||
import static net.woggioni.jwo.CollectionUtils.newArrayList;
|
||||
|
||||
public class JWOTest {
|
||||
|
||||
@Test
|
||||
@@ -43,6 +49,47 @@ public class JWOTest {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@RequiredArgsConstructor
|
||||
enum IndexOfWithEscapeTestCase {
|
||||
SIMPLE(" dsds $sdsa \\$dfivbdsf \\\\$sdgsga", '$', '\\',
|
||||
immutableList(6, 25)),
|
||||
SIMPLE2("asdasd$$vdfv$", '$', '$',
|
||||
immutableList(12)),
|
||||
NO_NEEDLE("asdasd$$vdfv$", '#', '\\',
|
||||
immutableList()),
|
||||
ESCAPED_NEEDLE("asdasd$$vdfv$#sdfs", '#', '$',
|
||||
immutableList()),
|
||||
NOT_ESCAPED_NEEDLE("asdasd$$#vdfv$#sdfs", '#', '$',
|
||||
immutableList(8)),
|
||||
|
||||
SDFSD("\n${sys:user.home}${env:HOME}", ':', '\\',
|
||||
immutableList(6, 22))
|
||||
|
||||
;
|
||||
final String haystack;
|
||||
final Character needle;
|
||||
|
||||
final Character escape;
|
||||
|
||||
final List<Integer> solution;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(IndexOfWithEscapeTestCase.class)
|
||||
public void testIndexOfWithEscape(IndexOfWithEscapeTestCase testCase) {
|
||||
String haystack = testCase.haystack;
|
||||
List<Integer> solution = newArrayList();
|
||||
int i = 0;
|
||||
while(true) {
|
||||
i = JWO.indexOfWithEscape(haystack, testCase.needle, testCase.escape, i, haystack.length());
|
||||
if(i < 0) break;
|
||||
solution.add(i);
|
||||
++i;
|
||||
}
|
||||
Assertions.assertEquals(testCase.solution, solution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
public void testRenderTemplate() {
|
||||
@@ -50,15 +97,30 @@ public class JWOTest {
|
||||
valuesMap.put("author", "John Doe");
|
||||
valuesMap.put("date", "2020-03-25 16:22");
|
||||
valuesMap.put("adjective", "simple");
|
||||
String expected = "This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!\n";
|
||||
String expected = """
|
||||
This is a simple test made by John Doe on 2020-03-25 16:22. It's really simple!
|
||||
/home/user
|
||||
/home/user
|
||||
defaultValue
|
||||
""";
|
||||
Map<String, Map<String, Object>> contextMap = new MapBuilder<String, Map<String, Object>>()
|
||||
.entry("env",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("HOME", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap)
|
||||
)
|
||||
.entry("sys",
|
||||
new MapBuilder<String, String>()
|
||||
.entry("user.home", "/home/user")
|
||||
.build(TreeMap::new, Collections::unmodifiableMap) ).build(TreeMap::new, Collections::unmodifiableMap);
|
||||
try (Reader reader = new InputStreamReader(
|
||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||
String rendered = JWO.renderTemplate(reader, valuesMap);
|
||||
String rendered = JWO.renderTemplate(reader, valuesMap, contextMap);
|
||||
Assertions.assertEquals(expected, rendered);
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(
|
||||
JWOTest.class.getResourceAsStream("/render_template_test.txt"))) {
|
||||
String rendered = JWO.renderTemplate(JWO.readAll(reader), valuesMap);
|
||||
String rendered = JWO.renderTemplate(JWO.readAll(reader), valuesMap, contextMap);
|
||||
Assertions.assertEquals(expected, rendered);
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1,4 @@
|
||||
This is a ${adjective} test made by ${author} on ${date}. It's really ${adjective}!
|
||||
${sys:user.home}
|
||||
${env:HOME}
|
||||
${env:SOME_STRANGE_VAR:defaultValue}
|
||||
|
Reference in New Issue
Block a user