package openmods.calc.command; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.io.Closer; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Arrays; import java.util.List; import java.util.Locale; import javax.annotation.Nullable; import net.minecraft.command.ICommandSender; import net.minecraft.util.ChatComponentText; import openmods.calc.ExprType; import openmods.calc.command.CalcState.CalculatorType; import openmods.calc.command.CalcState.NoSuchNameException; import openmods.config.simpler.ConfigurableClassAdapter.NoSuchPropertyException; import openmods.utils.CommandUtils; import openmods.utils.StackUnderflowException; public class CommandCalcFactory { private final File scriptDir; private final CalcState state = new CalcState(); public CommandCalcFactory(File scriptDir) { this.scriptDir = scriptDir.getAbsoluteFile(); } private final ICommandComponent root = MapCommandComponent.builder() .put("config", MapCommandComponent.builder() .put("new", new TerminalCommandComponent(Arrays.toString(CalculatorType.values())) { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String type = args.getNextPart().toUpperCase(Locale.ROOT); try { final CalculatorType newType = CalculatorType.valueOf(type); state.createCalculator(newType); } catch (IllegalArgumentException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_type", Joiner.on(',').join(CalculatorType.values())); } } @Override public List<String> getTabCompletions(IWhitespaceSplitter args) { final String type = args.getNextPart(); final Object[] values = CalculatorType.values(); final Iterable<String> types = stringifyList(values); return CommandUtils.filterPrefixes(type, types); } }) .put("load", new TerminalCommandComponent("<name>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { try { state.loadCalculator(args.getNextPart()); } catch (NoSuchNameException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_name"); } } @Override public List<String> getTabCompletions(IWhitespaceSplitter args) { final String name = args.getNextPart(); return CommandUtils.filterPrefixes(name, state.getCalculatorsNames()); } }) .put("store", new TerminalCommandComponent("<name>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { state.nameCalculator(args.getNextPart()); } }) .put("pop", new TerminalCommandComponent("") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final int size = state.pushCalculator(); CommandUtils.respond(sender, "openmodslib.command.calc_after_push", size); } }) .put("push", new TerminalCommandComponent("") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { try { final int size = state.popCalculator(); CommandUtils.respond(sender, "openmodslib.command.calc_after_pop", size); } catch (StackUnderflowException e) { throw new CommandSyntaxException("openmodslib.command.calc_stack_underflow"); } } }) .put("set", new TerminalCommandComponent("<key> <value>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String key = args.getNextPart(); final String value = args.getTail(); try { state.getActiveCalculator().setProperty(key, value); } catch (NoSuchPropertyException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_key"); } catch (Exception e) { throw new CommandSyntaxException("openmodslib.command.calc_cant_set"); } } @Override public List<String> getTabCompletions(IWhitespaceSplitter args) { final String key = args.getNextPart(); if (!args.isFinished()) return Lists.newArrayList(); return CommandUtils.filterPrefixes(key, state.getActiveCalculator().getProperties()); } }) .put("get", new TerminalCommandComponent("<key>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String key = args.getNextPart(); try { state.getActiveCalculator().getProperty(key); } catch (NoSuchPropertyException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_key"); } } @Override public List<String> getTabCompletions(IWhitespaceSplitter args) { final String key = args.getNextPart(); return CommandUtils.filterPrefixes(key, state.getActiveCalculator().getProperties()); } }) .put("mode", new TerminalCommandComponent(Arrays.toString(ExprType.values())) { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String type = args.getNextPart().toUpperCase(Locale.ROOT); try { state.exprType = ExprType.valueOf(type); } catch (IllegalArgumentException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_mode", Joiner.on(',').join(ExprType.values())); } } }) .build()) .put("execute", new TerminalCommandComponent("<path>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String path = args.getNextPart(); final File scriptFile = new File(scriptDir, path).getAbsoluteFile(); if (!checkIsParent(scriptDir, scriptFile)) throw new CommandExecutionException("openmodslib.command.calc_not_child", scriptFile, scriptDir); if (!scriptFile.isFile()) throw new CommandExecutionException("openmodslib.command.calc_not_file", scriptFile); final int count = executeScript(sender, scriptFile); CommandUtils.respond(sender, "openmodslib.command.calc_executed_count", count); } @Override public List<String> getTabCompletions(IWhitespaceSplitter args) { final String path = args.getNextPart().replace("\\", "/"); final File scriptFile = new File(scriptDir, path).getAbsoluteFile(); final File fileToScan = path.isEmpty() || path.endsWith("/")? scriptFile : scriptFile.getParentFile(); if (!fileToScan.isDirectory()) return null; final int parentLengthPath = scriptDir.getAbsolutePath().length() + 1; // adding / on end final List<String> propositions = Lists.newArrayList(); for (File child : fileToScan.listFiles()) { if (child.isFile()) propositions.add(child.getAbsolutePath().substring(parentLengthPath).replace("\\", "/")); else if (child.isDirectory()) { propositions.add(child.getAbsolutePath().substring(parentLengthPath).replace("\\", "/") + "/"); } } return CommandUtils.filterPrefixes(path, propositions); } }) .put("let", new TerminalCommandComponent("<name> <initializer expression>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String name = args.getNextPart(); final String expr = args.getTail(); final Object result = state.compileAndSetGlobalSymbol(sender, name, expr); CommandUtils.respond(sender, "openmodslib.command.calc_set", name, String.valueOf(result)); } }) .put("fun", new TerminalCommandComponent("<name> <arg count> <function body>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String name = args.getNextPart(); final String argCountStr = args.getNextPart(); final int argCount; try { argCount = Integer.parseInt(argCountStr); } catch (NumberFormatException e) { throw new CommandSyntaxException("openmodslib.command.calc_invalid_number", argCountStr); } final String expr = args.getTail(); state.compileAndDefineGlobalFunction(sender, name, argCount, expr); CommandUtils.respond(sender, "openmodslib.command.calc_function_defined", name); } }) .put("eval", new TerminalCommandComponent("<expression>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { final String expr = args.getTail(); if (state.exprType.hasSingleResult) { final String result = state.compileExecuteAndPrint(sender, expr); CommandUtils.respondText(sender, result); } else { state.compileAndExecute(sender, expr); CommandUtils.respond(sender, "openmodslib.command.calc_stack_size", state.getActiveCalculator().environment.topFrame().stack().size()); } } }) .put("echo", new TerminalCommandComponent("<str>") { @Override public void execute(ICommandSender sender, IWhitespaceSplitter args) { sender.addChatMessage(new ChatComponentText(args.getTail())); } }) .build(); public ICommandComponent getRoot() { return root; } private int executeScript(ICommandSender sender, File scriptFile) { int count = 0; try { final Closer closer = Closer.create(); try { final Reader r = closer.register(new FileReader(scriptFile)); final BufferedReader br = closer.register(new BufferedReader(r)); String line; while ((line = br.readLine()) != null) { final IWhitespaceSplitter args = WhitespaceSplitters.fromString(line); root.execute(sender, args); count++; } } finally { closer.close(); } return count; } catch (Exception e) { throw new CommandExecutionException(e); } } private static boolean checkIsParent(File dir, File target) { if (!dir.exists()) return false; try { final File canonicalDir = dir.getCanonicalFile(); while (target != null) { if (canonicalDir.equals(target)) return true; target = target.getParentFile(); } } catch (Throwable t) { throw Throwables.propagate(t); } return false; } private static Iterable<String> stringifyList(Object... values) { return Iterables.transform(Arrays.asList(values), new Function<Object, String>() { @Override @Nullable public String apply(@Nullable Object input) { return String.valueOf(input).toLowerCase(Locale.ROOT); } }); } }