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/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_compiler.cc b/src/rime/config/config_compiler.cc new file mode 100644 index 0000000000..f2b2830de6 --- /dev/null +++ b/src/rime/config/config_compiler.cc @@ -0,0 +1,402 @@ +#include +#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; +}; + +template +StreamT& operator<< (StreamT& stream, const Reference& reference) { + return stream << reference.resource_id << ":" << 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_id() const { + return key_stack.empty() ? string() + : boost::trim_right_copy_if(key_stack.front(), boost::is_any_of(":")); + } +}; + +bool PendingChild::Resolve(ConfigCompiler* compiler) { + return compiler->ResolveDependencies(child_path); +} + +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 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 (!TraverseCopyOnWrite(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(ResourceResolver* resource_resolver) + : resource_resolver_(resource_resolver), + graph_(new ConfigDependencyGraph) { +} + +ConfigCompiler::~ConfigCompiler() { +} + +Reference ConfigCompiler::CreateReference(const string& qualified_path) { + auto separator = qualified_path.find_first_of(":"); + 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); + 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), + 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_id) const { + return graph_->resources[resource_id]; +} + +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_->ResolvePath(resource_id).string(), 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 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) { + LOG(INFO) << "GetResolvedItem(" << resource->resource_id << ":/" << path << ")"; + string node_path = resource->resource_id + ":"; + if (!resource || compiler->blocking(node_path)) { + return nullptr; + } + an result = *resource; + if (path.empty() || path == "/") { + return if_resolved(compiler, result, node_path); + } + 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); + } 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); + } else { + result.reset(); + } + if (!result) { + LOG(INFO) << "missing node: " << node_path; + return nullptr; + } + } + return if_resolved(compiler, result, node_path); +} + +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_id); + if (!resource) { + LOG(INFO) << "resource not found, compiling: " << reference.resource_id; + resource = compiler->Compile(reference.resource_id); + } + 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(ConfigCompiler* compiler, + const an& item) { + if (Is(item)) { + auto path = As(item)->str(); + LOG(INFO) << "ParseInclude(" << path << ")"; + compiler->AddDependency( + New(compiler->CreateReference(path))); + 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)(ConfigCompiler*, const an&), + ConfigCompiler* compiler, + const an& item) { + if (Is(item)) { + for (auto list_item : *As(item)) { + if (!parser(compiler, list_item)) { + return false; + } + } + return true; + } + // not a list + 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(ConfigCompiler* compiler, + const an& item) { + if (Is(item)) { + auto path = As(item)->str(); + LOG(INFO) << "ParsePatch(" << path << ")"; + compiler->AddDependency( + New(compiler->CreateReference(path))); + return true; + } + if (Is(item)) { + LOG(INFO) << "ParsePatch()"; + compiler->AddDependency(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(this, item); + } + if (key == PATCH_DIRECTIVE) { + return ParseList(ParsePatch, this, item); + } + return false; +} + +bool ConfigCompiler::Link(an target) { + 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->resource_id; + return false; + } + return ResolveDependencies(found->first + ":"); +} + +bool ConfigCompiler::ResolveDependencies(const string& path) { + 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..975b82053e --- /dev/null +++ b/src/rime/config/config_compiler.h @@ -0,0 +1,69 @@ +// +// 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 resource_id; + an data; + + ConfigResource(const string& _id, an _data) + : ConfigItemRef(nullptr), resource_id(_id), data(_data) { + } + an GetItem() const override { + return data->root; + } + void SetItem(an item) override { + data->root = item; + } +}; + +struct Reference { + string resource_id; + string local_path; +}; + +class ResourceResolver; +struct Dependency; +struct ConfigDependencyGraph; + +class ConfigCompiler { + public: + static constexpr const char* INCLUDE_DIRECTIVE = "__include"; + static constexpr const char* PATCH_DIRECTIVE = "__patch"; + + explicit ConfigCompiler(ResourceResolver* resource_resolver); + 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_id) const; + an Compile(const string& file_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); + + private: + ResourceResolver* resource_resolver_; + 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 new file mode 100644 index 0000000000..fb0822be04 --- /dev/null +++ b/src/rime/config/config_component.cc @@ -0,0 +1,183 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +// 2011-04-06 Zou Xu +// + +#include +#include +#include +#include +#include +#include + +namespace rime { + +Config::Config() : ConfigItemRef(New()) { +} + +Config::~Config() { +} + +Config::Config(an data) : ConfigItemRef(data) { +} + +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, nullptr); +} + +bool Config::SaveToFile(const string& file_name) { + return data_->SaveToFile(file_name); +} + +bool Config::IsNull(const string& path) { + auto p = data_->Traverse(path); + return !p || p->type() == ConfigItem::kNull; +} + +bool Config::IsValue(const string& path) { + auto p = data_->Traverse(path); + return !p || p->type() == ConfigItem::kScalar; +} + +bool Config::IsList(const string& path) { + auto p = data_->Traverse(path); + return !p || p->type() == ConfigItem::kList; +} + +bool Config::IsMap(const string& path) { + auto p = data_->Traverse(path); + return !p || p->type() == ConfigItem::kMap; +} + +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& path, int* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); + return p && p->GetInt(value); +} + +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& path, string* value) { + DLOG(INFO) << "read: " << path; + auto p = As(data_->Traverse(path)); + return p && p->GetString(value); +} + +size_t Config::GetListSize(const string& path) { + DLOG(INFO) << "read: " << path; + auto list = GetList(path); + return list ? list->size() : 0; +} + +an Config::GetItem(const string& path) { + DLOG(INFO) << "read: " << path; + return data_->Traverse(path); +} + +an Config::GetValue(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); +} + +an Config::GetList(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); +} + +an Config::GetMap(const string& path) { + DLOG(INFO) << "read: " << path; + return As(data_->Traverse(path)); +} + +bool Config::SetBool(const string& path, bool value) { + return SetItem(path, New(value)); +} + +bool Config::SetInt(const string& path, int value) { + return SetItem(path, New(value)); +} + +bool Config::SetDouble(const string& path, double value) { + return SetItem(path, New(value)); +} + +bool Config::SetString(const string& path, const char* value) { + return SetItem(path, New(value)); +} + +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 { + return data_->root; +} + +void Config::SetItem(an item) { + data_->root = item; + set_modified(); +} + +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)); +} + +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 new file mode 100644 index 0000000000..e60b333e43 --- /dev/null +++ b/src/rime/config/config_component.h @@ -0,0 +1,82 @@ +// +// 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 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 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); + bool LoadFromFile(const string& file_name); + bool SaveToFile(const string& file_name); + + // 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& path); + an GetValue(const string& path); + an GetList(const string& path); + an GetMap(const string& path); + + // setters + 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; + void SetItem(an item); +}; + +class ResourceResolver; + +class ConfigComponent : public Config::Component { + public: + ConfigComponent(); + ~ConfigComponent(); + Config* Create(const string& file_name); + +private: + an GetConfigData(const string& file_name); + map> cache_; + the resource_resolver_; +}; + +} // 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..2ceb4c6040 --- /dev/null +++ b/src/rime/config/config_data.cc @@ -0,0 +1,377 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// +#include +#include +#include +#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, nullptr); + } + 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, + ConfigCompiler* compiler) { + // 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, compiler); + } + 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); +} + +bool ConfigData::IsListItemReference(const string& key) { + return !key.empty() && key[0] == '@'; +} + +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; + } + 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; +} + +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_; +}; + +template +static an Cow(const an& container, const string& key) { + if (!container) { + LOG(INFO) << "creating node: " << key; + return New(); + } + DLOG(INFO) << "copy on write: " << key; + return New(*container); +} + +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; + } + an head = root; + vector keys = ConfigData::SplitPath(path); + 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 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; + } + if (is_list) { + head = New(head, key); + } else { + head = New(head, key); + } + } + *head = item; + return true; +} + +bool ConfigData::TraverseWrite(const string& path, an item) { + LOG(INFO) << "write: " << path; + auto root = New(this); + bool result = TraverseCopyOnWrite(root, path, item); + if (result) { + set_modified(); + } + return result; +} + +vector ConfigData::SplitPath(const string& path) { + vector keys; + 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) { + 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, ConfigCompiler* compiler) { + 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) { + if (compiler) { + compiler->Push(config_list, config_list->size()); + } + config_list->Append(ConvertFromYaml(*it, compiler)); + if (compiler) { + compiler->Pop(); + } + } + 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(); + 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; + } + 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..c7305f84be --- /dev/null +++ b/src/rime/config/config_data.h @@ -0,0 +1,58 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#ifndef RIME_CONFIG_DATA_H_ +#define RIME_CONFIG_DATA_H_ + +#include +#include +#include + +namespace rime { + +class ConfigCompiler; +class ConfigItem; + +class ConfigData { + public: + ConfigData() = default; + ~ConfigData(); + + bool LoadFromStream(std::istream& stream); + bool SaveToStream(std::ostream& stream); + bool LoadFromFile(const string& file_name, ConfigCompiler* compiler); + bool SaveToFile(const string& file_name); + 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, + ConfigCompiler* compiler); + 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_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..27b0ec4ea0 --- /dev/null +++ b/src/rime/config/config_types.h @@ -0,0 +1,217 @@ +// +// 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_; +}; + +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; + +class ConfigItemRef { + public: + ConfigItemRef(const an& data) : data_(data) { + } + virtual ~ConfigItemRef() { + } + operator an () const { + return GetItem(); + } + an operator* () 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); + + 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_; +}; + +class ConfigListEntryRef : public ConfigItemRef { + public: + ConfigListEntryRef(an data, + an list, size_t index) + : ConfigItemRef(data), list_(list), index_(index) { + } + using ConfigItemRef::operator=; + 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) { + } + using ConfigItemRef::operator=; + 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_ 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.cc b/src/rime/resource.cc new file mode 100644 index 0000000000..82eb6c222e --- /dev/null +++ b/src/rime/resource.cc @@ -0,0 +1,50 @@ +// +// Copyright RIME Developers +// 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::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), + 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.h b/src/rime/resource.h new file mode 100644 index 0000000000..ce2a175a1e --- /dev/null +++ b/src/rime/resource.h @@ -0,0 +1,58 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#ifndef RIME_RESOURCE_H_ +#define RIME_RESOURCE_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) { + } + 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; + } + boost::filesystem::path root_path() const { + return root_path_; + } + 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_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/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..f7043d005e --- /dev/null +++ b/test/config_compiler_test.cc @@ -0,0 +1,118 @@ +// +// 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); + 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; + EXPECT_TRUE(config_->GetString(prefix + "protoss/ground_units/@6", &unit)); + EXPECT_EQ("dark templar", 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 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 new file mode 100644 index 0000000000..e11462a881 --- /dev/null +++ b/test/resource_resolver_test.cc @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +using namespace rime; + +static const ResourceType kMineralsType = ResourceType{ + "minerals", + "not_", + ".minerals", +}; + +TEST(RimeResourceResolverTest, ResolvePath) { + 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"); + 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"); + std::ofstream(existent_default.string()).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"); +}