Skip to content
Open
14 changes: 12 additions & 2 deletions docs/src/guide/essentials/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ configuration item in the configuration file.

The `comment` attribute is used to specify the comment of this configuration item.

As of v6.3.0, a `comment` that is exactly one language key, such as `comment = "{config.limit}"`, is resolved from your module's language catalogue (`lang/en.json`, `lang/zh.json`, or the `.yml` equivalents) in the server's current `language`, so one entry can ship its comment in every language the module supports. The framework writes that text on every write of the file, keys the operator already has included: the first-boot defaults write, a save, the shutdown save, a panel write, and the first start after an upgrade or a `language` switch. An operator's hand-written comment on such an entry is replaced, values keep their meaning, and a start whose comments already match writes nothing. The rewrite changes more than the comment lines: like every framework write, it renders the whole file again, so quotes the operator added are dropped, an inline list `[a, b]` becomes a multi-line list, `yes` becomes `true`, `1.50` becomes `1.5`, and a comment written beside a list item is lost. Values keep their meaning. This happens once on the first start after an upgrade and once after a `language` switch. A catalogue text with line breaks becomes several comment lines. A key missing from the catalogue is written as the token itself, with one warning naming the module, file, entry and key. Any other comment, including one that only contains a placeholder such as `{player}` inside other text, is written as it is, and only when the key is first added.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile the quote rewrite description

When the file contains a double-quoted string, this sentence says the operator-added quotes are dropped, but the unchanged saving section at line 190 says SnakeYAML re-emits the value with single quotes. Those descriptions are mutually inconsistent and give operators conflicting expectations about the whole-file diff caused by translating a comment; clarify whether quoting is removed or merely changed.

Useful? React with 👍 / 👎.


The `parser` attribute is used to specify the parser of this configuration item. The parser is used to convert the
object in the configuration file to the type of the configuration item. The default parser is `DefaultConfigParser`
, it can handle most of the case but not all. If you need to parse a more complex object, you can create a class that
Expand All @@ -90,7 +92,15 @@ The snippet below only illustrates its logic, import the framework class shown a

YAML stores a whole number such as `1800` as an integer. As of v6.3.0, a boxed `Long`, `Float` or `Double` field loads such a value, the same way a primitive `long`, `float` or `double` field does. Earlier versions could not set an integer into a boxed field of another numeric type, so such a field loaded on the first boot, when its default was written, and failed on every later boot and reload. Only widening conversions are applied, so a boxed field accepts exactly what its primitive type accepts.

