在 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
中。