Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ScriptCommand.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*
  2. * Copyright (c) 2006-2015 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.scriptplugin;
  23. import com.dmdirc.ClientModule.GlobalConfig;
  24. import com.dmdirc.DMDircMBassador;
  25. import com.dmdirc.commandline.CommandLineOptionsModule.Directory;
  26. import com.dmdirc.commandparser.BaseCommandInfo;
  27. import com.dmdirc.commandparser.CommandArguments;
  28. import com.dmdirc.commandparser.CommandInfo;
  29. import com.dmdirc.commandparser.CommandType;
  30. import com.dmdirc.commandparser.commands.Command;
  31. import com.dmdirc.commandparser.commands.IntelligentCommand;
  32. import com.dmdirc.commandparser.commands.context.CommandContext;
  33. import com.dmdirc.interfaces.CommandController;
  34. import com.dmdirc.interfaces.WindowModel;
  35. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  36. import com.dmdirc.plugins.PluginDomain;
  37. import com.dmdirc.ui.input.AdditionalTabTargets;
  38. import java.io.File;
  39. import java.io.FileNotFoundException;
  40. import java.io.FileWriter;
  41. import java.io.IOException;
  42. import java.util.Map;
  43. import java.util.stream.Collectors;
  44. import javax.annotation.Nonnull;
  45. import javax.inject.Inject;
  46. import javax.script.ScriptEngineManager;
  47. import javax.script.ScriptException;
  48. /**
  49. * The Script Command allows controlling of the script plugin.
  50. */
  51. public class ScriptCommand extends Command implements IntelligentCommand {
  52. /** A command info object for this command. */
  53. public static final CommandInfo INFO = new BaseCommandInfo("script",
  54. "script - Allows controlling the script plugin", CommandType.TYPE_GLOBAL);
  55. /** Global config to read settings from. */
  56. private final AggregateConfigProvider globalConfig;
  57. /** Plugin settings domain. */
  58. private final String domain;
  59. /** Manager used to retrieve script engines. */
  60. private final ScriptEngineManager scriptEngineManager;
  61. /** Script directory. */
  62. private final String scriptDirectory;
  63. /** Script manager to handle scripts. */
  64. private final ScriptManager scriptManager;
  65. /** The event bus to post errors to. */
  66. private final DMDircMBassador eventBus;
  67. /**
  68. * Creates a new instance of this command.
  69. *
  70. * @param scriptManager Used to manage scripts
  71. * @param eventBus The event bus to post errors to
  72. * @param globalConfig Global config
  73. * @param commandController The controller to use for command information.
  74. * @param domain This plugin's settings domain
  75. * @param scriptEngineManager Manager used to get script engines
  76. * @param scriptDirectory Directory to store scripts
  77. */
  78. @Inject
  79. public ScriptCommand(final ScriptManager scriptManager,
  80. final DMDircMBassador eventBus,
  81. @Directory(ScriptModule.SCRIPTS) final String scriptDirectory,
  82. @GlobalConfig final AggregateConfigProvider globalConfig,
  83. final CommandController commandController,
  84. @PluginDomain(ScriptPlugin.class) final String domain,
  85. final ScriptEngineManager scriptEngineManager) {
  86. super(commandController);
  87. this.globalConfig = globalConfig;
  88. this.domain = domain;
  89. this.scriptEngineManager = scriptEngineManager;
  90. this.scriptDirectory = scriptDirectory;
  91. this.scriptManager = scriptManager;
  92. this.eventBus = eventBus;
  93. }
  94. @Override
  95. public void execute(@Nonnull final WindowModel origin, final CommandArguments args,
  96. final CommandContext context) {
  97. final String[] sargs = args.getArguments();
  98. if (sargs.length > 0 && ("rehash".equalsIgnoreCase(sargs[0]) ||
  99. "reload".equalsIgnoreCase(sargs[0]))) {
  100. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Reloading scripts");
  101. scriptManager.rehash();
  102. } else if (sargs.length > 0 && "load".equalsIgnoreCase(sargs[0])) {
  103. if (sargs.length > 1) {
  104. final String filename = args.getArgumentsAsString(1);
  105. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Loading: " + filename + " ["
  106. + scriptManager.loadScript(scriptDirectory + filename) + ']');
  107. } else {
  108. sendLine(origin, args.isSilent(), FORMAT_ERROR, "You must specify a script to load");
  109. }
  110. } else if (sargs.length > 0 && "unload".equalsIgnoreCase(sargs[0])) {
  111. if (sargs.length > 1) {
  112. final String filename = args.getArgumentsAsString(1);
  113. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Unloading: " + filename + " ["
  114. + scriptManager.loadScript(scriptDirectory + filename) + ']');
  115. } else {
  116. sendLine(origin, args.isSilent(), FORMAT_ERROR,
  117. "You must specify a script to unload");
  118. }
  119. } else if (sargs.length > 0 && "eval".equalsIgnoreCase(sargs[0])) {
  120. if (sargs.length > 1) {
  121. final String script = args.getArgumentsAsString(1);
  122. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Evaluating: " + script);
  123. try {
  124. final ScriptEngineWrapper wrapper;
  125. if (globalConfig.hasOptionString(domain, "eval.baseFile")) {
  126. final String baseFile = scriptDirectory + '/'
  127. + globalConfig.getOption(domain, "eval.baseFile");
  128. if (new File(baseFile).exists()) {
  129. wrapper = new ScriptEngineWrapper(scriptEngineManager, eventBus,
  130. baseFile);
  131. } else {
  132. wrapper = new ScriptEngineWrapper(scriptEngineManager, eventBus, null);
  133. }
  134. } else {
  135. wrapper = new ScriptEngineWrapper(scriptEngineManager, eventBus, null);
  136. }
  137. wrapper.getScriptEngine().put("cmd_origin", origin);
  138. wrapper.getScriptEngine().put("cmd_isSilent", args.isSilent());
  139. wrapper.getScriptEngine().put("cmd_args", sargs);
  140. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Result: " + wrapper.
  141. getScriptEngine().eval(script));
  142. } catch (FileNotFoundException | ScriptException e) {
  143. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Exception: " + e + " -> " + e.
  144. getMessage());
  145. if (globalConfig.getOptionBool(domain, "eval.showStackTrace")) {
  146. final String[] stacktrace = getTrace(e);
  147. for (String line : stacktrace) {
  148. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Stack trace: " + line);
  149. }
  150. }
  151. }
  152. } else {
  153. sendLine(origin, args.isSilent(), FORMAT_ERROR,
  154. "You must specify some script to eval.");
  155. }
  156. } else if (sargs.length > 0 && "savetobasefile".equalsIgnoreCase(sargs[0])) {
  157. if (sargs.length > 2) {
  158. final String[] bits = sargs[1].split("/");
  159. final String functionName = bits[0];
  160. final String script = args.getArgumentsAsString(2);
  161. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "Saving as '" + functionName
  162. + "': " + script);
  163. if (globalConfig.hasOptionString(domain, "eval.baseFile")) {
  164. try {
  165. final String baseFile = scriptDirectory + '/'
  166. + globalConfig.getOption(domain, "eval.baseFile");
  167. try (FileWriter writer = new FileWriter(baseFile, true)) {
  168. writer.write("function ");
  169. writer.write(functionName);
  170. writer.write("(");
  171. for (int i = 1; i < bits.length; i++) {
  172. writer.write(bits[i]);
  173. writer.write(" ");
  174. }
  175. writer.write(") {\n");
  176. writer.write(script);
  177. writer.write("\n}\n");
  178. writer.flush();
  179. }
  180. } catch (IOException ioe) {
  181. sendLine(origin, args.isSilent(), FORMAT_ERROR, "IOException: " + ioe.
  182. getMessage());
  183. }
  184. } else {
  185. sendLine(origin, args.isSilent(), FORMAT_ERROR,
  186. "No baseFile specified, please /set " + domain
  187. + " eval.baseFile filename (stored in scripts dir of profile)");
  188. }
  189. } else if (sargs.length > 1) {
  190. sendLine(origin, args.isSilent(), FORMAT_ERROR,
  191. "You must specify some script to save.");
  192. } else {
  193. sendLine(origin, args.isSilent(), FORMAT_ERROR,
  194. "You must specify a function name and some script to save.");
  195. }
  196. } else if (sargs.length > 0 && "help".equalsIgnoreCase(sargs[0])) {
  197. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  198. "This command allows you to interact with the script plugin");
  199. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "-------------------");
  200. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  201. "reload/rehash - Reload all loaded scripts");
  202. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  203. "load <script> - load scripts/<script> (file name relative to scripts dir)");
  204. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  205. "unload <script> - unload <script> (full file name)");
  206. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  207. "eval <script> - evaluate the code <script> and return the result");
  208. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  209. "savetobasefile <name> <script> - save the code <script> to the eval basefile ("
  210. + domain + ".eval.basefile)");
  211. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  212. " as the function <name> (name/foo/bar will save it as 'name' with foo and");
  213. sendLine(origin, args.isSilent(), FORMAT_OUTPUT,
  214. " bar as arguments.");
  215. sendLine(origin, args.isSilent(), FORMAT_OUTPUT, "-------------------");
  216. } else {
  217. sendLine(origin, args.isSilent(), FORMAT_ERROR, "Unknown subcommand.");
  218. }
  219. }
  220. @Override
  221. public AdditionalTabTargets getSuggestions(final int arg,
  222. final IntelligentCommandContext context) {
  223. final AdditionalTabTargets res = new AdditionalTabTargets();
  224. res.excludeAll();
  225. if (arg == 0) {
  226. res.add("help");
  227. res.add("rehash");
  228. res.add("reload");
  229. res.add("load");
  230. res.add("unload");
  231. res.add("eval");
  232. res.add("savetobasefile");
  233. } else if (arg == 1) {
  234. final Map<String, ScriptEngineWrapper> scripts = scriptManager.getScripts();
  235. if ("load".equalsIgnoreCase(context.getPreviousArgs().get(0))) {
  236. res.addAll(scriptManager.getPossibleScripts().stream()
  237. .collect(Collectors.toList()));
  238. } else if ("unload".equalsIgnoreCase(context.getPreviousArgs().get(0))) {
  239. res.addAll(scripts.keySet().stream().collect(Collectors.toList()));
  240. }
  241. }
  242. return res;
  243. }
  244. /**
  245. * Converts an exception into a string array.
  246. *
  247. * @param throwable Exception to convert
  248. *
  249. * @return Exception string array
  250. */
  251. private static String[] getTrace(final Throwable throwable) {
  252. String[] trace;
  253. if (throwable == null) {
  254. trace = new String[0];
  255. } else {
  256. final StackTraceElement[] traceElements = throwable.getStackTrace();
  257. trace = new String[traceElements.length + 1];
  258. trace[0] = throwable.toString();
  259. for (int i = 0; i < traceElements.length; i++) {
  260. trace[i + 1] = traceElements[i].toString();
  261. }
  262. if (throwable.getCause() != null) {
  263. final String[] causeTrace = getTrace(throwable.getCause());
  264. final String[] newTrace = new String[trace.length + causeTrace.length];
  265. trace[0] = "\nWhich caused: " + trace[0];
  266. System.arraycopy(causeTrace, 0, newTrace, 0, causeTrace.length);
  267. System.arraycopy(trace, 0, newTrace, causeTrace.length, trace.length);
  268. trace = newTrace;
  269. }
  270. }
  271. return trace;
  272. }
  273. }