refactor(engine): 将 engine 从 submodule 内嵌为仓内一等组件
`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是 product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次 搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、 miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。 Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、 macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。 词库仍按 product-lock.json 在构建时取发布产物。 引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游 commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。 verify-contracts / verify-checkout 随之移除,verify-published 保留。 CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除 列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job; engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily 改为 monthly。 README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录 文档同步说明新的位置与边界。
This commit is contained in:
1 parent
987f404343
commit
a7779d2b05
406 files changed
+212487
-541
No files matched your search
@@ -0,0 +1,244 @@
|
||||
#include "../../core/data_path.h"
|
||||
#include "../../core/input_session.h"
|
||||
#include "test_directory_cleanup.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
class Database
|
||||
{
|
||||
public:
|
||||
explicit Database(const std::filesystem::path &path)
|
||||
{
|
||||
if (sqlite3_open(metasequoia::path_to_utf8(path).c_str(), &database_) != SQLITE_OK)
|
||||
{
|
||||
throw std::runtime_error("Failed to create a temporary-mode test dictionary.");
|
||||
}
|
||||
}
|
||||
|
||||
~Database()
|
||||
{
|
||||
sqlite3_close(database_);
|
||||
}
|
||||
|
||||
void execute(const char *sql)
|
||||
{
|
||||
char *error = nullptr;
|
||||
if (sqlite3_exec(database_, sql, nullptr, nullptr, &error) != SQLITE_OK)
|
||||
{
|
||||
const std::string message = error == nullptr ? "SQLite operation failed." : error;
|
||||
sqlite3_free(error);
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
sqlite3 *database_ = nullptr;
|
||||
};
|
||||
|
||||
void require(bool condition, const char *message)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const std::vector<WordItem> &candidates, const std::string &word)
|
||||
{
|
||||
return std::any_of(candidates.begin(), candidates.end(),
|
||||
[&](const WordItem &candidate) { return candidate.word == word; });
|
||||
}
|
||||
|
||||
void type(metasequoia::InputSession &session, const std::string &text)
|
||||
{
|
||||
for (const char character : text)
|
||||
{
|
||||
require(session.handle_character(character).handled, "A temporary-mode test character was not handled.");
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto suffix = std::to_string(std::chrono::high_resolution_clock::now().time_since_epoch().count());
|
||||
const auto data_directory = std::filesystem::temp_directory_path() / ("metasequoia-temporary-mode-" + suffix);
|
||||
metasequoia::test::ScopedDataDirectoryCleanup cleanup(data_directory);
|
||||
std::filesystem::create_directories(data_directory);
|
||||
#ifdef _WIN32
|
||||
if (_wputenv_s(L"METASEQUOIA_IME_DATA_DIR", data_directory.c_str()) != 0)
|
||||
#else
|
||||
if (setenv("METASEQUOIA_IME_DATA_DIR", metasequoia::path_to_utf8(data_directory).c_str(), 1) != 0)
|
||||
#endif
|
||||
{
|
||||
throw std::runtime_error("Failed to set the temporary-mode test data directory.");
|
||||
}
|
||||
{
|
||||
Database main_database(data_directory / "msime.db");
|
||||
Database english_database(data_directory / "english.db");
|
||||
english_database.execute("CREATE TABLE english_words(word TEXT COLLATE BINARY NOT NULL,display TEXT NOT NULL,"
|
||||
"weight INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(word,display)) WITHOUT ROWID;"
|
||||
"CREATE TABLE en_zh_glosses(english TEXT PRIMARY KEY,chinese_gloss TEXT NOT NULL);"
|
||||
"CREATE TABLE zh_en_glosses(chinese TEXT PRIMARY KEY,english_gloss TEXT NOT NULL);"
|
||||
"INSERT INTO english_words VALUES('he','HE',110);"
|
||||
"INSERT INTO english_words VALUES('hello','Hello',100);"
|
||||
"INSERT INTO english_words VALUES('help','Help',90);");
|
||||
}
|
||||
|
||||
metasequoia::InputSession english(SchemeType::Quanpin);
|
||||
require(english.handle_character('Y', true).handled &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::TemporaryEnglish &&
|
||||
english.preedit() == "Y" && english.candidates().empty(),
|
||||
"Shift+Y did not enter an empty temporary English composition.");
|
||||
const auto bare_english_punctuation = english.handle_punctuation(',');
|
||||
require(bare_english_punctuation.handled && bare_english_punctuation.commit == "," &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None && !english.has_composition(),
|
||||
"Engine punctuation committed or retained the bare temporary-English display prefix.");
|
||||
require(english.handle_character('Y', true).handled,
|
||||
"Temporary English could not restart after engine punctuation.");
|
||||
type(english, "he");
|
||||
require(english.preedit() == "Yhe" && english.candidates().size() == 3 && english.candidates()[0].word == "he" &&
|
||||
english.candidates()[0].source == CandidateSource::Generated &&
|
||||
english.candidates()[1].word == "Hello" && english.candidates()[2].word == "Help" &&
|
||||
std::none_of(english.candidates().begin() + 1, english.candidates().end(),
|
||||
[](const WordItem &candidate) { return candidate.word == "HE"; }),
|
||||
"Temporary English did not put raw input before deduplicated completions.");
|
||||
const auto english_commit = english.select_candidate(2);
|
||||
require(english_commit.handled && english_commit.commit == "Help" &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
english.scheme() == SchemeType::Quanpin,
|
||||
"Temporary English selection did not commit and return to Quanpin.");
|
||||
|
||||
require(english.handle_character('Y', true).handled &&
|
||||
english.handle_command(metasequoia::Command::Backspace).handled &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Backspace on a bare Y prefix did not leave temporary English mode.");
|
||||
require(english.handle_character('Y', true).handled, "Temporary English could not start for a bare-prefix commit.");
|
||||
const auto bare_english_commit = english.handle_command(metasequoia::Command::CommitCandidate);
|
||||
require(bare_english_commit.handled && !bare_english_commit.commit.has_value() &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"A bare temporary-English display prefix escaped into committed text.");
|
||||
require(english.handle_character('Y', true).handled, "Temporary English could not be re-entered.");
|
||||
type(english, "he");
|
||||
require(english.handle_command(metasequoia::Command::Cancel).handled &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Cancel did not leave temporary English mode.");
|
||||
require(english.handle_character('Y', true).handled, "Temporary English could not start before a scheme switch.");
|
||||
english.switch_scheme(SchemeType::Shuangpin);
|
||||
require(english.scheme() == SchemeType::Shuangpin &&
|
||||
english.local_input_mode() == metasequoia::LocalInputMode::None && !english.has_composition(),
|
||||
"A scheme switch did not clear temporary English mode.");
|
||||
|
||||
metasequoia::InputSession english_enter(SchemeType::Quanpin);
|
||||
require(english_enter.handle_character('Y', true).handled,
|
||||
"Temporary English could not start for raw Enter commit.");
|
||||
type(english_enter, "he");
|
||||
const auto english_raw = english_enter.handle_command(metasequoia::Command::CommitRaw);
|
||||
require(english_raw.handled && english_raw.commit == "he" &&
|
||||
english_enter.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Enter committed the display-only Y prefix with temporary English raw input.");
|
||||
|
||||
metasequoia::InputSession english_frequency(SchemeType::Quanpin);
|
||||
require(english_frequency.set_frequency_adjustment({metasequoia::FrequencyAdjustmentMode::Promote, 1, 1}),
|
||||
"A valid temporary-English frequency configuration was rejected.");
|
||||
require(english_frequency.handle_character('Y', true).handled,
|
||||
"Temporary English could not start for frequency learning.");
|
||||
type(english_frequency, "he");
|
||||
require(english_frequency.select_candidate(std::string("Help")).commit == "Help",
|
||||
"Temporary English could not select a completion for frequency learning.");
|
||||
metasequoia::InputSession english_reopened(SchemeType::Quanpin);
|
||||
require(english_reopened.handle_character('Y', true).handled,
|
||||
"Temporary English could not reopen after frequency learning.");
|
||||
type(english_reopened, "he");
|
||||
require(english_reopened.candidates().size() == 3 && english_reopened.candidates()[0].word == "he" &&
|
||||
english_reopened.candidates()[1].word == "Help" && english_reopened.candidates()[2].word == "Hello",
|
||||
"Temporary English frequency learning included the fixed raw candidate in ranking.");
|
||||
|
||||
metasequoia::InputSession japanese(SchemeType::Quanpin);
|
||||
require(japanese.handle_character('R', true).handled &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::TemporaryJapanese &&
|
||||
japanese.preedit() == "R" && japanese.scheme() == SchemeType::Quanpin &&
|
||||
japanese.scheme_type() == SchemeType::Quanpin,
|
||||
"Shift+R did not enter a temporary Japanese session with a visible prefix.");
|
||||
const auto bare_japanese_punctuation = japanese.handle_punctuation(',');
|
||||
require(bare_japanese_punctuation.handled && bare_japanese_punctuation.commit == "," &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
japanese.scheme() == SchemeType::Quanpin && japanese.scheme_type() == SchemeType::Quanpin,
|
||||
"Engine punctuation committed the bare R prefix or failed to restore Quanpin.");
|
||||
require(japanese.handle_character('R', true).handled,
|
||||
"Temporary Japanese could not restart after engine punctuation.");
|
||||
type(japanese, "ka");
|
||||
require(japanese.preedit() == "Rka" && contains(japanese.candidates(), "か"),
|
||||
"Temporary Japanese did not reuse Romaji conversion candidates.");
|
||||
const auto japanese_commit = japanese.select_candidate(std::string("か"));
|
||||
require(japanese_commit.handled && japanese_commit.commit == "か" &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
japanese.scheme() == SchemeType::Quanpin,
|
||||
"Temporary Japanese selection did not restore the original Chinese scheme.");
|
||||
|
||||
require(japanese.handle_character('R', true).handled &&
|
||||
japanese.handle_command(metasequoia::Command::Backspace).handled &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
japanese.scheme() == SchemeType::Quanpin,
|
||||
"Backspace on a bare R prefix did not restore Quanpin.");
|
||||
require(japanese.handle_character('R', true).handled,
|
||||
"Temporary Japanese could not start for a bare-prefix commit.");
|
||||
const auto bare_japanese_commit = japanese.handle_command(metasequoia::Command::CommitCandidate);
|
||||
require(bare_japanese_commit.handled && !bare_japanese_commit.commit.has_value() &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
japanese.scheme() == SchemeType::Quanpin,
|
||||
"A bare temporary-Japanese display prefix escaped into committed text.");
|
||||
require(japanese.handle_character('R', true).handled, "Temporary Japanese could not start for a flush.");
|
||||
const auto empty_flush = japanese.finish_composition();
|
||||
require(empty_flush.handled && !empty_flush.commit.has_value() && !japanese.has_composition(),
|
||||
"Flushing a bare local-mode prefix produced a spurious client insertion.");
|
||||
require(japanese.handle_character('R', true).handled, "Temporary Japanese could not be re-entered.");
|
||||
type(japanese, "ka");
|
||||
require(japanese.handle_command(metasequoia::Command::Cancel).handled && japanese.scheme() == SchemeType::Quanpin &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Cancel did not restore the original scheme from temporary Japanese.");
|
||||
require(japanese.handle_character('R', true).handled, "Temporary Japanese could not start before a scheme switch.");
|
||||
japanese.switch_scheme(SchemeType::Shuangpin);
|
||||
require(japanese.scheme() == SchemeType::Shuangpin &&
|
||||
japanese.local_input_mode() == metasequoia::LocalInputMode::None && !japanese.has_composition(),
|
||||
"An explicit scheme switch did not supersede temporary Japanese restoration.");
|
||||
|
||||
metasequoia::InputSession japanese_enter(SchemeType::Quanpin);
|
||||
require(japanese_enter.handle_character('R', true).handled,
|
||||
"Temporary Japanese could not start for raw Enter commit.");
|
||||
type(japanese_enter, "ka");
|
||||
const auto japanese_raw = japanese_enter.handle_command(metasequoia::Command::CommitRaw);
|
||||
require(japanese_raw.handled && japanese_raw.commit == "ka" &&
|
||||
japanese_enter.local_input_mode() == metasequoia::LocalInputMode::None &&
|
||||
japanese_enter.scheme() == SchemeType::Quanpin,
|
||||
"Enter committed the display-only R prefix or failed to restore Quanpin.");
|
||||
|
||||
metasequoia::InputSession shuangpin_japanese(SchemeType::Shuangpin);
|
||||
require(shuangpin_japanese.handle_character('R', true).handled, "Temporary Japanese did not start from Shuangpin.");
|
||||
type(shuangpin_japanese, "ka");
|
||||
require(shuangpin_japanese.select_candidate(std::string("か")).commit == "か" &&
|
||||
shuangpin_japanese.scheme() == SchemeType::Shuangpin,
|
||||
"Temporary Japanese did not return to the original Shuangpin scheme.");
|
||||
|
||||
metasequoia::LocalModeOptions disabled_options;
|
||||
disabled_options.temporary_english = false;
|
||||
disabled_options.temporary_japanese = false;
|
||||
metasequoia::InputSession disabled(SchemeType::Quanpin);
|
||||
disabled.set_local_mode_options(disabled_options);
|
||||
require(!disabled.handle_character('Y', true).handled && !disabled.has_composition() &&
|
||||
disabled.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Disabled temporary English swallowed Shift+Y.");
|
||||
require(!disabled.handle_character('R', true).handled && !disabled.has_composition() &&
|
||||
disabled.local_input_mode() == metasequoia::LocalInputMode::None,
|
||||
"Disabled temporary Japanese swallowed Shift+R.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user