From c4f3f955179cd217c6e7273561d06468b33e8973 Mon Sep 17 00:00:00 2001
From: Paul Duncan <pabs@pablotron.org>
Date: Thu, 6 Sep 2018 18:08:34 -0400
Subject: update errors, fix filters, add tests

---
 java/src/main/java/org/pablotron/luigi/Cache.java  |  27 ++--
 java/src/main/java/org/pablotron/luigi/Filter.java | 173 +++++++++++++++++----
 .../main/java/org/pablotron/luigi/FilterError.java |   7 +
 .../main/java/org/pablotron/luigi/Template.java    |  34 ++++
 .../org/pablotron/luigi/UnknownEntryError.java     |  13 ++
 .../org/pablotron/luigi/UnknownFilterError.java    |   7 +
 .../java/org/pablotron/luigi/UnknownKeyError.java  |   7 +
 .../org/pablotron/luigi/UnknownTemplateError.java  |   7 +
 .../org/pablotron/luigi/actions/FilterAction.java  |   6 +-
 9 files changed, 234 insertions(+), 47 deletions(-)
 create mode 100644 java/src/main/java/org/pablotron/luigi/FilterError.java
 create mode 100644 java/src/main/java/org/pablotron/luigi/UnknownEntryError.java
 create mode 100644 java/src/main/java/org/pablotron/luigi/UnknownFilterError.java
 create mode 100644 java/src/main/java/org/pablotron/luigi/UnknownKeyError.java
 create mode 100644 java/src/main/java/org/pablotron/luigi/UnknownTemplateError.java

(limited to 'java/src/main')

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);
-- 
cgit v1.2.3