diff options
Diffstat (limited to 'java/src')
11 files changed, 522 insertions, 0 deletions
diff --git a/java/src/main/java/org/pablotron/luigi/Cache.java b/java/src/main/java/org/pablotron/luigi/Cache.java new file mode 100644 index 0000000..97fa0ab --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/Cache.java @@ -0,0 +1,51 @@ +package org.pablotron.luigi; + +import java.util.Map; +import java.util.HashMap; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.Template; +import org.pablotron.luigi.LuigiError; +import org.pablotron.luigi.actions.Action; + +public final class Cache { + private final Map<String, String> strings; + private final Map<String, Filter.Handler> filters; + private final Map<String, Template> templates = new HashMap<String, Template>(); + + public Cache( + final Map<String, String> strings, + final Map<String, Filter.Handler> filters + ) { + this.strings = strings; + this.filters = filters; + } + + public Cache(final Map<String, String> strings) { + this(strings, Filter.FILTERS); + } + + public String run( + final String key, + final Map<String, String> args + ) throws LuigiError { + Template t; + + if (templates.containsKey(key)) { + // get template + t = templates.get(key); + } else { + // make sure template exists + if (!strings.containsKey(key)) + throw new LuigiError("unknown template: " + key); + + // create template + t = new Template(strings.get(key), filters); + + // cache template + templates.put(key, t); + } + + // run template with args + return t.run(args); + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/Filter.java b/java/src/main/java/org/pablotron/luigi/Filter.java new file mode 100644 index 0000000..b00491f --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/Filter.java @@ -0,0 +1,89 @@ +package org.pablotron.luigi; + +import java.util.Map; +import java.util.HashMap; + +public final class Filter { + public interface Handler { + public String filter(String val, String args[], Map<String, String> row); + }; + + 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) { + return ""; + } + }); + + put("s", new Handler() { + public String filter(String val, String args[], Map<String, String> row) { + int v = Integer.parseInt(val); + return (v == 1) ? "" : "s"; + } + }); + + put("uc", new Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return val.toUpperCase(); + } + }); + + put("lc", new Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return val.toLowerCase(); + } + }); + + put("length", new Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return Integer.toString(val.length()); + } + }); + + put("trim", new Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return val.trim(); + } + }); + + 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("&"); + break; + case '<': + r.append("<"); + break; + case '>': + r.append(">"); + break; + case '\'': + r.append("'"); + break; + case '"': + r.append("""); + break; + default: + r.append(c); + } + } + + return r.toString(); +/* + * return val + * .replace("&", "&") + * .replace("<", "<") + * .replace(">", ">") + * .replace("'", "'") + * .replace("\"", """); + */ + } + }); + }}; +}; diff --git a/java/src/main/java/org/pablotron/luigi/FilterReference.java b/java/src/main/java/org/pablotron/luigi/FilterReference.java new file mode 100644 index 0000000..38c134b --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/FilterReference.java @@ -0,0 +1,11 @@ +package org.pablotron.luigi; + +public final class FilterReference { + public final String name; + public final String[] args; + + public FilterReference(final String name, final String args[]) { + this.name = name; + this.args = args; + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/LuigiError.java b/java/src/main/java/org/pablotron/luigi/LuigiError.java new file mode 100644 index 0000000..edeb7c7 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/LuigiError.java @@ -0,0 +1,7 @@ +package org.pablotron.luigi; + +public class LuigiError extends Exception { + public LuigiError(final String message) { + super(message); + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/Parser.java b/java/src/main/java/org/pablotron/luigi/Parser.java new file mode 100644 index 0000000..5be4a9c --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/Parser.java @@ -0,0 +1,122 @@ +package org.pablotron.luigi; + +import java.util.ArrayList; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import org.pablotron.luigi.actions.Action; +import org.pablotron.luigi.actions.FilterAction; +import org.pablotron.luigi.actions.TextAction; +import org.pablotron.luigi.FilterReference; +import org.pablotron.luigi.LuigiError; + +public final class Parser { + private static final Pattern RE_ACTION = Pattern.compile( + // match opening brace + "%\\{" + + + // match optional whitespace + "\\s*" + + + // match key + // "(?<key>[^\\s\\|\\}]+)" + + "([^\\s\\|\\}]+)" + + + // match filter(s) + // "(?<filters>(\\s*\\|(\\s*[^\\s\\|\\}]+)+)*)" + + "((\\s*\\|(\\s*[^\\s\\|\\}]+)+)*)" + + + // match optional whitespace + "\\s*" + + + // match closing brace + "\\}" + + + // or match up all non-% chars or a single % char + // "| (?<text>[^%]* | %)", + "| ([^%]* | %)", + + Pattern.COMMENTS + ); + + private static final Pattern RE_FILTER = Pattern.compile( + // match filter name + // "(?<name>\\S+)" + + "(\\S+)" + + + // match filter arguments (optional) + // "(?<args>(\\s*\\S+)*)" + + "((\\s*\\S+)*)" + + + // optional trailing whitespace + "\\s*", + + Pattern.COMMENTS + ); + + private static final Pattern RE_DELIM_FILTERS = Pattern.compile( + "\\s*\\|\\s*" + ); + + private static final Pattern RE_DELIM_ARGS = Pattern.compile( + "\\s+" + ); + + public static Action[] parse_template( + final String template + ) throws LuigiError { + final ArrayList<Action> r = new ArrayList<Action>(); + + // match on text + final Matcher m = RE_ACTION.matcher(template); + + while (m.find()) { + // String key = m.group("key"); + final String key = m.group(1); + + if (key != null && key.length() > 0) { + // r.add(new FilterAction(key, parse_filters(m.group("filters")))); + r.add(new FilterAction(key, parse_filters(m.group(2)))); + } else { + // r.add(new TextAction(m.group("text"))); + r.add(new TextAction(m.group(5))); + } + } + + // build array of results + return r.toArray(new Action[r.size()]); + } + + private static final String[] NO_ARGS = {}; + + public static FilterReference[] parse_filters( + final String filters_str + ) throws LuigiError { + final ArrayList<FilterReference> r = new ArrayList<FilterReference>(); + + // split string into individual filters and handle each one + for (String f: RE_DELIM_FILTERS.split(filters_str)) { + // trim filter string and skip empty filters + f = f.trim(); + if (f.length() == 0) + continue; + + // match on filter and check for error + final Matcher m = RE_FILTER.matcher(f); + if (!m.find()) + throw new LuigiError("invalid filter: " + f); + + // get arguments string + final String args = m.group(2).trim(); + + // append new filter reference to result + r.add(new FilterReference( + m.group(1), + (args.length() > 0) ? RE_DELIM_ARGS.split(args) : NO_ARGS + )); + } + + // return result + return r.toArray(new FilterReference[r.size()]); + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/Template.java b/java/src/main/java/org/pablotron/luigi/Template.java new file mode 100644 index 0000000..2b17a54 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/Template.java @@ -0,0 +1,41 @@ +package org.pablotron.luigi; + +import java.util.Map; +import org.pablotron.luigi.Parser; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.LuigiError; +import org.pablotron.luigi.actions.Action; + +public final class Template { + private static final String VERSION = "0.4.0"; + + private final String template; + private final Action actions[]; + private final Map<String, Filter.Handler> filters; + + public Template( + final String template, + final Map<String, Filter.Handler> filters + ) throws LuigiError { + this.template = template; + this.filters = filters; + this.actions = Parser.parse_template(template); + } + + public Template(final String template) throws LuigiError { + this(template, Filter.FILTERS); + } + + public String run(final Map<String, String> args) throws LuigiError { + final StringBuilder r = new StringBuilder(); + + for (Action a: this.actions) + r.append(a.run(this.filters, args)); + + return r.toString(); + } + + public String toString() { + return this.template; + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/Test.java b/java/src/main/java/org/pablotron/luigi/Test.java new file mode 100644 index 0000000..2a560e2 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/Test.java @@ -0,0 +1,57 @@ +package org.pablotron.luigi; + +import java.util.Map; +import java.util.HashMap; + +import org.pablotron.luigi.LuigiError; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.Template; +import org.pablotron.luigi.Cache; + +public final class Test { + // test template + private static final String TEMPLATE = + "test basic: hello %{name}\n" + + "test filter: hello %{name | uc}\n" + + "test custom: %{name | custom | uc | lc}\n" + + "test custom_with_arg: %{name | custom_with_arg hello}\n"; + + // test template cache + private static final Cache cache = new Cache(new HashMap<String, String>() {{ + put("test-template", TEMPLATE); + }}); + + // test arguments + private static final Map<String, String> args = new HashMap<String, String>() {{ + put("name", "paul"); + }}; + + // custom filters + private static final Map<String, Filter.Handler> filters = new HashMap<String, Filter.Handler>() {{ + // add custom filter + put("custom", new Filter.Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return "custom"; + } + }); + + // add custom filter with argument + put("custom_with_arg", new Filter.Handler() { + public String filter(String val, String args[], Map<String, String> row) { + return (args.length > 0) ? args[0] : "custom"; + } + }); + }}; + + public static void main(String params[]) throws Exception { + // add custom filters + Filter.FILTERS.putAll(filters); + + // create and run template + final Template t = new Template(TEMPLATE); + System.out.print(t.run(args)); + + // run cache + System.out.print(cache.run("test-template", args)); + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/actions/Action.java b/java/src/main/java/org/pablotron/luigi/actions/Action.java new file mode 100644 index 0000000..9708199 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/actions/Action.java @@ -0,0 +1,12 @@ +package org.pablotron.luigi.actions; + +import java.util.Map; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.LuigiError; + +public interface Action { + public String run( + Map<String, Filter.Handler> filters, + Map<String, String> args + ) throws LuigiError; +}; diff --git a/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java b/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java new file mode 100644 index 0000000..9115a68 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/actions/FilterAction.java @@ -0,0 +1,41 @@ +package org.pablotron.luigi.actions; + +import java.util.Map; +import org.pablotron.luigi.actions.Action; +import org.pablotron.luigi.FilterReference; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.LuigiError; + +public final class FilterAction implements Action { + private final String key; + private final FilterReference filters[]; + + public FilterAction(final String key, final FilterReference filters[]) { + this.key = key; + this.filters = filters; + } + + public String run( + Map<String, Filter.Handler> filters, + Map<String, String> args + ) throws LuigiError { + // check for key + if (!args.containsKey(key)) + throw new LuigiError("unknown key: " + key); + + // reduce value to result + String r = args.get(key); + for (int i = 0, l = this.filters.length; i < l; i++) { + // get/check filter + Filter.Handler f = filters.get(this.filters[i].name); + if (f == null) + throw new LuigiError("unknown filter: " + this.filters[i].name); + + // run filter + r = f.filter(r, this.filters[i].args, args); + } + + // return result + return r; + } +}; diff --git a/java/src/main/java/org/pablotron/luigi/actions/TextAction.java b/java/src/main/java/org/pablotron/luigi/actions/TextAction.java new file mode 100644 index 0000000..0ad4382 --- /dev/null +++ b/java/src/main/java/org/pablotron/luigi/actions/TextAction.java @@ -0,0 +1,22 @@ +package org.pablotron.luigi.actions; + +import java.util.Map; +import org.pablotron.luigi.actions.Action; +import org.pablotron.luigi.Filter; +import org.pablotron.luigi.LuigiError; + +public final class TextAction implements Action { + private final String text; + + public TextAction(final String text) { + this.text = text; + } + + public String run( + Map<String, Filter.Handler> filters, + Map<String, String> args + ) throws LuigiError { + return this.text; + } +}; + diff --git a/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java b/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java new file mode 100644 index 0000000..5ff9ede --- /dev/null +++ b/java/src/test/java/org/pablotron/luigi/tests/TemplateTest.java @@ -0,0 +1,69 @@ +import java.util.Map; +import java.util.HashMap; + +import org.pablotron.luigi.LuigiError; +import org.pablotron.luigi.Template; + +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 TemplateTest { + 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"); + }}; + + @Test + public void testNew() throws LuigiError { + final Template t = new Template(""); + + assertNotNull(t); + } + + @Test + public void testRun() throws LuigiError { + final Template t = new Template("foo%{bar}"); + final String r = t.run(TEST_ARGS); + + assertEquals("foofoo", r); + } + + @Test + public void testMultipleKeys() throws LuigiError { + final Template t = new Template("foo%{bar}%{baz}"); + final String r = t.run(TEST_MULTIPLE_ARGS); + + assertEquals("foofoobar", r); + } + + @Test + public void testWhitespace() throws LuigiError { + final Template t = new Template("%{ bar}%{ bar }%{bar }"); + final String r = t.run(TEST_ARGS); + + assertEquals("foofoofoo", r); + } + + @Test + public void testNewlines() throws LuigiError { + final Template t = new Template("%{\nbar}%{\n bar\n }%{bar\n}"); + final String r = t.run(TEST_ARGS); + + assertEquals("foofoofoo", r); + } + + @Test + public void testToString() throws LuigiError { + final Template t = new Template("foo%{bar}"); + final String r = t.toString(); + + assertEquals("foo%{bar}", r); + } +}; |