Skip to content

Commit

Permalink
First iteration of an extendable and testable config system
Browse files Browse the repository at this point in the history
  • Loading branch information
DerEchtePilz committed Sep 15, 2024
1 parent f8b58cf commit 593bd74
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 67 deletions.
6 changes: 6 additions & 0 deletions commandapi-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.jorel.commandapi.config;

import java.util.List;

record CommentedSection(List<String> comment) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dev.jorel.commandapi.config;

import org.jetbrains.annotations.ApiStatus;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@SuppressWarnings("ClassEscapesDefinedScope")
@ApiStatus.Internal
public abstract class DefaultedConfig {

final Map<String, CommentedConfigOption<?>> allOptions = new LinkedHashMap<>();
final Map<String, CommentedSection> allSections = new LinkedHashMap<>();

public static final CommentedConfigOption<Boolean> VERBOSE_OUTPUTS = new CommentedConfigOption<>(
List.of(
"Verbose outputs (default: false)",
"If \"true\", outputs command registration and unregistration logs in the console"
), false
);

public static final CommentedConfigOption<Boolean> SILENT_LOGS = new CommentedConfigOption<>(
List.of(
"Silent logs (default: false)",
"If \"true\", turns off all logging from the CommandAPI, except for errors."
), false
);

public static final CommentedConfigOption<String> MISSING_EXECUTOR_IMPLEMENTATION = new CommentedConfigOption<>(
List.of(
"Missing executor implementation (default: \"This command has no implementations for %s\")",
"The message to display to senders when a command has no executor. Available",
"parameters are:",
" %s - the executor class (lowercase)",
" %S - the executor class (normal case)"
), "This command has no implementations for %s"
);

public static final CommentedConfigOption<Boolean> CREATE_DISPATCHER_JSON = new CommentedConfigOption<>(
List.of(
"Create dispatcher JSON (default: false)",
"If \"true\", the CommandAPI creates a command_registration.json file showing the",
"mapping of registered commands. This is designed to be used by developers -",
"setting this to \"false\" will improve command registration performance."
), false
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,28 @@
@ApiStatus.Internal
public class ConfigGenerator {

private ConfigGenerator() {}
private final DefaultedBukkitConfig defaultedBukkitConfig;

public static YamlConfiguration generateDefaultConfig() throws InvalidConfigurationException {
private ConfigGenerator() {
this.defaultedBukkitConfig = DefaultedBukkitConfig.createDefault();
}

private ConfigGenerator(DefaultedBukkitConfig defaultedBukkitConfig) {
this.defaultedBukkitConfig = defaultedBukkitConfig;
}

public static ConfigGenerator createNew() {
return new ConfigGenerator();
}

public static ConfigGenerator createNew(DefaultedBukkitConfig defaultedBukkitConfig) {
return new ConfigGenerator(defaultedBukkitConfig);
}

public YamlConfiguration generateDefaultConfig() throws InvalidConfigurationException {
YamlConfiguration config = new YamlConfiguration();
Set<String> sections = new HashSet<>();
for (Map.Entry<String, CommentedConfigOption<?>> commentedConfigOption : DefaultedBukkitConfig.ALL_OPTIONS.entrySet()) {
for (Map.Entry<String, CommentedConfigOption<?>> commentedConfigOption : defaultedBukkitConfig.getAllOptions().entrySet()) {
String path = commentedConfigOption.getKey();

tryCreateSection(config, path, sections);
Expand All @@ -30,14 +46,14 @@ public static YamlConfiguration generateDefaultConfig() throws InvalidConfigurat
return process(config.saveToString());
}

public static YamlConfiguration generateWithNewValues(YamlConfiguration existingConfig) throws InvalidConfigurationException {
public YamlConfiguration generateWithNewValues(YamlConfiguration existingConfig) throws InvalidConfigurationException {
YamlConfiguration config = new YamlConfiguration();

boolean shouldRemoveValues = shouldRemoveOptions(existingConfig);

boolean wasConfigUpdated = false;
Set<String> sections = new HashSet<>();
for (Map.Entry<String, CommentedConfigOption<?>> commentedConfigOption : DefaultedBukkitConfig.ALL_OPTIONS.entrySet()) {
for (Map.Entry<String, CommentedConfigOption<?>> commentedConfigOption : defaultedBukkitConfig.getAllOptions().entrySet()) {
String path = commentedConfigOption.getKey();

// Update config option
Expand Down Expand Up @@ -70,7 +86,7 @@ public static YamlConfiguration generateWithNewValues(YamlConfiguration existing
return (wasConfigUpdated) ? process(config.saveToString()) : null;
}

private static YamlConfiguration process(String configAsString) throws InvalidConfigurationException {
private YamlConfiguration process(String configAsString) throws InvalidConfigurationException {
String[] configStrings = configAsString.split("\n");
StringBuilder configBuilder = new StringBuilder();
for (String configString : configStrings) {
Expand All @@ -84,7 +100,7 @@ private static YamlConfiguration process(String configAsString) throws InvalidCo
return config;
}

private static void tryCreateSection(YamlConfiguration config, String path, Set<String> existingSections) {
private void tryCreateSection(YamlConfiguration config, String path, Set<String> existingSections) {
if (path.contains(".")) {
// We have to create a section, or multiple if applicable, first, if it doesn't exist already
String[] sectionNames = path.split("\\.");
Expand All @@ -93,18 +109,21 @@ private static void tryCreateSection(YamlConfiguration config, String path, Set<
String sectionName = sectionNames[i];
if (!existingSections.contains(sectionName)) {
config.createSection(sectionName);
config.setComments(sectionName, DefaultedBukkitConfig.ALL_SECTIONS.get(sectionName).comment());
List<String> comment = defaultedBukkitConfig.getAllSections().get(sectionName).comment();
if (comment != null) {
config.setComments(sectionName, comment);
}
}
existingSections.add(sectionName);
}
}
}

private static boolean shouldRemoveOptions(YamlConfiguration config) {
private boolean shouldRemoveOptions(YamlConfiguration config) {
Set<String> configOptions = config.getKeys(true);
configOptions.removeIf(config::isConfigurationSection);

Set<String> defaultConfigOptions = DefaultedBukkitConfig.ALL_OPTIONS.keySet();
Set<String> defaultConfigOptions = defaultedBukkitConfig.getAllOptions().keySet();

boolean shouldRemoveOptions = false;
for (String option : configOptions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,7 @@
*/
@SuppressWarnings("ClassEscapesDefinedScope")
@ApiStatus.Internal
public class DefaultedBukkitConfig {

public static final CommentedConfigOption<Boolean> VERBOSE_OUTPUTS = new CommentedConfigOption<>(
List.of(
"Verbose outputs (default: false)",
"If \"true\", outputs command registration and unregistration logs in the console"
), false
);

public static final CommentedConfigOption<Boolean> SILENT_LOGS = new CommentedConfigOption<>(
List.of(
"Silent logs (default: false)",
"If \"true\", turns off all logging from the CommandAPI, except for errors."
), false
);

public static final CommentedConfigOption<String> MISSING_EXECUTOR_IMPLEMENTATION = new CommentedConfigOption<>(
List.of(
"Missing executor implementation (default: \"This command has no implementations for %s\")",
"The message to display to senders when a command has no executor. Available",
"parameters are:",
" %s - the executor class (lowercase)",
" %S - the executor class (normal case)"
), "This command has no implementations for %s"
);

public static final CommentedConfigOption<Boolean> CREATE_DISPATCHER_JSON = new CommentedConfigOption<>(
List.of(
"Create dispatcher JSON (default: false)",
"If \"true\", the CommandAPI creates a command_registration.json file showing the",
"mapping of registered commands. This is designed to be used by developers -",
"setting this to \"false\" will improve command registration performance."
), false
);
public class DefaultedBukkitConfig extends DefaultedConfig {

public static final CommentedConfigOption<Boolean> USE_LATEST_NMS_VERSION = new CommentedConfigOption<>(
List.of(
Expand Down Expand Up @@ -111,30 +78,48 @@ public class DefaultedBukkitConfig {
), new ArrayList<>()
);

public static final Map<String, CommentedConfigOption<?>> ALL_OPTIONS = new LinkedHashMap<>();
public static final Map<String, CommentedConfigOption<?>> ALL_SECTIONS = new LinkedHashMap<>();

public static final CommentedConfigOption<?> SECTION_MESSAGE = new CommentedConfigOption<>(
public static final CommentedSection SECTION_MESSAGE = new CommentedSection(
List.of(
"Messages",
"Controls messages that the CommandAPI displays to players"
), null
)
);

static {
ALL_OPTIONS.put("verbose-outputs", VERBOSE_OUTPUTS);
ALL_OPTIONS.put("silent-logs", SILENT_LOGS);
ALL_OPTIONS.put("messages.missing-executor-implementation", MISSING_EXECUTOR_IMPLEMENTATION);
ALL_OPTIONS.put("create-dispatcher-json", CREATE_DISPATCHER_JSON);
ALL_OPTIONS.put("use-latest-nms-version", USE_LATEST_NMS_VERSION);
ALL_OPTIONS.put("be-lenient-for-minor-versions", BE_LENIENT_FOR_MINOR_VERSIONS);
ALL_OPTIONS.put("hook-paper-reload", SHOULD_HOOK_PAPER_RELOAD);
ALL_OPTIONS.put("skip-initial-datapack-reload", SKIP_RELOAD_DATAPACKS);
ALL_OPTIONS.put("plugins-to-convert", PLUGINS_TO_CONVERT);
ALL_OPTIONS.put("other-commands-to-convert", OTHER_COMMANDS_TO_CONVERT);
ALL_OPTIONS.put("skip-sender-proxy", SKIP_SENDER_PROXY);

ALL_SECTIONS.put("messages", SECTION_MESSAGE);
private DefaultedBukkitConfig() {}

public static DefaultedBukkitConfig createDefault() {
DefaultedBukkitConfig config = new DefaultedBukkitConfig();
config.allOptions.put("verbose-outputs", VERBOSE_OUTPUTS);
config.allOptions.put("silent-logs", SILENT_LOGS);
config.allOptions.put("messages.missing-executor-implementation", MISSING_EXECUTOR_IMPLEMENTATION);
config.allOptions.put("create-dispatcher-json", CREATE_DISPATCHER_JSON);
config.allOptions.put("use-latest-nms-version", USE_LATEST_NMS_VERSION);
config.allOptions.put("be-lenient-for-minor-versions", BE_LENIENT_FOR_MINOR_VERSIONS);
config.allOptions.put("hook-paper-reload", SHOULD_HOOK_PAPER_RELOAD);
config.allOptions.put("skip-initial-datapack-reload", SKIP_RELOAD_DATAPACKS);
config.allOptions.put("plugins-to-convert", PLUGINS_TO_CONVERT);
config.allOptions.put("other-commands-to-convert", OTHER_COMMANDS_TO_CONVERT);
config.allOptions.put("skip-sender-proxy", SKIP_SENDER_PROXY);

config.allSections.put("messages", SECTION_MESSAGE);

return config;
}

public static DefaultedBukkitConfig create(Map<String, CommentedConfigOption<?>> options, Map<String, CommentedSection> sections) {
DefaultedBukkitConfig config = new DefaultedBukkitConfig();

config.allOptions.putAll(options);
config.allSections.putAll(sections);

return config;
}

public Map<String, CommentedConfigOption<?>> getAllOptions() {
return allOptions;
}

public Map<String, CommentedSection> getAllSections() {
return allSections;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ public void onEnable() {
@Override
public void saveDefaultConfig() {
File configFile = new File(getDataFolder(), "config.yml");
ConfigGenerator configGenerator = ConfigGenerator.createNew();
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
try {
YamlConfiguration defaultConfig = ConfigGenerator.generateDefaultConfig();
YamlConfiguration defaultConfig = configGenerator.generateDefaultConfig();
defaultConfig.save(configFile);
} catch (Exception e) {
getLogger().severe("Could not create default config file! This is (probably) a bug.");
Expand All @@ -166,7 +167,7 @@ public void saveDefaultConfig() {
// Update the config if necessary
YamlConfiguration existingConfig = YamlConfiguration.loadConfiguration(configFile);
try {
YamlConfiguration updatedConfig = ConfigGenerator.generateWithNewValues(existingConfig);
YamlConfiguration updatedConfig = configGenerator.generateWithNewValues(existingConfig);
if (updatedConfig == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ public void onEnable() {
@Override
public void saveDefaultConfig() {
File configFile = new File(getDataFolder(), "config.yml");
ConfigGenerator configGenerator = ConfigGenerator.createNew();
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
try {
YamlConfiguration defaultConfig = ConfigGenerator.generateDefaultConfig();
YamlConfiguration defaultConfig = configGenerator.generateDefaultConfig();
defaultConfig.save(configFile);
} catch (Exception e) {
getLogger().severe("Could not create default config file! This is (probably) a bug.");
Expand All @@ -165,7 +166,7 @@ public void saveDefaultConfig() {
// Update the config if necessary
YamlConfiguration existingConfig = YamlConfiguration.loadConfiguration(configFile);
try {
YamlConfiguration updatedConfig = ConfigGenerator.generateWithNewValues(existingConfig);
YamlConfiguration updatedConfig = configGenerator.generateWithNewValues(existingConfig);
if (updatedConfig == null) {
return;
}
Expand Down

0 comments on commit 593bd74

Please sign in to comment.