diff --git a/src/rime/config/auto_patch_config_plugin.cc b/src/rime/config/auto_patch_config_plugin.cc new file mode 100644 index 000000000..52fb9a90e --- /dev/null +++ b/src/rime/config/auto_patch_config_plugin.cc @@ -0,0 +1,42 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#include +#include +#include + +namespace rime { + +static string remove_suffix(const string& input, const string& suffix) { + return boost::ends_with(input, suffix) ? + input.substr(0, input.length() - suffix.length()) : input; +} + +// auto-patch applies to all loaded config resources, including dependencies. +// therefore it's done at the end of Compile phase. +bool AutoPatchConfigPlugin::ReviewCompileOutput(ConfigCompiler* compiler, + an resource) { + if (boost::ends_with(resource->resource_id, ".custom")) + return true; + // skip auto-patch if there is already an explicit `__patch` at the root node + auto root_deps = compiler->GetDependencies(resource->resource_id + ":"); + if (!root_deps.empty() && root_deps.back()->priority() >= kPatch) + return true; + auto patch_resource_id = + remove_suffix(resource->resource_id, ".schema") + ".custom"; + LOG(INFO) << "auto-patch " << resource->resource_id << ":/__patch: " + << patch_resource_id << ":/patch?"; + compiler->Push(resource); + compiler->AddDependency( + New(Reference{patch_resource_id, "patch", true})); + compiler->Pop(); + return true; +} + +bool AutoPatchConfigPlugin::ReviewLinkOutput(ConfigCompiler* compiler, + an resource) { + return true; +} + +} // namespace rime diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 5c0c28eca..33ba0754c 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -5,6 +5,7 @@ #include #include #include +#include namespace rime { @@ -243,8 +244,10 @@ void ConfigDependencyGraph::Add(an dependency) { } } -ConfigCompiler::ConfigCompiler(ResourceResolver* resource_resolver) +ConfigCompiler::ConfigCompiler(ResourceResolver* resource_resolver, + ConfigCompilerPlugin* plugin) : resource_resolver_(resource_resolver), + plugin_(plugin), graph_(new ConfigDependencyGraph) { } @@ -303,6 +306,8 @@ an ConfigCompiler::Compile(const string& file_name) { resource->loaded = resource->data->LoadFromFile( resource_resolver_->ResolvePath(resource_id).string(), this); Pop(); + if (plugin_) + plugin_->ReviewCompileOutput(this, resource); return resource; } @@ -483,7 +488,8 @@ bool ConfigCompiler::Link(an target) { LOG(ERROR) << "resource not found: " << target->resource_id; return false; } - return ResolveDependencies(found->first + ":"); + return ResolveDependencies(found->first + ":") && + (plugin_ ? plugin_->ReviewLinkOutput(this, target) : true); } bool ConfigCompiler::ResolveDependencies(const string& path) { diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index 2975d2fcb..2de4dd424 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -35,6 +35,7 @@ struct Reference { string repr() const; }; +class ConfigCompilerPlugin; class ResourceResolver; struct Dependency; struct ConfigDependencyGraph; @@ -46,7 +47,8 @@ class ConfigCompiler { static constexpr const char* APPEND_DIRECTIVE = "__append"; static constexpr const char* MERGE_DIRECTIVE = "__merge"; - explicit ConfigCompiler(ResourceResolver* resource_resolver); + ConfigCompiler(ResourceResolver* resource_resolver, + ConfigCompilerPlugin* plugin); virtual ~ConfigCompiler(); Reference CreateReference(const string& qualified_path); @@ -69,6 +71,7 @@ class ConfigCompiler { private: ResourceResolver* resource_resolver_; + ConfigCompilerPlugin* plugin_; the graph_; }; diff --git a/src/rime/config/config_component.cc b/src/rime/config/config_component.cc index e787aa04b..7b9387386 100644 --- a/src/rime/config/config_component.cc +++ b/src/rime/config/config_component.cc @@ -11,6 +11,7 @@ #include #include #include +#include namespace rime { @@ -160,12 +161,53 @@ Config* ConfigComponent::Create(const string& file_name) { return new Config(GetConfigData(file_name)); } +void ConfigComponent::InstallPlugin(ConfigCompilerPlugin* plugin) { + plugins_.push_back(the(plugin)); +} + +template +struct MultiplePlugins : ConfigCompilerPlugin { + Container& plugins; + + MultiplePlugins(Container& _plugins) + : plugins(_plugins) { + } + bool ReviewCompileOutput(ConfigCompiler* compiler, + an resource) override { + return ReviewedByAll(&ConfigCompilerPlugin::ReviewCompileOutput, + compiler, resource); + } + bool ReviewLinkOutput(ConfigCompiler* compiler, + an resource) override { + return ReviewedByAll(&ConfigCompilerPlugin::ReviewLinkOutput, + compiler, resource); + } + + typedef bool (ConfigCompilerPlugin::*Reviewer)(ConfigCompiler* compiler, + an resource); + bool ReviewedByAll(Reviewer reviewer, + ConfigCompiler* compiler, + an resource); +}; + +template +bool MultiplePlugins::ReviewedByAll(Reviewer reviewer, + ConfigCompiler* compiler, + an resource) { + for (const auto& plugin : plugins) { + if(!((*plugin).*reviewer)(compiler, resource)) + return false; + } + return true; +} + an ConfigComponent::GetConfigData(const string& file_name) { auto config_id = resource_resolver_->ToResourceId(file_name); // keep a weak reference to the shared config data in the component weak& wp(cache_[config_id]); if (wp.expired()) { // create a new copy and load it - ConfigCompiler compiler(resource_resolver_.get()); + MultiplePlugins multiple_plugins(plugins_); + ConfigCompiler compiler(resource_resolver_.get(), &multiple_plugins); auto resource = compiler.Compile(file_name); if (resource->loaded && !compiler.Link(resource)) { LOG(ERROR) << "error loading config from: " << file_name; diff --git a/src/rime/config/config_component.h b/src/rime/config/config_component.h index e60b333e4..1c458e099 100644 --- a/src/rime/config/config_component.h +++ b/src/rime/config/config_component.h @@ -63,18 +63,24 @@ class Config : public Class, public ConfigItemRef { void SetItem(an item); }; +class ConfigCompiler; +class ConfigCompilerPlugin; class ResourceResolver; +struct ConfigResource; class ConfigComponent : public Config::Component { public: ConfigComponent(); ~ConfigComponent(); Config* Create(const string& file_name); + void InstallPlugin(ConfigCompilerPlugin *plugin); + bool ApplyPlugins(ConfigCompiler* compiler, an resource); private: an GetConfigData(const string& file_name); map> cache_; the resource_resolver_; + vector> plugins_; }; } // namespace rime diff --git a/src/rime/config/legacy_dictionary_config_plugin.cc b/src/rime/config/legacy_dictionary_config_plugin.cc new file mode 100644 index 000000000..c7e8d4786 --- /dev/null +++ b/src/rime/config/legacy_dictionary_config_plugin.cc @@ -0,0 +1,22 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#include +#include + +namespace rime { + +bool LegacyDictionaryConfigPlugin::ReviewCompileOutput( + ConfigCompiler* compiler, an resource) { + // TODO: unimplemented + return true; +} + +bool LegacyDictionaryConfigPlugin::ReviewLinkOutput( + ConfigCompiler* compiler, an resource) { + // TODO: unimplemented + return true; +} + +} // namespace rime diff --git a/src/rime/config/legacy_preset_config_plugin.cc b/src/rime/config/legacy_preset_config_plugin.cc new file mode 100644 index 000000000..865e5ee7e --- /dev/null +++ b/src/rime/config/legacy_preset_config_plugin.cc @@ -0,0 +1,75 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#include +#include +#include +#include + +namespace rime { + +bool LegacyPresetConfigPlugin::ReviewCompileOutput( + ConfigCompiler* compiler, an resource) { + return true; +} + +bool LegacyPresetConfigPlugin::ReviewLinkOutput( + ConfigCompiler* compiler, an resource) { + if (!boost::ends_with(resource->resource_id, ".schema")) + return true; + if (auto preset = resource->data->Traverse("key_binder/import_preset")) { + if (!Is(preset)) + return false; + auto preset_config_id = As(preset)->str(); + LOG(INFO) << "interpreting key_binder/import_preset: " << preset_config_id; + auto target = Cow(resource, "key_binder"); + auto map = As(**target); + if (map && map->HasKey("bindings")) { + // append to included list `key_binder/bindings/+` instead of overwriting + auto appended = map->Get("bindings"); + *Cow(target, "bindings/+") = appended; + // `*target` is already referencing a copied map, safe to edit directly + (*target)["bindings"] = nullptr; + } + Reference reference{preset_config_id, "key_binder", false}; + if (!IncludeReference{reference} + .TargetedAt(target).Resolve(compiler)) { + LOG(ERROR) << "failed to include section " << reference; + return false; + } + } + // NOTE: in the following cases, Cow() is not strictly necessary because + // we know for sure that no other ConfigResource is going to reference the + // root map node that will be modified. But other than the root node of the + // resource being linked, it's possbile a map or list has multiple references + // in the node tree, therefore Cow() is recommended to make sure the + // modifications only happen to one place. + if (auto preset = resource->data->Traverse("punctuator/import_preset")) { + if (!Is(preset)) + return false; + auto preset_config_id = As(preset)->str(); + LOG(INFO) << "interpreting punctuator/import_preset: " << preset_config_id; + Reference reference{preset_config_id, "punctuator", false}; + if (!IncludeReference{reference} + .TargetedAt(Cow(resource, "punctuator")).Resolve(compiler)) { + LOG(ERROR) << "failed to include section " << reference; + return false; + } + } + if (auto preset = resource->data->Traverse("recognizer/import_preset")) { + if (!Is(preset)) + return false; + auto preset_config_id = As(preset)->str(); + LOG(INFO) << "interpreting recognizer/import_preset: " << preset_config_id; + Reference reference{preset_config_id, "recognizer", false}; + if (!IncludeReference{reference} + .TargetedAt(Cow(resource, "recognizer")).Resolve(compiler)) { + LOG(ERROR) << "failed to include section " << reference; + return false; + } + } + return true; +} + +} // namespace rime diff --git a/src/rime/config/plugins.h b/src/rime/config/plugins.h new file mode 100644 index 000000000..a52a83ab7 --- /dev/null +++ b/src/rime/config/plugins.h @@ -0,0 +1,44 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#ifndef RIME_CONFIG_PLUGINS_H_ +#define RIME_CONFIG_PLUGINS_H_ + +#include + +namespace rime { + +class ConfigCompiler; +struct ConfigResource; + +class ConfigCompilerPlugin { + public: + typedef bool Review(ConfigCompiler* compiler, + an resource); + + virtual Review ReviewCompileOutput = 0; + virtual Review ReviewLinkOutput = 0; +}; + +class AutoPatchConfigPlugin : public ConfigCompilerPlugin { + public: + Review ReviewCompileOutput; + Review ReviewLinkOutput; +}; + +class LegacyPresetConfigPlugin : public ConfigCompilerPlugin { + public: + Review ReviewCompileOutput; + Review ReviewLinkOutput; +}; + +class LegacyDictionaryConfigPlugin : public ConfigCompilerPlugin { + public: + Review ReviewCompileOutput; + Review ReviewLinkOutput; +}; + +} // namespace rime + +#endif // RIME_CONFIG_PLUGINS_H_ diff --git a/src/rime/core_module.cc b/src/rime/core_module.cc index f9545734b..2a30f95c1 100644 --- a/src/rime/core_module.cc +++ b/src/rime/core_module.cc @@ -11,6 +11,7 @@ // built-in components #include +#include #include using namespace rime; @@ -20,6 +21,9 @@ static void rime_core_initialize() { Registry& r = Registry::instance(); auto config = new ConfigComponent; + config->InstallPlugin(new AutoPatchConfigPlugin); + config->InstallPlugin(new LegacyPresetConfigPlugin); + config->InstallPlugin(new LegacyDictionaryConfigPlugin); r.Register("config", config); r.Register("schema", new SchemaComponent(config)); }