diff --git a/src/rime/config/config_component.h b/src/rime/config/config_component.h index 072968179e..b6d015ae4d 100644 --- a/src/rime/config/config_component.h +++ b/src/rime/config/config_component.h @@ -117,6 +117,13 @@ class ConfigComponent : public ConfigComponentBase { ResourceProvider::kDefaultResourceType)) { setup(&loader_); } + ConfigComponent(function setup, + const string& loader_dir) + : ConfigComponentBase( + new ResourceResolver(ResourceProvider::kDefaultResourceType)) { + resource_resolver_->set_root_path(path(loader_dir)); + setup(&loader_); + } private: an LoadConfig(const string& config_id) override { diff --git a/src/rime/config/config_data.cc b/src/rime/config/config_data.cc index e3e73dbd05..0d0eb51284 100644 --- a/src/rime/config/config_data.cc +++ b/src/rime/config/config_data.cc @@ -86,13 +86,47 @@ bool ConfigData::SaveToFile(const path& file_path) { file_path_ = file_path; modified_ = false; if (file_path.empty()) { - // not really saving + LOG(ERROR) << "SaveToFile: file path is empty"; return false; } LOG(INFO) << "saving config file '" << file_path << "'."; - // dump tree + + // check if root node exists + if (!root) { + LOG(WARNING) << "SaveToFile: root node is null, creating empty map"; + root = New(); + } + + // ensure parent directory exists + auto parent_dir = file_path.parent_path(); + if (!parent_dir.empty() && !std::filesystem::exists(parent_dir)) { + LOG(INFO) << "SaveToFile: creating parent directory: " << parent_dir; + try { + std::filesystem::create_directories(parent_dir); + } catch (const std::exception& e) { + LOG(ERROR) << "SaveToFile: failed to create directory " << parent_dir + << ": " << e.what(); + return false; + } + } + + // try to open file std::ofstream out(file_path.c_str()); - return SaveToStream(out); + if (!out.good()) { + LOG(ERROR) << "SaveToFile: failed to open file for writing: " << file_path; + return false; + } + + bool result = SaveToStream(out); + out.close(); + + if (result) { + LOG(INFO) << "SaveToFile: successfully saved to " << file_path; + } else { + LOG(ERROR) << "SaveToFile: failed to save to " << file_path; + } + + return result; } bool ConfigData::IsListItemReference(const string& key) { diff --git a/src/rime/config/plugins.h b/src/rime/config/plugins.h index 4e1a963d98..29d1de82c6 100644 --- a/src/rime/config/plugins.h +++ b/src/rime/config/plugins.h @@ -58,6 +58,7 @@ struct ResourceType; class SaveOutputPlugin : public ConfigCompilerPlugin { public: SaveOutputPlugin(); + SaveOutputPlugin(const string& output_dir); virtual ~SaveOutputPlugin(); Review ReviewCompileOutput; diff --git a/src/rime/config/save_output_plugin.cc b/src/rime/config/save_output_plugin.cc index 89af4243f0..f51f267de4 100644 --- a/src/rime/config/save_output_plugin.cc +++ b/src/rime/config/save_output_plugin.cc @@ -2,6 +2,7 @@ // Copyright RIME Developers // Distributed under the BSD License // +#include #include #include #include @@ -16,6 +17,11 @@ SaveOutputPlugin::SaveOutputPlugin() : resource_resolver_( Service::instance().CreateStagingResourceResolver(kCompiledConfig)) {} +SaveOutputPlugin::SaveOutputPlugin(const string& output_dir) + : resource_resolver_(new ResourceResolver(kCompiledConfig)) { + resource_resolver_->set_root_path(path(output_dir)); +} + SaveOutputPlugin::~SaveOutputPlugin() {} bool SaveOutputPlugin::ReviewCompileOutput(ConfigCompiler* compiler, @@ -25,8 +31,33 @@ bool SaveOutputPlugin::ReviewCompileOutput(ConfigCompiler* compiler, bool SaveOutputPlugin::ReviewLinkOutput(ConfigCompiler* compiler, an resource) { + LOG(INFO) << "SaveOutputPlugin::ReviewLinkOutput for resource: " + << resource->resource_id; + auto file_path = resource_resolver_->ResolvePath(resource->resource_id); - return resource->data->SaveToFile(file_path); + LOG(INFO) << "Attempting to save to: " << file_path; + + // ensure directory exists + auto parent_dir = file_path.parent_path(); + if (!std::filesystem::exists(parent_dir)) { + LOG(INFO) << "Creating directory: " << parent_dir; + try { + std::filesystem::create_directories(parent_dir); + } catch (const std::exception& e) { + LOG(ERROR) << "Failed to create directory " << parent_dir << ": " + << e.what(); + return false; + } + } + + bool result = resource->data->SaveToFile(file_path); + if (result) { + LOG(INFO) << "Successfully saved config to: " << file_path; + } else { + LOG(ERROR) << "Failed to save config to: " << file_path; + } + + return result; } } // namespace rime diff --git a/src/rime/resource.cc b/src/rime/resource.cc index 045e047f8a..cde36beba8 100644 --- a/src/rime/resource.cc +++ b/src/rime/resource.cc @@ -9,13 +9,41 @@ namespace rime { +// Convert a file path to a resource ID by extracting the core name and +// directory structure string ResourceResolver::ToResourceId(const string& file_path) const { - string string_path = path(file_path).generic_u8string(); - bool has_prefix = boost::starts_with(string_path, type_.prefix); - bool has_suffix = boost::ends_with(string_path, type_.suffix); + path p(file_path); + // Get the filename from the path + string filename = p.filename().generic_u8string(); + + // Check if the filename has the expected prefix and suffix + bool has_prefix = boost::starts_with(filename, type_.prefix); + bool has_suffix = boost::ends_with(filename, type_.suffix); + + // Calculate the start and end positions to extract the core name size_t start = (has_prefix ? type_.prefix.length() : 0); - size_t end = string_path.length() - (has_suffix ? type_.suffix.length() : 0); - return string_path.substr(start, end); + size_t end = filename.length() - (has_suffix ? type_.suffix.length() : 0); + + // Handle files with parent directories + if (p.has_parent_path()) { + string parent_dir = p.parent_path().generic_u8string(); + string resource_name = filename.substr(start, end); + + // Remove leading slash from parent directory if present + if (!parent_dir.empty() && parent_dir[0] == '/') { + parent_dir = parent_dir.substr(1); + } + + // Return either just the resource name or include the parent directory + if (parent_dir.empty()) { + return resource_name; + } else { + return parent_dir + "/" + resource_name; + } + } else { + // For files without parent paths, just return the extracted name + return filename.substr(start, end); + } } string ResourceResolver::ToFilePath(const string& resource_id) const { diff --git a/src/rime_api.h b/src/rime_api.h index 2fccde0fb8..4e21824a24 100644 --- a/src/rime_api.h +++ b/src/rime_api.h @@ -505,6 +505,10 @@ typedef struct RIME_FLAVORED(rime_api_t) { size_t index); Bool (*change_page)(RimeSessionId session_id, Bool backward); + + Bool (*compile_config_file)(const char* src_path, + const char* dest_path, + const char* file_name); } RIME_FLAVORED(RimeApi); //! API entry diff --git a/src/rime_api_impl.h b/src/rime_api_impl.h index 6e906cfe45..088ed7a121 100644 --- a/src/rime_api_impl.h +++ b/src/rime_api_impl.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include using namespace rime; @@ -135,6 +137,47 @@ RIME_DEPRECATED Bool RimeDeployConfigFile(const char* file_name, return Bool(deployer.RunTask("config_file_update", args)); } +RIME_DEPRECATED Bool RimeCompileConfigFile(const char* src_path, + const char* dest_path, + const char* file_name) { + // Ensure destination directory exists + std::filesystem::path dest_dir(dest_path); + if (!std::filesystem::exists(dest_dir)) { + std::filesystem::create_directories(dest_dir); + LOG(INFO) << "Created destination directory: " << dest_path; + } + + // Create config builder + auto config_builder = new ConfigComponent( + [&](ConfigBuilder* builder) { + builder->InstallPlugin(new AutoPatchConfigPlugin); + builder->InstallPlugin(new DefaultConfigPlugin); + builder->InstallPlugin(new LegacyPresetConfigPlugin); + builder->InstallPlugin(new LegacyDictionaryConfigPlugin); + builder->InstallPlugin(new BuildInfoPlugin); + builder->InstallPlugin(new SaveOutputPlugin(dest_path)); + }, + src_path); + + // Compile file + LOG(INFO) << "Compiling YAML file: " << file_name; + LOG(INFO) << "Source path: " << src_path; + LOG(INFO) << "Destination path: " << dest_path; + + Config* config = config_builder->Create(file_name); + bool result = (config != nullptr); + + if (result) { + LOG(INFO) << "✓ Compilation successful!"; + } else { + LOG(ERROR) << "✗ Compilation failed!"; + } + + delete config; + delete config_builder; + return result; +} + RIME_DEPRECATED Bool RimeSyncUserData() { Service::instance().CleanupAllSessions(); Deployer& deployer(Service::instance().deployer()); @@ -1228,6 +1271,7 @@ RIME_API RIME_FLAVORED(RimeApi) * RIME_FLAVORED(rime_get_api)() { s_api.highlight_candidate_on_current_page = &RimeHighlightCandidateOnCurrentPage; s_api.change_page = &RimeChangePage; + s_api.compile_config_file = &RimeCompileConfigFile; } return &s_api; } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 090a0fc036..51d19d9daf 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -37,9 +37,19 @@ target_link_libraries(rime_patch ${rime_library} ${rime_levers_library}) +set(rime_yaml_compiler_src "rime_yaml_compiler.cc") +add_executable(rime_yaml_compiler ${rime_yaml_compiler_src}) +target_include_directories(rime_yaml_compiler BEFORE PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src) +target_link_libraries(rime_yaml_compiler + ${rime_library} + ${rime_gears_library}) + install(TARGETS rime_deployer DESTINATION ${BIN_INSTALL_DIR}) install(TARGETS rime_dict_manager DESTINATION ${BIN_INSTALL_DIR}) install(TARGETS rime_patch DESTINATION ${BIN_INSTALL_DIR}) +install(TARGETS rime_yaml_compiler DESTINATION ${BIN_INSTALL_DIR}) # do not work with Windows DLL; interfaces to dict are missing DLL export. if(NOT WIN32 OR NOT BUILD_SHARED_LIBS) diff --git a/tools/rime_yaml_compiler.cc b/tools/rime_yaml_compiler.cc new file mode 100644 index 0000000000..f471dadc11 --- /dev/null +++ b/tools/rime_yaml_compiler.cc @@ -0,0 +1,112 @@ +/* + * rime_yaml_compiler.cc + * RIME YAML 编译工具 + * 用于编译自定义的 YAML 配置文件 + */ +#include +#include +#include +#include +#include +#include +#include "codepage.h" +#include + +using namespace rime; +using namespace std; + +void print_usage(const char* program_name) { + cout << "用法: " << program_name << " [选项] " << endl; + cout << "选项:" << endl; + cout << " -s, --source <路径> 源路径 (默认: 当前目录)" << endl; + cout << " -d, --dest <路径> 目标路径 (默认: 当前目录)" << endl; + cout << " -h, --help 显示此帮助信息" << endl; + cout << endl; + cout << "示例:" << endl; + cout << " " << program_name << " theme/bim.yaml" << endl; + cout << " " << program_name + << " -s /path/to/source -d /path/to/dest config.yaml" << endl; +} +int main(int argc, char* argv[]) { + unsigned int codepage = SetConsoleOutputCodePage(); + + string src_path = "."; + string dest_path = "."; + string file_path; + + // 解析命令行参数 + for (int i = 1; i < argc; i++) { + string arg = argv[i]; + + if (arg == "-h" || arg == "--help") { + print_usage(argv[0]); + SetConsoleOutputCodePage(codepage); + return 0; + } else if (arg == "-s" || arg == "--source") { + if (i + 1 < argc) { + src_path = argv[++i]; + } else { + cerr << "错误: " << arg << " 需要一个参数" << endl; + SetConsoleOutputCodePage(codepage); + return 1; + } + } else if (arg == "-d" || arg == "--dest") { + if (i + 1 < argc) { + dest_path = argv[++i]; + } else { + cerr << "错误: " << arg << " 需要一个参数" << endl; + SetConsoleOutputCodePage(codepage); + return 1; + } + } else if (arg[0] != '-') { + // 这是文件路径 + if (file_path.empty()) { + file_path = arg; + } else { + cerr << "错误: 指定了多个文件路径" << endl; + SetConsoleOutputCodePage(codepage); + return 1; + } + } else { + cerr << "错误: 未知选项 " << arg << endl; + print_usage(argv[0]); + SetConsoleOutputCodePage(codepage); + return 1; + } + } + + // 检查是否指定了文件路径 + if (file_path.empty()) { + cerr << "错误: 必须指定 YAML 文件路径" << endl; + print_usage(argv[0]); + SetConsoleOutputCodePage(codepage); + return 1; + } + + // 检查文件是否存在 + string full_file_path = src_path + "/" + file_path; + if (!filesystem::exists(full_file_path)) { + cerr << "错误: 文件不存在: " << full_file_path << endl; + SetConsoleOutputCodePage(codepage); + return 1; + } + + cout << "=== RIME YAML 编译器 ===" << endl; + + // 编译文件 + // bool success = compile_yaml_file(src_path, dest_path, file_path); + RimeApi* rime = rime_get_api(); + + // 检查API版本并验证compile_config_file函数是否可用 + if (!RIME_API_AVAILABLE(rime, compile_config_file)) { + cerr << "错误: 当前版本的 librime 不支持 compile_config_file 函数" << endl; + SetConsoleOutputCodePage(codepage); + return 1; + } + + bool success = rime->compile_config_file(src_path.c_str(), dest_path.c_str(), + file_path.c_str()); + + SetConsoleOutputCodePage(codepage); + return success ? 0 : 1; +} \ No newline at end of file