"batch" command to allow scripting

This commit is contained in:
JFronny 2021-11-24 16:02:33 +01:00
parent 4b90821036
commit a7f5931730
No known key found for this signature in database
GPG Key ID: BEC5ACBBD4EE17E5
12 changed files with 313 additions and 82 deletions

View File

@ -2,10 +2,9 @@ package io.gitlab.jfronny.inceptum;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.inceptum.cli.*;
import io.gitlab.jfronny.inceptum.cli.InvokedCommandDescription;
import io.gitlab.jfronny.inceptum.gson.*;
import io.gitlab.jfronny.inceptum.model.ComparableVersion;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.model.inceptum.Config;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import io.gitlab.jfronny.inceptum.model.microsoft.OauthTokenResponse;
@ -22,7 +21,6 @@ import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
//TODO allow instance sync through metadata
public class Inceptum {
@ -34,7 +32,6 @@ public class Inceptum {
private static final Path CONFIG_PATH = MetaHolder.BASE_PATH.resolve("glaunch2.json");
public static final Path ACCOUNTS_PATH = MetaHolder.BASE_PATH.resolve("accounts.json");
public static final Path FORCE_LOAD_PATH = NATIVES_DIR.resolve("forceload");
public static final Set<Command> COMMANDS = Set.of(new HelpCommand(), new GuiCommand(), new LaunchCommand(), new UpdateCheckCommand(), new JvmStateCommand());
public static final Logger LOGGER = LoggerFactory.getLogger("Inceptum");
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(MinecraftArgument.class, new MinecraftArgumentDeserializer())
@ -52,20 +49,9 @@ public class Inceptum {
public static Config CONFIG;
public static boolean IS_GUI;
public static void main(String[] _args) throws IOException {
CommandArguments arg = new CommandArguments(_args);
Command cmd = null;
for (Command command : COMMANDS) {
if (command.isAlias(arg.command)) {
cmd = command;
break;
}
}
if (cmd == null) {
System.out.println("Command not found: " + arg.command);
return;
}
if (cmd.enableLog()) {
public static void main(String[] _args) throws Exception {
InvokedCommandDescription arg = new InvokedCommandDescription(_args);
if (arg.command.enableLog()) {
LOGGER.info("Launching Inceptum v" + MetaHolder.VERSION.version + " (" + MetaHolder.VERSION.flavor + ")");
LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
}
@ -91,7 +77,7 @@ public class Inceptum {
});
}
cmd.invoke(arg);
arg.command.invoke(arg);
}
public static void saveConfig() {

View File

@ -5,7 +5,7 @@ import imgui.ImGuiIO;
import imgui.flag.ImGuiConfigFlags;
import imgui.gl3.ImGuiImplGl3;
import imgui.glfw.ImGuiImplGlfw;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.cli.InvokedCommandDescription;
import io.gitlab.jfronny.inceptum.util.api.account.AccountManager;
import io.gitlab.jfronny.inceptum.windows.Window;
import org.lwjgl.glfw.Callbacks;
@ -34,7 +34,7 @@ public class InceptumGui {
protected static long handle;
private static String glslVersion = null;
public static void main(CommandArguments args, Runnable exec) {
public static void main(InvokedCommandDescription args, Runnable exec) {
AccountManager.loadAccounts();
Inceptum.LOGGER.info("Initializing UI");
init();

View File

@ -0,0 +1,51 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.util.ArgumentTokenizer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.Set;
public class BatchCommand extends Command {
public BatchCommand() {
super("Executes several commands specified in a text file in one setting", "batch");
}
@Override
public void invoke(InvokedCommandDescription args) {
if (args.length == 0) {
Inceptum.LOGGER.error("Could not start batch execution: No source file specified");
return;
}
Set<InvokedCommandDescription> argsSet = new LinkedHashSet<>();
for (String arg : args) {
Path p = Path.of(arg);
if (!Files.exists(p)) {
Inceptum.LOGGER.error("Could not find " + p);
return;
}
if (Files.isDirectory(p)) {
Inceptum.LOGGER.error("Path is a directory: " + p);
return;
}
try {
for (String line : Files.readAllLines(p)) {
argsSet.add(new InvokedCommandDescription(ArgumentTokenizer.tokenize(line).toArray(String[]::new)));
}
} catch (Exception e) {
Inceptum.LOGGER.error("Could not read file", e);
return;
}
}
for (InvokedCommandDescription commandDescription : argsSet) {
commandDescription.command.invoke(commandDescription);
}
}
@Override
public boolean enableLog() {
return true;
}
}

View File

@ -1,7 +1,5 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@ -31,5 +29,5 @@ public abstract class Command {
return false;
}
public abstract void invoke(CommandArguments args);
public abstract void invoke(InvokedCommandDescription args);
}

View File

@ -2,7 +2,6 @@ package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.InceptumGui;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateInfo;
import io.gitlab.jfronny.inceptum.util.Utils;
import io.gitlab.jfronny.inceptum.windows.MainWindow;
@ -17,7 +16,7 @@ public class GuiCommand extends Command {
}
@Override
public void invoke(CommandArguments args) {
public void invoke(InvokedCommandDescription args) {
Inceptum.IS_GUI = true;
UpdateInfo update = UpdateCheckCommand.getUpdate();
InceptumGui.main(args, () -> {

View File

@ -1,7 +1,5 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.util.MetaHolder;
public class HelpCommand extends Command {
@ -9,9 +7,9 @@ public class HelpCommand extends Command {
super("Displays this screen", "help");
}
@Override
public void invoke(CommandArguments args) {
public void invoke(InvokedCommandDescription args) {
System.out.println("Inceptum v" + MetaHolder.VERSION.version + " (" + MetaHolder.VERSION.flavor + ")\n\nCommands:");
for (Command command : Inceptum.COMMANDS) {
for (Command command : InvokedCommandDescription.COMMANDS) {
System.out.println(" " + command.getName() + " - " + command.getHelp());
}
}

View File

@ -0,0 +1,64 @@
package io.gitlab.jfronny.inceptum.cli;
import java.util.*;
public class InvokedCommandDescription implements Iterable<String> {
public static final Set<Command> COMMANDS = Set.of(new HelpCommand(), new GuiCommand(), new LaunchCommand(), new UpdateCheckCommand(), new JvmStateCommand(), new BatchCommand());
private final List<String> args;
public final int length;
public final boolean wrapped;
public final Command command;
public InvokedCommandDescription(String[] args) throws Exception {
if (args.length == 0) args = new String[]{"gui"};
wrapped = args[0].equals("wrapper");
if (wrapped && args.length == 1) args = new String[] {"wrapper", "gui"};
{
String cmdSel = wrapped ? args[1] : args[0];
Command cmd = null;
for (Command command : COMMANDS) {
if (command.isAlias(cmdSel)) {
cmd = command;
break;
}
}
if (cmd == null) {
throw new Exception("Command not found: " + cmdSel);
}
command = cmd;
}
this.args = Arrays.asList(args).subList(wrapped ? 2 : 1, args.length);
this.length = args.length;
}
public boolean contains(String param) {
return args.contains(param.replaceAll("^[-/]*", "").toLowerCase(Locale.ROOT));
}
public String last() {
return args.get(args.size() - 1);
}
public String get(int index) {
return args.get(index);
}
public List<String> after(String param) {
List<String> yes = null;
for (String arg : args) {
if (yes != null)
yes.add(arg);
else if (arg.equals(param.replaceAll("^[-/]*", "").toLowerCase(Locale.ROOT)))
yes = new ArrayList<>();
}
return yes;
}
public List<String> after(int index) {
return index + 2 < length ? args.subList(index + 1, length) : new ArrayList<>();
}
@Override
public Iterator<String> iterator() {
return args.iterator();
}
}

View File

@ -1,7 +1,5 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import java.net.URLClassLoader;
import java.util.Arrays;
@ -11,7 +9,7 @@ public class JvmStateCommand extends Command {
}
@Override
public void invoke(CommandArguments args) {
public void invoke(InvokedCommandDescription args) {
System.out.println(System.getProperty("java.class.path"));
dumpClasspath(JvmStateCommand.class.getClassLoader());
}

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.util.ClientLauncher;
import io.gitlab.jfronny.inceptum.util.ProcessUtils;
@ -15,11 +14,11 @@ import java.util.ArrayList;
public class LaunchCommand extends Command {
public LaunchCommand() {
super("Launches the game. Optionally specify \"server\" or \"client\"", "run", "launch", "start");
super("Launches the game. Optionally specify \"server\" or \"client\". Non-blocking (batch commands will continue if this is ran)", "run", "launch", "start");
}
@Override
public void invoke(CommandArguments args) {
public void invoke(InvokedCommandDescription args) {
if (args.length == 0) {
Inceptum.LOGGER.error("You must provide an instance name or path");
return;

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.inceptum.cli;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.inceptum.CommandArguments;
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateInfo;
import io.gitlab.jfronny.inceptum.util.*;
@ -16,7 +15,7 @@ public class UpdateCheckCommand extends Command {
}
@Override
public void invoke(CommandArguments args) {
public void invoke(InvokedCommandDescription args) {
if (args.contains("install") && !args.wrapped) {
System.err.println("Automatic updates are not supported without the wrapper");
return;

View File

@ -1,44 +0,0 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class CommandArguments {
private final List<String> args;
public final int length;
public final boolean wrapped;
public final String command;
public CommandArguments(String[] args) {
if (args.length == 0) args = new String[]{"gui"};
wrapped = args[0].equals("wrapper");
if (wrapped && args.length == 1) args = new String[] {"wrapper", "gui"};
command = wrapped ? args[1] : args[0];
this.args = Arrays.asList(args).subList(wrapped ? 2 : 1, args.length);
this.length = args.length;
}
public boolean contains(String param) {
return args.contains(param.replaceAll("^[-/]*", "").toLowerCase(Locale.ROOT));
}
public String last() {
return args.get(args.size() - 1);
}
public List<String> after(String param) {
List<String> yes = null;
for (String arg : args) {
if (yes != null)
yes.add(arg);
else if (arg.equals(param.replaceAll("^[-/]*", "").toLowerCase(Locale.ROOT)))
yes = new ArrayList<>();
}
return yes;
}
public List<String> after(int index) {
return index + 1 < length ? args.subList(index + 1, length) : new ArrayList<>();
}
}

View File

@ -0,0 +1,183 @@
package io.gitlab.jfronny.inceptum.util;
import java.util.LinkedList;
import java.util.List;
public class ArgumentTokenizer {
private static final int NO_TOKEN_STATE = 0;
private static final int NORMAL_TOKEN_STATE = 1;
private static final int SINGLE_QUOTE_STATE = 2;
private static final int DOUBLE_QUOTE_STATE = 3;
/** Tokenizes the given String into String tokens
* @param arguments A String containing one or more command-line style arguments to be tokenized.
* @return A list of parsed and properly escaped arguments.
*/
public static List<String> tokenize(String arguments) {
return tokenize(arguments, false);
}
/** Tokenizes the given String into String tokens.
* @param arguments A String containing one or more command-line style arguments to be tokenized.
* @param stringify whether to include escape special characters
* @return A list of parsed and properly escaped arguments.
*/
public static List<String> tokenize(String arguments, boolean stringify) {
List<String> argList = new LinkedList<>();
StringBuilder currArg = new StringBuilder();
boolean escaped = false;
int state = NO_TOKEN_STATE; // start in the NO_TOKEN_STATE
int len = arguments.length();
// Loop over each character in the string
for (int i = 0; i < len; i++) {
char c = arguments.charAt(i);
if (escaped) {
// Escaped state: just append the next character to the current arg.
escaped = false;
currArg.append(c);
}
else {
switch(state) {
case SINGLE_QUOTE_STATE:
if (c == '\'') {
// Seen the close quote; continue this arg until whitespace is seen
state = NORMAL_TOKEN_STATE;
}
else {
currArg.append(c);
}
break;
case DOUBLE_QUOTE_STATE:
if (c == '"') {
// Seen the close quote; continue this arg until whitespace is seen
state = NORMAL_TOKEN_STATE;
}
else if (c == '\\') {
// Look ahead, and only escape quotes or backslashes
i++;
char next = arguments.charAt(i);
if (next == '"' || next == '\\') {
currArg.append(next);
}
else {
currArg.append(c);
currArg.append(next);
}
}
else {
currArg.append(c);
}
break;
// case NORMAL_TOKEN_STATE:
// if (Character.isWhitespace(c)) {
// // Whitespace ends the token; start a new one
// argList.add(currArg.toString());
// currArg = new StringBuffer();
// state = NO_TOKEN_STATE;
// }
// else if (c == '\\') {
// // Backslash in a normal token: escape the next character
// escaped = true;
// }
// else if (c == '\'') {
// state = SINGLE_QUOTE_STATE;
// }
// else if (c == '"') {
// state = DOUBLE_QUOTE_STATE;
// }
// else {
// currArg.append(c);
// }
// break;
case NO_TOKEN_STATE:
case NORMAL_TOKEN_STATE:
switch(c) {
case '\\':
escaped = true;
state = NORMAL_TOKEN_STATE;
break;
case '\'':
state = SINGLE_QUOTE_STATE;
break;
case '"':
state = DOUBLE_QUOTE_STATE;
break;
default:
if (!Character.isWhitespace(c)) {
currArg.append(c);
state = NORMAL_TOKEN_STATE;
}
else if (state == NORMAL_TOKEN_STATE) {
// Whitespace ends the token; start a new one
argList.add(currArg.toString());
currArg = new StringBuilder();
state = NO_TOKEN_STATE;
}
}
break;
default:
throw new IllegalStateException("ArgumentTokenizer state " + state + " is invalid!");
}
}
}
// If we're still escaped, put in the backslash
if (escaped) {
currArg.append('\\');
argList.add(currArg.toString());
}
// Close the last argument if we haven't yet
else if (state != NO_TOKEN_STATE) {
argList.add(currArg.toString());
}
// Format each argument if we've been told to stringify them
if (stringify) {
for (int i = 0; i < argList.size(); i++) {
argList.set(i, "\"" + _escapeQuotesAndBackslashes(argList.get(i)) + "\"");
}
}
return argList;
}
/** Inserts backslashes before any occurrences of a backslash or
* quote in the given string. Also converts any special characters
* appropriately.
*/
protected static String _escapeQuotesAndBackslashes(String s) {
final StringBuilder buf = new StringBuilder(s);
// Walk backwards, looking for quotes or backslashes.
// If we see any, insert an extra backslash into the buffer at
// the same index. (By walking backwards, the index into the buffer
// will remain correct as we change the buffer.)
for (int i = s.length()-1; i >= 0; i--) {
char c = s.charAt(i);
if ((c == '\\') || (c == '"')) {
buf.insert(i, '\\');
}
// Replace any special characters with escaped versions
else if (c == '\n') {
buf.deleteCharAt(i);
buf.insert(i, "\\n");
}
else if (c == '\t') {
buf.deleteCharAt(i);
buf.insert(i, "\\t");
}
else if (c == '\r') {
buf.deleteCharAt(i);
buf.insert(i, "\\r");
}
else if (c == '\b') {
buf.deleteCharAt(i);
buf.insert(i, "\\b");
}
else if (c == '\f') {
buf.deleteCharAt(i);
buf.insert(i, "\\f");
}
}
return buf.toString();
}
}