├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── io │ │ └── github │ │ └── redstoneparadox │ │ └── paradoxconfig │ │ ├── ConfigFormatInitializer.kt │ │ ├── ConfigManager.kt │ │ ├── ParadoxConfig.kt │ │ ├── codec │ │ ├── ConfigCodec.kt │ │ ├── GsonConfigCodec.kt │ │ ├── JanksonConfigCodec.kt │ │ └── TomlConfigCodec.kt │ │ ├── compat │ │ └── ParadoxConfigLibCD.kt │ │ ├── config │ │ ├── CollectionConfigOption.kt │ │ ├── ConfigCategory.kt │ │ ├── ConfigOption.kt │ │ ├── DictionaryConfigOption.kt │ │ └── RangeConfigOption.kt │ │ └── util │ │ ├── Functions.kt │ │ └── Operators.kt └── resources │ ├── assets │ └── paradoxconfig │ │ └── icon.png │ ├── fabric.mod.json │ └── paradoxconfig.mixins.json └── testmod ├── java └── io │ └── github │ └── redstoneparadox │ └── paradoxconfig │ ├── GsonTestConfig.kt │ ├── JanksonTestConfig.kt │ ├── ParadoxConfigTest.kt │ └── TomlTestConfig.kt └── resources └── fabric.mod.json /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | 7 | # idea 8 | 9 | .idea/ 10 | *.iml 11 | *.ipr 12 | *.iws 13 | 14 | # fabric 15 | 16 | run/ 17 | minecraft/ 18 | 19 | classes/ 20 | *.class 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RedstoneParadox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paradox Config 2 | 3 | A lightweight, Kotlin-based config API for Minecraft. 4 | 5 | Discord: https://discord.gg/crZpcjtdJR 6 | 7 | ## Adding to your project: 8 | 9 | build.gradle: 10 | ```gradle 11 | repositories { 12 | //... 13 | maven { url "https://api.modrinth.com/maven" } 14 | } 15 | 16 | dependencies { 17 | // ... 18 | 19 | modApi "maven.modrinth:paradox-config:" 20 | include "maven.modrinth:paradox-config:" 21 | } 22 | ``` 23 | 24 | ## Adding versions prior to 0.5.0 Beta to your project: 25 | 26 | You probably can't. Sorry. 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' 3 | id 'maven-publish' 4 | id "org.jetbrains.kotlin.jvm" 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_16 8 | targetCompatibility = JavaVersion.VERSION_16 9 | 10 | archivesBaseName = project.archives_base_name 11 | version = project.mod_version 12 | group = project.maven_group 13 | 14 | minecraft { 15 | } 16 | 17 | sourceSets { 18 | testmod { 19 | compileClasspath += main.compileClasspath 20 | runtimeClasspath += main.runtimeClasspath 21 | } 22 | } 23 | 24 | repositories { 25 | mavenCentral() 26 | maven { url = "https://maven.fabricmc.net/" } 27 | maven { 28 | name = 'CottonMC' 29 | url = 'https://server.bbkr.space/artifactory/libs-release' 30 | } 31 | /* 32 | maven { 33 | url = "https://ytg1234.github.io/maven" 34 | } 35 | */ 36 | maven { 37 | name = "TerraformersMC" 38 | url = "https://maven.terraformersmc.com/" 39 | } 40 | // maven { url "https://maven.shedaniel.me/" } 41 | maven { 42 | name = 'JitPack' 43 | url = 'https://jitpack.io' 44 | } 45 | } 46 | 47 | dependencies { 48 | //to change the versions see the gradle.properties file 49 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 50 | mappings "net.fabricmc:yarn:${project.yarn_mappings}" 51 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 52 | 53 | // Fabric API. This is technically optional, but you probably want it anyway. 54 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 55 | 56 | modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}+kotlin.${project.kotlin_version}" 57 | 58 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 59 | // You may need to force-disable transitiveness on them. 60 | 61 | modImplementation "io.github.cottonmc:Jankson-Fabric:${project.jankson_version}" 62 | modImplementation "io.github.cottonmc:LibCD:${project.libcd_version}" 63 | modImplementation ("com.terraformersmc:modmenu:${project.modmenu_version}"){ 64 | transitive = false 65 | } 66 | 67 | /* 68 | modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { 69 | exclude(group: "net.fabricmc.fabric-api") 70 | } 71 | */ 72 | 73 | //include(modImplementation("io.github.ytg1234:fabric-recipe-conditions:0.3.0")) 74 | 75 | implementation('com.moandjiezana.toml:toml4j:0.7.2') 76 | include('com.moandjiezana.toml:toml4j:0.7.2') 77 | 78 | afterEvaluate { 79 | testmodImplementation sourceSets.main.output 80 | } 81 | } 82 | 83 | processResources { 84 | inputs.property "version", project.version 85 | 86 | filesMatching("fabric.mod.json") { 87 | expand version: project.version 88 | } 89 | } 90 | 91 | // ensure that the encoding is set to UTF-8, no matter what the system default is 92 | // this fixes some edge cases with special characters not displaying correctly 93 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 94 | tasks.withType(JavaCompile) { 95 | it.options.encoding = "UTF-8" 96 | 97 | // Minecraft 1.17 (21w19a) upwards uses Java 16. 98 | it.options.release = 16 99 | } 100 | 101 | java { 102 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 103 | // if it is present. 104 | // If you remove this line, sources will not be generated. 105 | withSourcesJar() 106 | } 107 | 108 | jar { 109 | from ("LICENSE") { 110 | rename { "${it}_${project.archivesBaseName}"} 111 | } 112 | } 113 | 114 | // configure the maven publication 115 | publishing { 116 | publications { 117 | mavenJava(MavenPublication) { 118 | // add all the jars that should be included when publishing to maven 119 | artifact(remapJar) { 120 | builtBy remapJar 121 | } 122 | artifact(sourcesJar) { 123 | builtBy remapSourcesJar 124 | } 125 | } 126 | } 127 | 128 | // select the repositories you want to publish to 129 | repositories { 130 | // uncomment to publish to the local maven 131 | // mavenLocal() 132 | } 133 | } 134 | 135 | compileKotlin.kotlinOptions.jvmTarget = "16" -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # Check these on https://modmuss50.me/fabric.html 6 | minecraft_version=1.17 7 | yarn_mappings=1.17+build.13 8 | loader_version=0.11.6 9 | 10 | #Fabric api 11 | fabric_version=0.36.0+1.17 12 | loom_version=0.9-SNAPSHOT 13 | 14 | # Mod Properties 15 | mod_version = 0.5.0-beta 16 | maven_group = io.github.redstoneparadox 17 | archives_base_name = ParadoxConfig 18 | 19 | # Dependencies 20 | jankson_version=3.0.1+j1.2.0 21 | libcd_version=3.0.3+1.16.3 22 | cloth_config_version=5.0.34 23 | modmenu_version=2.0.2 24 | 25 | # Kotlin 26 | kotlin_version=1.5.10 27 | fabric_kotlin_version=1.6.1 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedstoneParadox/ParadoxConfig/d1560efa232d700a232e605377294131df449e36/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 27 10:29:07 IDT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | 10 | plugins { 11 | id 'fabric-loom' version loom_version 12 | id "org.jetbrains.kotlin.jvm" version kotlin_version 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/ConfigFormatInitializer.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | /** 4 | * Entrypoint invoked before Paradox Config loads config files. 5 | * 6 | * This entrypoint should be used only for [io.github.redstoneparadox.goconfigure.codec.ConfigCodec] 7 | * implementations to support new file formats. You should not 8 | * mess with game state or anything else here. 9 | * 10 | * This entrypoint is exposed as [pconfigFormat] in the mod 11 | * json and runs for every environment. 12 | */ 13 | interface ConfigFormatInitializer { 14 | fun initializeConfigFormat() 15 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/ConfigManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import io.github.redstoneparadox.paradoxconfig.codec.ConfigCodec 4 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 5 | import net.fabricmc.loader.api.FabricLoader 6 | import java.io.File 7 | 8 | object ConfigManager { 9 | private val CONFIGS: MutableMap = mutableMapOf() 10 | 11 | fun getConfig(id: String): ConfigCategory? { 12 | return CONFIGS[id] 13 | } 14 | 15 | internal fun initConfigs(rootPackage: String, configNames: Collection, modid: String) { 16 | for (name in configNames) { 17 | val className = "${rootPackage}.$name" 18 | 19 | when (val config = Class.forName(className).kotlin.objectInstance) { 20 | is ConfigCategory -> { 21 | config.init() 22 | CONFIGS["$modid:${config.key}"] = config 23 | loadConfig(config, modid) 24 | } 25 | null -> ParadoxConfig.error("$className could not be found.") 26 | else -> ParadoxConfig.error("$className does not extend ConfigCategory") 27 | } 28 | } 29 | } 30 | 31 | private fun loadConfig(config: ConfigCategory, modid: String) { 32 | val ext = config.key.split('.').last() 33 | val configCodec = ConfigCodec.getCodec(ext) 34 | val file = File(FabricLoader.getInstance().configDirectory, "${modid}/${config.key}") 35 | 36 | if (file.exists()) { 37 | try { 38 | val configData = file.readText() 39 | 40 | configCodec.decode(configData, config) 41 | file.writeText(configCodec.encode(config)) 42 | } catch (e: Exception) { 43 | ParadoxConfig.error("Could not write to config file $modid:${config.key} due to an exception.") 44 | e.printStackTrace() 45 | } 46 | } else { 47 | ParadoxConfig.log("Config file $modid:${config.key} was not found; a new one will be created.") 48 | 49 | try { 50 | file.parentFile.mkdirs() 51 | file.createNewFile() 52 | file.writeText(configCodec.encode(config)) 53 | } catch (e: SecurityException) { 54 | ParadoxConfig.error("Could not create config file $modid:${config.key} due to security issues.") 55 | e.printStackTrace() 56 | } catch (e: Exception) { 57 | ParadoxConfig.error("Could not create config file $modid:${config.key} due to an exception.") 58 | e.printStackTrace() 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/ParadoxConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import io.github.redstoneparadox.paradoxconfig.codec.ConfigCodec 4 | import io.github.redstoneparadox.paradoxconfig.codec.GsonConfigCodec 5 | import io.github.redstoneparadox.paradoxconfig.codec.JanksonConfigCodec 6 | import io.github.redstoneparadox.paradoxconfig.codec.TomlConfigCodec 7 | import net.fabricmc.loader.api.FabricLoader 8 | import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint 9 | import org.apache.logging.log4j.LogManager 10 | 11 | object ParadoxConfig: PreLaunchEntrypoint { 12 | const val MOD_ID: String = "paradoxconfig" 13 | private const val MOD_NAME = "Paradox Config" 14 | private val LOGGER = LogManager.getFormatterLogger(MOD_NAME) 15 | 16 | @Suppress("unused") 17 | override fun onPreLaunch() { 18 | val loader = FabricLoader.getInstance() 19 | 20 | ConfigCodec.addCodec(GsonConfigCodec()) 21 | ConfigCodec.addCodec(TomlConfigCodec()) 22 | 23 | if (loader.isModLoaded("jankson")) { 24 | ConfigCodec.addCodec(JanksonConfigCodec()) 25 | } 26 | 27 | for (entrypoint in loader.getEntrypoints("pconfigFormat", ConfigFormatInitializer::class.java)) { 28 | entrypoint.initializeConfigFormat() 29 | } 30 | 31 | for (mod in loader.allMods) { 32 | if (mod.metadata.containsCustomValue(MOD_ID)) { 33 | val obj = mod.metadata.getCustomValue(MOD_ID).asObject 34 | val rootPackage = obj.get("package").asString 35 | val classNames = obj.get("configs").asArray.map { it.asString } 36 | 37 | if (mod.metadata.id == MOD_ID && !FabricLoader.getInstance().isDevelopmentEnvironment) continue 38 | 39 | ConfigManager.initConfigs(rootPackage, classNames, mod.metadata.id) 40 | } 41 | } 42 | } 43 | 44 | internal fun log(msg: String) { 45 | LOGGER.info("[$MOD_NAME] $msg") 46 | } 47 | 48 | internal fun warn(msg: String) { 49 | LOGGER.warn("[$MOD_NAME] $msg") 50 | } 51 | 52 | internal fun error(msg: String) { 53 | LOGGER.warn("[$MOD_NAME] $msg") 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/codec/ConfigCodec.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.codec 2 | 3 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 4 | import io.github.redstoneparadox.paradoxconfig.util.unwrap 5 | 6 | /** 7 | * Classes that implement this interface are used to read 8 | * and write config files using a specific format. 9 | * 10 | * Implementors should not save or load config files 11 | * themselves. 12 | * 13 | * Not to be confused with [com.mojang.serialization.Codec]. 14 | */ 15 | interface ConfigCodec { 16 | /** 17 | * The file extension for this codec (i.e. json5). 18 | */ 19 | val fileExtension: String 20 | 21 | /** 22 | * Reads config option data from the string to 23 | * the passed config. 24 | * 25 | * @param config The config to decode to. 26 | * @param data The formatted data to be decoded. 27 | */ 28 | fun decode(data: String, config: ConfigCategory) 29 | 30 | /** 31 | * Converts a config to the codec's specified 32 | * file format. 33 | * 34 | * @param config the config to encode. 35 | * @return The encoded config as a string. 36 | */ 37 | fun encode(config: ConfigCategory): String 38 | 39 | companion object { 40 | private val CODECS: MutableMap = mutableMapOf() 41 | 42 | /** 43 | * Adds a new codec for a file format. 44 | * 45 | * @param configCodec the [ConfigCodec] to add. 46 | */ 47 | fun addCodec(configCodec: ConfigCodec) { 48 | val ext = configCodec.fileExtension 49 | 50 | if (CODECS.containsKey(ext)) { 51 | throw Exception("ConfigCodec for file format $ext was already registered!") 52 | } 53 | 54 | CODECS[ext] = configCodec 55 | } 56 | 57 | fun getCodec(ext: String): ConfigCodec { 58 | return CODECS[ext].unwrap(Exception("ConfigCodec for file format $ext was never registered!")) 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/codec/GsonConfigCodec.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.codec 2 | 3 | import com.google.gson.* 4 | import com.google.gson.stream.JsonReader 5 | import com.google.gson.stream.JsonWriter 6 | import io.github.redstoneparadox.paradoxconfig.config.CollectionConfigOption 7 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 8 | import io.github.redstoneparadox.paradoxconfig.config.ConfigOption 9 | import io.github.redstoneparadox.paradoxconfig.config.DictionaryConfigOption 10 | import net.minecraft.util.Identifier 11 | 12 | class GsonConfigCodec: ConfigCodec { 13 | override val fileExtension: String = "json" 14 | private val gson: Gson = GsonBuilder() 15 | .registerTypeAdapter(Identifier::class.java, object : TypeAdapter() { 16 | val EMPTY: Identifier = Identifier("empty") 17 | 18 | override fun write(out: JsonWriter?, value: Identifier?) { 19 | out?.value(value.toString()) 20 | } 21 | 22 | override fun read(`in`: JsonReader?): Identifier { 23 | return Identifier.tryParse(`in`?.nextString()) ?: EMPTY 24 | } 25 | }) 26 | .setPrettyPrinting() 27 | .create() 28 | 29 | override fun decode(data: String, config: ConfigCategory) { 30 | val json = gson.fromJson(data, JsonObject::class.java) 31 | 32 | decodeCategory(json, config) 33 | } 34 | 35 | private fun decodeCategory(json: JsonObject, category: ConfigCategory) { 36 | for (subcategory in category.getSubcategories()) { 37 | val obj = json[subcategory.key] 38 | 39 | if (obj is JsonObject) decodeCategory(obj, subcategory) 40 | } 41 | 42 | for (option in category.getOptions()) { 43 | val element = json[option.key] 44 | 45 | if (element != null) decodeOption(element, option) 46 | } 47 | } 48 | 49 | private fun decodeOption(json: JsonElement, option: ConfigOption<*>) { 50 | if (json is JsonArray && option is CollectionConfigOption<*, *>) { 51 | val collection = mutableListOf() 52 | 53 | json.forEach { collection.add(gson.fromJson(it, option.getElementKClass().java)) } 54 | option.set(collection) 55 | } else if (json is JsonObject && option is DictionaryConfigOption<*, *>) { 56 | val map = mutableMapOf() 57 | 58 | json.entrySet().forEach { map[it.key] = gson.fromJson(it.value, option.getValueKClass().java) } 59 | option.set(map) 60 | } else { 61 | val any = gson.fromJson(json, option.getKClass().java) 62 | 63 | option.set(any) 64 | } 65 | } 66 | 67 | override fun encode(config: ConfigCategory): String { 68 | return gson.toJson(encodeCategory(config)) 69 | } 70 | 71 | private fun encodeCategory(category: ConfigCategory): JsonObject { 72 | val obj = JsonObject() 73 | 74 | for (option in category.getOptions()) { 75 | val optionElement = encodeOption(option) 76 | 77 | obj.add(option.key, optionElement) 78 | } 79 | 80 | for (subcategory in category.getSubcategories()) { 81 | val subcategoryObj = encodeCategory(subcategory) 82 | 83 | obj.add(subcategory.key, subcategoryObj) 84 | } 85 | 86 | return obj 87 | } 88 | 89 | private fun encodeOption(option: ConfigOption<*>): JsonElement { 90 | val any = option.get() 91 | 92 | if (any is Collection<*>) { 93 | val array = JsonArray() 94 | 95 | any.forEach { array.add(gson.toJsonTree(it, (option as CollectionConfigOption<*,*>).getElementKClass().java)) } 96 | return array 97 | } 98 | if (any is Map<*, *>) { 99 | val obj = JsonObject() 100 | 101 | any.keys.forEach { if (it is String) { obj.add(it, gson.toJsonTree(any[it], (option as DictionaryConfigOption<*, *>).getValueKClass().java)) } } 102 | return obj 103 | } 104 | 105 | return gson.toJsonTree(any, option.getKClass().java) 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/codec/JanksonConfigCodec.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.codec 2 | 3 | import blue.endless.jankson.Jankson 4 | import blue.endless.jankson.JsonArray 5 | import blue.endless.jankson.JsonElement 6 | import blue.endless.jankson.JsonObject 7 | import blue.endless.jankson.JsonPrimitive 8 | import blue.endless.jankson.api.Marshaller 9 | import io.github.redstoneparadox.paradoxconfig.config.CollectionConfigOption 10 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 11 | import io.github.redstoneparadox.paradoxconfig.config.ConfigOption 12 | import io.github.redstoneparadox.paradoxconfig.config.DictionaryConfigOption 13 | import net.minecraft.util.Identifier 14 | 15 | class JanksonConfigCodec: ConfigCodec { 16 | override val fileExtension: String = "json5" 17 | private val jankson: Jankson 18 | 19 | init { 20 | val builder = Jankson.builder() 21 | 22 | builder 23 | .registerDeserializer(String::class.java, Identifier::class.java) { s: String?, m: Marshaller? -> 24 | Identifier(s) 25 | } 26 | .registerSerializer(Identifier::class.java) { i: Identifier, m: Marshaller? -> 27 | JsonPrimitive(i.toString()) 28 | } 29 | 30 | jankson = builder.build() 31 | } 32 | 33 | override fun decode(data: String, config: ConfigCategory) { 34 | val json = jankson.load(data) 35 | 36 | deserializeCategory(json, config) 37 | } 38 | 39 | private fun deserializeCategory(json: JsonObject, category: ConfigCategory) { 40 | for (subcategory in category.getSubcategories()) { 41 | val obj = json[subcategory.key] 42 | 43 | if (obj is JsonObject) deserializeCategory(obj, subcategory) 44 | } 45 | 46 | for (option in category.getOptions()) { 47 | val element = json[option.key] 48 | 49 | if (element != null) deserializeOption(element, option) 50 | } 51 | } 52 | 53 | private fun deserializeOption(json: JsonElement, option: ConfigOption<*>) { 54 | if (json is JsonArray && option is CollectionConfigOption<*, *>) { 55 | val collection = mutableListOf() 56 | 57 | json.forEach { collection.add(jankson.marshaller.marshall(option.getElementKClass().java, it)) } 58 | option.set(collection) 59 | } else if (json is JsonObject && option is DictionaryConfigOption<*, *>) { 60 | val map = mutableMapOf() 61 | 62 | json.forEach { map[it.key] = jankson.marshaller.marshall(option.getValueKClass().java, it.value) } 63 | option.set(map) 64 | } else { 65 | val any = jankson.marshaller.marshall(option.getKClass().java, json) 66 | 67 | option.set(any) 68 | } 69 | } 70 | 71 | override fun encode(config: ConfigCategory): String { 72 | return encodeCategory(config).first.toJson(true, true) 73 | } 74 | 75 | private fun encodeCategory(category: ConfigCategory): Pair { 76 | val obj = JsonObject() 77 | 78 | for (option in category.getOptions()) { 79 | val pair = encodeOption(option) 80 | 81 | obj.put(option.key, pair.first, pair.second) 82 | } 83 | 84 | for (subcategory in category.getSubcategories()) { 85 | val pair = encodeCategory(subcategory) 86 | 87 | obj.put(subcategory.key, pair.first, pair.second) 88 | } 89 | 90 | return Pair(obj, category.comment) 91 | } 92 | 93 | private fun encodeOption(option: ConfigOption<*>): Pair { 94 | val any = option.get() 95 | 96 | if (any is Collection<*>) { 97 | val array = JsonArray() 98 | 99 | any.forEach { array.add(jankson.marshaller.serialize(it)) } 100 | return Pair(array, option.comment) 101 | } 102 | if (any is Map<*, *>) { 103 | val obj = JsonObject() 104 | 105 | any.keys.forEach { if (it is String) { obj[it] = jankson.marshaller.serialize(any[it]) } } 106 | return Pair(obj, option.comment) 107 | } 108 | 109 | val element = jankson.marshaller.serialize(any) 110 | 111 | return Pair(element, option.comment) 112 | } 113 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/codec/TomlConfigCodec.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.codec 2 | 3 | import com.moandjiezana.toml.Toml 4 | import com.moandjiezana.toml.TomlWriter 5 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 6 | import net.minecraft.util.Identifier 7 | 8 | 9 | class TomlConfigCodec: ConfigCodec { 10 | override val fileExtension: String = "toml" 11 | val writer: TomlWriter = TomlWriter() 12 | 13 | override fun decode(data: String, config: ConfigCategory) { 14 | val map = Toml().read(data).toMap() 15 | 16 | decodeCategory(config, map) 17 | } 18 | 19 | private fun decodeCategory(category: ConfigCategory, map: Map) { 20 | for (option in category.getOptions()) { 21 | val value = map[option.key] 22 | option.set(value) 23 | } 24 | 25 | for (subcategory in category.getSubcategories()) { 26 | val subcategoryTable = map[subcategory.key] 27 | 28 | if (subcategoryTable is Map<*, *>) decodeCategory(subcategory, subcategoryTable as Map) 29 | } 30 | } 31 | 32 | override fun encode(config: ConfigCategory): String { 33 | val map: Map = encodeCategory(config) 34 | 35 | return writer.write(map) 36 | } 37 | 38 | private fun encodeCategory(category: ConfigCategory): Map { 39 | val map: MutableMap = mutableMapOf() 40 | 41 | for (option in category.getOptions()) { 42 | val value = option.get() 43 | 44 | if (value is Identifier) { 45 | map[option.key] = value.toString() 46 | } else { 47 | map[option.key] = value 48 | } 49 | } 50 | 51 | for (subcategory in category.getSubcategories()) { 52 | val subcategoryMap: Map = encodeCategory(subcategory) 53 | map[subcategory.key] = subcategoryMap 54 | } 55 | 56 | return map 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/compat/ParadoxConfigLibCD.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.compat 2 | 3 | import com.google.gson.JsonArray 4 | import com.google.gson.JsonObject 5 | import com.google.gson.JsonPrimitive 6 | import io.github.cottonmc.libcd.api.LibCDInitializer 7 | import io.github.cottonmc.libcd.api.condition.ConditionManager 8 | import io.github.redstoneparadox.paradoxconfig.ConfigManager 9 | import io.github.redstoneparadox.paradoxconfig.ParadoxConfig.MOD_ID 10 | import io.github.redstoneparadox.paradoxconfig.util.compareTo 11 | import io.github.redstoneparadox.paradoxconfig.util.getValue 12 | import net.fabricmc.loader.api.FabricLoader 13 | import net.minecraft.util.Identifier 14 | 15 | /** 16 | * Created by RedstoneParadox on 11/9/2019. 17 | */ 18 | object ParadoxConfigLibCD: LibCDInitializer { 19 | override fun initConditions(manager: ConditionManager) { 20 | manager.registerCondition(Identifier(MOD_ID, "option")) { 21 | if (it is JsonObject) { 22 | val configID = (it["config"] as? JsonPrimitive)?.asString 23 | 24 | if (!FabricLoader.getInstance().isDevelopmentEnvironment && configID == "${MOD_ID}:test.json5") { 25 | return@registerCondition false 26 | } else if (configID != null) { 27 | val config = ConfigManager.getConfig(configID) 28 | val primitive = (it["value"] as? JsonPrimitive) 29 | val predicate = (it["predicate"] as? JsonPrimitive)?.asString 30 | val option = (it["option"] as? JsonPrimitive)?.asString 31 | 32 | if (config != null && primitive != null && option != null) { 33 | val op = config[option] 34 | 35 | if (predicate != null) { 36 | return@registerCondition when (predicate) { 37 | "==" -> op == primitive.getValue() 38 | "!=" -> op != primitive.getValue() 39 | "<" -> if (op is Number) op < primitive.asNumber else false 40 | ">" -> if (op is Number) op > primitive.asNumber else false 41 | "<=" -> if (op is Number) op <= primitive.asNumber else false 42 | ">=" -> if (op is Number) op >= primitive.asNumber else false 43 | else -> false 44 | } 45 | } 46 | else { 47 | return@registerCondition op == primitive.getValue() 48 | } 49 | } 50 | } 51 | } 52 | 53 | return@registerCondition false 54 | } 55 | 56 | manager.registerCondition(Identifier(MOD_ID, "contains")) { 57 | if (it is JsonObject) { 58 | val configID = (it["config"] as? JsonPrimitive)?.asString 59 | 60 | if (!FabricLoader.getInstance().isDevelopmentEnvironment && configID == "$MOD_ID:test.json5") { 61 | return@registerCondition false 62 | } else if (configID != null) { 63 | val config = ConfigManager.getConfig(configID) 64 | val contains = (it["contains"] as? JsonArray)?.toList() 65 | val option = (it["option"] as? JsonPrimitive)?.asString 66 | 67 | if (config != null && contains != null && option != null) { 68 | val optionCollection = config[option] as? Collection 69 | 70 | if (optionCollection != null && optionCollection.size >= contains.size) { 71 | for (element in contains) { 72 | if (!(element is JsonPrimitive && optionCollection.contains(element.getValue()))) { 73 | return@registerCondition false 74 | } 75 | 76 | } 77 | return@registerCondition true 78 | } 79 | } 80 | } 81 | } 82 | 83 | return@registerCondition false 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/config/CollectionConfigOption.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.config 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.full.cast 5 | class CollectionConfigOption>(private val innerType: KClass, collectionType: KClass, comment: String, key: String, value: U): ConfigOption(collectionType, value, key, comment) { 6 | override fun set(any: Any?) { 7 | if (any is MutableCollection<*>) { 8 | for (element in any) { 9 | if (!innerType.isInstance(element)) { 10 | return 11 | } 12 | } 13 | 14 | value.clear() 15 | value.addAll(type.cast(any)) 16 | } 17 | } 18 | 19 | fun getElementKClass(): KClass { 20 | return innerType 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/config/ConfigCategory.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.config 2 | 3 | import io.github.redstoneparadox.paradoxconfig.util.toImmutable 4 | import net.minecraft.util.Identifier 5 | import kotlin.reflect.KClass 6 | import kotlin.reflect.KProperty1 7 | import kotlin.reflect.full.declaredMemberProperties 8 | import kotlin.reflect.jvm.isAccessible 9 | 10 | /** 11 | * Inheritors of this class represent a category in your config file. When making 12 | * the root config category, [key] should represent the full file name + the file 13 | * extension of the config file. 14 | */ 15 | abstract class ConfigCategory(val key : String = "", val comment: String = "") { 16 | @PublishedApi 17 | internal val optionsMap: HashMap> = HashMap() 18 | private val categoriesMap: HashMap = HashMap() 19 | private val optionsList: MutableList> = mutableListOf() 20 | private val categoryList: MutableList = mutableListOf() 21 | 22 | /** 23 | * Creates a config option holding a value of type [T]. 24 | * 25 | * @param default The default value for this option. 26 | * @param key The config key for this option. 27 | * @param comment (optional) a comment for this option. 28 | * 29 | * @return A [ConfigOption] delegate that holds option values of type [T]. 30 | */ 31 | protected inline fun option(default: T, key: String, comment: String = ""): ConfigOption { 32 | val kClass = T::class 33 | return ConfigOption(kClass, default, key, "$comment [Values: ${getPossibleValues(kClass)}]") 34 | } 35 | 36 | /** 37 | * Creates a config option holding a value of type [T] which is bounded by 38 | * a [ClosedRange]. Note that [T] must extend [Comparable] 39 | * 40 | * @param default The default value for this option. 41 | * @param range The range to bound this option to. 42 | * @param key The config key for this option. 43 | * @param comment The comment for this option. 44 | * 45 | * @return A [RangeConfigOption] delegate that holds an option value of type [T]. 46 | */ 47 | protected inline fun option(default: T, range: ClosedRange, key: String, comment: String = ""): RangeConfigOption where T: Any, T: Comparable { 48 | val kClass = T::class 49 | return RangeConfigOption(kClass, default, key, "$comment [Values: ${getPossibleValues(kClass)} in $range]", range) 50 | } 51 | 52 | /** 53 | * Created a config option of a [MutableCollection] holding values of type. 54 | * [T]. 55 | * 56 | * @param default The default value for this option. 57 | * @param key The config key for this option. 58 | * @param comment (optional) The comment for this option. 59 | * 60 | * @return A [CollectionConfigOption] holding a [MutableCollection] 61 | * implementer containing values of type [T]. 62 | */ 63 | protected inline fun > option(default: U, key: String, comment: String = ""): CollectionConfigOption { 64 | val kClass = T::class 65 | return CollectionConfigOption(kClass, U::class, "$comment [Collection of ${getPossibleValues(kClass)}]", key, default) 66 | } 67 | 68 | /** 69 | * Creates a config option of a [MutableMap] with keys of type 70 | * [K] and values of type [V] 71 | * 72 | * @param default The default value for this option. 73 | * @param key the config key for this option. 74 | * @param comment (optional) The comment for this option. 75 | * 76 | * @return A [DictionaryConfigOption] holding an implementation of 77 | * [MutableMap] with keys of type [K] to values of type [V] 78 | */ 79 | protected inline fun > option(default: T, key: String, comment: String = ""): DictionaryConfigOption { 80 | val valueClass = V::class 81 | return DictionaryConfigOption(valueClass, T::class, default, key, "$comment [Keys: any string, Values: ${getPossibleValues(valueClass)}]") 82 | } 83 | 84 | internal fun init() { 85 | val kclass = this::class 86 | 87 | for (innerclass in kclass.nestedClasses) { 88 | val category = innerclass.objectInstance 89 | if (category is ConfigCategory) { 90 | categoriesMap[category.key] = category 91 | categoryList.add(category) 92 | category.init() 93 | } 94 | } 95 | 96 | for (property in kclass.declaredMemberProperties) { 97 | property.isAccessible = true 98 | val delegate = (property as KProperty1).getDelegate(this) 99 | if (delegate is ConfigOption<*>) { 100 | optionsMap[delegate.key] = delegate 101 | optionsList.add(delegate) 102 | } 103 | } 104 | } 105 | 106 | fun getSubcategories(): List { 107 | return categoryList.toImmutable() 108 | } 109 | 110 | fun getOptions(): List> { 111 | return optionsList.toImmutable() 112 | } 113 | 114 | operator fun get(key: String): Any? { 115 | return get(key.splitToSequence(".")) 116 | } 117 | 118 | private fun get(key: Sequence): Any? { 119 | val first = key.first() 120 | return if (key.last() == first) { 121 | optionsMap[first]?.get() 122 | } else { 123 | categoriesMap[first]?.get(key.drop(1)) 124 | } 125 | } 126 | 127 | operator fun set(key: String, value: Any) { 128 | set(key.splitToSequence("."), value) 129 | } 130 | 131 | private fun set(key: Sequence, value: Any) { 132 | val first = key.first() 133 | if (key.last() == first) { 134 | optionsMap[first]?.set(value) 135 | } 136 | else { 137 | categoriesMap[first]?.set(key.drop(1), value) 138 | } 139 | } 140 | 141 | @PublishedApi 142 | internal fun getPossibleValues(kclass: KClass): String { 143 | return when (kclass) { 144 | Boolean::class -> "true/false" 145 | String::class -> "any string" 146 | Char::class -> "any character" 147 | Byte::class, Short::class, Int::class, Long::class -> "any number" 148 | Float::class, Double::class -> "any decimal number" 149 | Identifier::class -> "any valid 'namespace:path' identifier" 150 | else -> "unknown" 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/config/ConfigOption.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.config 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.KProperty 5 | import kotlin.reflect.full.cast 6 | 7 | open class ConfigOption(protected val type: KClass, protected var value: T, internal val key: String, val comment: String) { 8 | open operator fun getValue(thisRef : Any?, property: KProperty<*>): T { 9 | return value 10 | } 11 | 12 | open operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 13 | this.value = value 14 | } 15 | 16 | open fun set(any: Any?) { 17 | if (type.isInstance(any)) { 18 | value = type.cast(any) 19 | } 20 | } 21 | 22 | open fun get(): Any { 23 | return value 24 | } 25 | 26 | fun getKClass(): KClass<*> { 27 | return type 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/config/DictionaryConfigOption.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.config 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.full.cast 5 | 6 | class DictionaryConfigOption>(private val valType: KClass, mapType: KClass, value: T, key: String, comment: String): ConfigOption(mapType, value, key, comment) { 7 | override fun set(any: Any?) { 8 | if (any is MutableMap<*, *>) { 9 | for (element in any.keys) { 10 | if (element !is String) { 11 | return 12 | } 13 | } 14 | 15 | for (element in any.values) { 16 | if (!valType.isInstance(element)) { 17 | return 18 | } 19 | } 20 | 21 | value.clear() 22 | type.cast(any).forEach { value[it.key] = it.value } 23 | } 24 | } 25 | 26 | fun getValueKClass(): KClass { 27 | return valType 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/config/RangeConfigOption.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.config 2 | 3 | import kotlin.reflect.KClass 4 | import kotlin.reflect.KProperty 5 | import kotlin.reflect.full.cast 6 | 7 | class RangeConfigOption(type: KClass, value: T, key: String, comment: String, private val range: ClosedRange): ConfigOption(type, value, key, comment) where T : Any, T: Comparable { 8 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 9 | if (range.contains(value)) { 10 | this.value = value 11 | } 12 | else { 13 | throw Exception() 14 | } 15 | } 16 | 17 | override fun set(any: Any?) { 18 | if (type.isInstance(any)) { 19 | val casted = type.cast(any) 20 | if (range.contains(casted)) { 21 | value = casted 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/util/Functions.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.util 2 | 3 | import com.google.gson.JsonPrimitive 4 | import io.github.redstoneparadox.paradoxconfig.ParadoxConfig 5 | import java.lang.reflect.Field 6 | 7 | fun T?.unwrap(exception: Exception): T { 8 | if (this == null) throw exception 9 | return this 10 | } 11 | 12 | fun > L.toImmutable(): List { 13 | return List(size) {this[it]} 14 | } 15 | 16 | fun T?.ifNull(t: T): T? { 17 | if (this == null) return t 18 | return this 19 | } 20 | 21 | fun JsonPrimitive.getValue(): Any? { 22 | return try { 23 | val valueField: Field = this.javaClass.getDeclaredField("value") 24 | 25 | valueField.isAccessible = true 26 | valueField[this] 27 | } catch (e: Exception) { 28 | ParadoxConfig.error("Encountered exception when attempting to unwrap GSON primitive!") 29 | e.printStackTrace() 30 | 31 | null 32 | } 33 | } 34 | 35 | val JsonPrimitive.asAny: Any? 36 | get() { 37 | return try { 38 | val valueField: Field = this.javaClass.getDeclaredField("value") 39 | 40 | valueField.isAccessible = true 41 | valueField[this] 42 | } catch (e: Exception) { 43 | ParadoxConfig.error("Encountered exception when attempting to unwrap GSON primitive!") 44 | e.printStackTrace() 45 | 46 | null 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/io/github/redstoneparadox/paradoxconfig/util/Operators.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig.util 2 | 3 | /** 4 | * Created by RedstoneParadox on 11/10/2019. 5 | */ 6 | 7 | internal operator fun Number.compareTo(other: Number): Int { 8 | return when (this) { 9 | is Byte -> this.compareTo(other.toLong()) 10 | is Short -> this.compareTo(other.toLong()) 11 | is Int -> this.compareTo(other.toLong()) 12 | is Long -> this.compareTo(other.toLong()) 13 | is Float -> this.compareTo(other.toFloat()) 14 | is Double -> this.compareTo(other.toDouble()) 15 | else -> throw Exception("Something strange happened!") 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/resources/assets/paradoxconfig/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedstoneParadox/ParadoxConfig/d1560efa232d700a232e605377294131df449e36/src/main/resources/assets/paradoxconfig/icon.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "paradoxconfig", 4 | "version": "${version}", 5 | 6 | "name": "Paradox Config", 7 | "description": "A Kotlin-based configuration api for Minecraft mods.", 8 | "authors": [ 9 | "RedstoneParadox" 10 | ], 11 | "contact": { 12 | "homepage": "https://fabricmc.net/", 13 | "sources": "https://github.com/RedstoneParadox/paradox-config" 14 | }, 15 | 16 | "license": "MIT", 17 | "icon": "assets/paradoxconfig/icon.png", 18 | 19 | "environment": "*", 20 | "entrypoints": { 21 | "preLaunch": [ 22 | { 23 | "adapter": "kotlin", 24 | "value": "io.github.redstoneparadox.paradoxconfig.ParadoxConfig" 25 | } 26 | ], 27 | "libcd": [ 28 | { 29 | "adapter": "kotlin", 30 | "value": "io.github.redstoneparadox.paradoxconfig.compat.ParadoxConfigLibCD" 31 | } 32 | ] 33 | }, 34 | "mixins": [ 35 | "paradoxconfig.mixins.json" 36 | ], 37 | "depends": { 38 | "fabricloader": ">=0.4.0", 39 | "fabric-language-kotlin": ">=1.3.71" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/paradoxconfig.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "io.github.goconfigure.paradoxconfig.mixin", 4 | "compatibilityLevel": "JAVA_8", 5 | "mixins": [ 6 | ], 7 | "client": [ 8 | ], 9 | "server": [ 10 | ], 11 | "injectors": { 12 | "defaultRequire": 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/testmod/java/io/github/redstoneparadox/paradoxconfig/GsonTestConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 4 | import net.minecraft.util.Identifier 5 | 6 | object GsonTestConfig: ConfigCategory("gson_test.json") { 7 | val boolean by option(true, "boolean", "A boolean value.") 8 | val double by option(2.5, "double", "A double value.") 9 | val string by option("Hi", "string", "A string value.") 10 | val rangedDouble by option(2.0, 1.0..4.0, "ranged_double", "A ranged double.") 11 | val identifier by option(Identifier("minecraft:empty"), "identifier") 12 | 13 | object Greetings: ConfigCategory("greetings") { 14 | val english by option(mutableListOf("Hi", "hey"), "english", "Greetings in English.") 15 | val mapping by option(mutableMapOf("Hi" to "Hola"), "mapping", "Maps English greetings to Spanish ones.") 16 | } 17 | } -------------------------------------------------------------------------------- /src/testmod/java/io/github/redstoneparadox/paradoxconfig/JanksonTestConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 4 | import net.minecraft.util.Identifier 5 | 6 | @Suppress("unused") 7 | object JanksonTestConfig: ConfigCategory("jankson_test.json5") { 8 | val boolean by option(true, "boolean", "A boolean value.") 9 | val double by option(2.5, "double", "A double value.") 10 | val string by option("Hi", "string", "A string value.") 11 | val rangedDouble by option(2.0, 1.0..4.0, "ranged_double", "A ranged double.") 12 | val identifier by option(Identifier("minecraft:empty"), "identifier") 13 | 14 | object Greetings: ConfigCategory("greetings") { 15 | val english by option(mutableListOf("Hi", "hey"), "english", "Greetings in English.") 16 | val mapping by option(mutableMapOf("Hi" to "Hola"), "mapping", "Maps English greetings to Spanish ones.") 17 | } 18 | } -------------------------------------------------------------------------------- /src/testmod/java/io/github/redstoneparadox/paradoxconfig/ParadoxConfigTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import net.fabricmc.api.ModInitializer 4 | 5 | object ParadoxConfigTest: ModInitializer { 6 | override fun onInitialize() { 7 | println() 8 | 9 | println("Jankson Test Config:") 10 | println("boolean: ${JanksonTestConfig.boolean}") 11 | println("double: ${JanksonTestConfig.double}") 12 | println("string: ${JanksonTestConfig.string}") 13 | println("rangedDouble: ${JanksonTestConfig.rangedDouble}") 14 | println("identifier: ${JanksonTestConfig.identifier}") 15 | println("greetings.english: ${JanksonTestConfig.Greetings.english}") 16 | println("greetings.mapping: ${JanksonTestConfig.Greetings.mapping}") 17 | println() 18 | 19 | println("Gson Test Config:") 20 | println("boolean: ${GsonTestConfig.boolean}") 21 | println("double: ${GsonTestConfig.double}") 22 | println("string: ${GsonTestConfig.string}") 23 | println("rangedDouble: ${GsonTestConfig.rangedDouble}") 24 | println("identifier: ${GsonTestConfig.identifier}") 25 | println("greetings.english: ${GsonTestConfig.Greetings.english}") 26 | println("greetings.mapping: ${GsonTestConfig.Greetings.mapping}") 27 | println() 28 | 29 | println("Toml Test Config:") 30 | println("boolean: ${TomlTestConfig.boolean}") 31 | println("double: ${TomlTestConfig.double}") 32 | println("string: ${TomlTestConfig.string}") 33 | println("rangedDouble: ${TomlTestConfig.rangedDouble}") 34 | println("identifier: ${TomlTestConfig.identifier}") 35 | println("greetings.english: ${TomlTestConfig.Greetings.english}") 36 | println("greetings.mapping: ${TomlTestConfig.Greetings.mapping}") 37 | println("greetings.subsubcategory.foo: ${TomlTestConfig.Greetings.SubSubCategory.foo}") 38 | println() 39 | } 40 | } -------------------------------------------------------------------------------- /src/testmod/java/io/github/redstoneparadox/paradoxconfig/TomlTestConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.redstoneparadox.paradoxconfig 2 | 3 | import io.github.redstoneparadox.paradoxconfig.config.ConfigCategory 4 | import net.minecraft.util.Identifier 5 | 6 | object TomlTestConfig: ConfigCategory("toml_test.toml") { 7 | val boolean by option(true, "boolean", "A boolean value.") 8 | val double by option(2.5, "double", "A double value.") 9 | val string by option("Hi", "string", "A string value.") 10 | val rangedDouble by option(2.0, 1.0..4.0, "ranged_double", "A ranged double.") 11 | val identifier by option(Identifier("minecraft:empty"), "identifier") 12 | val map by option(mutableMapOf("any" to Any()), "map", "A map of any.") 13 | 14 | object Greetings: ConfigCategory("greetings") { 15 | val english by option(mutableListOf("Hi", "hey"), "english", "Greetings in English.") 16 | val mapping by option(mutableMapOf("Hi" to "Hola"), "mapping", "Maps English greetings to Spanish ones.") 17 | 18 | object SubSubCategory: ConfigCategory("subsubcategory", "Checking how toml4j handles these") { 19 | val foo by option("bar", "foo", "") 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/testmod/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "paradoxconfig_test", 4 | "version": "${version}", 5 | 6 | "name": "Paradox Config Test Mod", 7 | "description": "Test Mod for Paradox Config.", 8 | "authors": [ 9 | "RedstoneParadox" 10 | ], 11 | "contact": { 12 | "homepage": "https://fabricmc.net/", 13 | "sources": "https://github.com/RedstoneParadox/ParadoxConfig" 14 | }, 15 | 16 | "license": "MIT", 17 | "icon": "assets/paradoxconfig/icon.png", 18 | 19 | "environment": "*", 20 | "entrypoints": { 21 | "main": [ 22 | { 23 | "adapter": "kotlin", 24 | "value": "io.github.redstoneparadox.paradoxconfig.ParadoxConfigTest" 25 | } 26 | ] 27 | }, 28 | "depends": { 29 | "fabricloader": ">=0.4.0", 30 | "fabric-language-kotlin": ">=1.3.71" 31 | }, 32 | 33 | "custom": { 34 | "paradoxconfig": { 35 | "package": "io.github.redstoneparadox.paradoxconfig", 36 | "configs": [ 37 | "GsonTestConfig", 38 | "JanksonTestConfig", 39 | "TomlTestConfig" 40 | ] 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------