summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2018-09-06 18:08:34 -0400
committerPaul Duncan <pabs@pablotron.org>2018-09-06 18:08:34 -0400
commitc4f3f955179cd217c6e7273561d06468b33e8973 (patch)
tree7a81b08c2847bdbd43f9fe4d0dbf4a1790ab3b15
parent2be7535e09e359d70b924013c9f4d25149fdf0ae (diff)
downloadluigi-template-c4f3f955179cd217c6e7273561d06468b33e8973.tar.bz2
luigi-template-c4f3f955179cd217c6e7273561d06468b33e8973.zip
update errors, fix filters, add tests
-rw-r--r--java/src/main/java/org/pablotron/luigi/Cache.java27
-rw-r--r--java/src/main/java/org/pablotron/luigi/Filter.java173
-rw-r--r--java/src/main/java/org/pablotron/luigi/FilterError.java7
-rw-r--r--java/src/main/java/org/pablotron/luigi/Template.java34
-rw-r--r--java/src/main/java/org/pablotron/luigi/UnknownEntryError.java13
-rw-r--r--java/src/main/java/org/pablotron/luigi/UnknownFilterError.java7
-rw-r--r--java/src/main/java/org/pablotron/luigi/UnknownKeyError.java7
-rw-r--r--java/src/main/java/org/pablotron/luigi/UnknownTemplateError.java7
-rw-r--r--java/src/main/java/org/pablotron/luigi/actions/FilterAction.java6
-rw-r--r--java/src/test/java/org/pablotron/luigi/tests/CacheTest.java46
-rw-r--r--java/src/test/java/org/pablotron/luigi/tests/DefaultFiltersTest.java53
-rw-r--r--java/src/test/java/org/pablotron/luigi/tests/FiltersTest.java88
-rw-r--r--java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java18
13 files changed, 439 insertions, 47 deletions
diff --git a/java/src/main/java/org/pablotron/luigi/Cache.java b/java/src/main/java/org/pablotron/luigi/Cache.java
index 97fa0ab..4a591bc 100644
--- a/java/src/main/java/org/pablotron/luigi/Cache.java
+++ b/java/src/main/java/org/pablotron/luigi/Cache.java
@@ -28,24 +28,25 @@ public final class Cache {
final String key,
final Map<String, String> args
) throws LuigiError {
- Template t;
+ // run template with args
+ return get(key).run(args);
+ }
+
+ public boolean containsKey(final String key) {
+ return strings.containsKey(key);
+ }
- if (templates.containsKey(key)) {
- // get template
- t = templates.get(key);
- } else {
+ public Template get(final String key) throws LuigiError {
+ if (!templates.containsKey(key)) {
// make sure template exists
if (!strings.containsKey(key))
- throw new LuigiError("unknown template: " + key);
-
- // create template
- t = new Template(strings.get(key), filters);
+ throw new UnknownTemplateError(key);
- // cache template
- templates.put(key, t);
+ // create and cache template
+ templates.put(key, new Template(strings.get(key), filters));
}
- // run template with args
- return t.run(args);
+ // get template
+ return templates.get(key);
}
};
diff --git a/java/src/main/java/org/pablotron/luigi/Filter.java b/java/src/main/java/org/pablotron/luigi/Filter.java
index b00491f..d2cd432 100644
--- a/java/src/main/java/org/pablotron/luigi/Filter.java
+++ b/java/src/main/java/org/pablotron/luigi/Filter.java
@@ -2,12 +2,26 @@ package org.pablotron.luigi;
import java.util.Map;
import java.util.HashMap;
+import java.nio.charset.Charset;
public final class Filter {
public interface Handler {
- public String filter(String val, String args[], Map<String, String> row);
+ public String filter(
+ String val,
+ String args[],
+ Map<String, String> row
+ ) throws FilterError;
};
+ protected static byte[] getBytes(final String val, final String args[]) {
+ final Charset charset = (args.length > 0) ? Charset.forName(args[0]) : Charset.defaultCharset();
+ return val.getBytes(charset);
+ }
+
+ protected static int toUInt(final byte b) {
+ return (b < 0) ? (256 + b) : b;
+ }
+
public static Map<String, Handler> FILTERS = new HashMap<String, Handler>() {{
put("null", new Handler() {
public String filter(String val, String args[], Map<String, String> row) {
@@ -48,41 +62,136 @@ public final class Filter {
put("h", new Handler() {
public String filter(String val, String args[], Map<String, String> row) {
- StringBuilder r = new StringBuilder(val.length());
-
- for (int i = 0, l = val.length(); i < l; i++) {
- char c = val.charAt(i);
-
- switch (c) {
- case '&':
- r.append("&amp;");
- break;
- case '<':
- r.append("&lt;");
- break;
- case '>':
- r.append("&gt;");
- break;
- case '\'':
- r.append("&apos;");
- break;
- case '"':
- r.append("&quot;");
- break;
- default:
- r.append(c);
+ StringBuilder r = new StringBuilder();
+ final byte bytes[] = getBytes(val, args);
+
+ for (int i = 0, l = bytes.length; i < l; i++) {
+ final byte b = bytes[i];
+
+ switch (b) {
+ case '&':
+ r.append("&amp;");
+ break;
+ case '<':
+ r.append("&lt;");
+ break;
+ case '>':
+ r.append("&gt;");
+ break;
+ case '\'':
+ r.append("&apos;");
+ break;
+ case '"':
+ r.append("&quot;");
+ break;
+ default:
+ if (b < 32 || b > 126) {
+ r.append(String.format("&#%d;", toUInt(b)));
+ } else {
+ final byte bs[] = {b};
+ r.append(new String(bs));
+ }
}
}
return r.toString();
-/*
- * return val
- * .replace("&", "&amp;")
- * .replace("<", "&lt;")
- * .replace(">", "&gt;")
- * .replace("'", "&apos;")
- * .replace("\"", "&quot;");
- */
+ }
+ });
+
+ put("u", new Handler() {
+ public String filter(String val, String args[], Map<String, String> row) {
+ final StringBuilder r = new StringBuilder();
+ final byte bytes[] = getBytes(val, args);
+
+ for (int i = 0, l = bytes.length; i < l; i++) {
+ final byte b = bytes[i];
+
+ switch (b) {
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ case '_':
+ case '.':
+ case '~':
+ // unreserved character
+ final byte bs[] = {b};
+ r.append(new String(bs));
+ break;
+ case ' ':
+ r.append("+");
+ break;
+ default:
+ r.append(String.format("%%%02X", toUInt(b)));
+ }
+ }
+
+ return r.toString();
+ }
+ });
+
+ put("trim", new Handler() {
+ public String filter(String val, String args[], Map<String, String> row) {
+ return val.replaceAll("\\A\\s+|\\s+\\Z", "");
}
});
}};
diff --git a/java/src/main/java/org/pablotron/luigi/FilterError.java b/java/src/main/java/org/pablotron/luigi/FilterError.java
new file mode 100644
index 0000000..cb631a7
--- /dev/null
+++ b/java/src/main/java/org/pablotron/luigi/FilterError.java
@@ -0,0 +1,7 @@
+package org.pablotron.luigi;
+
+public class FilterError extends LuigiError {
+ public FilterError(final String message) {
+ super(message);
+ }
+};
diff --git a/java/src/main/java/org/pablotron/luigi/Template.java b/java/src/main/java/org/pablotron/luigi/Template.java
index 9af3a74..e5f25a9 100644
--- a/java/src/main/java/org/pablotron/luigi/Template.java
+++ b/java/src/main/java/org/pablotron/luigi/Template.java
@@ -47,4 +47,38 @@ public final class Template {
public String toString() {
return this.template;
}
+
+ public static String run(
+ final String template,
+ final Map<String, String> args
+ ) throws LuigiError {
+ return Template.run(template, args, Filter.FILTERS);
+ }
+
+ public static void run(
+ final String template,
+ final Map<String, String> args,
+ final ResultHandler r
+ ) throws LuigiError {
+ run(template, args, Filter.FILTERS, r);
+ }
+
+ public static String run(
+ final String template,
+ final Map<String, String> args,
+ final Map<String, Filter.Handler> filters
+ ) throws LuigiError {
+ final Template t = new Template(template, filters);
+ return t.run(args);
+ }
+
+ public static void run(
+ final String template,
+ final Map<String, String> args,
+ final Map<String, Filter.Handler> filters,
+ final ResultHandler r
+ ) throws LuigiError {
+ final Template t = new Template(template, filters);
+ t.run(args, r);
+ }
};
diff --git a/java/src/main/java/org/pablotron/luigi/UnknownEntryError.java b/java/src/main/java/org/pablotron/luigi/UnknownEntryError.java
new file mode 100644
index 0000000..8b8a0fd
--- /dev/null
+++ b/java/src/main/java/org/pablotron/luigi/UnknownEntryError.java
@@ -0,0 +1,13 @@
+package org.pablotron.luigi;
+
+public class UnknownEntryError extends LuigiError {
+ public final String type;
+ public final String name;
+
+ public UnknownEntryError(final String type, final String name) {
+ super(String.format("unknown %s: %s", type, name));
+
+ this.type = type;
+ this.name = name;
+ }
+};
diff --git a/java/src/main/java/org/pablotron/luigi/UnknownFilterError.java b/java/src/main/java/org/pablotron/luigi/UnknownFilterError.java
new file mode 100644
index 0000000..a56a6c1
--- /dev/null
+++ b/java/src/main/java/org/pablotron/luigi/UnknownFilterError.java
@@ -0,0 +1,7 @@
+package org.pablotron.luigi;
+
+public class UnknownFilterError extends UnknownEntryError {
+ public UnknownFilterError(final String name) {
+ super("filter", name);
+ }
+};
diff --git a/java/src/main/java/org/pablotron/luigi/UnknownKeyError.java b/java/src/main/java/org/pablotron/luigi/UnknownKeyError.java
new file mode 100644
index 0000000..88da21a
--- /dev/null
+++ b/java/src/main/java/org/pablotron/luigi/UnknownKeyError.java
@@ -0,0 +1,7 @@
+package org.pablotron.luigi;
+
+public class UnknownKeyError extends UnknownEntryError {
+ public UnknownKeyError(final String name) {
+ super("key", name);
+ }
+};
diff --git a/java/src/main/java/org/pablotron/luigi/UnknownTemplateError.java b/java/src/main/java/org/pablotron/luigi/UnknownTemplateError.java
new file mode 100644
index 0000000..15dae97
--- /dev/null
+++ b/java/src/main/java/org/pablotron/luigi/UnknownTemplateError.java
@@ -0,0 +1,7 @@
+package org.pablotron.luigi;
+
+public class UnknownTemplateError extends UnknownEntryError {
+ public UnknownTemplateError(final String name) {
+ super("template", name);
+ }
+};
diff --git a/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java b/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java
index 9115a68..19565dc 100644
--- a/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java
+++ b/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java
@@ -5,6 +5,8 @@ import org.pablotron.luigi.actions.Action;
import org.pablotron.luigi.FilterReference;
import org.pablotron.luigi.Filter;
import org.pablotron.luigi.LuigiError;
+import org.pablotron.luigi.UnknownFilterError;
+import org.pablotron.luigi.UnknownKeyError;
public final class FilterAction implements Action {
private final String key;
@@ -21,7 +23,7 @@ public final class FilterAction implements Action {
) throws LuigiError {
// check for key
if (!args.containsKey(key))
- throw new LuigiError("unknown key: " + key);
+ throw new UnknownKeyError(key);
// reduce value to result
String r = args.get(key);
@@ -29,7 +31,7 @@ public final class FilterAction implements Action {
// get/check filter
Filter.Handler f = filters.get(this.filters[i].name);
if (f == null)
- throw new LuigiError("unknown filter: " + this.filters[i].name);
+ throw new UnknownFilterError(this.filters[i].name);
// run filter
r = f.filter(r, this.filters[i].args, args);
diff --git a/java/src/test/java/org/pablotron/luigi/tests/CacheTest.java b/java/src/test/java/org/pablotron/luigi/tests/CacheTest.java
new file mode 100644
index 0000000..c91b727
--- /dev/null
+++ b/java/src/test/java/org/pablotron/luigi/tests/CacheTest.java
@@ -0,0 +1,46 @@
+import java.util.Map;
+import java.util.HashMap;
+
+import org.pablotron.luigi.Template;
+import org.pablotron.luigi.Filter;
+import org.pablotron.luigi.Cache;
+import org.pablotron.luigi.LuigiError;
+import org.pablotron.luigi.UnknownKeyError;
+import org.pablotron.luigi.UnknownFilterError;
+import org.pablotron.luigi.UnknownTemplateError;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.Test;
+
+public final class CacheTest {
+ private static Map<String, String> TEST_ARGS = new HashMap<String, String>() {{
+ put("bar", "foo");
+ }};
+
+ private static Map<String, String> TEST_TEMPLATES = new HashMap<String, String>() {{
+ put("foo", "foo%{bar}foo");
+ put("foo-custom", "foo%{bar | custom-filter}foo");
+ }};
+
+ private static Map<String, Filter.Handler> TEST_FILTERS = new HashMap<String, Filter.Handler>() {{
+ put("custom-filter", new Filter.Handler() {
+ public String filter(String val, String args[], Map<String, String> row) {
+ return String.format("-custom-%s-filter-", val);
+ }
+ });
+ }};
+
+ @Test
+ public void testCache() throws LuigiError {
+ final Cache cache = new Cache(TEST_TEMPLATES);
+
+ assertEquals("foofoofoo", cache.run("foo", TEST_ARGS));
+ }
+
+ @Test
+ public void testCacheWithCustomFilters() throws LuigiError {
+ final Cache cache = new Cache(TEST_TEMPLATES, TEST_FILTERS);
+ assertEquals("foo-custom-foo-filter-foo", cache.run("foo-custom", TEST_ARGS));
+ }
+};
diff --git a/java/src/test/java/org/pablotron/luigi/tests/DefaultFiltersTest.java b/java/src/test/java/org/pablotron/luigi/tests/DefaultFiltersTest.java
new file mode 100644
index 0000000..c839ded
--- /dev/null
+++ b/java/src/test/java/org/pablotron/luigi/tests/DefaultFiltersTest.java
@@ -0,0 +1,53 @@
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.pablotron.luigi.LuigiError;
+import org.pablotron.luigi.Template;
+import org.pablotron.luigi.Filter;
+import org.pablotron.luigi.FilterError;
+import org.pablotron.luigi.ResultHandler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.junit.jupiter.api.Test;
+
+public final class DefaultFiltersTest {
+ private static final class TestCase {
+ private final String name;
+ private final String arg;
+ public final String expect;
+
+ public TestCase(final String name, final String arg, final String expect) {
+ this.name = name;
+ this.arg = arg;
+ this.expect = expect;
+ }
+
+ public String run() throws LuigiError {
+ final Map<String, String> args = new HashMap<String, String>();
+ args.put("val", arg);
+
+ return Template.run(String.format("%%{val|%s}", name), args);
+ }
+ };
+
+ private static final List<TestCase> TEST_CASES = new ArrayList<TestCase>() {{
+ add(new TestCase("uc", "bar", "BAR"));
+ add(new TestCase("lc", "BAR", "bar"));
+ add(new TestCase("h", "asdf<>&\"'\u000f", "asdf&lt;&gt;&amp;&quot;&apos;&#15;"));
+ add(new TestCase("u", "asdf<>&\"' \u000f", "asdf%3C%3E%26%22%27+%0F"));
+ add(new TestCase("trim", " \r\n\tfoo", "foo"));
+ add(new TestCase("trim", " \r\n\tfoo \r\n\t", "foo"));
+ add(new TestCase("trim", "foo \r\n\t", "foo"));
+ }};
+
+ @Test
+ public void testDefaultFilters() throws LuigiError {
+ for (final TestCase t: TEST_CASES) {
+ assertEquals(t.expect, t.run());
+ }
+ }
+};
diff --git a/java/src/test/java/org/pablotron/luigi/tests/FiltersTest.java b/java/src/test/java/org/pablotron/luigi/tests/FiltersTest.java
new file mode 100644
index 0000000..9168529
--- /dev/null
+++ b/java/src/test/java/org/pablotron/luigi/tests/FiltersTest.java
@@ -0,0 +1,88 @@
+import java.util.Map;
+import java.util.HashMap;
+
+import org.pablotron.luigi.LuigiError;
+import org.pablotron.luigi.Template;
+import org.pablotron.luigi.Filter;
+import org.pablotron.luigi.FilterError;
+import org.pablotron.luigi.ResultHandler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.junit.jupiter.api.Test;
+
+
+public final class FiltersTest {
+ private static final Map<String, String> TEST_ARGS = new HashMap<String, String>() {{
+ put("bar", "foo");
+ }};
+
+ private static final Map<String, String> TEST_MULTIPLE_ARGS = new HashMap<String, String>() {{
+ put("bar", "foo");
+ put("baz", "bar");
+ }};
+
+ private static final Map<String, Filter.Handler> TEST_FILTERS = new HashMap<String, Filter.Handler>() {{
+ put("barify", new Filter.Handler() {
+ public String filter(String val, String args[], Map<String, String> row) {
+ return String.format("bar-%s-bar", val);
+ }
+ });
+
+ put("wrap", new Filter.Handler() {
+ public String filter(
+ String val,
+ String args[],
+ Map<String, String> row
+ ) throws FilterError {
+ switch (args.length) {
+ case 2:
+ return String.format("(%s, %s, %s)", args[0], val, args[1]);
+ case 1:
+ return String.format("(%s in %s)", val, args[0], val);
+ case 0:
+ return val;
+ default:
+ throw new FilterError("invalid filter argument count");
+ }
+ }
+ });
+ }};
+
+ @Test
+ public void testFilter() throws LuigiError {
+ final String r = Template.run("foo%{bar | lc}", TEST_ARGS);
+
+ assertEquals("foofoo", r);
+ }
+
+ @Test
+ public void testFilterChain() throws LuigiError {
+ final String r = Template.run("foo%{bar | lc | uc}", TEST_ARGS);
+
+ assertEquals("fooFOO", r);
+ }
+
+ @Test
+ public void testCustomFilter() throws LuigiError {
+ final String r = Template.run("foo%{bar | barify}", TEST_ARGS, TEST_FILTERS);
+
+ assertEquals("foobar-foo-bar", r);
+ }
+
+ @Test
+ public void testCustomFilterWithArgs() throws LuigiError {
+ // test two arguments
+ final String plain = Template.run("%{bar | wrap}", TEST_ARGS, TEST_FILTERS);
+ assertEquals("foo", plain);
+
+ // test one argument
+ final String sandwich = Template.run("%{bar | wrap bread}", TEST_ARGS, TEST_FILTERS);
+ assertEquals("(foo in bread)", sandwich);
+
+ // test two arguments
+ final String pizza = Template.run("%{bar | wrap crust cheese}", TEST_ARGS, TEST_FILTERS);
+ assertEquals("(crust, foo, cheese)", pizza);
+ }
+};
diff --git a/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java b/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java
index 94e259e..eadaab1 100644
--- a/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java
+++ b/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java
@@ -36,6 +36,13 @@ public final class TemplateTest {
assertEquals("foofoo", r);
}
+ @Test
+ public void testStaticRun() throws LuigiError {
+ final String r = Template.run("foo%{bar}", TEST_ARGS);
+
+ assertEquals("foofoo", r);
+ }
+
private static final class TestResultHandler implements ResultHandler {
private final StringBuilder sb;
public TestResultHandler(final StringBuilder sb) {
@@ -60,6 +67,17 @@ public final class TemplateTest {
}
@Test
+ public void testStaticResultHandler() throws LuigiError {
+ final StringBuilder sb = new StringBuilder();
+ final TestResultHandler rh = new TestResultHandler(sb);
+
+ Template.run("foo%{bar}", TEST_ARGS, rh);
+ final String r = sb.toString();
+
+ assertEquals("foofoo", sb.toString());
+ }
+
+ @Test
public void testMultipleKeys() throws LuigiError {
final Template t = new Template("foo%{bar}%{baz}");
final String r = t.run(TEST_MULTIPLE_ARGS);