+ use Groovy instead of JavaScript in ExpressionFormat
This commit is contained in:
parent
384486631a
commit
b04f89b7fd
|
@ -92,12 +92,9 @@
|
||||||
<include name="com/sun/jna/**" />
|
<include name="com/sun/jna/**" />
|
||||||
</zipfileset>
|
</zipfileset>
|
||||||
|
|
||||||
<zipfileset src="${dir.lib}/js-engine.jar">
|
<zipfileset src="${dir.lib}/groovy.jar">
|
||||||
<include name="com/sun/phobos/script/**" />
|
<include name="groovy*/**" />
|
||||||
</zipfileset>
|
<include name="org/codehaus/groovy/**" />
|
||||||
|
|
||||||
<zipfileset src="${dir.lib}/js.jar">
|
|
||||||
<include name="org/mozilla/**" />
|
|
||||||
</zipfileset>
|
</zipfileset>
|
||||||
|
|
||||||
<zipfileset src="${dir.lib}/sublight-ws.jar">
|
<zipfileset src="${dir.lib}/sublight-ws.jar">
|
||||||
|
|
Binary file not shown.
Binary file not shown.
BIN
lib/js.jar
BIN
lib/js.jar
Binary file not shown.
|
@ -2,6 +2,9 @@
|
||||||
package net.sourceforge.filebot.format;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import groovy.lang.GroovyObject;
|
||||||
|
import groovy.lang.MetaClass;
|
||||||
|
|
||||||
import java.util.AbstractMap;
|
import java.util.AbstractMap;
|
||||||
import java.util.AbstractSet;
|
import java.util.AbstractSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -10,10 +13,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.mozilla.javascript.Scriptable;
|
|
||||||
|
|
||||||
|
public class AssociativeScriptObject implements GroovyObject {
|
||||||
public class AssociativeScriptObject implements Scriptable {
|
|
||||||
|
|
||||||
private final Map<String, Object> properties;
|
private final Map<String, Object> properties;
|
||||||
|
|
||||||
|
@ -23,24 +24,14 @@ public class AssociativeScriptObject implements Scriptable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines properties available by name.
|
|
||||||
*
|
|
||||||
* @param name the name of the property
|
|
||||||
* @param start the object where lookup began
|
|
||||||
*/
|
|
||||||
public boolean has(String name, Scriptable start) {
|
|
||||||
return properties.containsKey(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the property with the given name.
|
* Get the property with the given name.
|
||||||
*
|
*
|
||||||
* @param name the property name
|
* @param name the property name
|
||||||
* @param start the object where the lookup began
|
* @param start the object where the lookup began
|
||||||
*/
|
*/
|
||||||
public Object get(String name, Scriptable start) {
|
@Override
|
||||||
|
public Object getProperty(String name) {
|
||||||
Object value = properties.get(name);
|
Object value = properties.get(name);
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
|
@ -50,52 +41,28 @@ public class AssociativeScriptObject implements Scriptable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines properties available by index.
|
|
||||||
*
|
|
||||||
* @param index the index of the property
|
|
||||||
* @param start the object where lookup began
|
|
||||||
*/
|
|
||||||
public boolean has(int index, Scriptable start) {
|
|
||||||
// get property by index not supported
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get property by index.
|
|
||||||
*
|
|
||||||
* @param index the index of the property
|
|
||||||
* @param start the object where the lookup began
|
|
||||||
*/
|
|
||||||
public Object get(int index, Scriptable start) {
|
|
||||||
// get property by index not supported
|
|
||||||
throw new BindingException(String.valueOf(index), "undefined");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get property names.
|
|
||||||
*/
|
|
||||||
public Object[] getIds() {
|
|
||||||
return properties.keySet().toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of this JavaScript class.
|
|
||||||
*/
|
|
||||||
public String getClassName() {
|
|
||||||
return getClass().getSimpleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the string value of this object.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Object getDefaultValue(Class<?> typeHint) {
|
public void setProperty(String name, Object value) {
|
||||||
return this.toString();
|
// ignore, object is immutable
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invokeMethod(String name, Object args) {
|
||||||
|
// ignore, object is merely a structure
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MetaClass getMetaClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMetaClass(MetaClass clazz) {
|
||||||
|
// ignore, don't care about MetaClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,51 +73,6 @@ public class AssociativeScriptObject implements Scriptable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void put(String name, Scriptable start, Object value) {
|
|
||||||
// ignore, object is immutable
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void put(int index, Scriptable start, Object value) {
|
|
||||||
// ignore, object is immutable
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void delete(String id) {
|
|
||||||
// ignore, object is immutable
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void delete(int index) {
|
|
||||||
// ignore, object is immutable
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Scriptable getPrototype() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setPrototype(Scriptable prototype) {
|
|
||||||
// ignore, don't care about prototype
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Scriptable getParentScope() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setParentScope(Scriptable parent) {
|
|
||||||
// ignore, don't care about scope
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasInstance(Scriptable value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map allowing look-up of values by a fault-tolerant key as specified by the defining key.
|
* Map allowing look-up of values by a fault-tolerant key as specified by the defining key.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
// System, Math, Integer, etc.
|
|
||||||
importPackage(java.lang);
|
|
||||||
|
|
||||||
// Collection, Scanner, Random, UUID, etc.
|
|
||||||
importPackage(java.util);
|
|
||||||
|
|
||||||
// other useful classes
|
|
||||||
importClass(net.sourceforge.filebot.similarity.SeriesNameMatcher);
|
|
||||||
importClass(net.sourceforge.filebot.similarity.SeasonEpisodeMatcher);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience methods for String.toLowerCase() and String.toUpperCase().
|
|
||||||
*/
|
|
||||||
String.prototype.lower = String.prototype.toLowerCase;
|
|
||||||
String.prototype.upper = String.prototype.toUpperCase;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pad strings or numbers with given characters ('0' by default).
|
|
||||||
*
|
|
||||||
* e.g. "1" -> "01"
|
|
||||||
*/
|
|
||||||
String.prototype.pad = Number.prototype.pad = function(length, padding) {
|
|
||||||
var s = this.toString();
|
|
||||||
|
|
||||||
// use default padding, if padding is undefined or empty
|
|
||||||
var p = padding ? padding.toString() : '0';
|
|
||||||
|
|
||||||
while (s.length < length) {
|
|
||||||
s = p + s;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace space characters with a given characters.
|
|
||||||
*
|
|
||||||
* e.g. "Doctor Who" -> "Doctor_Who"
|
|
||||||
*/
|
|
||||||
String.prototype.space = function(replacement) {
|
|
||||||
return this.replace(/\s+/g, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upper-case all initials.
|
|
||||||
*
|
|
||||||
* e.g. "The Day a new Demon was born" -> "The Day A New Demon Was Born"
|
|
||||||
*/
|
|
||||||
String.prototype.upperInitial = function() {
|
|
||||||
return this.replace(/\b[a-z]/g, function(letter) { return letter.toUpperCase() });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lower-case all letters that are not initials.
|
|
||||||
*
|
|
||||||
* e.g. "Gundam SEED" -> "Gundam Seed"
|
|
||||||
*/
|
|
||||||
String.prototype.lowerTrail = function() {
|
|
||||||
return this.replace(/\b([a-z])([a-z]+)\b/gi, function(match, initial, trail) { return initial + trail.toLowerCase() });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove leading and trailing whitespace.
|
|
||||||
*/
|
|
||||||
String.prototype.trim = function() {
|
|
||||||
return this.replace(/^\s+|\s+$/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return substring before the given delimiter.
|
|
||||||
*/
|
|
||||||
String.prototype.before = function(delimiter) {
|
|
||||||
var endIndex = delimiter instanceof RegExp ? this.search(delimiter) : this.indexOf(delimiter);
|
|
||||||
|
|
||||||
// delimiter was found, return leading substring, else return original value
|
|
||||||
return endIndex >= 0 ? this.substring(0, endIndex) : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return substring after the given delimiter.
|
|
||||||
*/
|
|
||||||
String.prototype.after = function(delimiter) {
|
|
||||||
if (delimiter instanceof RegExp) {
|
|
||||||
var match = this.match(delimiter);
|
|
||||||
|
|
||||||
if (match == null)
|
|
||||||
return this;
|
|
||||||
|
|
||||||
// use pattern match as delimiter
|
|
||||||
delimiter = match[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
var startIndex = this.indexOf(delimiter);
|
|
||||||
|
|
||||||
// delimiter was found, return trailing substring, else return original value
|
|
||||||
return startIndex >= 0 ? this.substring(startIndex + delimiter.length, this.length) : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace trailing parenthesis including any leading whitespace.
|
|
||||||
*
|
|
||||||
* e.g. "The IT Crowd (UK)" -> "The IT Crowd"
|
|
||||||
*/
|
|
||||||
String.prototype.replaceTrailingBraces = function(replacement) {
|
|
||||||
// use empty string as default replacement
|
|
||||||
var r = replacement ? replacement : "";
|
|
||||||
|
|
||||||
return this.replace(/\s*[(]([^)]*)[)]$/, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace 'part section'.
|
|
||||||
*
|
|
||||||
* e.g. "Today Is the Day: Part 1" -> "Today Is the Day, Part 1"
|
|
||||||
* "Today Is the Day (1)" -> "Today Is the Day, Part 1"
|
|
||||||
*/
|
|
||||||
String.prototype.replacePart = function (replacement) {
|
|
||||||
// use empty string as default replacement
|
|
||||||
var r = replacement ? replacement : "";
|
|
||||||
|
|
||||||
// handle '(n)', '(Part n)' and ': Part n' like syntax
|
|
||||||
var pattern = [/\s*[(](\w+)[)]$/i, /\W*Part (\w+)\W*$/i];
|
|
||||||
|
|
||||||
for (var i = 0; i < pattern.length; i++) {
|
|
||||||
if (pattern[i].test(this)) {
|
|
||||||
return this.replace(pattern[i], r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no pattern matches, nothing to replace
|
|
||||||
return this;
|
|
||||||
}
|
|
|
@ -2,6 +2,10 @@
|
||||||
package net.sourceforge.filebot.format;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.ExceptionUtilities.*;
|
||||||
|
import groovy.lang.GroovyRuntimeException;
|
||||||
|
import groovy.lang.MissingPropertyException;
|
||||||
|
|
||||||
import java.io.FilePermission;
|
import java.io.FilePermission;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
|
@ -18,8 +22,6 @@ import java.text.ParsePosition;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.PropertyPermission;
|
import java.util.PropertyPermission;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
import javax.script.Compilable;
|
import javax.script.Compilable;
|
||||||
|
@ -29,9 +31,8 @@ import javax.script.ScriptEngine;
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import javax.script.SimpleScriptContext;
|
import javax.script.SimpleScriptContext;
|
||||||
|
|
||||||
import org.mozilla.javascript.EcmaError;
|
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
|
||||||
|
import org.codehaus.groovy.jsr223.GroovyScriptEngineFactory;
|
||||||
import com.sun.phobos.script.javascript.RhinoScriptEngine;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
@ -52,10 +53,10 @@ public class ExpressionFormat extends Format {
|
||||||
|
|
||||||
|
|
||||||
protected ScriptEngine initScriptEngine() throws ScriptException {
|
protected ScriptEngine initScriptEngine() throws ScriptException {
|
||||||
// don't use jdk rhino so we can use rhino specific features and classes (e.g. Scriptable)
|
// use groovy script engine
|
||||||
ScriptEngine engine = new RhinoScriptEngine();
|
ScriptEngine engine = new GroovyScriptEngineFactory().getScriptEngine();
|
||||||
|
|
||||||
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.global.js")));
|
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.lib.groovy")));
|
||||||
|
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
@ -69,29 +70,71 @@ public class ExpressionFormat extends Format {
|
||||||
protected Object[] compile(String expression, Compilable engine) throws ScriptException {
|
protected Object[] compile(String expression, Compilable engine) throws ScriptException {
|
||||||
List<Object> compilation = new ArrayList<Object>();
|
List<Object> compilation = new ArrayList<Object>();
|
||||||
|
|
||||||
Matcher matcher = Pattern.compile("\\{([^\\{]*?)\\}").matcher(expression);
|
char open = '{';
|
||||||
|
char close = '}';
|
||||||
|
|
||||||
int position = 0;
|
StringBuilder token = new StringBuilder();
|
||||||
|
int level = 0;
|
||||||
|
|
||||||
while (matcher.find()) {
|
// parse expressions and literals
|
||||||
if (position < matcher.start()) {
|
for (int i = 0; i < expression.length(); i++) {
|
||||||
// literal before
|
char c = expression.charAt(i);
|
||||||
compilation.add(expression.substring(position, matcher.start()));
|
|
||||||
|
if (c == open) {
|
||||||
|
if (level == 0) {
|
||||||
|
if (token.length() > 0) {
|
||||||
|
compilation.add(token.toString());
|
||||||
|
token.setLength(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
level++;
|
||||||
|
} else if (c == close) {
|
||||||
|
if (level == 1) {
|
||||||
|
if (token.length() > 0) {
|
||||||
|
try {
|
||||||
|
compilation.add(engine.compile(token.toString()));
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// try to extract syntax exception
|
||||||
|
ScriptException illegalSyntax = e;
|
||||||
|
|
||||||
|
try {
|
||||||
|
String message = findCause(e, MultipleCompilationErrorsException.class).getErrorCollector().getSyntaxError(0).getOriginalMessage();
|
||||||
|
illegalSyntax = new ScriptException("SyntaxError: " + message);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// ignore, just use original exception
|
||||||
|
}
|
||||||
|
|
||||||
|
throw illegalSyntax;
|
||||||
|
} finally {
|
||||||
|
token.setLength(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
token.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
level--;
|
||||||
|
} else {
|
||||||
|
token.append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
String script = matcher.group(1);
|
// sanity check
|
||||||
|
if (level < 0) {
|
||||||
if (script.length() > 0) {
|
throw new ScriptException("SyntaxError: unexpected token: " + close);
|
||||||
// compiled script, or literal
|
|
||||||
compilation.add(engine.compile(script));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
position = matcher.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position < expression.length()) {
|
// sanity check
|
||||||
// tail
|
if (level != 0) {
|
||||||
compilation.add(expression.substring(position, expression.length()));
|
throw new ScriptException("SyntaxError: missing token: " + close);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append tail
|
||||||
|
if (token.length() > 0) {
|
||||||
|
compilation.add(token.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return compilation.toArray();
|
return compilation.toArray();
|
||||||
|
@ -129,16 +172,7 @@ public class ExpressionFormat extends Format {
|
||||||
sb.append(value);
|
sb.append(value);
|
||||||
}
|
}
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
EcmaError ecmaError = ExceptionUtilities.findCause(e, EcmaError.class);
|
handleException(e);
|
||||||
|
|
||||||
// try to unwrap EcmaError
|
|
||||||
if (ecmaError != null) {
|
|
||||||
lastException = new ExpressionException(String.format("%s: %s", ecmaError.getName(), ecmaError.getErrorMessage()), e);
|
|
||||||
} else {
|
|
||||||
lastException = e;
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
lastException = new ExpressionException(e);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append(snipped);
|
sb.append(snipped);
|
||||||
|
@ -149,6 +183,17 @@ public class ExpressionFormat extends Format {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void handleException(ScriptException exception) {
|
||||||
|
if (findCause(exception, MissingPropertyException.class) != null) {
|
||||||
|
lastException = new ExpressionException(new BindingException(findCause(exception, MissingPropertyException.class).getProperty(), "undefined", exception));
|
||||||
|
} else if (findCause(exception, GroovyRuntimeException.class) != null) {
|
||||||
|
lastException = new ExpressionException(findCause(exception, GroovyRuntimeException.class).getMessage(), exception);
|
||||||
|
} else {
|
||||||
|
lastException = exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ScriptException caughtScriptException() {
|
public ScriptException caughtScriptException() {
|
||||||
return lastException;
|
return lastException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Collection, Scanner, Random, UUID, etc.
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience methods for String.toLowerCase()and String.toUpperCase()
|
||||||
|
*/
|
||||||
|
String.metaClass.lower = { toLowerCase() }
|
||||||
|
String.metaClass.upper = { toUpperCase() }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pad strings or numbers with given characters ('0' by default).
|
||||||
|
*
|
||||||
|
* e.g. "1" -> "01"
|
||||||
|
*/
|
||||||
|
String.metaClass.pad = Number.metaClass.pad = { length = 2, padding = "0" -> delegate.toString().padLeft(length, padding) }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace space characters with a given characters.
|
||||||
|
*
|
||||||
|
* e.g. "Doctor Who" -> "Doctor_Who"
|
||||||
|
*/
|
||||||
|
String.metaClass.space = { replacement -> replaceAll(/\s+/, replacement) }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upper-case all initials.
|
||||||
|
*
|
||||||
|
* e.g. "The Day a new Demon was born" -> "The Day A New Demon Was Born"
|
||||||
|
*/
|
||||||
|
String.metaClass.upperInitial = { replaceAll(/\b[a-z]/, { it.toUpperCase() }) }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lower-case all letters that are not initials.
|
||||||
|
*
|
||||||
|
* e.g. "Gundam SEED" -> "Gundam Seed"
|
||||||
|
*/
|
||||||
|
String.metaClass.lowerTrail = { replaceAll(/\b(\p{Alpha})(\p{Alpha}+)\b/, { match, initial, trail -> initial + trail.toLowerCase() }) }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return substring before the given delimiter.
|
||||||
|
*/
|
||||||
|
String.metaClass.before = {
|
||||||
|
def matcher = delegate =~ it
|
||||||
|
|
||||||
|
// delimiter was found, return leading substring, else return original value
|
||||||
|
return matcher.find() ? delegate.substring(0, matcher.start()) : delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return substring after the given delimiter.
|
||||||
|
*/
|
||||||
|
String.metaClass.after = {
|
||||||
|
def matcher = delegate =~ it
|
||||||
|
|
||||||
|
// delimiter was found, return trailing substring, else return original value
|
||||||
|
return matcher.find() ? delegate.substring(matcher.end(), delegate.length()) : delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace trailing parenthesis including any leading whitespace.
|
||||||
|
*
|
||||||
|
* e.g. "The IT Crowd (UK)" -> "The IT Crowd"
|
||||||
|
*/
|
||||||
|
String.metaClass.replaceTrailingBraces = { replacement = "" -> replaceAll(/\s*[(]([^)]*)[)]$/, replacement) }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace 'part section'.
|
||||||
|
*
|
||||||
|
* e.g. "Today Is the Day: Part 1" -> "Today Is the Day, Part 1"
|
||||||
|
* "Today Is the Day (1)" -> "Today Is the Day, Part 1"
|
||||||
|
*/
|
||||||
|
String.metaClass.replacePart = { replacement = "" ->
|
||||||
|
// handle '(n)', '(Part n)' and ': Part n' like syntax
|
||||||
|
for (pattern in [/\s*[(](\w+)[)]$/, /(?i)\W*Part (\w+)\W*$/]) {
|
||||||
|
if ((delegate =~ pattern).find()) {
|
||||||
|
return replaceAll(pattern, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no pattern matches, nothing to replace
|
||||||
|
return delegate;
|
||||||
|
}
|
|
@ -21,8 +21,6 @@ public class ExpressionFormatTest {
|
||||||
|
|
||||||
Object[] expression = format.compile("name: {name}, number: {number}", (Compilable) format.initScriptEngine());
|
Object[] expression = format.compile("name: {name}, number: {number}", (Compilable) format.initScriptEngine());
|
||||||
|
|
||||||
assertEquals(4, expression.length, 0);
|
|
||||||
|
|
||||||
assertTrue(expression[0] instanceof String);
|
assertTrue(expression[0] instanceof String);
|
||||||
assertTrue(expression[1] instanceof CompiledScript);
|
assertTrue(expression[1] instanceof CompiledScript);
|
||||||
assertTrue(expression[2] instanceof String);
|
assertTrue(expression[2] instanceof String);
|
||||||
|
@ -34,17 +32,97 @@ public class ExpressionFormatTest {
|
||||||
public void format() throws Exception {
|
public void format() throws Exception {
|
||||||
assertEquals("X5-452", new TestScriptFormat("X5-{value}").format("452"));
|
assertEquals("X5-452", new TestScriptFormat("X5-{value}").format("452"));
|
||||||
|
|
||||||
// test pad
|
// padding
|
||||||
assertEquals("[007]", new TestScriptFormat("[{value.pad(3)}]").format("7"));
|
assertEquals("[007]", new TestScriptFormat("[{value.pad(3)}]").format("7"));
|
||||||
|
assertEquals("[xx7]", new TestScriptFormat("[{value.pad(3, 'x')}]").format("7"));
|
||||||
|
|
||||||
|
// case
|
||||||
|
assertEquals("ALL_CAPS", new TestScriptFormat("{value.upper()}").format("all_caps"));
|
||||||
|
assertEquals("lower_case", new TestScriptFormat("{value.lower()}").format("LOWER_CASE"));
|
||||||
|
|
||||||
|
// normalize
|
||||||
|
assertEquals("Doctor_Who", new TestScriptFormat("{value.space('_')}").format("Doctor Who"));
|
||||||
|
assertEquals("The Day A New Demon Was Born", new TestScriptFormat("{value.upperInitial()}").format("The Day a new Demon was born"));
|
||||||
|
assertEquals("Gundam Seed", new TestScriptFormat("{value.lowerTrail()}").format("Gundam SEED"));
|
||||||
|
|
||||||
|
// substring
|
||||||
|
assertEquals("first", new TestScriptFormat("{value.before(/[^a-z]/)}").format("first|second"));
|
||||||
|
assertEquals("second", new TestScriptFormat("{value.after(/[^a-z]/)}").format("first|second"));
|
||||||
|
|
||||||
|
// replace trailing braces
|
||||||
|
assertEquals("The IT Crowd", new TestScriptFormat("{value.replaceTrailingBraces()}").format("The IT Crowd (UK)"));
|
||||||
|
|
||||||
|
// replace part
|
||||||
|
assertEquals("Today Is the Day, Part 1", new TestScriptFormat("{value.replacePart(', Part $1')}").format("Today Is the Day (1)"));
|
||||||
|
assertEquals("Today Is the Day, Part 1", new TestScriptFormat("{value.replacePart(', Part $1')}").format("Today Is the Day: part 1"));
|
||||||
|
|
||||||
// choice
|
// choice
|
||||||
assertEquals("not to be", new TestScriptFormat("{if (value) 'to be'; else 'not to be'}").format(null));
|
assertEquals("not to be", new TestScriptFormat("{value ? 'to be' : 'not to be'}").format(null));
|
||||||
|
assertEquals("default", new TestScriptFormat("{value ?: 'default'}").format(null));
|
||||||
|
}
|
||||||
|
|
||||||
// empty choice
|
|
||||||
assertEquals("", new TestScriptFormat("{if (value) 'to be'}").format(null));
|
|
||||||
|
|
||||||
// loop
|
@Test
|
||||||
assertEquals("0123456789", new TestScriptFormat("{var s=''; for (var i=0; i<parseInt(value);i++) s+=i;}").format("10"));
|
public void closures() throws Exception {
|
||||||
|
assertEquals("[ant, cat]", new TestScriptFormat("{['ant', 'buffalo', 'cat', 'dinosaur'].findAll{ it.size() <= 3 }}").format(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void illegalSyntax() throws Exception {
|
||||||
|
try {
|
||||||
|
// will throw exception
|
||||||
|
new TestScriptFormat("{value.}");
|
||||||
|
// exception must be thrown
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// check message
|
||||||
|
assertEquals("SyntaxError: unexpected token: .", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void illegalClosingBracket() throws Exception {
|
||||||
|
try {
|
||||||
|
// will throw exception
|
||||||
|
new TestScriptFormat("{{ it -> 'value' }}}");
|
||||||
|
// exception must be thrown
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// check message
|
||||||
|
assertEquals("SyntaxError: unexpected token: }", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void illegalBinding() throws Exception {
|
||||||
|
TestScriptFormat format = new TestScriptFormat("{xyz}");
|
||||||
|
format.format(new SimpleBindings());
|
||||||
|
|
||||||
|
// check message
|
||||||
|
assertEquals("BindingError: \"xyz\": undefined", format.caughtScriptException().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void illegalProperty() throws Exception {
|
||||||
|
TestScriptFormat format = new TestScriptFormat("{value.xyz}");
|
||||||
|
format.format("test");
|
||||||
|
|
||||||
|
// check message
|
||||||
|
assertEquals("BindingError: \"xyz\": undefined", format.caughtScriptException().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void illegalMethod() throws Exception {
|
||||||
|
TestScriptFormat format = new TestScriptFormat("{value.xyz()}");
|
||||||
|
format.format("test");
|
||||||
|
|
||||||
|
// check message
|
||||||
|
assertEquals("No signature of method: java.lang.String.xyz() is applicable for argument types: () values: []", format.caughtScriptException().getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue