001package net.filebot.cli;
002
003import static java.util.Collections.*;
004import static net.filebot.util.RegularExpressions.*;
005
006import java.util.Map;
007import java.util.ResourceBundle;
008
009import javax.script.Bindings;
010import javax.script.ScriptContext;
011import javax.script.ScriptException;
012
013import org.codehaus.groovy.control.CompilerConfiguration;
014import org.codehaus.groovy.control.customizers.CompilationCustomizer;
015import org.codehaus.groovy.control.customizers.ImportCustomizer;
016
017import groovy.lang.Closure;
018import net.filebot.Cache;
019import net.filebot.CacheType;
020import net.filebot.GroovyEngine;
021import net.filebot.format.ExpressionFilter;
022import net.filebot.format.ExpressionFormat;
023import net.filebot.format.ExpressionMapper;
024import net.filebot.format.QueryExpression;
025
026public class ScriptShell {
027
028        private static GroovyEngine createScriptEngine(Cache cache, CompilationCustomizer... customizers) {
029                ResourceBundle bundle = ResourceBundle.getBundle(ScriptShell.class.getName());
030
031                CompilerConfiguration configuration = new CompilerConfiguration();
032                configuration.setScriptBaseClass(bundle.getString("scriptBaseClass"));
033
034                // default imports
035                ImportCustomizer imports = new ImportCustomizer();
036                imports.addStarImports(COMMA.split(bundle.getString("starImport")));
037                imports.addStaticStars(COMMA.split(bundle.getString("starStaticImport")));
038                configuration.addCompilationCustomizers(imports);
039
040                // apply AST transform (e.g. make script cancellable)
041                for (CompilationCustomizer customizer : customizers) {
042                        configuration.addCompilationCustomizers(customizer);
043                }
044
045                // cache .class files for compiled script classes
046                return GroovyEngine.newCachedCompiledScriptEngine(configuration, cache);
047        }
048
049        public static final String ARGV_BINDING_NAME = "args";
050        public static final String SHELL_BINDING_NAME = "__shell";
051        public static final String SHELL_CLI_BINDING_NAME = "__cli";
052        public static final String SHELL_ARGS_BINDING_NAME = "__args";
053        public static final String SHELL_DEFS_BINDING_NAME = "__defs";
054
055        private final GroovyEngine engine;
056        private final ScriptProvider resolver;
057
058        public ScriptShell() {
059                this(null, null, emptyMap());
060        }
061
062        public ScriptShell(ScriptProvider resolver, CmdlineInterface cli, Map<String, ?> globals) {
063                this(resolver, cli, globals, Cache.getCache("script_classes", CacheType.Persistent));
064        }
065
066        public ScriptShell(ScriptProvider resolver, CmdlineInterface cli, Map<String, ?> globals, Cache cache, CompilationCustomizer... customizers) {
067                this.engine = createScriptEngine(cache, customizers);
068                this.resolver = resolver;
069
070                // setup bindings
071                Bindings bindings = engine.createBindings();
072                bindings.putAll(globals);
073
074                // bind API objects
075                bindings.put(SHELL_BINDING_NAME, this);
076                bindings.put(SHELL_CLI_BINDING_NAME, cli);
077
078                // setup script context
079                engine.getContext().setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
080        }
081
082        public Object runScript(String name, Bindings bindings) throws Throwable {
083                return evaluate(resolver.getScript(name), bindings);
084        }
085
086        public Closure<?> callable(String script) throws Throwable {
087                // evaluate user script
088                Object value = evaluate(script, engine.createBindings());
089                // value as target type
090                return GroovyEngine.asType(value, Closure.class);
091        }
092
093        public Object evaluate(String script, Bindings bindings) throws Throwable {
094                try {
095                        return engine.eval(script, bindings);
096                } catch (Throwable t) {
097                        throw sanitize(t);
098                }
099        }
100
101        public Throwable sanitize(Throwable t) {
102                // unwrap nested exception
103                while (t.getClass() == ScriptException.class && t.getCause() != null) {
104                        t = t.getCause();
105                }
106                // remove Groovy runtime methods from Java stack trace
107                return GroovyEngine.sanitizeStackTrace(t);
108        }
109
110        /**
111         * Scripting API methods for compiling String option values to executable objects
112         */
113
114        public GroovyComparator comparator(String script) throws Throwable {
115                return GroovyComparator.wrap(callable(script));
116        }
117
118        public GroovyAction action(String script) throws Throwable {
119                return GroovyAction.wrap(callable(script));
120        }
121
122        public ExpressionFormat format(String expression) throws Throwable {
123                return new ExpressionFormat(expression);
124        }
125
126        public ExpressionFilter filter(String expression) throws Throwable {
127                return new ExpressionFilter(expression);
128        }
129
130        public ExpressionMapper mapper(String expression) throws Throwable {
131                return new ExpressionMapper(expression);
132        }
133
134        public QueryExpression query(String expression) throws Throwable {
135                return new QueryExpression(expression);
136        }
137
138}