A decimal such as `0.5` is read as a `Double`, and narrowing it into a `float` or `Float` field is not supported ([#534](https://github.com/UltiKits/UltiTools-Reborn/issues/534)). Use `double` or `Double` for a value that may contain a decimal point.
A decimal such as `0.5` is read as a `Double`. As of v6.3.0, a `float` or `Float` field (or a `Float` list element) accepts it when the float nearest to it prints back as the same decimal, so `0.1`, `0.3` and `1.5` load, and a float the framework wrote always reads back ([#534](https://github.com/UltiKits/UltiTools-Reborn/issues/534)). A value with more digits than a float holds, such as `0.123456789`, keeps the default with a warning; use `double` or `Double` when you need that precision.

#### Collections, maps and wrongly shaped values

As of v6.3.0, a value is bound to the type its field declares. A `List<Integer>` receives `Integer`s, and a `Set`, `Long`, `Double`, `Boolean` or enum element type converts the same way; map keys and values are converted to the map's declared types, and a map whose values are your own class still receives the raw maps, as before. A quoted number that an earlier version wrote into the file, such as `'30'`, loads as the number. An element that cannot be converted, such as `abc` in a `List<Integer>`, is skipped with one warning naming the file, the key with the element's position, the value and the declared type, and the rest of the configuration loads; an empty list item is skipped the same way. `List`, `Set`, `SortedSet`, `Queue`, `EnumSet`, `Map`, `SortedMap`, `ConcurrentMap` and `EnumMap` fields are all supported, and a custom `parser`, including one that extends `DefaultConfigParser`, reads and writes exactly as before. Earlier versions bound every list element as its text, so a typed lookup such as `contains(30)` never matched. When a map or an object holds a `null` value, `save()` now leaves that value out of the file and logs one warning naming the file, the entry and the value's nested path; earlier versions stopped the save with a `NullPointerException` and left the file unchanged.

A value whose shape does not fit its field, such as a list or a plain value where a `Map` is declared, text in a number field, or a date such as `2024-01-01` in a text field, leaves the field at its declared default and logs one warning naming the file, the key, the declared type and what the file holds. The module still loads, and the operator's file is not rewritten. Earlier versions threw from the configuration load and the module did not start. A warning never prints a value when a key in it, or its own key, suggests a secret, such as `password` or `token`.

**Do not put a dot in a map key.** The configuration file uses `.` as its path separator, so in a map the file stores as a section - a map field, a map nested in one, or a map inside an object stored in one - a key such as `my.rule` is split into nested levels (`my` → `rule`) when it is written and when the file is loaded, and quoting it in YAML (`"my.rule":`) does not help. This is unchanged from earlier versions, and the framework neither refuses such a key nor warns about it, so use `-` or `_` in these keys, both in your declared defaults and in keys your module builds at run time, and tell operators the same. A map that is an element of a list is plain data the file keeps whole, so a dotted key there is kept.

As of v6.3.0, an `int`, `long`, `Integer` or `Long` field can also drive a task interval or a command cooldown: see [Config-Bound Timing](/guide/advanced/scheduled-tasks#config-bound-timing) for `@Scheduled` and [Binding the cooldown to a config key](/guide/essentials/cmd-executor#binding-the-cooldown-to-a-config-key) for `@CmdCD`. The config class must be registered exactly once for the module, so a directory `@ConfigEntity` cannot be bound. Do not also put a [`@Range`](/guide/advanced/config-validation) on a bound field: the binding enforces its own range, and a `@Range` violation during `/ul reload` aborts the rest of the module's reload ([#509](https://github.com/UltiKits/UltiTools-Reborn/issues/509)).

Expand Down Expand Up @@ -174,7 +184,7 @@ You don't need to worry about the loading and saving of configuration files, Ult
automatically.

::: info Comments, as of v6.3.0
Bukkit preserves existing comments across a save, and UltiTools sets `options().parseComments(true)` explicitly rather than relying on the default. A key added for the first time also gets its `@ConfigEntry(comment)` written alongside it; a key the operator already has is left untouched.
Bukkit preserves existing comments across a save, and UltiTools sets `options().parseComments(true)` explicitly rather than relying on the default. A key added for the first time also gets its `@ConfigEntry(comment)` written alongside it; a key the operator already has keeps its own comment unless that comment is a language key (see `@ConfigEntry` above).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the exception depend on the annotation comment

When a key already exists, replacement is controlled by whether its declared @ConfigEntry(comment) is a language key, not whether the operator's existing comment is itself a language key. As written, a reader can infer that an ordinary hand-written comment remains untouched even when the annotation contains {config.limit}, contradicting the behavior documented above; name the annotation comment explicitly here.

Useful? React with 👍 / 👎.

:::

One cosmetic side effect: SnakeYAML re-emits a double-quoted string value as single-quoted on save. The value itself does not change, only its quoting style. As of v6.3.0 this happens only when a file is actually saved: a configuration nothing changed is not rewritten on disable.
Expand Down
14 changes: 12 additions & 2 deletions docs/src/zh/guide/essentials/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ TestConfig config = BasicFunctions.getInstance().getConfig("test/test1.yml", Tes

`comment` 属性用于指定该配置项的注释;

自 v6.3.0 起,如果 `comment` 恰好是一个语言键,例如 `comment = "{config.limit}"`,框架会按服务器当前的 `language` 从模块的语言文件(`lang/en.json`、`lang/zh.json` 或对应的 `.yml`)中取出文字作为注释,这样同一个配置项就能以模块支持的每种语言提供注释。框架每次写这个配置文件时都会写入这段文字,包括服主文件里已有的键:首次启动写入默认值、保存、关服保存、面板写入,以及升级后或切换 `language` 后的第一次启动。服主在这类配置项上手写的注释会被替换,设置值的含义不变,注释已经一致时启动不会写文件。重写改动的不只是注释行:与框架的每一次写文件一样,整个文件会重新输出,服主加的引号会被去掉,行内列表 `[a, b]` 变成多行,`yes` 变成 `true`,`1.50` 变成 `1.5`,写在列表项旁边的注释会丢失;值的含义不变。升级后第一次启动和切换 `language` 后各发生一次。语言文件里带换行的文字会写成多行注释。语言文件里没有这个键时,写入的就是这个键本身,并记一条警告,写明模块、文件、配置项和键名。其他注释(包括只是在文字中含有 `{player}` 这类占位符的注释)按原样写入,并且只在该键首次加入文件时写入。

`parser` 属性用于指定该配置项的解析器。解析器用于将配置文件中的对象转换为配置项的类型。默认的解析器是 `DefaultConfigParser` ,
它可以处理大多数情况,但并不是所有情况。如果你需要解析一个更复杂的对象,你可以创建一个继承 `ConfigParser` 类的类,并在 `parser` 属性中指定它。

Expand All @@ -76,7 +78,15 @@ TestConfig config = BasicFunctions.getInstance().getConfig("test/test1.yml", Tes

YAML 会把 `1800` 这样的整数读成整数类型。自 v6.3.0 起,包装类型 `Long`、`Float`、`Double` 的字段也能载入这样的值,与基本类型 `long`、`float`、`double` 的字段一致。在此之前,整数无法直接写入其他数值类型的包装类字段,因此这类字段只在首次启动(写入默认值时)能正常载入,之后每次启动和重载都会失败。框架只做拓宽转换,所以包装类型字段能接受的值与其对应的基本类型完全相同。

`0.5` 这样的小数会被读成 `Double`,目前不支持把它收窄写入 `float` 或 `Float` 字段([#534](https://github.com/UltiKits/UltiTools-Reborn/issues/534))。可能包含小数的值请使用 `double` 或 `Double`。
`0.5` 这样的小数会被读成 `Double`。自 v6.3.0 起,`float` 或 `Float` 字段(以及 `Float` 列表元素)在小数按 float 读回来一样时接受它,所以 `0.1`、`0.3`、`1.5` 都能载入,框架自己写出的 float 也总能读回([#534](https://github.com/UltiKits/UltiTools-Reborn/issues/534))。位数超过 float 能保存的值(例如 `0.123456789`)保留默认值并记一条警告;需要这种精度时请使用 `double` 或 `Double`。

#### 集合、映射与形状不对的值

自 v6.3.0 起,配置值按字段声明的类型绑定。`List<Integer>` 拿到的是 `Integer`,元素类型为 `Set`、`Long`、`Double`、`Boolean` 或枚举时同样会转换;映射的键和值会转换成映射声明的类型,值类型是你自己的类时仍然拿到原始的映射,与以前一致。旧版本写进文件的带引号数字(例如 `'30'`)会作为数字载入。无法转换的元素(例如 `List<Integer>` 里的 `abc`)会被跳过,并记一条警告,写明文件、带元素位置的键、原值和声明类型,其余配置照常加载;空的列表项也同样跳过。`List`、`Set`、`SortedSet`、`Queue`、`EnumSet`、`Map`、`SortedMap`、`ConcurrentMap` 和 `EnumMap` 字段都支持,自定义 `parser`(包括继承 `DefaultConfigParser` 的)的读写与以前完全一致。在此之前,列表的每个元素都按文字绑定,因此 `contains(30)` 这类按类型查找永远匹配不上。映射或对象里有 `null` 值时,`save()` 现在不写这个值,并记一条警告,写明文件、配置项和它的嵌套路径;旧版本遇到它会抛 `NullPointerException` 中止保存,文件保持不变。

值的形状与字段不符时(例如声明为 `Map` 的地方写成了列表或单个值,数字字段里写了文字,或文字字段里写了 `2024-01-01` 这样的日期),字段保持声明的默认值,并记一条警告,写明文件、键、声明类型和文件里实际的内容。模块照常加载,服主的文件也不会被改写。在此之前,配置加载会抛出异常,模块无法启动。值本身的键名或其中任何一个键名像密钥时(例如 `password`、`token`),警告不会打印原值。

**映射的键里不要用点**。配置文件用 `.` 作路径分隔符,在文件以配置节保存的映射里(映射字段、嵌套在其中的映射、存在其中的对象里的映射),`my.rule` 这样的键在写入和读取时都会被拆成嵌套的几层(`my` → `rule`),在 YAML 里给键加引号(`"my.rule":`)也没用。这与旧版本相同,框架既不拒绝也不警告,所以这类键请改用 `-` 或 `_`,声明的默认值和模块运行时生成的键都一样,也请这样告诉服主。列表元素里的映射是普通数据,文件能原样保存,键里的点会保留。

自 v6.3.0 起,`int`、`long`、`Integer` 或 `Long` 类型的字段还可以用来控制任务间隔或命令冷却:`@Scheduled` 见[绑定到配置项的时间](/zh/guide/advanced/scheduled-tasks#绑定到配置项的时间),`@CmdCD` 见[配置项绑定的冷却时间](/zh/guide/essentials/cmd-executor#配置项绑定的冷却时间)。该配置类必须为模块恰好注册一次,因此指向目录的 `@ConfigEntity` 不能用于绑定。被绑定的字段不要再加 [`@Range`](/zh/guide/advanced/config-validation):绑定自带范围检查,而 `/ul reload` 期间违反 `@Range` 会中止该模块其余的重载步骤([#509](https://github.com/UltiKits/UltiTools-Reborn/issues/509))。

Expand Down Expand Up @@ -158,7 +168,7 @@ public List<AbstractConfigEntity> getAllConfigs() {
你无需担心配置文件的加载与保存等问题,UltiTools会自动为你做好一切。

::: info 注释(v6.3.0 起)
Bukkit 在保存时会保留已有注释,UltiTools 显式设置了 `options().parseComments(true)`,不依赖默认值。首次新增的键也会连同其 `@ConfigEntry(comment)` 一并写入;服主已有的键则不会被改动。
Bukkit 在保存时会保留已有注释,UltiTools 显式设置了 `options().parseComments(true)`,不依赖默认值。首次新增的键也会连同其 `@ConfigEntry(comment)` 一并写入;服主已有的键保留自己的注释,除非该注释是一个语言键(见上文 `@ConfigEntry`)。
:::

一个纯粹外观上的副作用:SnakeYAML 保存时会把双引号字符串值重新写成单引号,值本身不变,只是引号风格变化。自 v6.3.0 起,这只在文件确实被保存时发生:没有任何改动的配置在插件关闭时不会被重写。
Expand Down
Loading