From 2ea6f5bba1ad125dc7972621c41f44445945b3dc Mon Sep 17 00:00:00 2001 From: Kan-Ru Chen Date: Mon, 15 Jul 2024 17:52:30 +0900 Subject: [PATCH] feat(dict): Automatically load extra dictionaries found in search path --- src/dictionary/loader.rs | 25 ++++++++++++++++------ src/path.rs | 30 +++++++++++++++++++++++++++ tests/data/dictionary.d/01-extra.dat | Bin 0 -> 136 bytes tests/data/dictionary.d/02-empty.dat | 0 tests/data/extra.src | 2 ++ tests/test-config.c | 17 +++++++++++++++ 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 tests/data/dictionary.d/01-extra.dat create mode 100644 tests/data/dictionary.d/02-empty.dat create mode 100644 tests/data/extra.src diff --git a/src/dictionary/loader.rs b/src/dictionary/loader.rs index ef60ac7dc..205b428f1 100644 --- a/src/dictionary/loader.rs +++ b/src/dictionary/loader.rs @@ -7,11 +7,11 @@ use std::{ path::{Path, PathBuf}, }; -use log::info; +use log::{info, warn}; use crate::{ editor::{AbbrevTable, SymbolSelector}, - path::{find_path_by_files, sys_path_from_env_var, userphrase_path}, + path::{find_extra_dat_by_path, find_path_by_files, sys_path_from_env_var, userphrase_path}, }; #[cfg(feature = "sqlite")] @@ -70,15 +70,28 @@ impl SystemDictionaryLoader { let sys_path = find_path_by_files(&search_path, &[SD_WORD_FILE_NAME, SD_TSI_FILE_NAME]) .ok_or(LoadDictionaryError::NotFound)?; - let tsi_dict_path = sys_path.join(SD_TSI_FILE_NAME); - info!("Loading {SD_TSI_FILE_NAME}"); - let tsi_dict = Trie::open(tsi_dict_path).map_err(io_err)?; + let mut results: Vec> = vec![]; let word_dict_path = sys_path.join(SD_WORD_FILE_NAME); info!("Loading {SD_WORD_FILE_NAME}"); let word_dict = Trie::open(word_dict_path).map_err(io_err)?; + results.push(Box::new(word_dict)); + + let tsi_dict_path = sys_path.join(SD_TSI_FILE_NAME); + info!("Loading {SD_TSI_FILE_NAME}"); + let tsi_dict = Trie::open(tsi_dict_path).map_err(io_err)?; + results.push(Box::new(tsi_dict)); + + let extra_files = find_extra_dat_by_path(&search_path); + for path in extra_files { + info!("Loading {}", path.display()); + match Trie::open(&path) { + Ok(dict) => results.push(Box::new(dict)), + Err(e) => warn!("Failed to load {}: {e}", path.display()), + } + } - Ok(vec![Box::new(word_dict), Box::new(tsi_dict)]) + Ok(results) } pub fn load_abbrev(&self) -> Result { let search_path = if let Some(sys_path) = &self.sys_path { diff --git a/src/path.rs b/src/path.rs index 65222a72e..dc67c39bb 100644 --- a/src/path.rs +++ b/src/path.rs @@ -2,6 +2,7 @@ use std::{ env, + ffi::OsStr, path::{Path, PathBuf}, }; @@ -17,6 +18,8 @@ const SEARCH_PATH_SEP: char = ';'; #[cfg(target_family = "unix")] const SEARCH_PATH_SEP: char = ':'; +const DICT_FOLDER: &str = "dictionary.d"; + pub(crate) fn sys_path_from_env_var() -> String { let chewing_path = env::var("CHEWING_PATH"); if let Ok(chewing_path) = chewing_path { @@ -55,6 +58,33 @@ pub(crate) fn find_path_by_files(search_path: &str, files: &[&str]) -> Option Vec { + let mut results = vec![]; + for path in search_path.split(SEARCH_PATH_SEP) { + let prefix = Path::new(path).join(DICT_FOLDER); + info!("Search dictionary files in {}", prefix.display()); + if let Ok(read_dir) = prefix.read_dir() { + let mut files = vec![]; + for entry in read_dir { + if let Ok(entry) = entry { + let path = entry.path(); + let is_dat = path + .extension() + .and_then(OsStr::to_str) + .map_or(false, |ext| ext == "dat"); + if path.is_file() && is_dat { + info!("Found {}", path.display()); + files.push(path); + } + } + } + files.sort(); + results.extend(files); + } + } + results +} + /// Returns the path to the user's default chewing data directory. /// /// The returned value depends on the operating system and is either a diff --git a/tests/data/dictionary.d/01-extra.dat b/tests/data/dictionary.d/01-extra.dat new file mode 100644 index 0000000000000000000000000000000000000000..be03d5482f55dc5934ad5a9d02c646f25bd8068d GIT binary patch literal 136 zcmXqLY~^8b_HYenVq`F|;^BF@u=nYbX;0Tqf7-f(hdneeJ1@UH55ZtH)HBdC;1NvD zNG;FIOV>@#$y6}Vv(PhT(O_U;U}Rtdk_-&YK$aAcW?^7t)B%fdK}jHOAY#D5!wj?s MWFwFTv<}Du0Od>_qW}N^ literal 0 HcmV?d00001 diff --git a/tests/data/dictionary.d/02-empty.dat b/tests/data/dictionary.d/02-empty.dat new file mode 100644 index 000000000..e69de29bb diff --git a/tests/data/extra.src b/tests/data/extra.src new file mode 100644 index 000000000..3ad635f46 --- /dev/null +++ b/tests/data/extra.src @@ -0,0 +1,2 @@ +額 0 ㄜˊ +外 0 ㄨㄞˋ diff --git a/tests/test-config.c b/tests/test-config.c index c3263377f..0c3e9a348 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -745,6 +745,21 @@ void test_runtime_version() ok(strcmp(buf, version) == 0, "chewing_version can be created from components"); } +void test_dictionary_d() +{ + ChewingContext *ctx; + + ctx = chewing_new2(TEST_DATA_DIR, NULL, logger, fd); + start_testcase(ctx, fd); + + ok(ctx != NULL, "chewing_new2 returns `%#p' shall not be `%#p'", ctx, NULL); + + type_keystroke_by_string(ctx, "k6j94"); + ok_commit_buffer(ctx, "額外"); + + chewing_delete(ctx); +} + int main(int argc, char *argv[]) { char *logname; @@ -785,6 +800,8 @@ int main(int argc, char *argv[]) test_runtime_version(); + test_dictionary_d(); + fclose(fd); return exit_status();