chore(common/web): Merge branch 'master' into test/common/web/types/9052-unit-tests-kvk-file-writer

This commit is contained in:
Dr Mark C. Sinclair 2024-12-10 10:29:37 +00:00
commit d311bf7ecd
13 changed files with 436 additions and 71 deletions

View file

@ -1,5 +1,15 @@
# Keyman Version History
## 18.0.156 alpha 2024-12-09
* fix(developer): remove platforms from kmc-generate LM readme (#12803)
* test(developer): add test for ERROR_DescriptionIsMissing to kmc-keyboard-info (#12804)
* chore(developer): verify bundled node version when building installer (#12806)
* fix(developer): support hint property in displaymap (#12807)
* fix(common/web): delete replaceExtension in types/src/util/file-types.ts (#12762)
* chore(developer): add some docs for language examples in kmp.json (#12805)
* fix(core): implement ldml_processor::get_key_list() (#12644)
## 18.0.155 alpha 2024-12-07
* chore(android,windows): Update crowdin for Czech (#12792)

View file

@ -1 +1 @@
18.0.156
18.0.157

View file

@ -16,6 +16,7 @@
#pragma once
enum km_core_modifier_state {
KM_CORE_MODIFIER_NONE = 0,
KM_CORE_MODIFIER_LCTRL = 1 << 0,
KM_CORE_MODIFIER_RCTRL = 1 << 1,
KM_CORE_MODIFIER_LALT = 1 << 2,

View file

@ -455,7 +455,13 @@ public:
bool setLayr(const COMP_KMXPLUS_LAYR *newLayr);
bool valid() const;
/**
* @param list index from 0 to layr->listCount
*/
const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const;
/**
* @param entry index value: COMP_KMXPLUS_LAYR_LIST.layer but less than COMP_KMXPLUS_LAYR_LIST.layer+COMP_KMXPLUS_LAYR_LIST.count
*/
const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const;
const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const;
const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const;

View file

@ -328,8 +328,7 @@ km_core_attr const & ldml_processor::attributes() const {
}
km_core_keyboard_key * ldml_processor::get_key_list() const {
km_core_keyboard_key* key_list = new km_core_keyboard_key(KM_CORE_KEYBOARD_KEY_LIST_END);
return key_list;
return keys.get_key_list();
}
km_core_keyboard_imx * ldml_processor::get_imx_list() const {

View file

@ -34,10 +34,6 @@ class ldml_processor : public abstract_processor {
const std::vector<uint8_t> & data
);
static bool is_kmxplus_file(
const std::vector<uint8_t> & data
);
km_core_status
process_event(
km_core_state *state,

View file

@ -8,6 +8,8 @@
#include "ldml_vkeys.hpp"
#include "kmx_file.h"
#include <ldml/keyman_core_ldml.h>
#include <set>
#include <assert.h>
namespace km {
namespace core {
@ -22,6 +24,114 @@ vkeys::add(km_core_virtual_key vk, km_core_ldml_modifier_state modifier_state, s
const vkey_id id(vk, modifier_state);
// assign the string
vkey_to_string[id] = output;
// includes all keys - including gaps.
all_vkeys.insert(id);
}
km_core_keyboard_key *
vkeys::get_key_list() const {
// prescan to find out which modifier flags are used
std::size_t other_key_count = 0; // number of 'OTHER' keys, which will need to expand to all of the other_state set ( so other_state.size())
std::set<km::core::ldml::km_core_ldml_modifier_state> all_modifiers;
for (const auto &k : all_vkeys) {
const auto mod = k.second;
if (mod == LDML_KEYS_MOD_OTHER) {
other_key_count++;
}
all_modifiers.insert(mod);
}
std::set<km_core_modifier_state> other_state;
// Alt
if (all_modifiers.count(LDML_KEYS_MOD_ALT) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
// no ALT keys were seen, so OTHER includes ALT
other_state.insert(KM_CORE_MODIFIER_ALT);
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0) {
other_state.insert(KM_CORE_MODIFIER_RALT);
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
other_state.insert(KM_CORE_MODIFIER_LALT);
}
// ctrl
if (all_modifiers.count(LDML_KEYS_MOD_CTRL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
// no CTRL keys were seen, so OTHER includes CTRL
other_state.insert(KM_CORE_MODIFIER_CTRL);
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0) {
other_state.insert(KM_CORE_MODIFIER_RCTRL);
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
other_state.insert(KM_CORE_MODIFIER_LCTRL);
}
// shift
if (all_modifiers.count(LDML_KEYS_MOD_SHIFT) == 0) {
other_state.insert(KM_CORE_MODIFIER_SHIFT);
}
// caps
if (all_modifiers.count(LDML_KEYS_MOD_CAPS) == 0) {
other_state.insert(KM_CORE_MODIFIER_CAPS);
}
// none- it's possible there is no 'none' layer
if (all_modifiers.count(LDML_KEYS_MOD_NONE) == 0) {
other_state.insert(KM_CORE_MODIFIER_NONE);
}
// We need ALL combinations of the other_state, except for 'all off'.
// The number of additions will be (2**(other_state.size())-1
// Also, since the LDML_KEYS_MOD_OTHER modifier is excluded, we
// will need to subtract 1 when calculating new_list_size
const std::size_t other_expanded_count = (1 << other_state.size()) - 1;
std::vector<uint32_t> other_expanded_mods(other_expanded_count);
// populate the expanded list.
// we start at 1 because 0 is "all bits off" (00000b)
for (std::size_t expansion = 1; expansion <= other_expanded_count; expansion++) {
uint32_t &expanded_mod = other_expanded_mods.at(expansion-1) = KM_CORE_MODIFIER_NONE;
std::size_t bit_mask = 1;
for (const auto mod : other_state) {
// Check if this modifier is on in this iteration of the expansion
// bit_mask will be 2^0 … 2^(other_state().size()-1)
if (bit_mask & expansion) {
// do we include this entry? check if this bit is on
expanded_mod |= mod;
}
// shift the bitmask over
assert(expanded_mod <= KM_CORE_MODIFIER_MASK_CAPS);
bit_mask <<= 1;
}
}
const std::size_t new_list_size = all_vkeys.size() // original size
+ (other_key_count * (other_expanded_count - 1))// number of additional entries needed
+ 1; // terminator
km_core_keyboard_key *list = new km_core_keyboard_key[new_list_size];
std::size_t n = 0;
for (const auto &k : all_vkeys) {
const auto vkey = k.first;
const auto mod = k.second;
if (mod == LDML_KEYS_MOD_OTHER) {
// expand to all of other_state
for (const auto expanded_mod : other_expanded_mods) {
list[n].key = vkey;
list[n++].modifier_flag = expanded_mod;
assert(n <= new_list_size);
}
} else {
assert(mod <= KM_CORE_MODIFIER_MASK_CAPS); // that no LDMLisms escape
list[n].key = vkey;
list[n++].modifier_flag = mod;
assert(n <= new_list_size);
}
}
// add the list terminator
list[n++] = KM_CORE_KEYBOARD_KEY_LIST_END;
assert(n == new_list_size);
return list;
}
static const uint16_t BOTH_ALT = LALTFLAG | RALTFLAG;

View file

@ -12,6 +12,7 @@
#include <unordered_map>
#include <utility>
#include <vector>
#include <set>
#include "keyman_core.h"
@ -37,6 +38,7 @@ typedef std::pair<km_core_virtual_key, km_core_ldml_modifier_state> vkey_id;
class vkeys {
private:
std::map<vkey_id, std::u16string> vkey_to_string;
std::set<vkey_id> all_vkeys;
public:
vkeys();
@ -53,6 +55,11 @@ public:
std::u16string
lookup(km_core_virtual_key vk, uint16_t modifier_state, bool &found) const;
/**
* For implementing ldml_processor::get_key_list()
*/
km_core_keyboard_key* get_key_list() const;
private:
/**
* Non-recursive internal lookup of a specific ID

View file

@ -2,8 +2,9 @@
<!--
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE]
@@expected: \u0037\u1790\u17B6\u0127
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE][LCTRL K_BKQUOTE][RALT K_BKQUOTE]
@@expected: \u0037\u1790\u17B6\u0127\u1790\u17B6\u0065
@@keylist: [SHIFT K_BKQUOTE][SHIFT K_1][K_BKQUOTE][K_1][CTRL-do-not-use K_1][ALT-do-not-use K_BKQUOTE][CAPS K_BKQUOTE][ALT-do-not-use CAPS K_BKQUOTE]
-->
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
@ -23,5 +24,11 @@
<layer id="shift" modifiers="shift">
<row keys="seven eee" /> <!-- number row -->
</layer>
<layer id="control" modifiers="ctrl">
<row keys="that gap" /> <!-- number row -->
</layer>
<layer id="catchall" modifiers="other">
<row keys="eee gap" /> <!-- number row -->
</layer>
</layers>
</keyboard3>

View file

@ -16,6 +16,7 @@
#include <sstream>
#include <string>
#include <type_traits>
#include <set>
#include "path.hpp"
#include "state.hpp"
@ -268,6 +269,63 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
delete[] buf;
}
/**
* @param actual the list from get_key_list()
* @param expected optional list with keys to check, can be empty - not exhaistive
* @returns true if passing
*/
bool
verify_key_list(std::set<km::tests::key_event> &actual, std::set<km::tests::key_event> &expected) {
bool equals = true;
// error if any bad modifier keys
for(const auto &akey : actual) {
if (akey.modifier_state > KM_CORE_MODIFIER_MASK_CAPS) {
equals = false;
std::u16string dump = convert<char, char16_t>(akey.dump()); // akey.dump()
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had key with bad modifier " << akey.modifier_state << ": " << dump << console_color::reset() << std::endl;
}
}
// error if any expected keys missing (note expected may be empty)
for(const auto &ekey : expected) {
if (actual.count(ekey) == 0) {
equals = false;
std::u16string dump = convert<char, char16_t>(ekey.dump()); // akey.dump()
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had missing key " << dump << console_color::reset() << std::endl;
}
}
if (equals) {
std::wcout << console_color::fg(console_color::GREEN) << " " << actual.size() << " vkeys OK, verified " << expected.size() << console_color::reset() << std::endl;
}
return equals;
}
/**
* @param actual_list the list from get_key_list()
* @param keylist optional string with keys to check, can be empty
* @param test the LDML test source, for additional data
* @returns true if passing
*/
bool
verify_key_list(const km_core_keyboard_key *actual_list, const std::u16string &expected_list, const km::tests::LdmlTestSource &test) {
std::set<km::tests::key_event> actual, expected;
// convert actual list
while (actual_list != nullptr && !(actual_list->key == 0 && actual_list->modifier_flag == 0)) {
km::tests::key_event k(actual_list->key, (uint16_t)actual_list->modifier_flag);
actual.insert(k);
actual_list++; // advance pointer
}
// parse the expected list
std::string keylist = convert<char16_t, char>(expected_list);
while (!keylist.empty() && keylist[0] == '[') {
const km::tests::key_event k = km::tests::LdmlEmbeddedTestSource::parse_next_key(keylist);
if (!k.empty()) {
expected.emplace(k);
}
}
return verify_key_list(actual, expected);
}
int
run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source) {
km_core_keyboard * test_kb = nullptr;
@ -374,6 +432,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
errorLine = __LINE__;
}
} break;
case km::tests::LDML_ACTION_CHECK_KEYLIST: {
std::cout << "- checking keylist" << std::endl;
// get keylist from kbd
const km_core_keyboard_key* actual_list = test_kb->get_key_list();
if (!verify_key_list(actual_list, action.string, test_source)) {
errorLine = __LINE__;
} else {
std::cout << " .. passes." << std::endl;
}
delete [] actual_list;
} break;
case km::tests::LDML_ACTION_FAIL: {
// test requested failure
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL: " << action.string << console_color::reset()
@ -424,7 +493,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
std::vector<std::string> failures; // track failures for summary
int embedded_result = embedded_test_source.load_source(source);
int embedded_result = embedded_test_source.load_source(source, compiled);
if (!filter.empty()) {
// Always skip the embedded test if there's a filter.

View file

@ -127,6 +127,50 @@ bool LdmlTestSource::get_expected_beep() const {
return false;
}
int LdmlTestSource::load_kmx_plus(const km::core::path &compiled) {
// check and load the KMX (yes, once again)
rawdata = km::tests::load_kmx_file(compiled);
if(!km::core::ldml_processor::is_handled(rawdata)) {
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
return __LINE__;
}
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
// initialize the kmxplus object with our copy
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
if (!kmxplus->is_valid()) {
std::cerr << "kmx_plus invalid" << std::endl;
return __LINE__;
}
if (!kmxplus->key2Helper.valid()) {
std::cerr << "kmx_plus invalid" << std::endl;
return __LINE__;
}
return 0; // success
}
bool LdmlTestSource::get_vkey_table(std::set<key_event> &fillin) const {
if (!kmxplus || !kmxplus->is_valid()) {
return false; // fail
}
// just dump the kmap table
for (KMX_DWORD kmapIdx = 0; kmapIdx < kmxplus->key2->kmapCount; kmapIdx++) {
const km::core::kmx::COMP_KMXPLUS_KEYS_KMAP *kmap = kmxplus->key2Helper.getKmap(kmapIdx);
if (kmap == nullptr) {
return false;
}
if (kmap->vkey > 0xFF) {
continue; // synthetic key- skip
}
fillin.insert(key_event(kmap->vkey, kmap->mod));
}
return true;
}
// String trim functions from https://stackoverflow.com/a/217605/1836776
// trim from start (in place)
static inline void
@ -264,12 +308,13 @@ LdmlEmbeddedTestSource::is_token(const std::string token, std::string &line) {
}
int
LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
LdmlEmbeddedTestSource::load_source( const km::core::path &path, const km::core::path &compiled ) {
const std::string s_keys = "@@keys: ";
const std::string s_expected = "@@expected: ";
const std::string s_context = "@@context: ";
const std::string s_capsLock = "@@capsLock: ";
const std::string s_expecterror = "@@expect-error: ";
const std::string s_keylist = "@@keylist: ";
// Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected
// result
@ -301,6 +346,10 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
context = parse_source_string(line);
} else if (is_token(s_capsLock, line)) {
set_caps_lock_on(parse_source_string(line).compare(u"1") == 0);
} else if (is_token(s_keylist, line)) {
set_keylist(line);
} else if (line[0] == '@') {
std::cerr << path << " warning, unknown @-command " << line << std::endl;
}
}
@ -316,7 +365,13 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
return __LINE__;
}
return 0;
// and load KMX+ for keylist
if (!expected_error) {
// don't attempt to load KMX+ on expected error
return load_kmx_plus(compiled);
} else {
return 0;
}
}
km_core_status
@ -357,7 +412,7 @@ LdmlTestSource::char_to_event(char ch) {
}
uint16_t
LdmlTestSource::get_modifier(std::string const m) {
LdmlTestSource::get_modifier(std::string const &m) {
for (int i = 0; km::core::kmx::s_modifier_names[i].name; i++) {
if (m == km::core::kmx::s_modifier_names[i].name) {
return km::core::kmx::s_modifier_names[i].modifier;
@ -366,6 +421,12 @@ LdmlTestSource::get_modifier(std::string const m) {
return 0;
}
std::string key_event::dump() const {
std::stringstream f;
f << "Key: {" << km::core::kmx::Debug_VirtualKey(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}";
return f.str();
}
key_event
LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
// vkey format is MODIFIER MODIFIER K_NAME
@ -401,34 +462,37 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
void
LdmlEmbeddedTestSource::next_action(ldml_action &fillin) {
if (is_done || keys.empty()) {
// We were already done. return done.
fillin.type = LDML_ACTION_DONE;
return;
if (keys.empty()) {
// #3 we are almost done, let's run the key check
if (check_keylist) {
fillin.type = LDML_ACTION_CHECK_KEYLIST;
fillin.string = expected_keylist; // could be empty
check_keylist = false;
} else {
fillin.type = LDML_ACTION_DONE;
}
} else if(keys[0].empty()) {
// #2. Then, when we finish a key set, we check the 'expected' at the end of it.
// Got to the end of a key set. time to check
fillin.type = LDML_ACTION_CHECK_EXPECTED;
fillin.string = expected[0]; // copy expected
expected.pop_front();
keys.pop_front();
if (keys.empty()) {
is_done = true; // so we get DONE next time
}
} else {
// #1 First, we process each key
fillin.type = LDML_ACTION_KEY_EVENT;
fillin.k = next_key();
}
}
key_event
LdmlEmbeddedTestSource::next_key() {
// mutate this->keys
return next_key(keys[0]);
return parse_next_key(keys[0]);
}
key_event
LdmlEmbeddedTestSource::next_key(std::string &keys) {
LdmlEmbeddedTestSource::parse_next_key(std::string &keys) {
// Parse the next element of the string, chop it off, and return it
// mutates keys
if (keys.length() == 0)
@ -453,10 +517,10 @@ LdmlEmbeddedTestSource::next_key(std::string &keys) {
class LdmlJsonTestSource : public LdmlTestSource {
public:
LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
LdmlJsonTestSource(const std::string &path);
virtual ~LdmlJsonTestSource();
virtual const std::u16string &get_context();
int load(const nlohmann::json &test);
int load(const nlohmann::json &test, const km::core::path &compiled);
virtual void next_action(ldml_action &fillin);
private:
std::string path;
@ -467,14 +531,14 @@ private:
* Which action are we on?
*/
std::size_t action_index = -1;
const km::core::kmx::kmx_plus *kmxplus;
/** @return false if not found */
bool set_key_from_id(key_event& k, const std::u16string& id);
bool loaded_context = false;
bool check_keys = true;
};
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
:path(path), kmxplus(k) {
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path)
:path(path) {
}
@ -519,6 +583,13 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id)
void
LdmlJsonTestSource::next_action(ldml_action &fillin) {
if ((action_index+1) >= data["/actions"_json_pointer].size()) {
// add check keylist
if (check_keys) {
fillin.type = LDML_ACTION_CHECK_KEYLIST;
fillin.string.clear();
check_keys = false;
return;
}
// at end, done
fillin.type = LDML_ACTION_DONE;
return;
@ -586,19 +657,21 @@ LdmlJsonTestSource::get_context() {
return context;
}
int LdmlJsonTestSource::load(const nlohmann::json &data) {
int LdmlJsonTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
this->data = data;
// TODO-LDML: validate here?
return 0;
// TODO-LDML: validate JSON here?
// load up the kmx_plus
return load_kmx_plus(compiled);
}
#if defined(HAVE_ICU4C)
class LdmlJsonRepertoireTestSource : public LdmlTestSource {
public:
LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
LdmlJsonRepertoireTestSource(const std::string &path );
virtual ~LdmlJsonRepertoireTestSource();
virtual const std::u16string &get_context();
int load(const nlohmann::json &test);
int load(const nlohmann::json &test, const km::core::path &compiled);
virtual void next_action(ldml_action &fillin);
private:
std::string path;
@ -610,11 +683,10 @@ private:
std::unique_ptr<icu::UnicodeSet> uset;
std::unique_ptr<icu::UnicodeSetIterator> iterator;
bool need_check = false; // set this after each char
const km::core::kmx::kmx_plus *kmxplus;
};
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
:path(path), kmxplus(k){
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path)
:path(path) {
}
@ -702,7 +774,7 @@ LdmlJsonRepertoireTestSource::get_context() {
return context; // no context needed
}
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
this->data = data; // TODO-LDML
// Need an update to json.hpp to use contains()
// if (data.contains("/type"_json_pointer)) {
@ -733,7 +805,7 @@ int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
// }
// #endif
iterator = std::unique_ptr<icu::UnicodeSetIterator>(new icu::UnicodeSetIterator(*uset));
return 0;
return load_kmx_plus(compiled);
}
#endif // HAVE_ICU4C
@ -757,26 +829,6 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
return __LINE__; // empty
}
// check and load the KMX (yes, once again)
rawdata = km::tests::load_kmx_file(compiled);
if (!km::core::ldml_processor::is_handled(rawdata)) {
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
return __LINE__;
}
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
// initialize the kmxplus object with our copy
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
if (!kmxplus->is_valid()) {
std::cerr << "kmx_plus invalid" << std::endl;
return __LINE__;
}
if (!kmxplus->key2Helper.valid()) {
std::cerr << "kmx_plus invalid" << std::endl;
return __LINE__;
}
auto conformsTo = data["/keyboardTest3/conformsTo"_json_pointer].get<std::string>();
assert_or_return(std::string(LDML_CLDR_TEST_VERSION_LATEST) == conformsTo);
@ -799,8 +851,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
test_path.append(info_name).append("/tests/").append(tests_name).append("/").append(test_name);
// std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl;
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path, kmxplus.get()));
assert_or_return(subtest->load(test) == 0);
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path));
assert_or_return(subtest->load(test, compiled) == 0);
test_map[test_path] = std::unique_ptr<LdmlTestSource>(subtest.release());
}
}
@ -815,8 +867,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
std::string test_path;
test_path.append(info_name).append("/repertoire/").append(rep_name);
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path, kmxplus.get()));
assert_or_return(reptest->load(rep) == 0);
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path));
assert_or_return(reptest->load(rep, compiled) == 0);
test_map[test_path] = std::unique_ptr<LdmlTestSource>(reptest.release());
}

View file

@ -4,6 +4,7 @@
#include "path.hpp"
#include <map>
#include <set>
#include <memory>
#include "kmx/kmx_plus.h"
@ -17,6 +18,34 @@ namespace tests {
struct key_event {
km_core_virtual_key vk;
uint16_t modifier_state;
public:
key_event() : vk(0), modifier_state(0) {
}
key_event(km_core_virtual_key k, uint16_t m) :vk(k), modifier_state(m) {
}
std::string dump() const;
int compare(const key_event &other) const {
if (vk < other.vk) return -1;
if (vk > other.vk) return 1;
if (modifier_state < other.modifier_state) return -1;
if (modifier_state > other.modifier_state) return 1;
return 0;
}
bool operator<(const key_event &other) const {
return compare(other) < 0;
}
bool operator>(const key_event &other) const {
return compare(other) > 0;
}
bool operator==(const key_event &other) const {
return compare(other) == 0;
}
/** true if unset (null) key */
bool empty() const {
return vk == 0 && modifier_state == 0;
}
};
enum ldml_action_type {
@ -40,6 +69,10 @@ enum ldml_action_type {
* expected text
*/
LDML_ACTION_CHECK_EXPECTED,
/**
* string - keylist to check
*/
LDML_ACTION_CHECK_KEYLIST,
// TODO-LDML: gestures, etc? Depends on touch.
/**
@ -80,10 +113,17 @@ public:
virtual km_core_status get_expected_load_status();
virtual const std::u16string &get_context() = 0;
virtual bool get_expected_beep() const;
/**
* fillin a list
* @param fillin key list to be filled in with all vkeys in the hardware map.
* @param modifier modifier to load key list for
* @returns false on load fail, true on OK
*/
bool get_vkey_table(std::set<key_event> &fillin) const;
// helper functions
static key_event char_to_event(char ch);
static uint16_t get_modifier(std::string const m);
static uint16_t get_modifier(std::string const &m);
static std::u16string parse_source_string(std::string const &s);
static std::u16string parse_u8_source_string(std::string const &s);
@ -105,6 +145,12 @@ private:
private:
bool _caps_lock_on = false;
protected:
/** populate rawdata and kmxplus */
int load_kmx_plus(const km::core::path &compiled);
// copy of the kbd data, for lookups
std::vector<uint8_t> rawdata;
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
};
typedef std::map<std::string, std::unique_ptr<km::tests::LdmlTestSource>> JsonTestMap;
@ -123,9 +169,6 @@ class LdmlJsonTestSourceFactory {
const JsonTestMap& get_tests() const;
private:
JsonTestMap test_map;
// copy of the kbd data, for lookups
std::vector<uint8_t> rawdata;
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
};
@ -137,7 +180,7 @@ public:
/**
* Load the test_source from comments in the .xml source
*/
int load_source(const km::core::path &path);
int load_source(const km::core::path &path, const km::core::path &compiled);
virtual km_core_status get_expected_load_status();
virtual const std::u16string &get_context();
@ -146,10 +189,6 @@ public:
virtual void next_action(ldml_action &fillin);
private:
bool is_token(const std::string token, std::string &line);
key_event vkey_to_event(std::string const &vk_event);
key_event next_key(std::string &keys);
key_event next_key();
std::deque<std::string> keys;
@ -158,6 +197,24 @@ private:
bool expected_beep = false;
bool expected_error = false;
bool is_done = false;
/** did we check the keylist yet? */
bool check_keylist = true;
/** set the expected keys in the keylist */
void set_keylist(std::string const& s) {
expected_keylist = parse_source_string(s);
}
std::u16string expected_keylist;
/** returns false on fail and updates the message */
bool handle_check_keylist(std::string &message) const;
// utility
static key_event vkey_to_event(std::string const &vk_event);
static bool is_token(const std::string token, std::string &line);
public:
static key_event parse_next_key(std::string &keys);
};
} // namespace tests

View file

@ -243,6 +243,12 @@ The `Keyboard` object describes an individual keyboard in the Keyman package. A
The filename of the font for the OSK
`examples`
: `Array`
An array of [`Example`](#obj-example) objects linked to the keyboard.
### The Language object
The `Language` object describes the language that can be typed with the keyboard
@ -259,6 +265,51 @@ The `Language` object describes the language that can be typed with the keyboard
[BCP 47 language code](../bcp-47)
### The Example object
The `Example` object describes a keying sequence example text for the keyboard.
This can be the easiest way for a new user to start using a keyboard, and is
particularly helpful when the keyboard makes use of keying sequences that may
not be immediately obvious.
`id`
: `string`
The BCP 47 language code for the example.
`keys`
: `string`
The key sequence to type the example. The key sequence must list each key
combination, separated by space. The actual text for each key is reasonably
arbitrary, to allow you to provide examples for touch keyboards as well as
desktop keyboards. There are three special kinds of key strings:
* Modifier keys may be specified with the `+` character, e.g. `shift+e` or
`right-alt+k`. Use lower case keys. The suggested standard modifiers are:
`shift`, `ctrl`, `alt`, `left-alt`, `right-alt`, `left-ctrl`, `right-ctrl`,
and `option` (mac).
* The <kbd>space</kbd> key itself may be specified with `space`.
* To avoid confusion with modifier keys, the <kbd>+</kbd> key can be specified
with `plus`.
For example, the key sequence <kbd>x</kbd>, <kbd>j</kbd>, <kbd>m</kbd>,
<kbd>Shift</kbd>+<kbd>e</kbd>, <kbd>r</kbd> may be specified as
`x j m shift+e r` or `x j m E r`.
`text`
: `string`
The expected output when the `keys` are typed
`note`
: `string`
A brief explanation of the example, e.g. "Name of language"
### The LexicalModel object
The `LexicalModel` object describes an individual model in the Keyman package. A package cannot contain both lexical models and keyboards.