From 5a7f906efb7d12d71914b00767f1b7215f75df7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Tue, 6 Jun 2017 18:22:54 +0800 Subject: [PATCH 01/11] chore(Makefile): add target test-debug --- Makefile | 15 +++++++++------ Makefile.xcode | 3 +++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 407292b931..ac53009474 100644 --- a/Makefile +++ b/Makefile @@ -28,22 +28,25 @@ release: cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release cmake --build build -install: - cmake --build build --target install - -uninstall: - cmake --build build --target uninstall - debug: cmake . -Bdebug-build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug cmake --build debug-build +install: + cmake --build build --target install + install-debug: cmake --build debug-build --target install +uninstall: + cmake --build build --target uninstall + uninstall-debug: cmake --build debug-build --target uninstall test: release (cd build/test; ./rime_test) +test-debug: release + (cd build/test; ./rime_test) + diff --git a/Makefile.xcode b/Makefile.xcode index e33f9d52af..48786bc740 100644 --- a/Makefile.xcode +++ b/Makefile.xcode @@ -23,6 +23,9 @@ clean: test: release (cd xbuild/test; LD_LIBRARY_PATH=../lib/Release Release/rime_test) +test-debug: debug + (cd xdebug/test; Debug/rime_test) + thirdparty: $(RIME_COMPILER_OPTIONS) make -f Makefile.thirdparty From 8888b6da90be329efde2a584389617cbc82be352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Fri, 7 Apr 2017 00:51:36 +0800 Subject: [PATCH 02/11] refactor(config): split into multiple source files under rime/config --- src/CMakeLists.txt | 1 + src/rime/config.cc | 822 ------------------------- src/rime/config.h | 275 +-------- src/rime/config/config_component.cc | 151 +++++ src/rime/config/config_component.h | 81 +++ src/rime/config/config_data.cc | 306 +++++++++ src/rime/config/config_data.h | 48 ++ src/rime/config/config_data_manager.cc | 49 ++ src/rime/config/config_data_manager.h | 28 + src/rime/config/config_types.cc | 312 ++++++++++ src/rime/config/config_types.h | 215 +++++++ 11 files changed, 1193 insertions(+), 1095 deletions(-) delete mode 100644 src/rime/config.cc create mode 100644 src/rime/config/config_component.cc create mode 100644 src/rime/config/config_component.h create mode 100644 src/rime/config/config_data.cc create mode 100644 src/rime/config/config_data.h create mode 100644 src/rime/config/config_data_manager.cc create mode 100644 src/rime/config/config_data_manager.h create mode 100644 src/rime/config/config_types.cc create mode 100644 src/rime/config/config_types.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4675311d00..25dbee8af4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) aux_source_directory(. rime_src_api) aux_source_directory(rime rime_src_base) aux_source_directory(rime/algo rime_src_algo) +aux_source_directory(rime/config rime_src_config) aux_source_directory(rime/dict rime_src_dict) aux_source_directory(rime/gear rime_src_gear) aux_source_directory(rime/lever rime_src_lever) diff --git a/src/rime/config.cc b/src/rime/config.cc deleted file mode 100644 index f82d0522f5..0000000000 --- a/src/rime/config.cc +++ /dev/null @@ -1,822 +0,0 @@ -// -// Copyright RIME Developers -// Distributed under the BSD License -// -// 2011-04-06 Zou Xu -// -#include -#include -#include -#include -#include -#include -#include -#include - -namespace rime { - -class ConfigData { - public: - ConfigData() = default; - ~ConfigData(); - - bool LoadFromStream(std::istream& stream); - bool SaveToStream(std::ostream& stream); - bool LoadFromFile(const string& file_name); - bool SaveToFile(const string& file_name); - an Traverse(const string& key); - - bool modified() const { return modified_; } - void set_modified() { modified_ = true; } - - an root; - - protected: - static an ConvertFromYaml(const YAML::Node& yaml_node); - static void EmitYaml(an node, - YAML::Emitter* emitter, - int depth); - static void EmitScalar(const string& str_value, - YAML::Emitter* emitter); - - string file_name_; - bool modified_ = false; -}; - -class ConfigDataManager : public map> { - public: - an GetConfigData(const string& config_file_path); - bool ReloadConfigData(const string& config_file_path); - - static ConfigDataManager& instance(); - - private: - ConfigDataManager() = default; -}; - -// ConfigValue members - -ConfigValue::ConfigValue(bool value) - : ConfigItem(kScalar) { - SetBool(value); -} - -ConfigValue::ConfigValue(int value) - : ConfigItem(kScalar) { - SetInt(value); -} - -ConfigValue::ConfigValue(double value) - : ConfigItem(kScalar) { - SetDouble(value); -} - -ConfigValue::ConfigValue(const char* value) - : ConfigItem(kScalar), value_(value) { -} - -ConfigValue::ConfigValue(const string& value) - : ConfigItem(kScalar), value_(value) { -} - -bool ConfigValue::GetBool(bool* value) const { - if (!value || value_.empty()) - return false; - string bstr = value_; - boost::to_lower(bstr); - if ("true" == bstr) { - *value = true; - return true; - } - else if ("false" == bstr) { - *value = false; - return true; - } - else - return false; -} - -bool ConfigValue::GetInt(int* value) const { - if (!value || value_.empty()) - return false; - // try to parse hex number - if (boost::starts_with(value_, "0x")) { - char* p = NULL; - unsigned int hex = std::strtoul(value_.c_str(), &p, 16); - if (*p == '\0') { - *value = static_cast(hex); - return true; - } - } - // decimal - try { - *value = boost::lexical_cast(value_); - } - catch (...) { - return false; - } - return true; -} - -bool ConfigValue::GetDouble(double* value) const { - if (!value || value_.empty()) - return false; - try { - *value = boost::lexical_cast(value_); - } - catch (...) { - return false; - } - return true; -} - -bool ConfigValue::GetString(string* value) const { - if (!value) return false; - *value = value_; - return true; -} - -bool ConfigValue::SetBool(bool value) { - value_ = value ? "true" : "false"; - return true; -} - -bool ConfigValue::SetInt(int value) { - value_ = boost::lexical_cast(value); - return true; -} - -bool ConfigValue::SetDouble(double value) { - value_ = boost::lexical_cast(value); - return true; -} - -bool ConfigValue::SetString(const char* value) { - value_ = value; - return true; -} - -bool ConfigValue::SetString(const string& value) { - value_ = value; - return true; -} - -// ConfigList members - -an ConfigList::GetAt(size_t i) const { - if (i >= seq_.size()) - return nullptr; - else - return seq_[i]; -} - -an ConfigList::GetValueAt(size_t i) const { - return As(GetAt(i)); -} - -bool ConfigList::SetAt(size_t i, an element) { - if (i >= seq_.size()) - seq_.resize(i + 1); - seq_[i] = element; - return true; -} - -bool ConfigList::Insert(size_t i, an element) { - if (i > seq_.size()) { - seq_.resize(i); - } - seq_.insert(seq_.begin() + i, element); - return true; -} - -bool ConfigList::Append(an element) { - seq_.push_back(element); - return true; -} - -bool ConfigList::Resize(size_t size) { - seq_.resize(size); - return true; -} - -bool ConfigList::Clear() { - seq_.clear(); - return true; -} - -size_t ConfigList::size() const { - return seq_.size(); -} - -ConfigList::Iterator ConfigList::begin() { - return seq_.begin(); -} - -ConfigList::Iterator ConfigList::end() { - return seq_.end(); -} - -// ConfigMap members - -bool ConfigMap::HasKey(const string& key) const { - return bool(Get(key)); -} - -an ConfigMap::Get(const string& key) const { - auto it = map_.find(key); - if (it == map_.end()) - return nullptr; - else - return it->second; -} - -an ConfigMap::GetValue(const string& key) const { - return As(Get(key)); -} - -bool ConfigMap::Set(const string& key, an element) { - map_[key] = element; - return true; -} - -bool ConfigMap::Clear() { - map_.clear(); - return true; -} - -ConfigMap::Iterator ConfigMap::begin() { - return map_.begin(); -} - -ConfigMap::Iterator ConfigMap::end() { - return map_.end(); -} - -// ConfigItemRef members - -bool ConfigItemRef::IsNull() const { - auto item = GetItem(); - return !item || item->type() == ConfigItem::kNull; -} - -bool ConfigItemRef::IsValue() const { - auto item = GetItem(); - return item && item->type() == ConfigItem::kScalar; -} - -bool ConfigItemRef::IsList() const { - auto item = GetItem(); - return item && item->type() == ConfigItem::kList; -} - -bool ConfigItemRef::IsMap() const { - auto item = GetItem(); - return item && item->type() == ConfigItem::kMap; -} - -bool ConfigItemRef::ToBool() const { - bool value = false; - if (auto item = As(GetItem())) { - item->GetBool(&value); - } - return value; -} - -int ConfigItemRef::ToInt() const { - int value = 0; - if (auto item = As(GetItem())) { - item->GetInt(&value); - } - return value; -} - -double ConfigItemRef::ToDouble() const { - double value = 0.0; - if (auto item = As(GetItem())) { - item->GetDouble(&value); - } - return value; -} - -string ConfigItemRef::ToString() const { - string value; - if (auto item = As(GetItem())) { - item->GetString(&value); - } - return value; -} - -an ConfigItemRef::AsList() { - auto list = As(GetItem()); - if (!list) - SetItem(list = New()); - return list; -} - -an ConfigItemRef::AsMap() { - auto map = As(GetItem()); - if (!map) - SetItem(map = New()); - return map; -} - -void ConfigItemRef::Clear() { - SetItem(nullptr); -} - -bool ConfigItemRef::Append(an item) { - if (AsList()->Append(item)) { - set_modified(); - return true; - } - return false; -} - -size_t ConfigItemRef::size() const { - auto list = As(GetItem()); - return list ? list->size() : 0; -} - -bool ConfigItemRef::HasKey(const string& key) const { - auto map = As(GetItem()); - return map ? map->HasKey(key) : false; -} - -bool ConfigItemRef::modified() const { - return data_ && data_->modified(); -} - -void ConfigItemRef::set_modified() { - if (data_) - data_->set_modified(); -} - -// Config members - -Config::Config() : ConfigItemRef(New()) { -} - -Config::~Config() { -} - -Config::Config(const string& file_name) - : ConfigItemRef(ConfigDataManager::instance().GetConfigData(file_name)) { -} - -bool Config::LoadFromStream(std::istream& stream) { - return data_->LoadFromStream(stream); -} - -bool Config::SaveToStream(std::ostream& stream) { - return data_->SaveToStream(stream); -} - -bool Config::LoadFromFile(const string& file_name) { - return data_->LoadFromFile(file_name); -} - -bool Config::SaveToFile(const string& file_name) { - return data_->SaveToFile(file_name); -} - -bool Config::IsNull(const string& key) { - auto p = data_->Traverse(key); - return !p || p->type() == ConfigItem::kNull; -} - -bool Config::IsValue(const string& key) { - auto p = data_->Traverse(key); - return !p || p->type() == ConfigItem::kScalar; -} - -bool Config::IsList(const string& key) { - auto p = data_->Traverse(key); - return !p || p->type() == ConfigItem::kList; -} - -bool Config::IsMap(const string& key) { - auto p = data_->Traverse(key); - return !p || p->type() == ConfigItem::kMap; -} - -bool Config::GetBool(const string& key, bool* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); - return p && p->GetBool(value); -} - -bool Config::GetInt(const string& key, int* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); - return p && p->GetInt(value); -} - -bool Config::GetDouble(const string& key, double* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); - return p && p->GetDouble(value); -} - -bool Config::GetString(const string& key, string* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); - return p && p->GetString(value); -} - -an Config::GetItem(const string& key) { - DLOG(INFO) << "read: " << key; - return data_->Traverse(key); -} - -an Config::GetValue(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); -} - -an Config::GetList(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); -} - -an Config::GetMap(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); -} - -bool Config::SetBool(const string& key, bool value) { - return SetItem(key, New(value)); -} - -bool Config::SetInt(const string& key, int value) { - return SetItem(key, New(value)); -} - -bool Config::SetDouble(const string& key, double value) { - return SetItem(key, New(value)); -} - -bool Config::SetString(const string& key, const char* value) { - return SetItem(key, New(value)); -} - -bool Config::SetString(const string& key, const string& value) { - return SetItem(key, New(value)); -} - -static inline bool IsListItemReference(const string& key) { - return !key.empty() && key[0] == '@'; -} - -static size_t ResolveListIndex(an p, const string& key, - bool read_only = false) { - //if (!IsListItemReference(key)) { - // return 0; - //} - an list = As(p); - if (!list) { - return 0; - } - const string kAfter("after"); - const string kBefore("before"); - const string kLast("last"); - const string kNext("next"); - size_t cursor = 1; - unsigned int index = 0; - bool will_insert = false; - if (key.compare(cursor, kNext.length(), kNext) == 0) { - cursor += kNext.length(); - index = list->size(); - } - else if (key.compare(cursor, kBefore.length(), kBefore) == 0) { - cursor += kBefore.length(); - will_insert = true; - } - else if (key.compare(cursor, kAfter.length(), kAfter) == 0) { - cursor += kAfter.length(); - index += 1; // after i == before i+1 - will_insert = true; - } - if (cursor < key.length() && key[cursor] == ' ') { - ++cursor; - } - if (key.compare(cursor, kLast.length(), kLast) == 0) { - cursor += kLast.length(); - index += list->size(); - if (index != 0) { // when list is empty, (before|after) last == 0 - --index; - } - } - else { - index += std::strtoul(key.c_str() + cursor, NULL, 10); - } - if (will_insert && !read_only) { - list->Insert(index, nullptr); - } - return index; -} - -bool Config::SetItem(const string& key, an item) { - LOG(INFO) << "write: " << key; - if (key.empty() || key == "/") { - data_->root = item; - data_->set_modified(); - return true; - } - if (!data_->root) { - data_->root = New(); - } - an p(data_->root); - vector keys; - boost::split(keys, key, boost::is_any_of("/")); - size_t k = keys.size() - 1; - for (size_t i = 0; i <= k; ++i) { - ConfigItem::ValueType node_type = ConfigItem::kMap; - size_t list_index = 0; - if (IsListItemReference(keys[i])) { - node_type = ConfigItem::kList; - list_index = ResolveListIndex(p, keys[i]); - DLOG(INFO) << "list index " << keys[i] << " == " << list_index; - } - if (!p || p->type() != node_type) { - return false; - } - if (i == k) { - if (node_type == ConfigItem::kList) { - As(p)->SetAt(list_index, item); - } - else { - As(p)->Set(keys[i], item); - } - data_->set_modified(); - return true; - } - else { - an next; - if (node_type == ConfigItem::kList) { - next = As(p)->GetAt(list_index); - } - else { - next = As(p)->Get(keys[i]); - } - if (!next) { - if (IsListItemReference(keys[i + 1])) { - DLOG(INFO) << "creating list node for key: " << keys[i + 1]; - next = New(); - } - else { - DLOG(INFO) << "creating map node for key: " << keys[i + 1]; - next = New(); - } - if (node_type == ConfigItem::kList) { - As(p)->SetAt(list_index, next); - } - else { - As(p)->Set(keys[i], next); - } - } - p = next; - } - } - return false; -} - -an Config::GetItem() const { - return data_->root; -} - -void Config::SetItem(an item) { - data_->root = item; - set_modified(); -} - -// ConfigComponent members - -string ConfigComponent::GetConfigFilePath(const string& config_id) { - return boost::str(boost::format(pattern_) % config_id); -} - -Config* ConfigComponent::Create(const string& config_id) { - string path(GetConfigFilePath(config_id)); - DLOG(INFO) << "config file path: " << path; - return new Config(path); -} - -// ConfigDataManager memebers - -ConfigDataManager& ConfigDataManager::instance() { - static the s_instance; - if (!s_instance) { - s_instance.reset(new ConfigDataManager); - } - return *s_instance; -} - -an -ConfigDataManager::GetConfigData(const string& config_file_path) { - an sp; - // keep a weak reference to the shared config data in the manager - weak& wp((*this)[config_file_path]); - if (wp.expired()) { // create a new copy and load it - sp = New(); - sp->LoadFromFile(config_file_path); - wp = sp; - } - else { // obtain the shared copy - sp = wp.lock(); - } - return sp; -} - -bool ConfigDataManager::ReloadConfigData(const string& config_file_path) { - iterator it = find(config_file_path); - if (it == end()) { // never loaded - return false; - } - an sp = it->second.lock(); - if (!sp) { // already been freed - erase(it); - return false; - } - sp->LoadFromFile(config_file_path); - return true; -} - -// ConfigData members - -ConfigData::~ConfigData() { - if (modified_ && !file_name_.empty()) - SaveToFile(file_name_); -} - -bool ConfigData::LoadFromStream(std::istream& stream) { - if (!stream.good()) { - LOG(ERROR) << "failed to load config from stream."; - return false; - } - try { - YAML::Node doc = YAML::Load(stream); - root = ConvertFromYaml(doc); - } - catch (YAML::Exception& e) { - LOG(ERROR) << "Error parsing YAML: " << e.what(); - return false; - } - return true; -} - -bool ConfigData::SaveToStream(std::ostream& stream) { - if (!stream.good()) { - LOG(ERROR) << "failed to save config to stream."; - return false; - } - try { - YAML::Emitter emitter(stream); - EmitYaml(root, &emitter, 0); - } - catch (YAML::Exception& e) { - LOG(ERROR) << "Error emitting YAML: " << e.what(); - return false; - } - return true; -} - -bool ConfigData::LoadFromFile(const string& file_name) { - // update status - file_name_ = file_name; - modified_ = false; - root.reset(); - if (!boost::filesystem::exists(file_name)) { - LOG(WARNING) << "nonexistent config file '" << file_name << "'."; - return false; - } - LOG(INFO) << "loading config file '" << file_name << "'."; - try { - YAML::Node doc = YAML::LoadFile(file_name); - root = ConvertFromYaml(doc); - } - catch (YAML::Exception& e) { - LOG(ERROR) << "Error parsing YAML: " << e.what(); - return false; - } - return true; -} - -bool ConfigData::SaveToFile(const string& file_name) { - // update status - file_name_ = file_name; - modified_ = false; - if (file_name.empty()) { - // not really saving - return false; - } - LOG(INFO) << "saving config file '" << file_name << "'."; - // dump tree - std::ofstream out(file_name.c_str()); - return SaveToStream(out); -} - -an ConfigData::Traverse(const string& key) { - DLOG(INFO) << "traverse: " << key; - if (key.empty() || key == "/") { - return root; - } - vector keys; - boost::split(keys, key, boost::is_any_of("/")); - // find the YAML::Node, and wrap it! - an p = root; - for (auto it = keys.begin(), end = keys.end(); it != end; ++it) { - ConfigItem::ValueType node_type = ConfigItem::kMap; - size_t list_index = 0; - if (IsListItemReference(*it)) { - node_type = ConfigItem::kList; - list_index = ResolveListIndex(p, *it, true); - } - if (!p || p->type() != node_type) { - return nullptr; - } - if (node_type == ConfigItem::kList) { - p = As(p)->GetAt(list_index); - } - else { - p = As(p)->Get(*it); - } - } - return p; -} - -an ConfigData::ConvertFromYaml(const YAML::Node& node) { - if (YAML::NodeType::Null == node.Type()) { - return nullptr; - } - if (YAML::NodeType::Scalar == node.Type()) { - return New(node.as()); - } - if (YAML::NodeType::Sequence == node.Type()) { - auto config_list = New(); - for (auto it = node.begin(), end = node.end(); it != end; ++it) { - config_list->Append(ConvertFromYaml(*it)); - } - return config_list; - } - else if (YAML::NodeType::Map == node.Type()) { - auto config_map = New(); - for (auto it = node.begin(), end = node.end(); it != end; ++it) { - string key = it->first.as(); - config_map->Set(key, ConvertFromYaml(it->second)); - } - return config_map; - } - return nullptr; -} - -void ConfigData::EmitScalar(const string& str_value, - YAML::Emitter* emitter) { - if (str_value.find_first_of("\r\n") != string::npos) { - *emitter << YAML::Literal; - } - else if (!boost::algorithm::all(str_value, - boost::algorithm::is_alnum() || - boost::algorithm::is_any_of("_."))) { - *emitter << YAML::DoubleQuoted; - } - *emitter << str_value; -} - -void ConfigData::EmitYaml(an node, - YAML::Emitter* emitter, - int depth) { - if (!node || !emitter) return; - if (node->type() == ConfigItem::kScalar) { - auto value = As(node); - EmitScalar(value->str(), emitter); - } - else if (node->type() == ConfigItem::kList) { - if (depth >= 3) { - *emitter << YAML::Flow; - } - *emitter << YAML::BeginSeq; - auto list = As(node); - for (auto it = list->begin(), end = list->end(); it != end; ++it) { - EmitYaml(*it, emitter, depth + 1); - } - *emitter << YAML::EndSeq; - } - else if (node->type() == ConfigItem::kMap) { - if (depth >= 3) { - *emitter << YAML::Flow; - } - *emitter << YAML::BeginMap; - auto map = As(node); - for (auto it = map->begin(), end = map->end(); it != end; ++it) { - if (!it->second || it->second->type() == ConfigItem::kNull) - continue; - *emitter << YAML::Key; - EmitScalar(it->first, emitter); - *emitter << YAML::Value; - EmitYaml(it->second, emitter, depth + 1); - } - *emitter << YAML::EndMap; - } -} - -} // namespace rime diff --git a/src/rime/config.h b/src/rime/config.h index 7ecdb7bffa..0f052b66d1 100644 --- a/src/rime/config.h +++ b/src/rime/config.h @@ -2,281 +2,10 @@ // Copyright RIME Developers // Distributed under the BSD License // -// 2011-4-6 Zou xu -// #ifndef RIME_CONFIG_H_ #define RIME_CONFIG_H_ -#include -#include -#include -#include - -namespace rime { - -// config item base class -class ConfigItem { - public: - enum ValueType { kNull, kScalar, kList, kMap }; - - ConfigItem() = default; // null - virtual ~ConfigItem() = default; - - ValueType type() const { return type_; } - - protected: - ConfigItem(ValueType type) : type_(type) {} - - ValueType type_ = kNull; -}; - -class ConfigValue : public ConfigItem { - public: - ConfigValue() : ConfigItem(kScalar) {} - ConfigValue(bool value); - ConfigValue(int value); - ConfigValue(double value); - ConfigValue(const char* value); - ConfigValue(const string& value); - - // schalar value accessors - bool GetBool(bool* value) const; - bool GetInt(int* value) const; - bool GetDouble(double* value) const; - bool GetString(string* value) const; - bool SetBool(bool value); - bool SetInt(int value); - bool SetDouble(double value); - bool SetString(const char* value); - bool SetString(const string& value); - - const string& str() const { return value_; } - - protected: - string value_; -}; - -class ConfigList : public ConfigItem { - public: - using Sequence = vector>; - using Iterator = Sequence::iterator; - - ConfigList() : ConfigItem(kList) {} - an GetAt(size_t i) const; - an GetValueAt(size_t i) const; - bool SetAt(size_t i, an element); - bool Insert(size_t i, an element); - bool Append(an element); - bool Resize(size_t size); - bool Clear(); - size_t size() const; - - Iterator begin(); - Iterator end(); - - protected: - Sequence seq_; -}; - -// limitation: map keys have to be strings, preferably alphanumeric -class ConfigMap : public ConfigItem { - public: - using Map = map>; - using Iterator = Map::iterator; - - ConfigMap() : ConfigItem(kMap) {} - bool HasKey(const string& key) const; - an Get(const string& key) const; - an GetValue(const string& key) const; - bool Set(const string& key, an element); - bool Clear(); - - Iterator begin(); - Iterator end(); - - protected: - Map map_; -}; - -class ConfigData; -class ConfigListEntryRef; -class ConfigMapEntryRef; - -class ConfigItemRef { - public: - ConfigItemRef(const an& data) : data_(data) { - } - operator an () const { - return GetItem(); - } - ConfigListEntryRef operator[] (size_t index); - ConfigMapEntryRef operator[] (const string& key); - - bool IsNull() const; - bool IsValue() const; - bool IsList() const; - bool IsMap() const; - - bool ToBool() const; - int ToInt() const; - double ToDouble() const; - string ToString() const; - - an AsList(); - an AsMap(); - void Clear(); - - // list - bool Append(an item); - size_t size() const; - // map - bool HasKey(const string& key) const; - - bool modified() const; - void set_modified(); - - protected: - virtual an GetItem() const = 0; - virtual void SetItem(an item) = 0; - - an data_; -}; - -namespace { - -template -an AsConfigItem(const T& x, const std::false_type&) { - return New(x); -}; - -template -an AsConfigItem(const T& x, const std::true_type&) { - return x; -}; - -} // namespace - -class ConfigListEntryRef : public ConfigItemRef { - public: - ConfigListEntryRef(an data, - an list, size_t index) - : ConfigItemRef(data), list_(list), index_(index) { - } - template - ConfigListEntryRef& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } - protected: - an GetItem() const { - return list_->GetAt(index_); - } - void SetItem(an item) { - list_->SetAt(index_, item); - set_modified(); - } - private: - an list_; - size_t index_; -}; - -class ConfigMapEntryRef : public ConfigItemRef { - public: - ConfigMapEntryRef(an data, - an map, const string& key) - : ConfigItemRef(data), map_(map), key_(key) { - } - template - ConfigMapEntryRef& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } - protected: - an GetItem() const { - return map_->Get(key_); - } - void SetItem(an item) { - map_->Set(key_, item); - set_modified(); - } - private: - an map_; - string key_; -}; - -inline ConfigListEntryRef ConfigItemRef::operator[] (size_t index) { - return ConfigListEntryRef(data_, AsList(), index); -} - -inline ConfigMapEntryRef ConfigItemRef::operator[] (const string& key) { - return ConfigMapEntryRef(data_, AsMap(), key); -} - -// Config class - -class Config : public Class, public ConfigItemRef { - public: - // CAVEAT: Config instances created without argument will NOT - // be managed by ConfigComponent - Config(); - virtual ~Config(); - // instances of Config with identical file_name share a copy of config data - // that could be reloaded by ConfigComponent once notified changes to the file - explicit Config(const string& file_name); - - bool LoadFromStream(std::istream& stream); - bool SaveToStream(std::ostream& stream); - bool LoadFromFile(const string& file_name); - bool SaveToFile(const string& file_name); - - // access a tree node of a particular type with "path/to/key" - bool IsNull(const string& key); - bool IsValue(const string& key); - bool IsList(const string& key); - bool IsMap(const string& key); - bool GetBool(const string& key, bool* value); - bool GetInt(const string& key, int* value); - bool GetDouble(const string& key, double* value); - bool GetString(const string& key, string* value); - - an GetItem(const string& key); - an GetValue(const string& key); - an GetList(const string& key); - an GetMap(const string& key); - - // setters - bool SetBool(const string& key, bool value); - bool SetInt(const string& key, int value); - bool SetDouble(const string& key, double value); - bool SetString(const string& key, const char* value); - bool SetString(const string& key, const string& value); - // setter for adding / replacing items to the tree - bool SetItem(const string& key, an item); - - template - Config& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } - - protected: - an GetItem() const; - void SetItem(an item); - }; - -// ConfigComponent class - -class ConfigComponent : public Config::Component { - public: - ConfigComponent(const string& pattern) : pattern_(pattern) {} - Config* Create(const string& config_id); - string GetConfigFilePath(const string& config_id); - const string& pattern() const { return pattern_; } - - private: - string pattern_; -}; - -} // namespace rime +#include +#include #endif // RIME_CONFIG_H_ diff --git a/src/rime/config/config_component.cc b/src/rime/config/config_component.cc new file mode 100644 index 0000000000..a22a737245 --- /dev/null +++ b/src/rime/config/config_component.cc @@ -0,0 +1,151 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +// 2011-04-06 Zou Xu +// +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rime { + +Config::Config() : ConfigItemRef(New()) { +} + +Config::~Config() { +} + +Config::Config(const string& file_name) + : ConfigItemRef(ConfigDataManager::instance().GetConfigData(file_name)) { +} + +bool Config::LoadFromStream(std::istream& stream) { + return data_->LoadFromStream(stream); +} + +bool Config::SaveToStream(std::ostream& stream) { + return data_->SaveToStream(stream); +} + +bool Config::LoadFromFile(const string& file_name) { + return data_->LoadFromFile(file_name); +} + +bool Config::SaveToFile(const string& file_name) { + return data_->SaveToFile(file_name); +} + +bool Config::IsNull(const string& key) { + auto p = data_->Traverse(key); + return !p || p->type() == ConfigItem::kNull; +} + +bool Config::IsValue(const string& key) { + auto p = data_->Traverse(key); + return !p || p->type() == ConfigItem::kScalar; +} + +bool Config::IsList(const string& key) { + auto p = data_->Traverse(key); + return !p || p->type() == ConfigItem::kList; +} + +bool Config::IsMap(const string& key) { + auto p = data_->Traverse(key); + return !p || p->type() == ConfigItem::kMap; +} + +bool Config::GetBool(const string& key, bool* value) { + DLOG(INFO) << "read: " << key; + auto p = As(data_->Traverse(key)); + return p && p->GetBool(value); +} + +bool Config::GetInt(const string& key, int* value) { + DLOG(INFO) << "read: " << key; + auto p = As(data_->Traverse(key)); + return p && p->GetInt(value); +} + +bool Config::GetDouble(const string& key, double* value) { + DLOG(INFO) << "read: " << key; + auto p = As(data_->Traverse(key)); + return p && p->GetDouble(value); +} + +bool Config::GetString(const string& key, string* value) { + DLOG(INFO) << "read: " << key; + auto p = As(data_->Traverse(key)); + return p && p->GetString(value); +} + +an Config::GetItem(const string& key) { + DLOG(INFO) << "read: " << key; + return data_->Traverse(key); +} + +an Config::GetValue(const string& key) { + DLOG(INFO) << "read: " << key; + return As(data_->Traverse(key)); +} + +an Config::GetList(const string& key) { + DLOG(INFO) << "read: " << key; + return As(data_->Traverse(key)); +} + +an Config::GetMap(const string& key) { + DLOG(INFO) << "read: " << key; + return As(data_->Traverse(key)); +} + +bool Config::SetBool(const string& key, bool value) { + return SetItem(key, New(value)); +} + +bool Config::SetInt(const string& key, int value) { + return SetItem(key, New(value)); +} + +bool Config::SetDouble(const string& key, double value) { + return SetItem(key, New(value)); +} + +bool Config::SetString(const string& key, const char* value) { + return SetItem(key, New(value)); +} + +bool Config::SetString(const string& key, const string& value) { + return SetItem(key, New(value)); +} + +bool Config::SetItem(const string& key, an item) { + return data_->TraverseWrite(key, item); +} + +an Config::GetItem() const { + return data_->root; +} + +void Config::SetItem(an item) { + data_->root = item; + set_modified(); +} + +string ConfigComponent::GetConfigFilePath(const string& config_id) { + return boost::str(boost::format(pattern_) % config_id); +} + +Config* ConfigComponent::Create(const string& config_id) { + string path(GetConfigFilePath(config_id)); + DLOG(INFO) << "config file path: " << path; + return new Config(path); +} + +} // namespace rime diff --git a/src/rime/config/config_component.h b/src/rime/config/config_component.h new file mode 100644 index 0000000000..eb8dc67300 --- /dev/null +++ b/src/rime/config/config_component.h @@ -0,0 +1,81 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +// 2011-4-6 Zou xu +// +#ifndef RIME_CONFIG_COMPONENT_H_ +#define RIME_CONFIG_COMPONENT_H_ + +#include +#include +#include +#include +#include + +namespace rime { + +class Config : public Class, public ConfigItemRef { + public: + // CAVEAT: Config instances created without argument will NOT + // be managed by ConfigComponent + Config(); + virtual ~Config(); + // instances of Config with identical file_name share a copy of config data + // that could be reloaded by ConfigComponent once notified changes to the file + explicit Config(const string& file_name); + + bool LoadFromStream(std::istream& stream); + bool SaveToStream(std::ostream& stream); + bool LoadFromFile(const string& file_name); + bool SaveToFile(const string& file_name); + + // access a tree node of a particular type with "path/to/key" + bool IsNull(const string& key); + bool IsValue(const string& key); + bool IsList(const string& key); + bool IsMap(const string& key); + bool GetBool(const string& key, bool* value); + bool GetInt(const string& key, int* value); + bool GetDouble(const string& key, double* value); + bool GetString(const string& key, string* value); + + an GetItem(const string& key); + an GetValue(const string& key); + an GetList(const string& key); + an GetMap(const string& key); + + // setters + bool SetBool(const string& key, bool value); + bool SetInt(const string& key, int value); + bool SetDouble(const string& key, double value); + bool SetString(const string& key, const char* value); + bool SetString(const string& key, const string& value); + // setter for adding / replacing items in the tree + bool SetItem(const string& key, an item); + + template + Config& operator= (const T& x) { + SetItem(AsConfigItem(x, std::is_convertible>())); + return *this; + } + + protected: + an GetItem() const; + void SetItem(an item); +}; + +class ConfigComponent : public Config::Component { + public: + ConfigComponent(const string& pattern) : pattern_(pattern) {} + Config* Create(const string& config_id); + string GetConfigFilePath(const string& config_id); + const string& pattern() const { return pattern_; } + + private: + string pattern_; +}; + +} // namespace rime + +#endif // RIME_CONFIG_COMPONENT_H_ diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc new file mode 100644 index 0000000000..7cd5e23a8b --- /dev/null +++ b/src/rime/config/config_data.cc @@ -0,0 +1,306 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#include +#include +#include +#include + +namespace rime { + +ConfigData::~ConfigData() { + if (modified_ && !file_name_.empty()) + SaveToFile(file_name_); +} + +bool ConfigData::LoadFromStream(std::istream& stream) { + if (!stream.good()) { + LOG(ERROR) << "failed to load config from stream."; + return false; + } + try { + YAML::Node doc = YAML::Load(stream); + root = ConvertFromYaml(doc); + } + catch (YAML::Exception& e) { + LOG(ERROR) << "Error parsing YAML: " << e.what(); + return false; + } + return true; +} + +bool ConfigData::SaveToStream(std::ostream& stream) { + if (!stream.good()) { + LOG(ERROR) << "failed to save config to stream."; + return false; + } + try { + YAML::Emitter emitter(stream); + EmitYaml(root, &emitter, 0); + } + catch (YAML::Exception& e) { + LOG(ERROR) << "Error emitting YAML: " << e.what(); + return false; + } + return true; +} + +bool ConfigData::LoadFromFile(const string& file_name) { + // update status + file_name_ = file_name; + modified_ = false; + root.reset(); + if (!boost::filesystem::exists(file_name)) { + LOG(WARNING) << "nonexistent config file '" << file_name << "'."; + return false; + } + LOG(INFO) << "loading config file '" << file_name << "'."; + try { + YAML::Node doc = YAML::LoadFile(file_name); + root = ConvertFromYaml(doc); + } + catch (YAML::Exception& e) { + LOG(ERROR) << "Error parsing YAML: " << e.what(); + return false; + } + return true; +} + +bool ConfigData::SaveToFile(const string& file_name) { + // update status + file_name_ = file_name; + modified_ = false; + if (file_name.empty()) { + // not really saving + return false; + } + LOG(INFO) << "saving config file '" << file_name << "'."; + // dump tree + std::ofstream out(file_name.c_str()); + return SaveToStream(out); +} + +static inline bool IsListItemReference(const string& key) { + return !key.empty() && key[0] == '@'; +} + +static size_t ResolveListIndex(an p, const string& key, + bool read_only = false) { + //if (!IsListItemReference(key)) { + // return 0; + //} + an list = As(p); + if (!list) { + return 0; + } + const string kAfter("after"); + const string kBefore("before"); + const string kLast("last"); + const string kNext("next"); + size_t cursor = 1; + unsigned int index = 0; + bool will_insert = false; + if (key.compare(cursor, kNext.length(), kNext) == 0) { + cursor += kNext.length(); + index = list->size(); + } + else if (key.compare(cursor, kBefore.length(), kBefore) == 0) { + cursor += kBefore.length(); + will_insert = true; + } + else if (key.compare(cursor, kAfter.length(), kAfter) == 0) { + cursor += kAfter.length(); + index += 1; // after i == before i+1 + will_insert = true; + } + if (cursor < key.length() && key[cursor] == ' ') { + ++cursor; + } + if (key.compare(cursor, kLast.length(), kLast) == 0) { + cursor += kLast.length(); + index += list->size(); + if (index != 0) { // when list is empty, (before|after) last == 0 + --index; + } + } + else { + index += std::strtoul(key.c_str() + cursor, NULL, 10); + } + if (will_insert && !read_only) { + list->Insert(index, nullptr); + } + return index; +} + +bool ConfigData::TraverseWrite(const string& key, an item) { + LOG(INFO) << "write: " << key; + if (key.empty() || key == "/") { + root = item; + set_modified(); + return true; + } + if (!root) { + root = New(); + } + an p(root); + vector keys; + boost::split(keys, key, boost::is_any_of("/")); + size_t k = keys.size() - 1; + for (size_t i = 0; i <= k; ++i) { + ConfigItem::ValueType node_type = ConfigItem::kMap; + size_t list_index = 0; + if (IsListItemReference(keys[i])) { + node_type = ConfigItem::kList; + list_index = ResolveListIndex(p, keys[i]); + DLOG(INFO) << "list index " << keys[i] << " == " << list_index; + } + if (!p || p->type() != node_type) { + return false; + } + if (i == k) { + if (node_type == ConfigItem::kList) { + As(p)->SetAt(list_index, item); + } + else { + As(p)->Set(keys[i], item); + } + set_modified(); + return true; + } + else { + an next; + if (node_type == ConfigItem::kList) { + next = As(p)->GetAt(list_index); + } + else { + next = As(p)->Get(keys[i]); + } + if (!next) { + if (IsListItemReference(keys[i + 1])) { + DLOG(INFO) << "creating list node for key: " << keys[i + 1]; + next = New(); + } + else { + DLOG(INFO) << "creating map node for key: " << keys[i + 1]; + next = New(); + } + if (node_type == ConfigItem::kList) { + As(p)->SetAt(list_index, next); + } + else { + As(p)->Set(keys[i], next); + } + } + p = next; + } + } + return false; +} + +an ConfigData::Traverse(const string& key) { + DLOG(INFO) << "traverse: " << key; + if (key.empty() || key == "/") { + return root; + } + vector keys; + boost::split(keys, key, boost::is_any_of("/")); + // find the YAML::Node, and wrap it! + an p = root; + for (auto it = keys.begin(), end = keys.end(); it != end; ++it) { + ConfigItem::ValueType node_type = ConfigItem::kMap; + size_t list_index = 0; + if (IsListItemReference(*it)) { + node_type = ConfigItem::kList; + list_index = ResolveListIndex(p, *it, true); + } + if (!p || p->type() != node_type) { + return nullptr; + } + if (node_type == ConfigItem::kList) { + p = As(p)->GetAt(list_index); + } + else { + p = As(p)->Get(*it); + } + } + return p; +} + +an ConfigData::ConvertFromYaml(const YAML::Node& node) { + if (YAML::NodeType::Null == node.Type()) { + return nullptr; + } + if (YAML::NodeType::Scalar == node.Type()) { + return New(node.as()); + } + if (YAML::NodeType::Sequence == node.Type()) { + auto config_list = New(); + for (auto it = node.begin(), end = node.end(); it != end; ++it) { + config_list->Append(ConvertFromYaml(*it)); + } + return config_list; + } + else if (YAML::NodeType::Map == node.Type()) { + auto config_map = New(); + for (auto it = node.begin(), end = node.end(); it != end; ++it) { + string key = it->first.as(); + config_map->Set(key, ConvertFromYaml(it->second)); + } + return config_map; + } + return nullptr; +} + +void ConfigData::EmitScalar(const string& str_value, + YAML::Emitter* emitter) { + if (str_value.find_first_of("\r\n") != string::npos) { + *emitter << YAML::Literal; + } + else if (!boost::algorithm::all(str_value, + boost::algorithm::is_alnum() || + boost::algorithm::is_any_of("_."))) { + *emitter << YAML::DoubleQuoted; + } + *emitter << str_value; +} + +void ConfigData::EmitYaml(an node, + YAML::Emitter* emitter, + int depth) { + if (!node || !emitter) return; + if (node->type() == ConfigItem::kScalar) { + auto value = As(node); + EmitScalar(value->str(), emitter); + } + else if (node->type() == ConfigItem::kList) { + if (depth >= 3) { + *emitter << YAML::Flow; + } + *emitter << YAML::BeginSeq; + auto list = As(node); + for (auto it = list->begin(), end = list->end(); it != end; ++it) { + EmitYaml(*it, emitter, depth + 1); + } + *emitter << YAML::EndSeq; + } + else if (node->type() == ConfigItem::kMap) { + if (depth >= 3) { + *emitter << YAML::Flow; + } + *emitter << YAML::BeginMap; + auto map = As(node); + for (auto it = map->begin(), end = map->end(); it != end; ++it) { + if (!it->second || it->second->type() == ConfigItem::kNull) + continue; + *emitter << YAML::Key; + EmitScalar(it->first, emitter); + *emitter << YAML::Value; + EmitYaml(it->second, emitter, depth + 1); + } + *emitter << YAML::EndMap; + } +} + +} // namespace rime diff --git a/src/rime/config/config_data.h b/src/rime/config/config_data.h new file mode 100644 index 0000000000..128006b3ef --- /dev/null +++ b/src/rime/config/config_data.h @@ -0,0 +1,48 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#ifndef RIME_CONFIG_DATA_H_ +#define RIME_CONFIG_DATA_H_ + +#include +#include +#include + +namespace rime { + +class ConfigItem; + +class ConfigData { + public: + ConfigData() = default; + ~ConfigData(); + + bool LoadFromStream(std::istream& stream); + bool SaveToStream(std::ostream& stream); + bool LoadFromFile(const string& file_name); + bool SaveToFile(const string& file_name); + bool TraverseWrite(const string& key, an item); + an Traverse(const string& key); + + bool modified() const { return modified_; } + void set_modified() { modified_ = true; } + + an root; + + protected: + static an ConvertFromYaml(const YAML::Node& yaml_node); + static void EmitYaml(an node, + YAML::Emitter* emitter, + int depth); + static void EmitScalar(const string& str_value, + YAML::Emitter* emitter); + + string file_name_; + bool modified_ = false; +}; + +} // namespace rime + +#endif // RIME_CONFIG_DATA_H_ diff --git a/src/rime/config/config_data_manager.cc b/src/rime/config/config_data_manager.cc new file mode 100644 index 0000000000..25a6ee0d02 --- /dev/null +++ b/src/rime/config/config_data_manager.cc @@ -0,0 +1,49 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#include +#include + +namespace rime { + +ConfigDataManager& ConfigDataManager::instance() { + static the s_instance; + if (!s_instance) { + s_instance.reset(new ConfigDataManager); + } + return *s_instance; +} + +an +ConfigDataManager::GetConfigData(const string& config_file_path) { + an sp; + // keep a weak reference to the shared config data in the manager + weak& wp((*this)[config_file_path]); + if (wp.expired()) { // create a new copy and load it + sp = New(); + sp->LoadFromFile(config_file_path); + wp = sp; + } + else { // obtain the shared copy + sp = wp.lock(); + } + return sp; +} + +bool ConfigDataManager::ReloadConfigData(const string& config_file_path) { + iterator it = find(config_file_path); + if (it == end()) { // never loaded + return false; + } + an sp = it->second.lock(); + if (!sp) { // already been freed + erase(it); + return false; + } + sp->LoadFromFile(config_file_path); + return true; +} + +} // namespace rime diff --git a/src/rime/config/config_data_manager.h b/src/rime/config/config_data_manager.h new file mode 100644 index 0000000000..cd4bf9e2ad --- /dev/null +++ b/src/rime/config/config_data_manager.h @@ -0,0 +1,28 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#ifndef RIME_CONFIG_DATA_MANAGER_H_ +#define RIME_CONFIG_DATA_MANAGER_H_ + +#include + +namespace rime { + +class ConfigData; + +class ConfigDataManager : public map> { + public: + an GetConfigData(const string& config_file_path); + bool ReloadConfigData(const string& config_file_path); + + static ConfigDataManager& instance(); + + private: + ConfigDataManager() = default; +}; + +} // namespace rime + +#endif // RIME_CONFIG_DATA_MANAGER_H_ diff --git a/src/rime/config/config_types.cc b/src/rime/config/config_types.cc new file mode 100644 index 0000000000..99d2ca3d6f --- /dev/null +++ b/src/rime/config/config_types.cc @@ -0,0 +1,312 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +// 2011-04-06 Zou Xu +// +#include +#include +#include +#include +#include + +namespace rime { + +// ConfigValue members + +ConfigValue::ConfigValue(bool value) + : ConfigItem(kScalar) { + SetBool(value); +} + +ConfigValue::ConfigValue(int value) + : ConfigItem(kScalar) { + SetInt(value); +} + +ConfigValue::ConfigValue(double value) + : ConfigItem(kScalar) { + SetDouble(value); +} + +ConfigValue::ConfigValue(const char* value) + : ConfigItem(kScalar), value_(value) { +} + +ConfigValue::ConfigValue(const string& value) + : ConfigItem(kScalar), value_(value) { +} + +bool ConfigValue::GetBool(bool* value) const { + if (!value || value_.empty()) + return false; + string bstr = value_; + boost::to_lower(bstr); + if ("true" == bstr) { + *value = true; + return true; + } + else if ("false" == bstr) { + *value = false; + return true; + } + else + return false; +} + +bool ConfigValue::GetInt(int* value) const { + if (!value || value_.empty()) + return false; + // try to parse hex number + if (boost::starts_with(value_, "0x")) { + char* p = NULL; + unsigned int hex = std::strtoul(value_.c_str(), &p, 16); + if (*p == '\0') { + *value = static_cast(hex); + return true; + } + } + // decimal + try { + *value = boost::lexical_cast(value_); + } + catch (...) { + return false; + } + return true; +} + +bool ConfigValue::GetDouble(double* value) const { + if (!value || value_.empty()) + return false; + try { + *value = boost::lexical_cast(value_); + } + catch (...) { + return false; + } + return true; +} + +bool ConfigValue::GetString(string* value) const { + if (!value) return false; + *value = value_; + return true; +} + +bool ConfigValue::SetBool(bool value) { + value_ = value ? "true" : "false"; + return true; +} + +bool ConfigValue::SetInt(int value) { + value_ = boost::lexical_cast(value); + return true; +} + +bool ConfigValue::SetDouble(double value) { + value_ = boost::lexical_cast(value); + return true; +} + +bool ConfigValue::SetString(const char* value) { + value_ = value; + return true; +} + +bool ConfigValue::SetString(const string& value) { + value_ = value; + return true; +} + +// ConfigList members + +an ConfigList::GetAt(size_t i) const { + if (i >= seq_.size()) + return nullptr; + else + return seq_[i]; +} + +an ConfigList::GetValueAt(size_t i) const { + return As(GetAt(i)); +} + +bool ConfigList::SetAt(size_t i, an element) { + if (i >= seq_.size()) + seq_.resize(i + 1); + seq_[i] = element; + return true; +} + +bool ConfigList::Insert(size_t i, an element) { + if (i > seq_.size()) { + seq_.resize(i); + } + seq_.insert(seq_.begin() + i, element); + return true; +} + +bool ConfigList::Append(an element) { + seq_.push_back(element); + return true; +} + +bool ConfigList::Resize(size_t size) { + seq_.resize(size); + return true; +} + +bool ConfigList::Clear() { + seq_.clear(); + return true; +} + +size_t ConfigList::size() const { + return seq_.size(); +} + +ConfigList::Iterator ConfigList::begin() { + return seq_.begin(); +} + +ConfigList::Iterator ConfigList::end() { + return seq_.end(); +} + +// ConfigMap members + +bool ConfigMap::HasKey(const string& key) const { + return bool(Get(key)); +} + +an ConfigMap::Get(const string& key) const { + auto it = map_.find(key); + if (it == map_.end()) + return nullptr; + else + return it->second; +} + +an ConfigMap::GetValue(const string& key) const { + return As(Get(key)); +} + +bool ConfigMap::Set(const string& key, an element) { + map_[key] = element; + return true; +} + +bool ConfigMap::Clear() { + map_.clear(); + return true; +} + +ConfigMap::Iterator ConfigMap::begin() { + return map_.begin(); +} + +ConfigMap::Iterator ConfigMap::end() { + return map_.end(); +} + +// ConfigItemRef members + +bool ConfigItemRef::IsNull() const { + auto item = GetItem(); + return !item || item->type() == ConfigItem::kNull; +} + +bool ConfigItemRef::IsValue() const { + auto item = GetItem(); + return item && item->type() == ConfigItem::kScalar; +} + +bool ConfigItemRef::IsList() const { + auto item = GetItem(); + return item && item->type() == ConfigItem::kList; +} + +bool ConfigItemRef::IsMap() const { + auto item = GetItem(); + return item && item->type() == ConfigItem::kMap; +} + +bool ConfigItemRef::ToBool() const { + bool value = false; + if (auto item = As(GetItem())) { + item->GetBool(&value); + } + return value; +} + +int ConfigItemRef::ToInt() const { + int value = 0; + if (auto item = As(GetItem())) { + item->GetInt(&value); + } + return value; +} + +double ConfigItemRef::ToDouble() const { + double value = 0.0; + if (auto item = As(GetItem())) { + item->GetDouble(&value); + } + return value; +} + +string ConfigItemRef::ToString() const { + string value; + if (auto item = As(GetItem())) { + item->GetString(&value); + } + return value; +} + +an ConfigItemRef::AsList() { + auto list = As(GetItem()); + if (!list) + SetItem(list = New()); + return list; +} + +an ConfigItemRef::AsMap() { + auto map = As(GetItem()); + if (!map) + SetItem(map = New()); + return map; +} + +void ConfigItemRef::Clear() { + SetItem(nullptr); +} + +bool ConfigItemRef::Append(an item) { + if (AsList()->Append(item)) { + set_modified(); + return true; + } + return false; +} + +size_t ConfigItemRef::size() const { + auto list = As(GetItem()); + return list ? list->size() : 0; +} + +bool ConfigItemRef::HasKey(const string& key) const { + auto map = As(GetItem()); + return map ? map->HasKey(key) : false; +} + +bool ConfigItemRef::modified() const { + return data_ && data_->modified(); +} + +void ConfigItemRef::set_modified() { + if (data_) + data_->set_modified(); +} + +} // namespace rime diff --git a/src/rime/config/config_types.h b/src/rime/config/config_types.h new file mode 100644 index 0000000000..9057d5cd75 --- /dev/null +++ b/src/rime/config/config_types.h @@ -0,0 +1,215 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +// 2011-4-6 Zou xu +// +#ifndef RIME_CONFIG_TYPES_H_ +#define RIME_CONFIG_TYPES_H_ + +#include +#include + +namespace rime { + +// config item base class +class ConfigItem { + public: + enum ValueType { kNull, kScalar, kList, kMap }; + + ConfigItem() = default; // null + virtual ~ConfigItem() = default; + + ValueType type() const { return type_; } + + protected: + ConfigItem(ValueType type) : type_(type) {} + + ValueType type_ = kNull; +}; + +class ConfigValue : public ConfigItem { + public: + ConfigValue() : ConfigItem(kScalar) {} + ConfigValue(bool value); + ConfigValue(int value); + ConfigValue(double value); + ConfigValue(const char* value); + ConfigValue(const string& value); + + // schalar value accessors + bool GetBool(bool* value) const; + bool GetInt(int* value) const; + bool GetDouble(double* value) const; + bool GetString(string* value) const; + bool SetBool(bool value); + bool SetInt(int value); + bool SetDouble(double value); + bool SetString(const char* value); + bool SetString(const string& value); + + const string& str() const { return value_; } + + protected: + string value_; +}; + +class ConfigList : public ConfigItem { + public: + using Sequence = vector>; + using Iterator = Sequence::iterator; + + ConfigList() : ConfigItem(kList) {} + an GetAt(size_t i) const; + an GetValueAt(size_t i) const; + bool SetAt(size_t i, an element); + bool Insert(size_t i, an element); + bool Append(an element); + bool Resize(size_t size); + bool Clear(); + size_t size() const; + + Iterator begin(); + Iterator end(); + + protected: + Sequence seq_; +}; + +// limitation: map keys have to be strings, preferably alphanumeric +class ConfigMap : public ConfigItem { + public: + using Map = map>; + using Iterator = Map::iterator; + + ConfigMap() : ConfigItem(kMap) {} + bool HasKey(const string& key) const; + an Get(const string& key) const; + an GetValue(const string& key) const; + bool Set(const string& key, an element); + bool Clear(); + + Iterator begin(); + Iterator end(); + + protected: + Map map_; +}; + +class ConfigData; +class ConfigListEntryRef; +class ConfigMapEntryRef; + +class ConfigItemRef { + public: + ConfigItemRef(const an& data) : data_(data) { + } + operator an () const { + return GetItem(); + } + ConfigListEntryRef operator[] (size_t index); + ConfigMapEntryRef operator[] (const string& key); + + bool IsNull() const; + bool IsValue() const; + bool IsList() const; + bool IsMap() const; + + bool ToBool() const; + int ToInt() const; + double ToDouble() const; + string ToString() const; + + an AsList(); + an AsMap(); + void Clear(); + + // list + bool Append(an item); + size_t size() const; + // map + bool HasKey(const string& key) const; + + bool modified() const; + void set_modified(); + + protected: + virtual an GetItem() const = 0; + virtual void SetItem(an item) = 0; + + an data_; +}; + +namespace { + +template +an AsConfigItem(const T& x, const std::false_type&) { + return New(x); +}; + +template +an AsConfigItem(const T& x, const std::true_type&) { + return x; +}; + +} // namespace + +class ConfigListEntryRef : public ConfigItemRef { + public: + ConfigListEntryRef(an data, + an list, size_t index) + : ConfigItemRef(data), list_(list), index_(index) { + } + template + ConfigListEntryRef& operator= (const T& x) { + SetItem(AsConfigItem(x, std::is_convertible>())); + return *this; + } + protected: + an GetItem() const { + return list_->GetAt(index_); + } + void SetItem(an item) { + list_->SetAt(index_, item); + set_modified(); + } + private: + an list_; + size_t index_; +}; + +class ConfigMapEntryRef : public ConfigItemRef { + public: + ConfigMapEntryRef(an data, + an map, const string& key) + : ConfigItemRef(data), map_(map), key_(key) { + } + template + ConfigMapEntryRef& operator= (const T& x) { + SetItem(AsConfigItem(x, std::is_convertible>())); + return *this; + } + protected: + an GetItem() const { + return map_->Get(key_); + } + void SetItem(an item) { + map_->Set(key_, item); + set_modified(); + } + private: + an map_; + string key_; +}; + +inline ConfigListEntryRef ConfigItemRef::operator[] (size_t index) { + return ConfigListEntryRef(data_, AsList(), index); +} + +inline ConfigMapEntryRef ConfigItemRef::operator[] (const string& key) { + return ConfigMapEntryRef(data_, AsMap(), key); +} + +} // namespace rime + +#endif // RIME_CONFIG_TYPES_H_ From b6201be8338a93209745ae6d7f06f59eaa2b7b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Wed, 14 Jun 2017 08:59:58 +0800 Subject: [PATCH 03/11] WIP(config): implement __include, __patch directives without COW --- src/rime/config/config_compiler.cc | 404 +++++++++++++++++++++++++ src/rime/config/config_compiler.h | 59 ++++ src/rime/config/config_component.cc | 102 ++++--- src/rime/config/config_component.h | 48 ++- src/rime/config/config_data.cc | 168 ++++++---- src/rime/config/config_data.h | 18 +- src/rime/config/config_data_manager.cc | 36 +-- src/rime/config/config_data_manager.h | 1 - src/rime/config/config_types.h | 47 ++- src/rime_api.cc | 1 + test/CMakeLists.txt | 2 + test/config_compiler_test.cc | 104 +++++++ 12 files changed, 806 insertions(+), 184 deletions(-) create mode 100644 src/rime/config/config_compiler.cc create mode 100644 src/rime/config/config_compiler.h create mode 100644 test/config_compiler_test.cc diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc new file mode 100644 index 0000000000..90266fc0d7 --- /dev/null +++ b/src/rime/config/config_compiler.cc @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include + +namespace rime { + +struct Dependency { + an target; + + virtual bool blocking() const = 0; + virtual bool Resolve(ConfigCompiler* compiler) = 0; +}; + +struct PendingChild : Dependency { + string child_path; + an child_ref; + + PendingChild(const string& path, const an& ref) + : child_path(path), child_ref(ref) { + } + bool blocking() const override { + return false; + } + bool Resolve(ConfigCompiler* compiler) override; +}; + +struct Reference { + string resource_name; + string local_path; + + Reference(const string& qualified_path, ConfigDependencyGraph* graph); +}; + +template +StreamT& operator<< (StreamT& stream, const Reference& reference) { + return stream << reference.resource_name << ":" << reference.local_path; +} + +struct IncludeReference : Dependency { + IncludeReference(const Reference& r) : reference(r) { + } + bool blocking() const override { + return true; + } + bool Resolve(ConfigCompiler* compiler) override; + + Reference reference; +}; + +struct PatchReference : Dependency { + PatchReference(const Reference& r) : reference(r) { + } + bool blocking() const override { + return true; + } + bool Resolve(ConfigCompiler* compiler) override; + + Reference reference; +}; + +struct PatchLiteral : Dependency { + an patch; + + PatchLiteral(an map) : patch(map) { + } + bool blocking() const override { + return true; + } + bool Resolve(ConfigCompiler* compiler) override; +}; + +struct ConfigDependencyGraph { + map> resources; + vector> node_stack; + vector key_stack; + map>> deps; + + void Add(an dependency); + + void Push(an item, const string& key) { + node_stack.push_back(item); + key_stack.push_back(key); + } + + void Pop() { + node_stack.pop_back(); + key_stack.pop_back(); + } + + string current_resource_name() const { + return key_stack.empty() ? string() + : boost::trim_right_copy_if(key_stack.front(), boost::is_any_of(":")); + } +}; + +// TODO: create a ResourceResolver component. + +static string FilePathToResource(const string& file_path) { + if (boost::ends_with(file_path, ".yaml")) { + return boost::erase_last_copy(file_path, ".yaml"); + } + return file_path; +} + +static string ResourceToFilePath(const string& resource_name) { + if (boost::ends_with(resource_name, ".yaml")) { + return resource_name; + } + return resource_name + ".yaml"; +} + +Reference::Reference(const string& qualified_path, + ConfigDependencyGraph* graph) { + auto separator = qualified_path.find_first_of(":"); + if (separator == string::npos || separator == 0) { + resource_name = graph->current_resource_name(); + } else { + resource_name = FilePathToResource(qualified_path.substr(0, separator)); + } + if (separator == string::npos) { + local_path = qualified_path; + } else { + local_path = qualified_path.substr(separator + 1); + } +} + +bool PendingChild::Resolve(ConfigCompiler* compiler) { + return compiler->ResolveDependencies(child_path, child_ref); +} + +static an ResolveReference(ConfigCompiler* compiler, + const Reference& reference); + +bool IncludeReference::Resolve(ConfigCompiler* compiler) { + LOG(INFO) << "IncludeReference::Resolve(reference = " << reference << ")"; + auto item = ResolveReference(compiler, reference); + if (!item) { + return false; + } + *target = item; + return true; +} + +bool PatchReference::Resolve(ConfigCompiler* compiler) { + auto item = ResolveReference(compiler, reference); + if (!item) { + return false; + } + if (!Is(item)) { + LOG(ERROR) << "invalid patch at " << reference; + return false; + } + PatchLiteral patch{As(item)}; + patch.target = target; + return patch.Resolve(compiler); +} + +// defined in config_data.cc +bool TraverseWriteFrom(ConfigItemRef& root, const string& path, + an item); + +bool PatchLiteral::Resolve(ConfigCompiler* compiler) { + bool success = true; + for (const auto& entry : *patch) { + const auto& path = entry.first; + const auto& value = entry.second; + if (!TraverseWriteFrom(*target, path, value)) { + LOG(ERROR) << "error applying patch to " << path; + success = false; + } + } + return success; +} + +void ConfigDependencyGraph::Add(an dependency) { + LOG(INFO) << "ConfigDependencyGraph::Add(), node_stack.size() = " << node_stack.size(); + if (node_stack.empty()) return; + const auto& target = node_stack.back(); + dependency->target = target; + auto target_path = ConfigData::JoinPath(key_stack); + deps[target_path].push_back(dependency); + LOG(INFO) << "target_path = " << target_path << ", #deps = " << deps[target_path].size(); + // The current pending node becomes a prioritized dependency of parent node + auto child = target; + auto keys = key_stack; + for (auto prev = node_stack.rbegin() + 1; prev != node_stack.rend(); ++prev) { + auto last_key = keys.back(); + keys.pop_back(); + auto parent_path = ConfigData::JoinPath(keys); + auto& parent_deps = deps[parent_path]; + bool parent_was_pending = !parent_deps.empty(); + // Pending children should be resolved before applying __include or __patch + parent_deps.push_front(New(parent_path + "/" + last_key, child)); + LOG(INFO) << "parent_path = " << parent_path << ", #deps = " << parent_deps.size(); + if (parent_was_pending) { + // so was all ancestors + break; + } + child = *prev; + } +} + +ConfigCompiler::ConfigCompiler() + : graph_(new ConfigDependencyGraph) { +} + +ConfigCompiler::~ConfigCompiler() { +} + +void ConfigCompiler::Push(an config_list, size_t index) { + graph_->Push( + New(nullptr, config_list, index), + ConfigData::FormatListIndex(index)); +} + +void ConfigCompiler::Push(an config_map, const string& key) { + graph_->Push( + New(nullptr, config_map, key), + key); +} + +void ConfigCompiler::Pop() { + graph_->Pop(); +} + +an ConfigCompiler::GetCompiledResource( + const string& resource_name) const { + return graph_->resources[resource_name]; +} + +an ConfigCompiler::Compile(const string& file_path) { + auto resource_name = FilePathToResource(file_path); + auto resource = New(resource_name, New()); + graph_->resources[resource_name] = resource; + graph_->Push(resource, resource_name + ":"); + if (!resource->data->LoadFromFile(ResourceToFilePath(resource_name), this)) { + resource.reset(); + } + graph_->Pop(); + return resource; +} + +static inline an if_resolved(ConfigCompiler* compiler, + an item, + const string& path) { + return item && compiler->resolved(path) ? item : nullptr; +} + +static an GetResolvedItem(ConfigCompiler* compiler, + an resource, + const string& path) { + if (!resource || compiler->blocking(resource->name + ":")) { + return nullptr; + } + LOG(INFO) << "GetResolvedItem(" << resource->name << ":/" << path << ")"; + an result = *resource; + if (path.empty() || path == "/") { + return if_resolved(compiler, result, resource->name + ":"); + } + vector normalized_keys = {resource->name + ":"}; + vector keys = ConfigData::SplitPath(path); + for (const auto& key : keys) { + if (Is(result)) { + if (ConfigData::IsListItemReference(key)) { + size_t index = ConfigData::ResolveListIndex(result, key, true); + result = As(result)->GetAt(index); + normalized_keys.push_back(ConfigData::FormatListIndex(index)); + } else { + result.reset(); + } + } else if (Is(result)) { + LOG(INFO) << "advance with key: " << key; + result = As(result)->Get(key); + normalized_keys.push_back(key); + } else { + result.reset(); + } + auto node_path = ConfigData::JoinPath(normalized_keys); + if (!result || compiler->blocking(node_path)) { + LOG(INFO) << (result ? "blocking: " : "missing: ") << node_path; + return nullptr; + } + } + return if_resolved(compiler, result, ConfigData::JoinPath(normalized_keys)); +} + +bool ConfigCompiler::blocking(const string& full_path) const { + auto found = graph_->deps.find(full_path); + return found != graph_->deps.end() && !found->second.empty() + && found->second.back()->blocking(); +} + +bool ConfigCompiler::pending(const string& full_path) const { + return !resolved(full_path); +} + +bool ConfigCompiler::resolved(const string& full_path) const { + auto found = graph_->deps.find(full_path); + return found == graph_->deps.end() || found->second.empty(); +} + +static an ResolveReference(ConfigCompiler* compiler, + const Reference& reference) { + auto resource = compiler->GetCompiledResource(reference.resource_name); + if (!resource) { + LOG(INFO) << "resource not found, compiling: " << reference.resource_name; + resource = compiler->Compile(reference.resource_name); + } + return GetResolvedItem(compiler, resource, reference.local_path); +} + +// Includes contents of nodes at specified paths. +// __include: path/to/local/node +// __include: filename[.yaml]:/path/to/external/node +static bool ParseInclude(ConfigDependencyGraph* graph, + const an& item) { + if (Is(item)) { + auto path = As(item)->str(); + LOG(INFO) << "ParseInclude(" << path << ")"; + graph->Add(New(Reference{path, graph})); + return true; + } + return false; +} + +// Applies `parser` to every list element if `item` is a list. +// __patch: [ first/patch, filename:/second/patch ] +// __patch: [{list/@next: 1}, {list/@next: 2}] +static bool ParseList(bool (*parser)(ConfigDependencyGraph*, + const an&), + ConfigDependencyGraph* graph, + const an& item) { + if (Is(item)) { + for (auto list_item : *As(item)) { + if (!parser(graph, list_item)) { + return false; + } + } + return true; + } + // not a list + return parser(graph, item); +} + +// Modifies subnodes or list elements at specified paths. +// __patch: path/to/node +// __patch: filename[.yaml]:/path/to/node +// __patch: { key/alpha: value, key/beta: value } +static bool ParsePatch(ConfigDependencyGraph* graph, + const an& item) { + if (Is(item)) { + auto path = As(item)->str(); + LOG(INFO) << "ParsePatch(" << path << ")"; + graph->Add(New(Reference{path, graph})); + return true; + } + if (Is(item)) { + LOG(INFO) << "ParsePatch()"; + graph->Add(New(As(item))); + return true; + } + return false; +} + +bool ConfigCompiler::Parse(const string& key, const an& item) { + LOG(INFO) << "ConfigCompiler::Parse(" << key << ")"; + if (key == INCLUDE_DIRECTIVE) { + return ParseInclude(graph_.get(), item); + } + if (key == PATCH_DIRECTIVE) { + return ParseList(ParsePatch, graph_.get(), item); + } + return false; +} + +bool ConfigCompiler::Link(an target) { + LOG(INFO) << "Link(" << target->name << ")"; + auto found = graph_->resources.find(target->name); + if (found == graph_->resources.end()) { + LOG(INFO) << "resource not found: " << target->name; + return false; + } + return ResolveDependencies(found->first + ":", found->second); +} + +bool ConfigCompiler::ResolveDependencies(const string& path, + an target) { + LOG(INFO) << "ResolveDependencies(" << path << ")"; + auto& deps = graph_->deps[path]; + for (auto iter = deps.begin(); iter != deps.end(); ) { + if (!(*iter)->Resolve(this)) { + LOG(INFO) << "unesolved dependency!"; + return false; + } + LOG(INFO) << "resolved."; + iter = deps.erase(iter); + } + LOG(INFO) << "all dependencies resolved."; + return true; +} + +} // namespace rime diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h new file mode 100644 index 0000000000..f714a269bc --- /dev/null +++ b/src/rime/config/config_compiler.h @@ -0,0 +1,59 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#ifndef RIME_CONFIG_COMPILER_H_ +#define RIME_CONFIG_COMPILER_H_ + +#include +#include +#include + +namespace rime { + +struct ConfigResource : ConfigItemRef { + string name; + an data; + + ConfigResource(const string& _name, an _data) + : ConfigItemRef(nullptr), name(_name), data(_data) { + } + an GetItem() const override { + return data->root; + } + void SetItem(an item) override { + data->root = item; + } +}; + +struct ConfigDependencyGraph; + +class ConfigCompiler { + public: + static constexpr const char* INCLUDE_DIRECTIVE = "__include"; + static constexpr const char* PATCH_DIRECTIVE = "__patch"; + + ConfigCompiler(); + virtual ~ConfigCompiler(); + + void Push(an config_list, size_t index); + void Push(an config_map, const string& key); + bool Parse(const string& key, const an& item); + void Pop(); + + an GetCompiledResource(const string& resource_name) const; + an Compile(const string& resource_name); + bool Link(an target); + + bool blocking(const string& full_path) const; + bool pending(const string& full_path) const; + bool resolved(const string& full_path) const; + bool ResolveDependencies(const string& path, an target); + + private: + the graph_; +}; + +} // namespace rime + +#endif // RIME_CONFIG_COMPILER_H_ diff --git a/src/rime/config/config_component.cc b/src/rime/config/config_component.cc index a22a737245..f8c5c4695d 100644 --- a/src/rime/config/config_component.cc +++ b/src/rime/config/config_component.cc @@ -34,99 +34,105 @@ bool Config::SaveToStream(std::ostream& stream) { } bool Config::LoadFromFile(const string& file_name) { - return data_->LoadFromFile(file_name); + return data_->LoadFromFile(file_name, nullptr); } bool Config::SaveToFile(const string& file_name) { return data_->SaveToFile(file_name); } -bool Config::IsNull(const string& key) { - auto p = data_->Traverse(key); +bool Config::IsNull(const string& path) { + auto p = data_->Traverse(path); return !p || p->type() == ConfigItem::kNull; } -bool Config::IsValue(const string& key) { - auto p = data_->Traverse(key); +bool Config::IsValue(const string& path) { + auto p = data_->Traverse(path); return !p || p->type() == ConfigItem::kScalar; } -bool Config::IsList(const string& key) { - auto p = data_->Traverse(key); +bool Config::IsList(const string& path) { + auto p = data_->Traverse(path); return !p || p->type() == ConfigItem::kList; } -bool Config::IsMap(const string& key) { - auto p = data_->Traverse(key); +bool Config::IsMap(const string& path) { + auto p = data_->Traverse(path); return !p || p->type() == ConfigItem::kMap; } -bool Config::GetBool(const string& key, bool* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); +bool Config::GetBool(const string& path, bool* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); return p && p->GetBool(value); } -bool Config::GetInt(const string& key, int* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); +bool Config::GetInt(const string& path, int* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); return p && p->GetInt(value); } -bool Config::GetDouble(const string& key, double* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); +bool Config::GetDouble(const string& path, double* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); return p && p->GetDouble(value); } -bool Config::GetString(const string& key, string* value) { - DLOG(INFO) << "read: " << key; - auto p = As(data_->Traverse(key)); +bool Config::GetString(const string& path, string* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); return p && p->GetString(value); } -an Config::GetItem(const string& key) { - DLOG(INFO) << "read: " << key; - return data_->Traverse(key); +size_t Config::GetListSize(const string& path) { + DLOG(INFO) << "read: " << path; + auto list = GetList(path); + return list ? list->size() : 0; } -an Config::GetValue(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); +an Config::GetItem(const string& path) { + DLOG(INFO) << "read: " << path; + return data_->Traverse(path); } -an Config::GetList(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); +an Config::GetValue(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); } -an Config::GetMap(const string& key) { - DLOG(INFO) << "read: " << key; - return As(data_->Traverse(key)); +an Config::GetList(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); } -bool Config::SetBool(const string& key, bool value) { - return SetItem(key, New(value)); +an Config::GetMap(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); } -bool Config::SetInt(const string& key, int value) { - return SetItem(key, New(value)); +bool Config::SetBool(const string& path, bool value) { + return SetItem(path, New(value)); } -bool Config::SetDouble(const string& key, double value) { - return SetItem(key, New(value)); +bool Config::SetInt(const string& path, int value) { + return SetItem(path, New(value)); } -bool Config::SetString(const string& key, const char* value) { - return SetItem(key, New(value)); +bool Config::SetDouble(const string& path, double value) { + return SetItem(path, New(value)); } -bool Config::SetString(const string& key, const string& value) { - return SetItem(key, New(value)); +bool Config::SetString(const string& path, const char* value) { + return SetItem(path, New(value)); } -bool Config::SetItem(const string& key, an item) { - return data_->TraverseWrite(key, item); +bool Config::SetString(const string& path, const string& value) { + return SetItem(path, New(value)); +} + +bool Config::SetItem(const string& path, an item) { + return data_->TraverseWrite(path, item); } an Config::GetItem() const { @@ -143,9 +149,9 @@ string ConfigComponent::GetConfigFilePath(const string& config_id) { } Config* ConfigComponent::Create(const string& config_id) { - string path(GetConfigFilePath(config_id)); - DLOG(INFO) << "config file path: " << path; - return new Config(path); + string file_path(GetConfigFilePath(config_id)); + DLOG(INFO) << "config file path: " << file_path; + return new Config(file_path); } } // namespace rime diff --git a/src/rime/config/config_component.h b/src/rime/config/config_component.h index eb8dc67300..8b03b5b246 100644 --- a/src/rime/config/config_component.h +++ b/src/rime/config/config_component.h @@ -30,35 +30,31 @@ class Config : public Class, public ConfigItemRef { bool LoadFromFile(const string& file_name); bool SaveToFile(const string& file_name); - // access a tree node of a particular type with "path/to/key" - bool IsNull(const string& key); - bool IsValue(const string& key); - bool IsList(const string& key); - bool IsMap(const string& key); - bool GetBool(const string& key, bool* value); - bool GetInt(const string& key, int* value); - bool GetDouble(const string& key, double* value); - bool GetString(const string& key, string* value); + // access a tree node of a particular type with "path/to/node" + bool IsNull(const string& path); + bool IsValue(const string& path); + bool IsList(const string& path); + bool IsMap(const string& path); + bool GetBool(const string& path, bool* value); + bool GetInt(const string& path, int* value); + bool GetDouble(const string& path, double* value); + bool GetString(const string& path, string* value); + size_t GetListSize(const string& path); - an GetItem(const string& key); - an GetValue(const string& key); - an GetList(const string& key); - an GetMap(const string& key); + an GetItem(const string& path); + an GetValue(const string& path); + an GetList(const string& path); + an GetMap(const string& path); // setters - bool SetBool(const string& key, bool value); - bool SetInt(const string& key, int value); - bool SetDouble(const string& key, double value); - bool SetString(const string& key, const char* value); - bool SetString(const string& key, const string& value); - // setter for adding / replacing items in the tree - bool SetItem(const string& key, an item); - - template - Config& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } + bool SetBool(const string& path, bool value); + bool SetInt(const string& path, int value); + bool SetDouble(const string& path, double value); + bool SetString(const string& path, const char* value); + bool SetString(const string& path, const string& value); + // setter for adding or replacing items in the tree + bool SetItem(const string& path, an item); + using ConfigItemRef::operator=; protected: an GetItem() const; diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 7cd5e23a8b..c391f08ca2 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include @@ -22,7 +24,7 @@ bool ConfigData::LoadFromStream(std::istream& stream) { } try { YAML::Node doc = YAML::Load(stream); - root = ConvertFromYaml(doc); + root = ConvertFromYaml(doc, nullptr); } catch (YAML::Exception& e) { LOG(ERROR) << "Error parsing YAML: " << e.what(); @@ -47,7 +49,8 @@ bool ConfigData::SaveToStream(std::ostream& stream) { return true; } -bool ConfigData::LoadFromFile(const string& file_name) { +bool ConfigData::LoadFromFile(const string& file_name, + ConfigCompiler* compiler) { // update status file_name_ = file_name; modified_ = false; @@ -59,7 +62,7 @@ bool ConfigData::LoadFromFile(const string& file_name) { LOG(INFO) << "loading config file '" << file_name << "'."; try { YAML::Node doc = YAML::LoadFile(file_name); - root = ConvertFromYaml(doc); + root = ConvertFromYaml(doc, compiler); } catch (YAML::Exception& e) { LOG(ERROR) << "Error parsing YAML: " << e.what(); @@ -82,23 +85,28 @@ bool ConfigData::SaveToFile(const string& file_name) { return SaveToStream(out); } -static inline bool IsListItemReference(const string& key) { +bool ConfigData::IsListItemReference(const string& key) { return !key.empty() && key[0] == '@'; } -static size_t ResolveListIndex(an p, const string& key, - bool read_only = false) { - //if (!IsListItemReference(key)) { - // return 0; - //} - an list = As(p); +string ConfigData::FormatListIndex(size_t index) { + return boost::str(boost::format("@%u") % index); +} + +static const string kAfter("after"); +static const string kBefore("before"); +static const string kLast("last"); +static const string kNext("next"); + +size_t ConfigData::ResolveListIndex(an item, const string& key, + bool read_only) { + if (!IsListItemReference(key)) { + return 0; + } + an list = As(item); if (!list) { return 0; } - const string kAfter("after"); - const string kBefore("before"); - const string kLast("last"); - const string kNext("next"); size_t cursor = 1; unsigned int index = 0; bool will_insert = false; @@ -134,51 +142,50 @@ static size_t ResolveListIndex(an p, const string& key, return index; } -bool ConfigData::TraverseWrite(const string& key, an item) { - LOG(INFO) << "write: " << key; - if (key.empty() || key == "/") { +class ConfigDataRootRef : public ConfigItemRef { + public: + ConfigDataRootRef(ConfigData* data) : ConfigItemRef(nullptr), data_(data) { + } + an GetItem() const override { + return data_->root; + } + void SetItem(an item) override { + data_->root = item; + } + private: + ConfigData* data_; +}; + +bool TraverseWriteFrom(ConfigItemRef& root, const string& path, + an item) { + if (path.empty() || path == "/") { root = item; - set_modified(); return true; } - if (!root) { - root = New(); - } - an p(root); - vector keys; - boost::split(keys, key, boost::is_any_of("/")); + an p = root; + vector keys = ConfigData::SplitPath(path); size_t k = keys.size() - 1; for (size_t i = 0; i <= k; ++i) { - ConfigItem::ValueType node_type = ConfigItem::kMap; - size_t list_index = 0; - if (IsListItemReference(keys[i])) { - node_type = ConfigItem::kList; - list_index = ResolveListIndex(p, keys[i]); - DLOG(INFO) << "list index " << keys[i] << " == " << list_index; - } - if (!p || p->type() != node_type) { + bool is_list = ConfigData::IsListItemReference(keys[i]); + auto node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; + if (p && p->type() != node_type) { return false; } - if (i == k) { - if (node_type == ConfigItem::kList) { - As(p)->SetAt(list_index, item); + if (i == 0 && !p) { + if (is_list) { + p = New(); + } else { + p = New(); } - else { - As(p)->Set(keys[i], item); - } - set_modified(); - return true; + root = p; } - else { - an next; - if (node_type == ConfigItem::kList) { - next = As(p)->GetAt(list_index); - } - else { - next = As(p)->Get(keys[i]); - } + size_t list_index = is_list ? ConfigData::ResolveListIndex(p, keys[i]) : 0; + if (i < k) { + an next = is_list ? + As(p)->GetAt(list_index) : + As(p)->Get(keys[i]); if (!next) { - if (IsListItemReference(keys[i + 1])) { + if (ConfigData::IsListItemReference(keys[i + 1])) { DLOG(INFO) << "creating list node for key: " << keys[i + 1]; next = New(); } @@ -186,7 +193,7 @@ bool ConfigData::TraverseWrite(const string& key, an item) { DLOG(INFO) << "creating map node for key: " << keys[i + 1]; next = New(); } - if (node_type == ConfigItem::kList) { + if (is_list) { As(p)->SetAt(list_index, next); } else { @@ -194,18 +201,45 @@ bool ConfigData::TraverseWrite(const string& key, an item) { } } p = next; + } else { + if (is_list) { + As(p)->SetAt(list_index, item); + } else { + As(p)->Set(keys[i], item); + } } } - return false; + return true; } -an ConfigData::Traverse(const string& key) { - DLOG(INFO) << "traverse: " << key; - if (key.empty() || key == "/") { - return root; +bool ConfigData::TraverseWrite(const string& path, an item) { + LOG(INFO) << "write: " << path; + ConfigDataRootRef root(this); + bool result = TraverseWriteFrom(root, path, item); + if (result) { + set_modified(); } + return result; +} + +vector ConfigData::SplitPath(const string& path) { vector keys; - boost::split(keys, key, boost::is_any_of("/")); + auto is_separator = boost::is_any_of("/"); + auto trimmed_path = boost::trim_left_copy_if(path, is_separator); + boost::split(keys, trimmed_path, is_separator); + return keys; +} + +string ConfigData::JoinPath(const vector& keys) { + return boost::join(keys, "/"); +} + +an ConfigData::Traverse(const string& path) { + DLOG(INFO) << "traverse: " << path; + if (path.empty() || path == "/") { + return root; + } + vector keys = SplitPath(path); // find the YAML::Node, and wrap it! an p = root; for (auto it = keys.begin(), end = keys.end(); it != end; ++it) { @@ -228,7 +262,8 @@ an ConfigData::Traverse(const string& key) { return p; } -an ConfigData::ConvertFromYaml(const YAML::Node& node) { +an ConfigData::ConvertFromYaml( + const YAML::Node& node, ConfigCompiler* compiler) { if (YAML::NodeType::Null == node.Type()) { return nullptr; } @@ -238,7 +273,13 @@ an ConfigData::ConvertFromYaml(const YAML::Node& node) { if (YAML::NodeType::Sequence == node.Type()) { auto config_list = New(); for (auto it = node.begin(), end = node.end(); it != end; ++it) { - config_list->Append(ConvertFromYaml(*it)); + if (compiler) { + compiler->Push(config_list, config_list->size()); + } + config_list->Append(ConvertFromYaml(*it, compiler)); + if (compiler) { + compiler->Pop(); + } } return config_list; } @@ -246,7 +287,16 @@ an ConfigData::ConvertFromYaml(const YAML::Node& node) { auto config_map = New(); for (auto it = node.begin(), end = node.end(); it != end; ++it) { string key = it->first.as(); - config_map->Set(key, ConvertFromYaml(it->second)); + if (compiler) { + compiler->Push(config_map, key); + } + auto value = ConvertFromYaml(it->second, compiler); + if (compiler) { + compiler->Pop(); + } + if (!compiler || !compiler->Parse(key, value)) { + config_map->Set(key, value); + } } return config_map; } diff --git a/src/rime/config/config_data.h b/src/rime/config/config_data.h index 128006b3ef..c7305f84be 100644 --- a/src/rime/config/config_data.h +++ b/src/rime/config/config_data.h @@ -12,6 +12,7 @@ namespace rime { +class ConfigCompiler; class ConfigItem; class ConfigData { @@ -21,18 +22,27 @@ class ConfigData { bool LoadFromStream(std::istream& stream); bool SaveToStream(std::ostream& stream); - bool LoadFromFile(const string& file_name); + bool LoadFromFile(const string& file_name, ConfigCompiler* compiler); bool SaveToFile(const string& file_name); - bool TraverseWrite(const string& key, an item); - an Traverse(const string& key); + bool TraverseWrite(const string& path, an item); + an Traverse(const string& path); + static vector SplitPath(const string& path); + static string JoinPath(const vector& keys); + static bool IsListItemReference(const string& key); + static string FormatListIndex(size_t index); + static size_t ResolveListIndex(an list, const string& key, + bool read_only = false); + + const string& file_name() const { return file_name_; } bool modified() const { return modified_; } void set_modified() { modified_ = true; } an root; protected: - static an ConvertFromYaml(const YAML::Node& yaml_node); + static an ConvertFromYaml(const YAML::Node& yaml_node, + ConfigCompiler* compiler); static void EmitYaml(an node, YAML::Emitter* emitter, int depth); diff --git a/src/rime/config/config_data_manager.cc b/src/rime/config/config_data_manager.cc index 25a6ee0d02..302613a07a 100644 --- a/src/rime/config/config_data_manager.cc +++ b/src/rime/config/config_data_manager.cc @@ -3,6 +3,8 @@ // Distributed under the BSD License // +#include +#include #include #include @@ -18,32 +20,22 @@ ConfigDataManager& ConfigDataManager::instance() { an ConfigDataManager::GetConfigData(const string& config_file_path) { - an sp; // keep a weak reference to the shared config data in the manager weak& wp((*this)[config_file_path]); if (wp.expired()) { // create a new copy and load it - sp = New(); - sp->LoadFromFile(config_file_path); - wp = sp; + ConfigCompiler compiler; + auto resource = compiler.Compile(config_file_path); + if (!resource || !compiler.Link(resource)) { + LOG(ERROR) << "error loading config from " << config_file_path; + } + if (!resource) { + return New(); + } + wp = resource->data; + return resource->data; } - else { // obtain the shared copy - sp = wp.lock(); - } - return sp; -} - -bool ConfigDataManager::ReloadConfigData(const string& config_file_path) { - iterator it = find(config_file_path); - if (it == end()) { // never loaded - return false; - } - an sp = it->second.lock(); - if (!sp) { // already been freed - erase(it); - return false; - } - sp->LoadFromFile(config_file_path); - return true; + // obtain the shared copy + return wp.lock(); } } // namespace rime diff --git a/src/rime/config/config_data_manager.h b/src/rime/config/config_data_manager.h index cd4bf9e2ad..d87eb45478 100644 --- a/src/rime/config/config_data_manager.h +++ b/src/rime/config/config_data_manager.h @@ -15,7 +15,6 @@ class ConfigData; class ConfigDataManager : public map> { public: an GetConfigData(const string& config_file_path); - bool ReloadConfigData(const string& config_file_path); static ConfigDataManager& instance(); diff --git a/src/rime/config/config_types.h b/src/rime/config/config_types.h index 9057d5cd75..4f2a34ec96 100644 --- a/src/rime/config/config_types.h +++ b/src/rime/config/config_types.h @@ -96,6 +96,20 @@ class ConfigMap : public ConfigItem { Map map_; }; +namespace { + +template +an AsConfigItem(const T& x, const std::false_type&) { + return New(x); +}; + +template +an AsConfigItem(const T& x, const std::true_type&) { + return x; +}; + +} // namespace + class ConfigData; class ConfigListEntryRef; class ConfigMapEntryRef; @@ -104,9 +118,16 @@ class ConfigItemRef { public: ConfigItemRef(const an& data) : data_(data) { } + virtual ~ConfigItemRef() { + } operator an () const { return GetItem(); } + template + ConfigItemRef& operator= (const T& x) { + SetItem(AsConfigItem(x, std::is_convertible>())); + return *this; + } ConfigListEntryRef operator[] (size_t index); ConfigMapEntryRef operator[] (const string& key); @@ -140,31 +161,13 @@ class ConfigItemRef { an data_; }; -namespace { - -template -an AsConfigItem(const T& x, const std::false_type&) { - return New(x); -}; - -template -an AsConfigItem(const T& x, const std::true_type&) { - return x; -}; - -} // namespace - class ConfigListEntryRef : public ConfigItemRef { public: ConfigListEntryRef(an data, an list, size_t index) : ConfigItemRef(data), list_(list), index_(index) { } - template - ConfigListEntryRef& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } + using ConfigItemRef::operator=; protected: an GetItem() const { return list_->GetAt(index_); @@ -184,11 +187,7 @@ class ConfigMapEntryRef : public ConfigItemRef { an map, const string& key) : ConfigItemRef(data), map_(map), key_(key) { } - template - ConfigMapEntryRef& operator= (const T& x) { - SetItem(AsConfigItem(x, std::is_convertible>())); - return *this; - } + using ConfigItemRef::operator=; protected: an GetItem() const { return map_->Get(key_); diff --git a/src/rime_api.cc b/src/rime_api.cc index f0f838b3da..0998421287 100644 --- a/src/rime_api.cc +++ b/src/rime_api.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 89ef556cbe..f458d64f49 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,6 +10,8 @@ add_dependencies(rime_test ${rime_library} ${rime_gears_library}) file(COPY ${PROJECT_SOURCE_DIR}/data/test/config_test.yaml DESTINATION ${EXECUTABLE_OUTPUT_PATH}) +file(COPY ${PROJECT_SOURCE_DIR}/data/test/config_compiler_test.yaml + DESTINATION ${EXECUTABLE_OUTPUT_PATH}) file(COPY ${PROJECT_SOURCE_DIR}/data/test/dictionary_test.dict.yaml DESTINATION ${EXECUTABLE_OUTPUT_PATH}) diff --git a/test/config_compiler_test.cc b/test/config_compiler_test.cc new file mode 100644 index 0000000000..42e6304d23 --- /dev/null +++ b/test/config_compiler_test.cc @@ -0,0 +1,104 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#include +#include +#include +#include + +using namespace rime; + +class RimeConfigCompilerTest : public ::testing::Test { + protected: + RimeConfigCompilerTest() = default; + + virtual void SetUp() { + component_.reset(new ConfigComponent("%s.yaml")); + config_.reset(component_->Create("config_compiler_test")); + } + + virtual void TearDown() { + } + + the component_; + the config_; +}; + +TEST_F(RimeConfigCompilerTest, IncludeLocalReference) { + for (size_t i = 0; i < 4; ++i) { + auto prefix = boost::str(boost::format("include_local_reference/@%u/") % i); + EXPECT_TRUE(config_->IsNull(prefix + "__include")); + string actual; + EXPECT_TRUE(config_->GetString(prefix + "terrans/player", &actual)); + EXPECT_EQ("slayers_boxer", actual); + EXPECT_TRUE(config_->GetString(prefix + "protoss/player", &actual)); + EXPECT_EQ("grrrr", actual); + EXPECT_TRUE(config_->GetString(prefix + "zerg/player", &actual)); + EXPECT_EQ("yellow", actual); + } +} + +TEST_F(RimeConfigCompilerTest, IncludeExternalReference) { + const string& prefix = "include_external_reference/"; + EXPECT_TRUE(config_->IsNull(prefix + "terrans/__include")); + int mineral = 0; + EXPECT_TRUE(config_->GetInt(prefix + "terrans/tank/cost/mineral", &mineral)); + EXPECT_EQ(150, mineral); + int gas = 0; + EXPECT_TRUE(config_->GetInt(prefix + "terrans/tank/cost/gas", &gas)); + EXPECT_EQ(100, gas); + EXPECT_TRUE(config_->IsNull(prefix + "protoss")); + EXPECT_TRUE(config_->IsNull(prefix + "zerg")); +} + +TEST_F(RimeConfigCompilerTest, IncludeExternalFile) { + const string& prefix = "include_external_file/"; + EXPECT_TRUE(config_->IsNull(prefix + "__include")); + EXPECT_TRUE(config_->IsMap(prefix + "terrans")); + EXPECT_TRUE(config_->IsMap(prefix + "protoss")); + EXPECT_TRUE(config_->IsMap(prefix + "zerg")); + int number = 0; + EXPECT_TRUE(config_->GetInt(prefix + "terrans/supply/produced", &number)); + EXPECT_EQ(28, number); + EXPECT_TRUE(config_->IsList(prefix + "protoss/air_force")); + string name; + EXPECT_TRUE(config_->GetString(prefix + "zerg/queen", &name)); + EXPECT_EQ("Kerrigan", name); +} + +TEST_F(RimeConfigCompilerTest, PatchReference) { + const string& prefix = "patch_reference/"; + EXPECT_TRUE(config_->IsNull(prefix + "__patch")); + EXPECT_EQ(4, config_->GetListSize(prefix + "battlefields")); + string map; + EXPECT_TRUE(config_->GetString(prefix + "battlefields/@3", &map)); + EXPECT_EQ("match point", map); +} + +TEST_F(RimeConfigCompilerTest, PatchLiteral) { + const string& prefix = "patch_literal/"; + EXPECT_TRUE(config_->IsNull(prefix + "__patch")); + EXPECT_EQ(6, config_->GetListSize(prefix + "zerg/ground_units")); + string unit; + EXPECT_TRUE(config_->GetString(prefix + "zerg/ground_units/@5", &unit)); + EXPECT_EQ("lurker", unit); + // assert that we patched on a copy of the included node + EXPECT_EQ(5, config_->GetListSize("starcraft/zerg/ground_units")); +} + +TEST_F(RimeConfigCompilerTest, PatchList) { + const string& prefix = "patch_list/"; + EXPECT_TRUE(config_->IsNull(prefix + "protoss/__patch")); + EXPECT_EQ(8, config_->GetListSize(prefix + "protoss/ground_units")); + string unit; + config_->GetString(prefix + "protoss/ground_units/@6", &unit); + EXPECT_EQ("dark templar", unit); + config_->GetString(prefix + "protoss/ground_units/@7", &unit); + EXPECT_EQ("dark archon", unit); + // assert that we patched on a copy of the included node + EXPECT_EQ(6, config_->GetListSize("starcraft/protoss/ground_units")); +} + +// TODO: test failure cases From c2abe7e06feeb226754e81977e88600e62766fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Mon, 26 Jun 2017 15:08:44 +0800 Subject: [PATCH 04/11] config_compiler: add test case for chaining dependencies --- data/test/config_compiler_test.yaml | 68 +++++++++++++++++++++++++++++ src/rime/config/config_compiler.cc | 45 +++++++++++++------ src/rime/config/config_compiler.h | 2 +- test/config_compiler_test.cc | 18 +++++++- 4 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 data/test/config_compiler_test.yaml diff --git a/data/test/config_compiler_test.yaml b/data/test/config_compiler_test.yaml new file mode 100644 index 0000000000..9a29bab788 --- /dev/null +++ b/data/test/config_compiler_test.yaml @@ -0,0 +1,68 @@ +# tests for ConfigCompiler features + +include_local_reference: + - __include: starcraft + - __include: /starcraft + - __include: :starcraft + - __include: :/starcraft + +include_external_reference: + terrans: + __include: config_test:/terrans + +include_external_file: + __include: config_test:/ + +patch_reference: + __patch: /local/patch + battlefields: + - lost temple + - luna + - hunters + +patch_literal: + __patch: + zerg/ground_units/@next: lurker + zerg: + __include: /starcraft/zerg + +patch_list: + protoss: + __include: /starcraft/protoss + __patch: + - protoss/ground_units/@next: dark templar + - protoss/ground_units/@next: dark archon + +dependency_chaining: + alpha: + __include: /dependency_chaining/beta + beta: + __include: /dependency_chaining/epsilon + delta: + __include: /dependency_chaining/beta + epsilon: success + +local: + patch: + battlefields/@next: match point + +starcraft: + terrans: + player: slayers_boxer + protoss: + ground_units: + - probe + - zealot + - dragoon + - high templar + - archon + - reaver + player: grrrr + zerg: + ground_units: + - drone + - zergling + - hydralisk + - ultralisk + - defiler + player: yellow diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 90266fc0d7..286fffa8cc 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -127,7 +127,7 @@ Reference::Reference(const string& qualified_path, } bool PendingChild::Resolve(ConfigCompiler* compiler) { - return compiler->ResolveDependencies(child_path, child_ref); + return compiler->ResolveDependencies(child_path); } static an ResolveReference(ConfigCompiler* compiler, @@ -248,42 +248,60 @@ static inline an if_resolved(ConfigCompiler* compiler, return item && compiler->resolved(path) ? item : nullptr; } +static bool ResolveBlockingDependencies(ConfigCompiler* compiler, + const string& path) { + if (!compiler->blocking(path)) { + return true; + } + LOG(INFO) << "blocking node: " << path; + if (compiler->ResolveDependencies(path)) { + LOG(INFO) << "resolved blocking node:" << path; + return true; + } + return false; +} + static an GetResolvedItem(ConfigCompiler* compiler, an resource, const string& path) { - if (!resource || compiler->blocking(resource->name + ":")) { + LOG(INFO) << "GetResolvedItem(" << resource->name << ":/" << path << ")"; + string node_path = resource->name + ":"; + if (!resource || compiler->blocking(node_path)) { return nullptr; } - LOG(INFO) << "GetResolvedItem(" << resource->name << ":/" << path << ")"; an result = *resource; if (path.empty() || path == "/") { - return if_resolved(compiler, result, resource->name + ":"); + return if_resolved(compiler, result, node_path); } - vector normalized_keys = {resource->name + ":"}; vector keys = ConfigData::SplitPath(path); for (const auto& key : keys) { if (Is(result)) { if (ConfigData::IsListItemReference(key)) { size_t index = ConfigData::ResolveListIndex(result, key, true); + (node_path += "/") += ConfigData::FormatListIndex(index); + if (!ResolveBlockingDependencies(compiler, node_path)) { + return nullptr; + } result = As(result)->GetAt(index); - normalized_keys.push_back(ConfigData::FormatListIndex(index)); } else { result.reset(); } } else if (Is(result)) { LOG(INFO) << "advance with key: " << key; + (node_path += "/") += key; + if (!ResolveBlockingDependencies(compiler, node_path)) { + return nullptr; + } result = As(result)->Get(key); - normalized_keys.push_back(key); } else { result.reset(); } - auto node_path = ConfigData::JoinPath(normalized_keys); - if (!result || compiler->blocking(node_path)) { - LOG(INFO) << (result ? "blocking: " : "missing: ") << node_path; + if (!result) { + LOG(INFO) << "missing node: " << node_path; return nullptr; } } - return if_resolved(compiler, result, ConfigData::JoinPath(normalized_keys)); + return if_resolved(compiler, result, node_path); } bool ConfigCompiler::blocking(const string& full_path) const { @@ -382,11 +400,10 @@ bool ConfigCompiler::Link(an target) { LOG(INFO) << "resource not found: " << target->name; return false; } - return ResolveDependencies(found->first + ":", found->second); + return ResolveDependencies(found->first + ":"); } -bool ConfigCompiler::ResolveDependencies(const string& path, - an target) { +bool ConfigCompiler::ResolveDependencies(const string& path) { LOG(INFO) << "ResolveDependencies(" << path << ")"; auto& deps = graph_->deps[path]; for (auto iter = deps.begin(); iter != deps.end(); ) { diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index f714a269bc..78f602501f 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -48,7 +48,7 @@ class ConfigCompiler { bool blocking(const string& full_path) const; bool pending(const string& full_path) const; bool resolved(const string& full_path) const; - bool ResolveDependencies(const string& path, an target); + bool ResolveDependencies(const string& path); private: the graph_; diff --git a/test/config_compiler_test.cc b/test/config_compiler_test.cc index 42e6304d23..a6a5a212a9 100644 --- a/test/config_compiler_test.cc +++ b/test/config_compiler_test.cc @@ -93,12 +93,26 @@ TEST_F(RimeConfigCompilerTest, PatchList) { EXPECT_TRUE(config_->IsNull(prefix + "protoss/__patch")); EXPECT_EQ(8, config_->GetListSize(prefix + "protoss/ground_units")); string unit; - config_->GetString(prefix + "protoss/ground_units/@6", &unit); + EXPECT_TRUE(config_->GetString(prefix + "protoss/ground_units/@6", &unit)); EXPECT_EQ("dark templar", unit); - config_->GetString(prefix + "protoss/ground_units/@7", &unit); + EXPECT_TRUE(config_->GetString(prefix + "protoss/ground_units/@7", &unit)); EXPECT_EQ("dark archon", unit); // assert that we patched on a copy of the included node EXPECT_EQ(6, config_->GetListSize("starcraft/protoss/ground_units")); } +TEST_F(RimeConfigCompilerTest, DependencyChaining) { + const string& prefix = "dependency_chaining/"; + EXPECT_TRUE(config_->IsNull(prefix + "alpha/__include")); + EXPECT_TRUE(config_->IsNull(prefix + "beta/__include")); + EXPECT_TRUE(config_->IsNull(prefix + "delta/__include")); + string value; + EXPECT_TRUE(config_->GetString(prefix + "alpha", &value)); + EXPECT_EQ("success", value); + EXPECT_TRUE(config_->GetString(prefix + "beta", &value)); + EXPECT_EQ("success", value); + EXPECT_TRUE(config_->GetString(prefix + "delta", &value)); + EXPECT_EQ("success", value); +} + // TODO: test failure cases From 3c4e5f5fa5676505e8178d61abe35965998209c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Mon, 26 Jun 2017 20:35:36 +0800 Subject: [PATCH 05/11] config_data: implement copy on write --- src/rime/config/config_compiler.cc | 4 +- src/rime/config/config_data.cc | 82 ++++++++++++++---------------- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 286fffa8cc..3ff9e676ad 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -158,7 +158,7 @@ bool PatchReference::Resolve(ConfigCompiler* compiler) { } // defined in config_data.cc -bool TraverseWriteFrom(ConfigItemRef& root, const string& path, +bool TraverseWriteFrom(an root, const string& path, an item); bool PatchLiteral::Resolve(ConfigCompiler* compiler) { @@ -166,7 +166,7 @@ bool PatchLiteral::Resolve(ConfigCompiler* compiler) { for (const auto& entry : *patch) { const auto& path = entry.first; const auto& value = entry.second; - if (!TraverseWriteFrom(*target, path, value)) { + if (!TraverseWriteFrom(target, path, value)) { LOG(ERROR) << "error applying patch to " << path; success = false; } diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index c391f08ca2..8cf7f39ebb 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -156,65 +156,59 @@ class ConfigDataRootRef : public ConfigItemRef { ConfigData* data_; }; -bool TraverseWriteFrom(ConfigItemRef& root, const string& path, +static an CopyOnWrite(const an& item, + const string& key, + bool is_list) { + if (!item) { + LOG(INFO) << "creating node: " << key; + if (is_list) + return New(); + else + return New(); + } + auto expected_node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; + if (item->type() != expected_node_type) { + LOG(ERROR) << "copy on write failed; incompatible node type: " << key; + return nullptr; + } + DLOG(INFO) << "copy on write: " << key; + if (is_list) + return New(*As(item)); + else + return New(*As(item)); +} + +bool TraverseWriteFrom(an root, const string& path, an item) { if (path.empty() || path == "/") { - root = item; + *root = item; return true; } - an p = root; + an head = root; vector keys = ConfigData::SplitPath(path); - size_t k = keys.size() - 1; - for (size_t i = 0; i <= k; ++i) { - bool is_list = ConfigData::IsListItemReference(keys[i]); - auto node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; - if (p && p->type() != node_type) { + size_t n = keys.size(); + for (size_t i = 0; i < n; ++i) { + const auto& key = keys[i]; + bool is_list = ConfigData::IsListItemReference(key); + auto copy = CopyOnWrite(*head, key, is_list); + if (!copy) { return false; } - if (i == 0 && !p) { - if (is_list) { - p = New(); - } else { - p = New(); - } - root = p; - } - size_t list_index = is_list ? ConfigData::ResolveListIndex(p, keys[i]) : 0; - if (i < k) { - an next = is_list ? - As(p)->GetAt(list_index) : - As(p)->Get(keys[i]); - if (!next) { - if (ConfigData::IsListItemReference(keys[i + 1])) { - DLOG(INFO) << "creating list node for key: " << keys[i + 1]; - next = New(); - } - else { - DLOG(INFO) << "creating map node for key: " << keys[i + 1]; - next = New(); - } - if (is_list) { - As(p)->SetAt(list_index, next); - } - else { - As(p)->Set(keys[i], next); - } - } - p = next; + *head = copy; + if (is_list) { + head = New(nullptr, As(copy), + ConfigData::ResolveListIndex(copy, key)); } else { - if (is_list) { - As(p)->SetAt(list_index, item); - } else { - As(p)->Set(keys[i], item); - } + head = New(nullptr, As(copy), key); } } + *head = item; return true; } bool ConfigData::TraverseWrite(const string& path, an item) { LOG(INFO) << "write: " << path; - ConfigDataRootRef root(this); + auto root = New(this); bool result = TraverseWriteFrom(root, path, item); if (result) { set_modified(); From 20385951ecf9e4f9292f48190bafaeb81feb0143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Tue, 27 Jun 2017 00:58:10 +0800 Subject: [PATCH 06/11] config_data: transactional copy on write --- src/rime/config/config_compiler.cc | 6 +-- src/rime/config/config_data.cc | 79 ++++++++++++++++++++---------- src/rime/config/config_types.h | 3 ++ 3 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 3ff9e676ad..687cfef0a1 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -158,15 +158,15 @@ bool PatchReference::Resolve(ConfigCompiler* compiler) { } // defined in config_data.cc -bool TraverseWriteFrom(an root, const string& path, - an item); +bool TraverseCopyOnWrite(an root, const string& path, + an item); bool PatchLiteral::Resolve(ConfigCompiler* compiler) { bool success = true; for (const auto& entry : *patch) { const auto& path = entry.first; const auto& value = entry.second; - if (!TraverseWriteFrom(target, path, value)) { + if (!TraverseCopyOnWrite(target, path, value)) { LOG(ERROR) << "error applying patch to " << path; success = false; } diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 8cf7f39ebb..7e3f6acae4 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -156,30 +156,57 @@ class ConfigDataRootRef : public ConfigItemRef { ConfigData* data_; }; -static an CopyOnWrite(const an& item, - const string& key, - bool is_list) { - if (!item) { +template +static an Cow(const an& container, const string& key) { + if (!container) { LOG(INFO) << "creating node: " << key; - if (is_list) - return New(); - else - return New(); - } - auto expected_node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; - if (item->type() != expected_node_type) { - LOG(ERROR) << "copy on write failed; incompatible node type: " << key; - return nullptr; + return New(); } DLOG(INFO) << "copy on write: " << key; - if (is_list) - return New(*As(item)); - else - return New(*As(item)); + return New(*container); } -bool TraverseWriteFrom(an root, const string& path, - an item) { +class ConfigMapEntryCowRef : public ConfigItemRef { + public: + ConfigMapEntryCowRef(an parent, string key) + : ConfigItemRef(nullptr), parent_(parent), key_(key) { + } + an GetItem() const override { + auto map = As(**parent_); + return map ? map->Get(key_) : nullptr; + } + void SetItem(an item) override { + auto copy = Cow(As(**parent_), key_); + copy->Set(key_, item); + *parent_ = copy; + } + protected: + an parent_; + string key_; +}; + +class ConfigListEntryCowRef : public ConfigMapEntryCowRef { + public: + ConfigListEntryCowRef(an parent, string key) + : ConfigMapEntryCowRef(parent, key) { + } + an GetItem() const override { + auto list = As(**parent_); + return list ? list->GetAt(index(list, true)) : nullptr; + } + void SetItem(an item) override { + auto copy = Cow(As(**parent_), key_); + copy->SetAt(index(copy, false), item); + *parent_ = copy; + } + private: + size_t index(an list, bool read_only) const { + return ConfigData::ResolveListIndex(list, key_, read_only); + } +}; + +bool TraverseCopyOnWrite(an root, const string& path, + an item) { if (path.empty() || path == "/") { *root = item; return true; @@ -190,16 +217,16 @@ bool TraverseWriteFrom(an root, const string& path, for (size_t i = 0; i < n; ++i) { const auto& key = keys[i]; bool is_list = ConfigData::IsListItemReference(key); - auto copy = CopyOnWrite(*head, key, is_list); - if (!copy) { + auto expected_node_type = is_list ? ConfigItem::kList : ConfigItem::kMap; + an existing_node = *head; + if (existing_node && existing_node->type() != expected_node_type) { + LOG(ERROR) << "copy on write failed; incompatible node type: " << key; return false; } - *head = copy; if (is_list) { - head = New(nullptr, As(copy), - ConfigData::ResolveListIndex(copy, key)); + head = New(head, key); } else { - head = New(nullptr, As(copy), key); + head = New(head, key); } } *head = item; @@ -209,7 +236,7 @@ bool TraverseWriteFrom(an root, const string& path, bool ConfigData::TraverseWrite(const string& path, an item) { LOG(INFO) << "write: " << path; auto root = New(this); - bool result = TraverseWriteFrom(root, path, item); + bool result = TraverseCopyOnWrite(root, path, item); if (result) { set_modified(); } diff --git a/src/rime/config/config_types.h b/src/rime/config/config_types.h index 4f2a34ec96..27b0ec4ea0 100644 --- a/src/rime/config/config_types.h +++ b/src/rime/config/config_types.h @@ -123,6 +123,9 @@ class ConfigItemRef { operator an () const { return GetItem(); } + an operator* () const { + return GetItem(); + } template ConfigItemRef& operator= (const T& x) { SetItem(AsConfigItem(x, std::is_convertible>())); From 03ee8b43909e42d863f8492abe9d91ccb9bac84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Wed, 16 Aug 2017 16:55:06 +0800 Subject: [PATCH 07/11] feat(resource_resolver): add class and unit test --- src/rime/resource_resolver.cc | 16 ++++++++++++++ src/rime/resource_resolver.h | 40 ++++++++++++++++++++++++++++++++++ test/resource_resolver_test.cc | 17 +++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/rime/resource_resolver.cc create mode 100644 src/rime/resource_resolver.h create mode 100644 test/resource_resolver_test.cc diff --git a/src/rime/resource_resolver.cc b/src/rime/resource_resolver.cc new file mode 100644 index 0000000000..507e36bdb3 --- /dev/null +++ b/src/rime/resource_resolver.cc @@ -0,0 +1,16 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#include + +namespace rime { + +boost::filesystem::path ResourceResolver::ResolvePath(const string& resource_id) { + return boost::filesystem::absolute( + boost::filesystem::path(type_.prefix + resource_id + type_.suffix), + root_path_); +} + +} // namespace rime diff --git a/src/rime/resource_resolver.h b/src/rime/resource_resolver.h new file mode 100644 index 0000000000..8837086c41 --- /dev/null +++ b/src/rime/resource_resolver.h @@ -0,0 +1,40 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#ifndef RIME_RESOURCE_RESOLVER_H_ +#define RIME_RESOURCE_RESOLVER_H_ + +#include +#include + +namespace rime { + +class ResourceResolver; + +struct ResourceType { + string name; + string prefix; + string suffix; +}; + +class ResourceResolver { + public: + explicit ResourceResolver(const ResourceType type) : type_(type) { + } + boost::filesystem::path ResolvePath(const string& resource_id); + void set_root_path(const boost::filesystem::path& root_path) { + root_path_ = root_path; + } + boost::filesystem::path root_path() const { + return root_path_; + } + private: + const ResourceType type_; + boost::filesystem::path root_path_; +}; + +} // namespace rime + +#endif // RIME_RESOURCE_RESOLVER_H_ diff --git a/test/resource_resolver_test.cc b/test/resource_resolver_test.cc new file mode 100644 index 0000000000..9a0aafcab9 --- /dev/null +++ b/test/resource_resolver_test.cc @@ -0,0 +1,17 @@ +#include +#include + +using namespace rime; + +TEST(RimeResourceResolverTest, ResolvePath) { + const auto type = ResourceType{ + "minerals", + "not_", + ".minerals", + }; + the rr(new ResourceResolver(type)); + rr->set_root_path("/starcraft"); + auto actual = rr->ResolvePath("enough"); + boost::filesystem::path expected = "/starcraft/not_enough.minerals"; + EXPECT_TRUE(expected == actual); +} From 02151dadc1c345ee89fd0d121076e822d40e7ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Wed, 16 Aug 2017 21:38:16 +0800 Subject: [PATCH 08/11] feat(resource_resolver): fallback root path --- src/rime/resource_resolver.cc | 14 ++++++++++ src/rime/resource_resolver.h | 20 ++++++++++++-- test/resource_resolver_test.cc | 49 ++++++++++++++++++++++++++++------ 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/rime/resource_resolver.cc b/src/rime/resource_resolver.cc index 507e36bdb3..a479268d03 100644 --- a/src/rime/resource_resolver.cc +++ b/src/rime/resource_resolver.cc @@ -13,4 +13,18 @@ boost::filesystem::path ResourceResolver::ResolvePath(const string& resource_id) root_path_); } +boost::filesystem::path +FallbackResourceResolver::ResolvePath(const string& resource_id) { + auto default_path = ResourceResolver::ResolvePath(resource_id); + if (!boost::filesystem::exists(default_path)) { + auto fallback_path = boost::filesystem::absolute( + boost::filesystem::path(type_.prefix + resource_id + type_.suffix), + fallback_root_path_); + if (boost::filesystem::exists(fallback_path)) { + return fallback_path; + } + } + return default_path; +} + } // namespace rime diff --git a/src/rime/resource_resolver.h b/src/rime/resource_resolver.h index 8837086c41..1f5aa366bb 100644 --- a/src/rime/resource_resolver.h +++ b/src/rime/resource_resolver.h @@ -23,18 +23,34 @@ class ResourceResolver { public: explicit ResourceResolver(const ResourceType type) : type_(type) { } - boost::filesystem::path ResolvePath(const string& resource_id); + virtual ~ResourceResolver() { + } + virtual boost::filesystem::path ResolvePath(const string& resource_id); void set_root_path(const boost::filesystem::path& root_path) { root_path_ = root_path; } boost::filesystem::path root_path() const { return root_path_; } - private: + protected: const ResourceType type_; boost::filesystem::path root_path_; }; +// try fallback path if target file doesn't exist in root path +class FallbackResourceResolver : public ResourceResolver { + public: + explicit FallbackResourceResolver(const ResourceType& type) + : ResourceResolver(type) { + } + boost::filesystem::path ResolvePath(const string& resource_id) override; + void set_fallback_root_path(const boost::filesystem::path& fallback_root_path) { + fallback_root_path_ = fallback_root_path; + } + private: + boost::filesystem::path fallback_root_path_; +}; + } // namespace rime #endif // RIME_RESOURCE_RESOLVER_H_ diff --git a/test/resource_resolver_test.cc b/test/resource_resolver_test.cc index 9a0aafcab9..a9d9b8a6e4 100644 --- a/test/resource_resolver_test.cc +++ b/test/resource_resolver_test.cc @@ -3,15 +3,48 @@ using namespace rime; +static const ResourceType kMineralsType = ResourceType{ + "minerals", + "not_", + ".minerals", +}; + TEST(RimeResourceResolverTest, ResolvePath) { - const auto type = ResourceType{ - "minerals", - "not_", - ".minerals", - }; - the rr(new ResourceResolver(type)); - rr->set_root_path("/starcraft"); - auto actual = rr->ResolvePath("enough"); + ResourceResolver rr(kMineralsType); + rr.set_root_path("/starcraft"); + auto actual = rr.ResolvePath("enough"); boost::filesystem::path expected = "/starcraft/not_enough.minerals"; + EXPECT_TRUE(actual.is_absolute()); EXPECT_TRUE(expected == actual); } + +TEST(RimeResourceResolverTest, FallbackRootPath) { + FallbackResourceResolver rr(kMineralsType); + rr.set_fallback_root_path("fallback"); + boost::filesystem::create_directory("fallback"); + { + boost::filesystem::path nonexistent_default = "not_present.minerals"; + boost::filesystem::remove(nonexistent_default); + auto fallback = boost::filesystem::absolute("fallback/not_present.minerals"); + boost::filesystem::ofstream(fallback).close(); + auto actual = rr.ResolvePath("present"); + EXPECT_TRUE(fallback == actual); + boost::filesystem::remove(fallback); + } + { + auto existent_default = boost::filesystem::absolute("not_falling_back.minerals"); + boost::filesystem::ofstream(existent_default).close(); + auto actual = rr.ResolvePath("falling_back"); + EXPECT_TRUE(existent_default == actual); + boost::filesystem::remove(existent_default); + } + { + auto nonexistent_default = boost::filesystem::absolute("not_any.minerals"); + boost::filesystem::remove(nonexistent_default); + auto nonexistent_fallback = boost::filesystem::absolute("fallback/not_any.minerals"); + boost::filesystem::remove(nonexistent_fallback); + auto actual = rr.ResolvePath("any"); + EXPECT_TRUE(nonexistent_default == actual); + } + boost::filesystem::remove_all("fallback"); +} From 258ea12eb93ea56bc82cb753cb6aa99edc62db5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Thu, 17 Aug 2017 12:21:21 +0800 Subject: [PATCH 09/11] refactor(config_compiler): use resource_resolver --- src/rime/config/config_compiler.cc | 122 +++++++++++++---------------- src/rime/config/config_compiler.h | 20 +++-- src/rime/resource_resolver.cc | 22 ++++++ src/rime/resource_resolver.h | 2 + 4 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 687cfef0a1..9e0f99400d 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -26,16 +26,9 @@ struct PendingChild : Dependency { bool Resolve(ConfigCompiler* compiler) override; }; -struct Reference { - string resource_name; - string local_path; - - Reference(const string& qualified_path, ConfigDependencyGraph* graph); -}; - template StreamT& operator<< (StreamT& stream, const Reference& reference) { - return stream << reference.resource_name << ":" << reference.local_path; + return stream << reference.resource_id << ":" << reference.local_path; } struct IncludeReference : Dependency { @@ -89,43 +82,12 @@ struct ConfigDependencyGraph { key_stack.pop_back(); } - string current_resource_name() const { + string current_resource_id() const { return key_stack.empty() ? string() : boost::trim_right_copy_if(key_stack.front(), boost::is_any_of(":")); } }; -// TODO: create a ResourceResolver component. - -static string FilePathToResource(const string& file_path) { - if (boost::ends_with(file_path, ".yaml")) { - return boost::erase_last_copy(file_path, ".yaml"); - } - return file_path; -} - -static string ResourceToFilePath(const string& resource_name) { - if (boost::ends_with(resource_name, ".yaml")) { - return resource_name; - } - return resource_name + ".yaml"; -} - -Reference::Reference(const string& qualified_path, - ConfigDependencyGraph* graph) { - auto separator = qualified_path.find_first_of(":"); - if (separator == string::npos || separator == 0) { - resource_name = graph->current_resource_name(); - } else { - resource_name = FilePathToResource(qualified_path.substr(0, separator)); - } - if (separator == string::npos) { - local_path = qualified_path; - } else { - local_path = qualified_path.substr(separator + 1); - } -} - bool PendingChild::Resolve(ConfigCompiler* compiler) { return compiler->ResolveDependencies(child_path); } @@ -202,13 +164,35 @@ void ConfigDependencyGraph::Add(an dependency) { } } +static const ResourceType kConfigResourceType = { + "config", + "", + ".yaml", +}; + ConfigCompiler::ConfigCompiler() - : graph_(new ConfigDependencyGraph) { + : resource_resolver_(kConfigResourceType), + graph_(new ConfigDependencyGraph) { } ConfigCompiler::~ConfigCompiler() { } +Reference ConfigCompiler::CreateReference(const string& qualified_path) { + auto separator = qualified_path.find_first_of(":"); + string resource_id = (separator == string::npos || separator == 0) ? + graph_->current_resource_id() : + resource_resolver_.ToResourceId(qualified_path.substr(0, separator)); + string local_path = (separator == string::npos) ? + qualified_path : + qualified_path.substr(separator + 1); + return Reference{resource_id, local_path}; +} + +void ConfigCompiler::AddDependency(an dependency) { + graph_->Add(dependency); +} + void ConfigCompiler::Push(an config_list, size_t index) { graph_->Push( New(nullptr, config_list, index), @@ -226,16 +210,17 @@ void ConfigCompiler::Pop() { } an ConfigCompiler::GetCompiledResource( - const string& resource_name) const { - return graph_->resources[resource_name]; + const string& resource_id) const { + return graph_->resources[resource_id]; } an ConfigCompiler::Compile(const string& file_path) { - auto resource_name = FilePathToResource(file_path); - auto resource = New(resource_name, New()); - graph_->resources[resource_name] = resource; - graph_->Push(resource, resource_name + ":"); - if (!resource->data->LoadFromFile(ResourceToFilePath(resource_name), this)) { + auto resource_id = resource_resolver_.ToResourceId(file_path); + auto resource = New(resource_id, New()); + graph_->resources[resource_id] = resource; + graph_->Push(resource, resource_id + ":"); + if (!resource->data->LoadFromFile( + resource_resolver_.ToFilePath(resource_id), this)) { resource.reset(); } graph_->Pop(); @@ -264,8 +249,8 @@ static bool ResolveBlockingDependencies(ConfigCompiler* compiler, static an GetResolvedItem(ConfigCompiler* compiler, an resource, const string& path) { - LOG(INFO) << "GetResolvedItem(" << resource->name << ":/" << path << ")"; - string node_path = resource->name + ":"; + LOG(INFO) << "GetResolvedItem(" << resource->resource_id << ":/" << path << ")"; + string node_path = resource->resource_id + ":"; if (!resource || compiler->blocking(node_path)) { return nullptr; } @@ -321,10 +306,10 @@ bool ConfigCompiler::resolved(const string& full_path) const { static an ResolveReference(ConfigCompiler* compiler, const Reference& reference) { - auto resource = compiler->GetCompiledResource(reference.resource_name); + auto resource = compiler->GetCompiledResource(reference.resource_id); if (!resource) { - LOG(INFO) << "resource not found, compiling: " << reference.resource_name; - resource = compiler->Compile(reference.resource_name); + LOG(INFO) << "resource not found, compiling: " << reference.resource_id; + resource = compiler->Compile(reference.resource_id); } return GetResolvedItem(compiler, resource, reference.local_path); } @@ -332,12 +317,13 @@ static an ResolveReference(ConfigCompiler* compiler, // Includes contents of nodes at specified paths. // __include: path/to/local/node // __include: filename[.yaml]:/path/to/external/node -static bool ParseInclude(ConfigDependencyGraph* graph, +static bool ParseInclude(ConfigCompiler* compiler, const an& item) { if (Is(item)) { auto path = As(item)->str(); LOG(INFO) << "ParseInclude(" << path << ")"; - graph->Add(New(Reference{path, graph})); + compiler->AddDependency( + New(compiler->CreateReference(path))); return true; } return false; @@ -346,37 +332,37 @@ static bool ParseInclude(ConfigDependencyGraph* graph, // Applies `parser` to every list element if `item` is a list. // __patch: [ first/patch, filename:/second/patch ] // __patch: [{list/@next: 1}, {list/@next: 2}] -static bool ParseList(bool (*parser)(ConfigDependencyGraph*, - const an&), - ConfigDependencyGraph* graph, +static bool ParseList(bool (*parser)(ConfigCompiler*, const an&), + ConfigCompiler* compiler, const an& item) { if (Is(item)) { for (auto list_item : *As(item)) { - if (!parser(graph, list_item)) { + if (!parser(compiler, list_item)) { return false; } } return true; } // not a list - return parser(graph, item); + return parser(compiler, item); } // Modifies subnodes or list elements at specified paths. // __patch: path/to/node // __patch: filename[.yaml]:/path/to/node // __patch: { key/alpha: value, key/beta: value } -static bool ParsePatch(ConfigDependencyGraph* graph, +static bool ParsePatch(ConfigCompiler* compiler, const an& item) { if (Is(item)) { auto path = As(item)->str(); LOG(INFO) << "ParsePatch(" << path << ")"; - graph->Add(New(Reference{path, graph})); + compiler->AddDependency( + New(compiler->CreateReference(path))); return true; } if (Is(item)) { LOG(INFO) << "ParsePatch()"; - graph->Add(New(As(item))); + compiler->AddDependency(New(As(item))); return true; } return false; @@ -385,19 +371,19 @@ static bool ParsePatch(ConfigDependencyGraph* graph, bool ConfigCompiler::Parse(const string& key, const an& item) { LOG(INFO) << "ConfigCompiler::Parse(" << key << ")"; if (key == INCLUDE_DIRECTIVE) { - return ParseInclude(graph_.get(), item); + return ParseInclude(this, item); } if (key == PATCH_DIRECTIVE) { - return ParseList(ParsePatch, graph_.get(), item); + return ParseList(ParsePatch, this, item); } return false; } bool ConfigCompiler::Link(an target) { - LOG(INFO) << "Link(" << target->name << ")"; - auto found = graph_->resources.find(target->name); + LOG(INFO) << "Link(" << target->resource_id << ")"; + auto found = graph_->resources.find(target->resource_id); if (found == graph_->resources.end()) { - LOG(INFO) << "resource not found: " << target->name; + LOG(INFO) << "resource not found: " << target->resource_id; return false; } return ResolveDependencies(found->first + ":"); diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index 78f602501f..719f7e1e33 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -6,17 +6,18 @@ #define RIME_CONFIG_COMPILER_H_ #include +#include #include #include namespace rime { struct ConfigResource : ConfigItemRef { - string name; + string resource_id; an data; - ConfigResource(const string& _name, an _data) - : ConfigItemRef(nullptr), name(_name), data(_data) { + ConfigResource(const string& _id, an _data) + : ConfigItemRef(nullptr), resource_id(_id), data(_data) { } an GetItem() const override { return data->root; @@ -26,6 +27,12 @@ struct ConfigResource : ConfigItemRef { } }; +struct Reference { + string resource_id; + string local_path; +}; + +struct Dependency; struct ConfigDependencyGraph; class ConfigCompiler { @@ -36,13 +43,15 @@ class ConfigCompiler { ConfigCompiler(); virtual ~ConfigCompiler(); + Reference CreateReference(const string& qualified_path); + void AddDependency(an dependency); void Push(an config_list, size_t index); void Push(an config_map, const string& key); bool Parse(const string& key, const an& item); void Pop(); - an GetCompiledResource(const string& resource_name) const; - an Compile(const string& resource_name); + an GetCompiledResource(const string& resource_id) const; + an Compile(const string& resource_id); bool Link(an target); bool blocking(const string& full_path) const; @@ -51,6 +60,7 @@ class ConfigCompiler { bool ResolveDependencies(const string& path); private: + ResourceResolver resource_resolver_; the graph_; }; diff --git a/src/rime/resource_resolver.cc b/src/rime/resource_resolver.cc index a479268d03..a452dfdd1b 100644 --- a/src/rime/resource_resolver.cc +++ b/src/rime/resource_resolver.cc @@ -3,10 +3,32 @@ // Distributed under the BSD License // +#include +#include #include namespace rime { +string ResourceResolver::ToResourceId(const string& file_path) const { + string path_string = boost::filesystem::path(file_path).generic_string(); + bool has_prefix = boost::starts_with(path_string, type_.prefix); + bool has_suffix = boost::ends_with(path_string, type_.suffix); + size_t start = (has_prefix ? type_.prefix.length() : 0); + size_t end = path_string.length() - (has_suffix ? type_.suffix.length() : 0); + return path_string.substr(start, end); +} + +string ResourceResolver::ToFilePath(const string& resource_id) const { + boost::filesystem::path file_path(resource_id); + bool missing_prefix = !file_path.has_parent_path() && + !boost::starts_with(resource_id, type_.prefix); + bool missing_suffix = boost::starts_with(type_.prefix, ".") ? + !file_path.has_extension() : + !boost::ends_with(resource_id, type_.suffix); + return (missing_prefix ? type_.prefix : "") + resource_id + + (missing_suffix ? type_.suffix : ""); +} + boost::filesystem::path ResourceResolver::ResolvePath(const string& resource_id) { return boost::filesystem::absolute( boost::filesystem::path(type_.prefix + resource_id + type_.suffix), diff --git a/src/rime/resource_resolver.h b/src/rime/resource_resolver.h index 1f5aa366bb..8c519a9528 100644 --- a/src/rime/resource_resolver.h +++ b/src/rime/resource_resolver.h @@ -26,6 +26,8 @@ class ResourceResolver { virtual ~ResourceResolver() { } virtual boost::filesystem::path ResolvePath(const string& resource_id); + string ToResourceId(const string& file_path) const; + string ToFilePath(const string& resource_id) const; void set_root_path(const boost::filesystem::path& root_path) { root_path_ = root_path; } From 712e0cf01f51240ba216d10631af8a6329aa364a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Tue, 22 Aug 2017 22:45:23 +0800 Subject: [PATCH 10/11] refactor(config): use a resource_resolver configured by the service --- src/rime/config/config_compiler.cc | 23 ++++---- src/rime/config/config_compiler.h | 8 +-- src/rime/config/config_component.cc | 52 ++++++++++++++----- src/rime/config/config_component.h | 23 ++++---- src/rime/config/config_data_manager.cc | 41 --------------- src/rime/config/config_data_manager.h | 27 ---------- src/rime/core_module.cc | 14 ++--- src/rime/dict/dict_compiler.cc | 4 +- .../{resource_resolver.cc => resource.cc} | 6 +-- src/rime/{resource_resolver.h => resource.h} | 6 +-- src/rime/schema.cc | 9 ++-- src/rime/service.cc | 8 +++ src/rime/service.h | 5 ++ test/config_compiler_test.cc | 2 +- test/config_test.cc | 8 +-- test/resource_resolver_test.cc | 2 +- 16 files changed, 98 insertions(+), 140 deletions(-) delete mode 100644 src/rime/config/config_data_manager.cc delete mode 100644 src/rime/config/config_data_manager.h rename src/rime/{resource_resolver.cc => resource.cc} (90%) rename src/rime/{resource_resolver.h => resource.h} (92%) diff --git a/src/rime/config/config_compiler.cc b/src/rime/config/config_compiler.cc index 9e0f99400d..f2b2830de6 100644 --- a/src/rime/config/config_compiler.cc +++ b/src/rime/config/config_compiler.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -164,14 +165,8 @@ void ConfigDependencyGraph::Add(an dependency) { } } -static const ResourceType kConfigResourceType = { - "config", - "", - ".yaml", -}; - -ConfigCompiler::ConfigCompiler() - : resource_resolver_(kConfigResourceType), +ConfigCompiler::ConfigCompiler(ResourceResolver* resource_resolver) + : resource_resolver_(resource_resolver), graph_(new ConfigDependencyGraph) { } @@ -180,9 +175,9 @@ ConfigCompiler::~ConfigCompiler() { Reference ConfigCompiler::CreateReference(const string& qualified_path) { auto separator = qualified_path.find_first_of(":"); - string resource_id = (separator == string::npos || separator == 0) ? - graph_->current_resource_id() : - resource_resolver_.ToResourceId(qualified_path.substr(0, separator)); + string resource_id = resource_resolver_->ToResourceId( + (separator == string::npos || separator == 0) ? + graph_->current_resource_id() : qualified_path.substr(0, separator)); string local_path = (separator == string::npos) ? qualified_path : qualified_path.substr(separator + 1); @@ -214,13 +209,13 @@ an ConfigCompiler::GetCompiledResource( return graph_->resources[resource_id]; } -an ConfigCompiler::Compile(const string& file_path) { - auto resource_id = resource_resolver_.ToResourceId(file_path); +an ConfigCompiler::Compile(const string& file_name) { + auto resource_id = resource_resolver_->ToResourceId(file_name); auto resource = New(resource_id, New()); graph_->resources[resource_id] = resource; graph_->Push(resource, resource_id + ":"); if (!resource->data->LoadFromFile( - resource_resolver_.ToFilePath(resource_id), this)) { + resource_resolver_->ResolvePath(resource_id).string(), this)) { resource.reset(); } graph_->Pop(); diff --git a/src/rime/config/config_compiler.h b/src/rime/config/config_compiler.h index 719f7e1e33..975b82053e 100644 --- a/src/rime/config/config_compiler.h +++ b/src/rime/config/config_compiler.h @@ -6,7 +6,6 @@ #define RIME_CONFIG_COMPILER_H_ #include -#include #include #include @@ -32,6 +31,7 @@ struct Reference { string local_path; }; +class ResourceResolver; struct Dependency; struct ConfigDependencyGraph; @@ -40,7 +40,7 @@ class ConfigCompiler { static constexpr const char* INCLUDE_DIRECTIVE = "__include"; static constexpr const char* PATCH_DIRECTIVE = "__patch"; - ConfigCompiler(); + explicit ConfigCompiler(ResourceResolver* resource_resolver); virtual ~ConfigCompiler(); Reference CreateReference(const string& qualified_path); @@ -51,7 +51,7 @@ class ConfigCompiler { void Pop(); an GetCompiledResource(const string& resource_id) const; - an Compile(const string& resource_id); + an Compile(const string& file_name); bool Link(an target); bool blocking(const string& full_path) const; @@ -60,7 +60,7 @@ class ConfigCompiler { bool ResolveDependencies(const string& path); private: - ResourceResolver resource_resolver_; + ResourceResolver* resource_resolver_; the graph_; }; diff --git a/src/rime/config/config_component.cc b/src/rime/config/config_component.cc index f8c5c4695d..fb0822be04 100644 --- a/src/rime/config/config_component.cc +++ b/src/rime/config/config_component.cc @@ -4,13 +4,12 @@ // // 2011-04-06 Zou Xu // -#include -#include -#include -#include + +#include +#include #include +#include #include -#include #include namespace rime { @@ -21,8 +20,7 @@ Config::Config() : ConfigItemRef(New()) { Config::~Config() { } -Config::Config(const string& file_name) - : ConfigItemRef(ConfigDataManager::instance().GetConfigData(file_name)) { +Config::Config(an data) : ConfigItemRef(data) { } bool Config::LoadFromStream(std::istream& stream) { @@ -144,14 +142,42 @@ void Config::SetItem(an item) { set_modified(); } -string ConfigComponent::GetConfigFilePath(const string& config_id) { - return boost::str(boost::format(pattern_) % config_id); +static const ResourceType kConfigResourceType = { + "config", + "", + ".yaml", +}; + +ConfigComponent::ConfigComponent() + : resource_resolver_( + Service::instance().CreateResourceResolver(kConfigResourceType)) { +} + +ConfigComponent::~ConfigComponent() { +} + +Config* ConfigComponent::Create(const string& file_name) { + return new Config(GetConfigData(file_name)); } -Config* ConfigComponent::Create(const string& config_id) { - string file_path(GetConfigFilePath(config_id)); - DLOG(INFO) << "config file path: " << file_path; - return new Config(file_path); +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()); + auto resource = compiler.Compile(file_name); + if (!resource || !compiler.Link(resource)) { + LOG(ERROR) << "error loading config from: " << file_name; + } + if (!resource) { + return New(); + } + wp = resource->data; + return resource->data; + } + // obtain the shared copy + return wp.lock(); } } // namespace rime diff --git a/src/rime/config/config_component.h b/src/rime/config/config_component.h index 8b03b5b246..e60b333e43 100644 --- a/src/rime/config/config_component.h +++ b/src/rime/config/config_component.h @@ -15,15 +15,17 @@ namespace rime { +class ConfigData; + class Config : public Class, public ConfigItemRef { public: // CAVEAT: Config instances created without argument will NOT // be managed by ConfigComponent Config(); virtual ~Config(); - // instances of Config with identical file_name share a copy of config data - // that could be reloaded by ConfigComponent once notified changes to the file - explicit Config(const string& file_name); + // instances of Config with identical config id share a copy of config data + // in the ConfigComponent + explicit Config(an data); bool LoadFromStream(std::istream& stream); bool SaveToStream(std::ostream& stream); @@ -61,15 +63,18 @@ class Config : public Class, public ConfigItemRef { void SetItem(an item); }; +class ResourceResolver; + class ConfigComponent : public Config::Component { public: - ConfigComponent(const string& pattern) : pattern_(pattern) {} - Config* Create(const string& config_id); - string GetConfigFilePath(const string& config_id); - const string& pattern() const { return pattern_; } + ConfigComponent(); + ~ConfigComponent(); + Config* Create(const string& file_name); - private: - string pattern_; +private: + an GetConfigData(const string& file_name); + map> cache_; + the resource_resolver_; }; } // namespace rime diff --git a/src/rime/config/config_data_manager.cc b/src/rime/config/config_data_manager.cc deleted file mode 100644 index 302613a07a..0000000000 --- a/src/rime/config/config_data_manager.cc +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright RIME Developers -// Distributed under the BSD License -// - -#include -#include -#include -#include - -namespace rime { - -ConfigDataManager& ConfigDataManager::instance() { - static the s_instance; - if (!s_instance) { - s_instance.reset(new ConfigDataManager); - } - return *s_instance; -} - -an -ConfigDataManager::GetConfigData(const string& config_file_path) { - // keep a weak reference to the shared config data in the manager - weak& wp((*this)[config_file_path]); - if (wp.expired()) { // create a new copy and load it - ConfigCompiler compiler; - auto resource = compiler.Compile(config_file_path); - if (!resource || !compiler.Link(resource)) { - LOG(ERROR) << "error loading config from " << config_file_path; - } - if (!resource) { - return New(); - } - wp = resource->data; - return resource->data; - } - // obtain the shared copy - return wp.lock(); -} - -} // namespace rime diff --git a/src/rime/config/config_data_manager.h b/src/rime/config/config_data_manager.h deleted file mode 100644 index d87eb45478..0000000000 --- a/src/rime/config/config_data_manager.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright RIME Developers -// Distributed under the BSD License -// - -#ifndef RIME_CONFIG_DATA_MANAGER_H_ -#define RIME_CONFIG_DATA_MANAGER_H_ - -#include - -namespace rime { - -class ConfigData; - -class ConfigDataManager : public map> { - public: - an GetConfigData(const string& config_file_path); - - static ConfigDataManager& instance(); - - private: - ConfigDataManager() = default; -}; - -} // namespace rime - -#endif // RIME_CONFIG_DATA_MANAGER_H_ diff --git a/src/rime/core_module.cc b/src/rime/core_module.cc index 2b84c61c10..ea897bf20e 100644 --- a/src/rime/core_module.cc +++ b/src/rime/core_module.cc @@ -5,28 +5,20 @@ // 2013-10-17 GONG Chen // -#include #include #include #include -#include // built-in components #include -static void rime_core_initialize() { - using namespace rime; - using boost::filesystem::path; +using namespace rime; +static void rime_core_initialize() { LOG(INFO) << "registering core components."; Registry& r = Registry::instance(); - path user_data_dir = Service::instance().deployer().user_data_dir; - path config_path = user_data_dir / "%s.yaml"; - path schema_path = user_data_dir / "%s.schema.yaml"; - - r.Register("config", new ConfigComponent(config_path.string())); - r.Register("schema_config", new ConfigComponent(schema_path.string())); + r.Register("config", new ConfigComponent); } static void rime_core_finalize() { diff --git a/src/rime/dict/dict_compiler.cc b/src/rime/dict/dict_compiler.cc index c87e96cc59..ce6ce09a19 100644 --- a/src/rime/dict/dict_compiler.cc +++ b/src/rime/dict/dict_compiler.cc @@ -195,8 +195,8 @@ bool DictCompiler::BuildPrism(const string &schema_file, Script script; if (!schema_file.empty()) { Projection p; - Config config(schema_file); - auto algebra = config.GetList("speller/algebra"); + the config(Config::Require("config")->Create(schema_file)); + auto algebra = config->GetList("speller/algebra"); if (algebra && p.Load(algebra)) { for (const auto& x : syllabary) { script.AddSyllable(x); diff --git a/src/rime/resource_resolver.cc b/src/rime/resource.cc similarity index 90% rename from src/rime/resource_resolver.cc rename to src/rime/resource.cc index a452dfdd1b..82eb6c222e 100644 --- a/src/rime/resource_resolver.cc +++ b/src/rime/resource.cc @@ -5,7 +5,7 @@ #include #include -#include +#include namespace rime { @@ -22,9 +22,7 @@ string ResourceResolver::ToFilePath(const string& resource_id) const { boost::filesystem::path file_path(resource_id); bool missing_prefix = !file_path.has_parent_path() && !boost::starts_with(resource_id, type_.prefix); - bool missing_suffix = boost::starts_with(type_.prefix, ".") ? - !file_path.has_extension() : - !boost::ends_with(resource_id, type_.suffix); + bool missing_suffix = !boost::ends_with(resource_id, type_.suffix); return (missing_prefix ? type_.prefix : "") + resource_id + (missing_suffix ? type_.suffix : ""); } diff --git a/src/rime/resource_resolver.h b/src/rime/resource.h similarity index 92% rename from src/rime/resource_resolver.h rename to src/rime/resource.h index 8c519a9528..ce2a175a1e 100644 --- a/src/rime/resource_resolver.h +++ b/src/rime/resource.h @@ -3,8 +3,8 @@ // Distributed under the BSD License // -#ifndef RIME_RESOURCE_RESOLVER_H_ -#define RIME_RESOURCE_RESOLVER_H_ +#ifndef RIME_RESOURCE_H_ +#define RIME_RESOURCE_H_ #include #include @@ -55,4 +55,4 @@ class FallbackResourceResolver : public ResourceResolver { } // namespace rime -#endif // RIME_RESOURCE_RESOLVER_H_ +#endif // RIME_RESOURCE_H_ diff --git a/src/rime/schema.cc b/src/rime/schema.cc index 737bc88be3..33df9e8c54 100644 --- a/src/rime/schema.cc +++ b/src/rime/schema.cc @@ -17,12 +17,9 @@ Schema::Schema() Schema::Schema(const string& schema_id) : schema_id_(schema_id) { - if (boost::starts_with(schema_id_, L".")) { - config_.reset(Config::Require("config")->Create(schema_id.substr(1))); - } - else { - config_.reset(Config::Require("schema_config")->Create(schema_id)); - } + config_.reset(Config::Require("config")->Create( + boost::starts_with(schema_id_, L".") ? + schema_id.substr(1) : schema_id + ".schema")); FetchUsefulConfigItems(); } diff --git a/src/rime/service.cc b/src/rime/service.cc index d085db5193..fddcdbb398 100644 --- a/src/rime/service.cc +++ b/src/rime/service.cc @@ -6,6 +6,7 @@ // #include #include +#include #include #include @@ -168,6 +169,13 @@ void Service::Notify(SessionId session_id, } } +ResourceResolver* Service::CreateResourceResolver(const ResourceType& type) { + the resolver(new FallbackResourceResolver(type)); + resolver->set_root_path(deployer().user_data_dir); + resolver->set_fallback_root_path(deployer().shared_data_dir); + return resolver.release(); +} + Service& Service::instance() { static the s_instance; if (!s_instance) { diff --git a/src/rime/service.h b/src/rime/service.h index 404e6d6155..6e6183ed7d 100644 --- a/src/rime/service.h +++ b/src/rime/service.h @@ -53,6 +53,9 @@ class Session { string commit_text_; }; +class ResourceResolver; +struct ResourceType; + class Service { public: ~Service(); @@ -72,6 +75,8 @@ class Service { const string& message_type, const string& message_value); + ResourceResolver* CreateResourceResolver(const ResourceType& type); + Deployer& deployer() { return deployer_; } bool disabled() { return !started_ || deployer_.IsMaintenanceMode(); } diff --git a/test/config_compiler_test.cc b/test/config_compiler_test.cc index a6a5a212a9..f7043d005e 100644 --- a/test/config_compiler_test.cc +++ b/test/config_compiler_test.cc @@ -15,7 +15,7 @@ class RimeConfigCompilerTest : public ::testing::Test { RimeConfigCompilerTest() = default; virtual void SetUp() { - component_.reset(new ConfigComponent("%s.yaml")); + component_.reset(new ConfigComponent); config_.reset(component_->Create("config_compiler_test")); } diff --git a/test/config_test.cc b/test/config_test.cc index 95465f071a..30c5ecaf7c 100644 --- a/test/config_test.cc +++ b/test/config_test.cc @@ -16,7 +16,7 @@ class RimeConfigTest : public ::testing::Test { RimeConfigTest() = default; virtual void SetUp() { - component_.reset(new ConfigComponent("%s.yaml")); + component_.reset(new ConfigComponent); config_.reset(component_->Create("config_test")); } @@ -30,11 +30,11 @@ class RimeConfigTest : public ::testing::Test { TEST(RimeConfigComponentTest, RealCreationWorkflow) { // registration Registry& r = Registry::instance(); - r.Register("test_config", new ConfigComponent("%s.yaml")); - // finding component + r.Register("test_config", new ConfigComponent); + // find component Config::Component* cc = Config::Require("test_config"); ASSERT_TRUE(cc != NULL); - // creation + // create Config with id the config(cc->Create("config_test")); EXPECT_TRUE(bool(config)); r.Unregister("test_config"); diff --git a/test/resource_resolver_test.cc b/test/resource_resolver_test.cc index a9d9b8a6e4..942271dc15 100644 --- a/test/resource_resolver_test.cc +++ b/test/resource_resolver_test.cc @@ -1,5 +1,5 @@ #include -#include +#include using namespace rime; From fa9f21f198ab08e2b0786338c39a4af5298d7479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Tue, 29 Aug 2017 00:16:04 +0800 Subject: [PATCH 11/11] chore: fix breaking build with gcc --- src/rime/config/config_data.cc | 2 +- test/resource_resolver_test.cc | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index 7e3f6acae4..2ceb4c6040 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -2,7 +2,7 @@ // Copyright RIME Developers // Distributed under the BSD License // - +#include #include #include #include diff --git a/test/resource_resolver_test.cc b/test/resource_resolver_test.cc index 942271dc15..e11462a881 100644 --- a/test/resource_resolver_test.cc +++ b/test/resource_resolver_test.cc @@ -1,3 +1,5 @@ +#include +#include #include #include @@ -26,14 +28,14 @@ TEST(RimeResourceResolverTest, FallbackRootPath) { boost::filesystem::path nonexistent_default = "not_present.minerals"; boost::filesystem::remove(nonexistent_default); auto fallback = boost::filesystem::absolute("fallback/not_present.minerals"); - boost::filesystem::ofstream(fallback).close(); + std::ofstream(fallback.string()).close(); auto actual = rr.ResolvePath("present"); EXPECT_TRUE(fallback == actual); boost::filesystem::remove(fallback); } { auto existent_default = boost::filesystem::absolute("not_falling_back.minerals"); - boost::filesystem::ofstream(existent_default).close(); + std::ofstream(existent_default.string()).close(); auto actual = rr.ResolvePath("falling_back"); EXPECT_TRUE(existent_default == actual); boost::filesystem::remove(existent_default);