在 RIME 中使用 Yaml 作为配置文件格式,根据 Yaml 语言的特点,在 RIME 中,将配置项的值分为三类:
- 基础数据类型:int,double,string,bool。
- 列表(List)类型:可以理解为数组。
- 映射(Map)类型:可以理解为对象类型,即 key-value 类型。
举个几个例子:
配置项键盘高度,假设它的配置项叫:
keyboardHeight,那么这个项对应的值可以用 int 或 double 来表示,如:keyboardHeight: 54。输入方案列表,它的配置项为
schema_list,因为这个配置项的值是个列表,所需要用数组表示,如:schema_list: - schema: rime_ice - schema: flypy配色方案,因为一个配色方案是由多个选项构成,所以这个配置项的值是个对象,即 key-value 类型,如:
solarized_light:
schemaName: solarized_light
name: 曬經・日/Solarized Light
author: 雪齋 <lyc20041@gmail.com>/Morse <morse.hsiao@gmail.com>
back_color: 0xF0E5F6FB # 键盘背景色
button_back_color: 0xF0E5F6FB # 按键背景色
button_pressed_back_color: 0xD7E8ED # 按下时按键背景色
在 RIME 的代码中分别使用:ConfigItem,ConfigValue, ConfigList, ConfigMap 来表示上述的值模型。

ConfigItem是一个抽象类,用来表示 Yaml 配置文件中的任意配置项的值。ConfigValue用来表示「基础数据类型」的值。ConfigList用来表示列表(List)类型的值。ConfigMap用来表示映射(Map)类型的值。
以上类型的声明代码在 src/rime/config/config_types.h 中。
有了上面的基础,那么就有两个问题:
- RIME 中如何读取一个 Yaml 文件。
- RIME 中如何编译一个 Yaml 文件。
RIME 中如何读取一个 Yaml 文件?
我们先下面一段 Example 代码:
void readDefaultConfig() {
path defaultConfigFilePath;
defaultConfigFilePath /= "/Users/morse/Downloads/default.yaml";
the<Config> config(new Config());
config->loadFromFile(defaultConfigFilePath);
string config_version;
if (config->getString("config_version", &config_version)) {
std::cout << "config version: " << config_version << std::endl;
} else {
std::cout << "read config file error" << std::endl;
}
int pageSize;
if (config->getInt("menu/page_size", &pageSize)) {
std::cout << "menu page_size: " << pageSize << std::endl;
} else {
std::cout << "read menu/page_size error" << std::endl;
}
if (auto keyBindings =
as<ConfigList>(config->getItem("key_binder/bindings"))) {
std::for_each(
keyBindings->begin(), keyBindings->end(), [](an<ConfigItem> item) {
if (an<ConfigMap> map = std::dynamic_pointer_cast<ConfigMap>(item)) {
string accept;
if (map->getValue("accept")->getString(&accept)) {
std::cout << "binding accept: " << accept << std::endl;
}
}
});
}
}
代码中读取固定路径 /Users/morse/Downloads/default.yaml 的 Yaml 文件,在读取后就可以获取每个配置项的值了。
在示例中,分别获取了 string, int, 列表,map 四种类型的值。
从上面代码可以看出,读取 Yaml 文件主要是通过 Config 类来实现的。

还记得系列中之前介绍的「组件的注册与发现」吗,Config 实现了模板类 Class, 所以在 RIME 可以通过注册与发现来获取 Config 实例,进而可以读取 Yaml 文件。
RIME 中如何编译一个 Yaml 文件?
我们知道 RIME 中对 Yaml 文件格式做了扩展,支持 __include 等指令,方便对 Yaml 进行模块化,补丁化等处理。
那么这些指令是如何实现的呢?其实这里就是对 Yaml 文件一个编译的过程:将这些扩展指令编译为正确的,符合 Yaml 语法规范的内容,然后在 build 目录下生成编译好的 yaml 文件。
我们看个示例, 我们有三个 yaml 文件:hamster_color_schemas.yaml,hamster_keyboards.yaml,hamster_swipe.yaml,hamster.yaml
hamster_color_schemas.yaml定义配色相关内容。hamster_keyboards.yaml定义键盘布局相关内容。hamster_swipe.yaml定义按键划动相关内容。hamster.yaml:全部配置内容,并使用__include指令将hamster_color_schemas.yaml,hamster_keyboards.yaml,hamster_swipe.yaml中的内容导入进来。
我们看代码如何编译这个过程:
void buildConfig() {
an<ConfigComponent<ConfigBuilder>> configBuilder(new ConfigComponent<ConfigBuilder>([&](ConfigBuilder* builder) {
builder->installPlugin(new AutoPatchConfigPlugin);
builder->installPlugin(new DefaultConfigPlugin);
builder->installPlugin(new LegacyPresetConfigPlugin);
builder->installPlugin(new LegacyDictionaryConfigPlugin);
builder->installPlugin(new BuildInfoPlugin);
builder->installPlugin(new SaveOutputPlugin);
}));
Config* defaultConfig = configBuilder->create("hamster");
string value = defaultConfig->getValue("keyboard/useKeyboardType")->str();
printf("value: %s \n", value.c_str());
}

ConfigComponent 是模板类,实现了基类 ConfigComponentBase 中 an<ConfigData> loadConfig(const string& config_id) 虚方法。
同时在方法的实现中会强制调用它的实例化类的 an<ConfigData> loadConfig(ResourceResolver* resource_resolver, const string& config_id) 方法。
而在 ConfigBuilder 中的 loadConfig() 方法会依次调用自身的 plugins_,即上面示例中 new 的各种 plugin。而这些 plugin 的实现中,完成了如 .custom.yaml 文件中 patch 解析,编译生成 yaml 中的 __build_info 内容,保存文件等等。
而向类似 __include 这种指令的解析,是在 loadConfig() 方法中调用了 ConfigCompiler 类的 compile() 方法实现的。
总结
- RIME 中有关配置文件的解析,编译等代码逻辑在
src/rime/config中。 - RIME 中默认的配置文件注册代码在
src/rime/core_module.cc中。

