Introduce a new arguments parser (#1629)

Original commits:

* Clean-up

* Introduce a new application arguments parser & handler

* Clean-up and deprecate `Tools#getLanguageOption`

* Fix `-debug` and `-debugall` parameters

* found this while debugging, ...why

* Remove deprecated parameters

* Invoke startup argument parser before handbook generation

* Move command map instantiation to `Grasscutter `(prevent making 3 instances on startup)

* Ensure \n at EOF

Co-authored-by: AnimeGitB <AnimeGitB@bigblueball.in>
This commit is contained in:
Magix 2022-08-09 22:24:43 -04:00 committed by GitHub
parent e20b185dc0
commit 3121e3e67d
11 changed files with 134 additions and 75 deletions

View File

@ -5,7 +5,7 @@ charset = utf-8
end_of_line = lf end_of_line = lf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
insert_final_newline = false insert_final_newline = true
max_line_length = 120 max_line_length = 120
tab_width = 4 tab_width = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
*.java text=auto
*.json text=auto
*.md text=auto
*.properties text=auto
*.py text=auto
*.sh text=auto

View File

@ -29,7 +29,10 @@ import emu.grasscutter.server.http.handlers.LogHandler;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.StartupArguments;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import lombok.Getter;
import lombok.Setter;
import org.jline.reader.EndOfFileException; import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader; import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder; import org.jline.reader.LineReaderBuilder;
@ -57,10 +60,13 @@ public final class Grasscutter {
public static final File configFile = new File("./config.json"); public static final File configFile = new File("./config.json");
private static int day; // Current day of week. private static int day; // Current day of week.
@Getter @Setter private static String preferredLanguage;
private static HttpServer httpServer; private static HttpServer httpServer;
private static GameServer gameServer; private static GameServer gameServer;
private static PluginManager pluginManager; private static PluginManager pluginManager;
@Getter private static CommandMap commandMap;
private static AuthenticationSystem authenticationSystem; private static AuthenticationSystem authenticationSystem;
private static PermissionHandler permissionHandler; private static PermissionHandler permissionHandler;
@ -89,59 +95,16 @@ public final class Grasscutter {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers. Crypto.loadKeys(); // Load keys from buffers.
Tools.createGmHandbooks();
// Parse arguments. // Parse start-up arguments.
boolean exitEarly = false; if(StartupArguments.parse(args)) {
for (String arg : args) { System.exit(0); // Exit early.
switch (arg.toLowerCase()) {
case "-dumppacketids" -> {
PacketOpcodesUtils.dumpPacketIds();
exitEarly = true;
}
case "-version" -> {
System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH);
exitEarly = true;
}
case "-debug" -> {
// Set the logger to debug.
log.setLevel(Level.DEBUG);
log.debug("The logger is now running in debug mode.");
// Change loggers to debug.
((Logger) LoggerFactory.getLogger("express"))
.setLevel(Level.INFO);
((Logger) LoggerFactory.getLogger("org.quartz"))
.setLevel(Level.INFO);
((Logger) LoggerFactory.getLogger("org.reflections"))
.setLevel(Level.INFO);
((Logger) LoggerFactory.getLogger("org.eclipse.jetty"))
.setLevel(Level.INFO);
((Logger) LoggerFactory.getLogger("org.mongodb.driver"))
.setLevel(Level.INFO);
}
case "-debugall" -> {
// Set the logger to debug.
log.setLevel(Level.DEBUG);
log.debug("The logger is now running in debug mode.");
// Change loggers to debug.
((Logger) LoggerFactory.getLogger("express"))
.setLevel(Level.DEBUG);
((Logger) LoggerFactory.getLogger("org.quartz"))
.setLevel(Level.DEBUG);
((Logger) LoggerFactory.getLogger("org.reflections"))
.setLevel(Level.DEBUG);
((Logger) LoggerFactory.getLogger("org.eclipse.jetty"))
.setLevel(Level.DEBUG);
((Logger) LoggerFactory.getLogger("org.mongodb.driver"))
.setLevel(Level.DEBUG);
}
}
} }
// Exit early if an argument sets it. // Create command map.
if (exitEarly) System.exit(0); commandMap = new CommandMap(true);
// Generate handbooks.
Tools.createGmHandbooks();
// Initialize server. // Initialize server.
Grasscutter.getLogger().info(translate("messages.status.starting")); Grasscutter.getLogger().info(translate("messages.status.starting"));

View File

@ -23,7 +23,7 @@ public final class CommandMap {
} }
public static CommandMap getInstance() { public static CommandMap getInstance() {
return Grasscutter.getGameServer().getCommandMap(); return Grasscutter.getCommandMap();
} }
/** /**

View File

@ -421,7 +421,7 @@ public class ResourceLoader {
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
} }
try (Reader reader = new FileReader(new File(RESOURCE("QuestEncryptionKeys.json")))) { try (Reader reader = new FileReader(RESOURCE("QuestEncryptionKeys.json"))) {
List<QuestEncryptionKey> keys = Grasscutter.getGsonFactory().fromJson( List<QuestEncryptionKey> keys = Grasscutter.getGsonFactory().fromJson(
reader, reader,
TypeToken.getParameterized(List.class, QuestEncryptionKey.class).getType()); TypeToken.getParameterized(List.class, QuestEncryptionKey.class).getType());

View File

@ -85,7 +85,6 @@ public final class PluginManager {
PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class); PluginConfig pluginConfig = Grasscutter.getGsonFactory().fromJson(fileReader, PluginConfig.class);
// Check if the plugin config is valid. // Check if the plugin config is valid.
if (!pluginConfig.validate()) { if (!pluginConfig.validate()) {
Utils.logObject(pluginConfig);
Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file."); Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid config file.");
return; return;
} }
@ -211,11 +210,7 @@ public final class PluginManager {
public void disablePlugins() { public void disablePlugins() {
this.plugins.forEach((name, plugin) -> { this.plugins.forEach((name, plugin) -> {
Grasscutter.getLogger().info("Disabling plugin: " + name); Grasscutter.getLogger().info("Disabling plugin: " + name);
try { this.disablePlugin(plugin);
plugin.onDisable();
} catch (Throwable exception) {
Grasscutter.getLogger().error("Failed to disable plugin: " + name, exception);
}
}); });
} }

View File

@ -4,6 +4,7 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.command.PermissionHandler; import emu.grasscutter.command.PermissionHandler;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
@ -72,7 +73,7 @@ public final class ServerHook {
if(!clazz.isAnnotationPresent(Command.class)) if(!clazz.isAnnotationPresent(Command.class))
throw new IllegalArgumentException("Command handler must be annotated with @Command."); throw new IllegalArgumentException("Command handler must be annotated with @Command.");
Command commandData = clazz.getAnnotation(Command.class); Command commandData = clazz.getAnnotation(Command.class);
this.gameServer.getCommandMap().registerCommand(commandData.label(), handler); CommandMap.getInstance().registerCommand(commandData.label(), handler);
} }
/** /**

View File

@ -72,7 +72,6 @@ public final class GameServer extends KcpServer {
// Extra // Extra
private final ServerTaskScheduler scheduler; private final ServerTaskScheduler scheduler;
private final CommandMap commandMap;
private final TaskMap taskMap; private final TaskMap taskMap;
private ChatManagerHandler chatManager; private ChatManagerHandler chatManager;
@ -107,7 +106,6 @@ public final class GameServer extends KcpServer {
// Extra // Extra
this.scheduler = new ServerTaskScheduler(); this.scheduler = new ServerTaskScheduler();
this.commandMap = new CommandMap(true);
this.taskMap = new TaskMap(true); this.taskMap = new TaskMap(true);
// Create game systems // Create game systems

View File

@ -54,7 +54,7 @@ final class HandbookRequestHandler implements DocumentationHandler {
sbs.add(new StringBuilder("")); sbs.add(new StringBuilder(""));
// Commands table // Commands table
new CommandMap(true).getHandlersAsList().forEach(cmd -> { CommandMap.getInstance().getHandlersAsList().forEach(cmd -> {
String label = cmd.getLabel(); String label = cmd.getLabel();
String descKey = cmd.getDescriptionKey(); String descKey = cmd.getDescriptionKey();
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)

View File

@ -48,7 +48,7 @@ public final class Tools {
.append("// Created " + now + "\n\n") .append("// Created " + now + "\n\n")
.append("// Commands\n")); .append("// Commands\n"));
// Commands // Commands
final List<CommandHandler> cmdList = new CommandMap(true).getHandlersAsList(); final List<CommandHandler> cmdList = CommandMap.getInstance().getHandlersAsList();
final String padCmdLabel = "%" + cmdList.stream().map(CommandHandler::getLabel).map(String::length).max(Integer::compare).get().toString() + "s : "; final String padCmdLabel = "%" + cmdList.stream().map(CommandHandler::getLabel).map(String::length).max(Integer::compare).get().toString() + "s : ";
for (CommandHandler cmd : cmdList) { for (CommandHandler cmd : cmdList) {
final String label = padCmdLabel.formatted(cmd.getLabel()); final String label = padCmdLabel.formatted(cmd.getLabel());
@ -65,7 +65,7 @@ public final class Tools {
final Int2IntMap h = handbookNames[section]; final Int2IntMap h = handbookNames[section];
final String s = "\n\n// " + handbookSections[section] + "\n"; final String s = "\n\n// " + handbookSections[section] + "\n";
handbookBuilders.forEach(b -> b.append(s)); handbookBuilders.forEach(b -> b.append(s));
final String padId = "%" + Integer.toString(Integer.toString(h.keySet().intStream().max().getAsInt()).length()) + "s : "; final String padId = "%" + Integer.toString(h.keySet().intStream().max().getAsInt()).length() + "s : ";
h.keySet().intStream().sorted().forEach(id -> { h.keySet().intStream().sorted().forEach(id -> {
final String sId = padId.formatted(id); final String sId = padId.formatted(id);
final TextStrings t = textMaps.get(h.get(id)); final TextStrings t = textMaps.get(h.get(id));
@ -76,7 +76,7 @@ public final class Tools {
// Scenes - no translations // Scenes - no translations
handbookBuilders.forEach(b -> b.append("\n\n// Scenes\n")); handbookBuilders.forEach(b -> b.append("\n\n// Scenes\n"));
final var sceneDataMap = GameData.getSceneDataMap(); final var sceneDataMap = GameData.getSceneDataMap();
final String padSceneId = "%" + Integer.toString(Integer.toString(sceneDataMap.keySet().intStream().max().getAsInt()).length()) + "d : "; final String padSceneId = "%" + Integer.toString(sceneDataMap.keySet().intStream().max().getAsInt()).length() + "d : ";
sceneDataMap.keySet().intStream().sorted().forEach(id -> { sceneDataMap.keySet().intStream().sorted().forEach(id -> {
final String sId = padSceneId.formatted(id); final String sId = padSceneId.formatted(id);
final String data = sceneDataMap.get(id).getScriptData(); final String data = sceneDataMap.get(id).getScriptData();
@ -85,7 +85,7 @@ public final class Tools {
// Quests // Quests
handbookBuilders.forEach(b -> b.append("\n\n// Quests\n")); handbookBuilders.forEach(b -> b.append("\n\n// Quests\n"));
final var questDataMap = GameData.getQuestDataMap(); final var questDataMap = GameData.getQuestDataMap();
final String padQuestId = "%" + Integer.toString(Integer.toString(questDataMap.keySet().intStream().max().getAsInt()).length()) + "d : "; final String padQuestId = "%" + Integer.toString(questDataMap.keySet().intStream().max().getAsInt()).length() + "d : ";
questDataMap.keySet().intStream().sorted().forEach(id -> { questDataMap.keySet().intStream().sorted().forEach(id -> {
final String sId = padQuestId.formatted(id); final String sId = padQuestId.formatted(id);
final QuestData data = questDataMap.get(id); final QuestData data = questDataMap.get(id);
@ -206,7 +206,7 @@ public final class Tools {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) {
// if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" // if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us"
// since it's the fallback language and there will be no difference in the gacha record page. // since it's the fallback language and there will be no difference in the gacha record page.
// The enduser can still modify the `gacha/mappings.js` directly to enable multilingual for the gacha record system. // The end-user can still modify the `gacha/mappings.js` directly to enable multilingual for the gacha record system.
writer.println(sb); writer.println(sb);
Grasscutter.getLogger().info("Mappings generated to " + location + " !"); Grasscutter.getLogger().info("Mappings generated to " + location + " !");
} }
@ -220,6 +220,7 @@ public final class Tools {
} return availableLangList; } return availableLangList;
} }
@Deprecated(forRemoval = true, since = "1.2.3")
public static String getLanguageOption() { public static String getLanguageOption() {
List<String> availableLangList = getAvailableLanguage(); List<String> availableLangList = getAvailableLanguage();
@ -248,17 +249,13 @@ public final class Tools {
stagedMessage.append(groupedLangList).append("\n"); stagedMessage.append(groupedLangList).append("\n");
} }
stagedMessage.append("\nYour choice:[EN] "); stagedMessage.append("\nYour choice: [EN] ");
input = Grasscutter.getConsole().readLine(stagedMessage.toString()); input = Grasscutter.getConsole().readLine(stagedMessage.toString());
if (availableLangList.contains(input.toLowerCase())) { if (availableLangList.contains(input.toLowerCase())) {
return input.toUpperCase(); return input.toUpperCase();
} }
Grasscutter.getLogger().info("Invalid option. Will use EN(English) as fallback");
return "EN"; Grasscutter.getLogger().info("Invalid option. Will use EN (English) as fallback."); return "EN";
} }
} }
final class ToolsWithLanguageOption {
}

View File

@ -0,0 +1,99 @@
package emu.grasscutter.utils;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.BuildConfig;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.packet.PacketOpcodesUtils;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.function.Function;
/**
* A parser for start-up arguments.
*/
public final class StartupArguments {
private StartupArguments() {
// This class is not meant to be instantiated.
}
/* A map of parameter -> argument handler. */
private static final Map<String, Function<String, Boolean>> argumentHandlers = Map.of(
"-dumppacketids", parameter -> {
PacketOpcodesUtils.dumpPacketIds(); return true;
},
"-version", StartupArguments::printVersion,
"-debug", StartupArguments::enableDebug,
"-lang", parameter -> {
Grasscutter.setPreferredLanguage(parameter); return false;
},
// Aliases.
"-v", StartupArguments::printVersion,
"-debugall", parameter -> {
StartupArguments.enableDebug("all"); return false;
}
);
/**
* Parses the provided start-up arguments.
* @param args The application start-up arguments.
* @return If the application should exit.
*/
public static boolean parse(String[] args) {
boolean exitEarly = false;
// Parse the arguments.
for(var input : args) {
var containsParameter = input.contains("=");
var argument = containsParameter ? input.split("=")[0] : input;
var handler = argumentHandlers.get(argument.toLowerCase());
if (handler != null) {
exitEarly |= handler.apply(containsParameter ? input.split("=")[1] : null);
}
}
return exitEarly;
}
/**
* Prints the server version.
* @param parameter Additional parameters.
* @return True to exit early.
*/
private static boolean printVersion(String parameter) {
System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); return true;
}
/**
* Enables debug logging.
* @param parameter Additional parameters.
* @return False to continue execution.
*/
private static boolean enableDebug(String parameter) {
// Get the level by parameter.
var loggerLevel = parameter != null && parameter.equals("all")
? Level.DEBUG : Level.INFO;
// Set the logger to debug.
Grasscutter.getLogger().setLevel(Level.DEBUG);
Grasscutter.getLogger().debug("The logger is now running in debug mode.");
// Change loggers to debug.
((Logger) LoggerFactory.getLogger("express"))
.setLevel(loggerLevel);
((Logger) LoggerFactory.getLogger("org.quartz"))
.setLevel(loggerLevel);
((Logger) LoggerFactory.getLogger("org.reflections"))
.setLevel(loggerLevel);
((Logger) LoggerFactory.getLogger("org.eclipse.jetty"))
.setLevel(loggerLevel);
((Logger) LoggerFactory.getLogger("org.mongodb.driver"))
.setLevel(loggerLevel);
return false;
}
}