Configuration
Package dev.versatia.util.config. A value a plugin only has to hold needs none
of this: mark the property @Value and the runtime reads it with these
getters.
Strict getters
Bukkit's own getters are lenient: getInt returns 0 for a missing key and for a value that is
not a number, so a typo in a config file turns into a silent default. The extension functions on
ConfigurationSection separate the two cases:
| Family | Key absent | Value present but invalid |
|---|---|---|
getXOrNull(key) | null | throws ConfigurationValueException |
getXOrThrow(key, msg?) | throws | throws |
ifXPresent(key) { } | action not run | throws |
getX(key, default) | default | throws |
isX(key) | false | false |
getXListOrNull(key) | null | throws for a single value or an invalid element, naming its index |
getXListOrThrow(key, msg?) | throws | the same |
getXList(key) | empty list | the same |
val size = config.getIntOrThrow("chest.size")
val limit = config.getIntOrNull("chest.limit") ?: 54
val chance = config.getPercent("drop.chance", Percent.of(50.0))
config.ifStringPresent("motd") { VersatiaEngine.broadcast(Component.text(it)) }
val hours = config.getIntListOrThrow("shop.hours")
val slots = config.getUIntList("slots")
Which families exist for which X:
X | Families |
|---|---|
Boolean, Int, Long, Float, Double, String | getXOrNull, getXOrThrow, ifXPresent, getXListOrNull, getXListOrThrow |
UInt, ULong, Percent, Duration, Secret, any enum | all single-value families, and getXList |
UByte, UShort | getXList |
Color, ItemStack, Location, Vector, OfflinePlayer, ConfigurationSection | getXOrThrow, ifXPresent |
Enums are read with getEnumElementOrThrow<Material>("block"), or
getEnumElementOrThrow("block", enumClass) for an enum class known only at runtime.
For the types Bukkit reads itself, the lenient Bukkit member of the same name is what Kotlin calls:
config.getInt("size", 5) is Bukkit's getInt(String, int), which returns 5 for a value that
is not a number, and config.getIntegerList("hours") skips elements it cannot read. Members
always win over extensions, so no strict getX(key, default), isX or getXList exists for
those types. Write getIntOrNull("size") ?: 5 and getIntListOrThrow("hours") instead.
"Invalid" is type-specific and strict:
getIntOrNullaccepts only anInt. A YAML value such as3000000000loads asLongand is rejected; usegetLongOrNull.- Unsigned getters reject negative numbers instead of wrapping them.
getPercent*requires a trailing%("12.5%").getDuration*reads akotlin.time.Durationwritten in Kotlin's notation,30s,5m,1h 30m,500ms,2d, or in ISO-8601,PT5M. A bare number such as300is rejected, since it names no unit:Value at "period" is not a duration such as "30s", "5m" or "1h 30m" (units d, h, m, s, ms; must not be negative): "300". Negative and infinite durations (-5s,Infinity) are rejected with the same message.- Enum getters match the constant name ignoring case and list the allowed names in the error.
- String getters accept only text: an unquoted number, boolean or date (
01234,1.10,yes) is read by YAML as another type, so Bukkit'sgetStringreturns"668","1.1","true". The strict getters reject it and ask for quotes:Value at "code" is read as a number (668), not text; quote it, .... getSecret*reads aSecret: text, with every${env:NAME}replaced by the environment variable and every${file:PATH}by the file's content (a relative path starts at the server folder, an absolute one is taken as it is).${pluginfile:PATH}starts at the plugin's data folder, so it works in a file loaded withloadYamlOrThrow(file, plugin)(what@ValueandforEachYamlFileuse); in a file loaded otherwise it fails, naming the${file:plugins/<plugin>/PATH}to write instead. A variable that is not set, a file that cannot be read, any other${...}and a value YAML read as a number, a boolean or a date (an unquoted0123oryes) are rejected; no message shows the value.
Every ConfigurationValueException message contains the full dotted path of the key
(section.pathOf(key)), so the user can find the line in the file. The optional exceptionMessage
parameter of the OrThrow family replaces the default message for both failure modes.
Adding your own type
Build on the generic entry point:
fun ConfigurationSection.getMaterialOrThrow(key: String) =
getOrThrow(key, "a Material") { Material.matchMaterial(getString(it)!!) }
Other helpers
mapChildren { key, section -> }maps every child section.asStringsMap()maps the direct children of a section intoMap<String, String>; a child that is a section or a list throws.
YAML files
loadYamlOrThrow(file)propagates parse errors. Bukkit'sYamlConfiguration.loadConfigurationlogs them and returns an empty configuration, which hides broken files.Plugin.forEachYamlFile(relativePath) { path, config -> }andPlugin.mapYamlFileswalk a directory under the plugin's data folder recursively, in path order, and visit only files with a.ymlor.yamlextension (YAML_EXTENSIONS).