mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-11 11:25:34 +00:00
Merge branch 'epic/kmc-convert' into feat/developer/kmc-convert
This commit is contained in:
commit
7c4ea1bb5e
49 changed files with 11455 additions and 3972 deletions
46
HISTORY.md
46
HISTORY.md
|
|
@ -1,5 +1,40 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.202 alpha 2026-02-12
|
||||
|
||||
* fix(linux): check for existing file before trying to install (#15573)
|
||||
|
||||
## 19.0.201 alpha 2026-02-09
|
||||
|
||||
* fix(linux): include missing artifacts in source tarball (#15566)
|
||||
* chore(linux): use xz compression for source tarballs (#15568)
|
||||
|
||||
## 19.0.200 alpha 2026-02-06
|
||||
|
||||
* change(web): define, utilize cleaner Web test-resource import paths (#15564)
|
||||
* maint(mac): upgrade to Xcode 26.2 (#15484)
|
||||
|
||||
## 19.0.199 alpha 2026-02-05
|
||||
|
||||
* chore(deps): bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /developer/src/server/src/win32/trayicon/addon-src (#15552)
|
||||
* fix(windows): unresponsive splash screen (#15269)
|
||||
* maint(common): Update langtags.json to 2026-02-03 release (v1.4) (#15459)
|
||||
* feat(android): Use current display language for keyboard search (#15510)
|
||||
* chore(linux): Update debian changelog (#15557)
|
||||
* chore(linux): update copyright year and standards version (#15559)
|
||||
|
||||
## 19.0.198 alpha 2026-02-04
|
||||
|
||||
* fix(core): handle backspace decomposition (#15488)
|
||||
* fix(core): normalization segment should end on NFC boundary, not NFD (#15506)
|
||||
|
||||
## 19.0.197 alpha 2026-02-02
|
||||
|
||||
* chore(deps): bump tar from 7.5.6 to 7.5.7 in /developer/src/server/src/win32/trayicon/addon-src (#15514)
|
||||
* chore(deps): bump fast-xml-parser from 5.2.2 to 5.3.4 (#15526)
|
||||
* fix(windows): handle symstore correctly from bash script (#15524)
|
||||
* chore(ios): Add fv_tlingityooxatangi to FirstVoices for iOS app (#15485)
|
||||
|
||||
## 19.0.196 alpha 2026-01-30
|
||||
|
||||
* chore(common): Update Crowdin strings for `de` (#15511)
|
||||
|
|
@ -1170,6 +1205,17 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.246 stable 2026-02-04
|
||||
|
||||
* cherrypick(common): Update FirstVoices versions in keyboards.csv (#15273)
|
||||
* docs(ios): Use /cdn/dev assets for Engine guides (#15274)
|
||||
* chore(linux): Update debian changelog (#15281)
|
||||
* maint(linux): remove EOL Ubuntu 25.04 Plucky (#15424)
|
||||
* docs(developer): update primerprep link (#15468)
|
||||
* chore(ios): Add fv_tlingityooxatangi to FirstVoices for iOS app (#15496)
|
||||
* fix(core): handle backspace decomposition (#15494)
|
||||
* fix(core): normalization segment should end on NFC boundary, not NFD (#15551)
|
||||
|
||||
## 18.0.245 stable 2025-12-03
|
||||
|
||||
* chore(windows): VS2022 patch for stable-18.0 (#15081)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.197
|
||||
19.0.203
|
||||
|
|
@ -11,6 +11,7 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
|
@ -38,7 +39,7 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
private static final String TAG = "KMPBrowserActivity";
|
||||
|
||||
// URL for keyboard search web page presented to user when they add a keyboard in the app.
|
||||
private static final String KMP_SEARCH_KEYBOARDS_FORMATSTR = "https://%s/go/android/%s/download-keyboards%s";
|
||||
private static final String KMP_SEARCH_KEYBOARDS_FORMATSTR = "https://%s/go/android/%s/download-keyboards%s?lang=%s";
|
||||
private static final String KMP_SEARCH_KEYBOARDS_LANGUAGES = "/languages/%s";
|
||||
|
||||
// Patterns for determining if a link should be opened in external browser
|
||||
|
|
@ -163,13 +164,7 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
}
|
||||
});
|
||||
|
||||
// Tier determines the keyboard search host
|
||||
String host = KMPLink.getHost();
|
||||
// If language ID is provided, include it in the keyboard search
|
||||
String languageID = getIntent().getStringExtra("languageCode");
|
||||
String languageStr = (languageID != null) ? KMString.format(KMP_SEARCH_KEYBOARDS_LANGUAGES, languageID) : "";
|
||||
String appMajorVersion = KMManager.getMajorVersion();
|
||||
String kmpSearchUrl = KMString.format(KMP_SEARCH_KEYBOARDS_FORMATSTR, host, appMajorVersion, languageStr);
|
||||
String kmpSearchUrl = determineSearchUrl();
|
||||
webView.loadUrl(kmpSearchUrl);
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +173,8 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
super.onResume();
|
||||
|
||||
if (webView != null) {
|
||||
webView.reload();
|
||||
String kmpSearchUrl = determineSearchUrl();
|
||||
webView.loadUrl(kmpSearchUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +217,32 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the keyman.com keyboard search URL while accounting for:
|
||||
* host (production or staging)
|
||||
* languageID - language ID to search for
|
||||
* display language - request search results to be displayed in this language
|
||||
* @return String
|
||||
*/
|
||||
private String determineSearchUrl() {
|
||||
// Tier determines the keyboard search host
|
||||
String host = KMPLink.getHost();
|
||||
|
||||
// If display language is provided, include it in the keyboard search. Otherwise fallback to default locale
|
||||
String fallbackLang = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) ?
|
||||
LocaleList.getDefault().get(0).toLanguageTag() : "en";
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
String displayLang = (bundle != null) && bundle.containsKey("lang") ? bundle.getString("lang") : fallbackLang;
|
||||
|
||||
// If language ID is provided for keyboard search, include it in the keyboard search
|
||||
String languageID = getIntent().getStringExtra("languageCode");
|
||||
String languageStr = (languageID != null) ? KMString.format(KMP_SEARCH_KEYBOARDS_LANGUAGES, languageID) : "";
|
||||
|
||||
String appMajorVersion = KMManager.getMajorVersion();
|
||||
String kmpSearchUrl = KMString.format(KMP_SEARCH_KEYBOARDS_FORMATSTR, host, appMajorVersion, languageStr, displayLang);
|
||||
return kmpSearchUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL is a valid internal Keyman keyboard link
|
||||
* @param url String of the URL to parse
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import java.util.HashMap;
|
|||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -24,6 +26,7 @@ import android.widget.SimpleAdapter;
|
|||
import android.widget.TextView;
|
||||
|
||||
import com.keyman.engine.BaseActivity;
|
||||
import com.keyman.engine.DisplayLanguages;
|
||||
import com.keyman.engine.KMManager;
|
||||
import com.keyman.engine.data.KeyboardController;
|
||||
import com.keyman.engine.util.MapCompat;
|
||||
|
|
@ -127,12 +130,19 @@ public class KeymanSettingsInstallActivity extends BaseActivity {
|
|||
HashMap<String, String> hashMap = (HashMap<String, String>) parent.getItemAtPosition(position);
|
||||
String itemTitle = MapCompat.getOrDefault(hashMap, titleKey, "");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String languageTag = prefs.getString(DisplayLanguages.displayLanguageKey, "");
|
||||
// Install from keyman.com
|
||||
if (itemTitle.equals(getString(R.string.install_from_keyman_dot_com))) {
|
||||
if (KMManager.hasConnection(context)) {
|
||||
Intent i = new Intent(context, KMPBrowserActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
if (languageTag != null && !languageTag.isEmpty()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("lang", languageTag);
|
||||
i.putExtras(bundle);
|
||||
}
|
||||
context.startActivity(i);
|
||||
} else {
|
||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ import androidx.annotation.NonNull;
|
|||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.keyman.engine.BaseActivity;
|
||||
import com.keyman.engine.DisplayLanguages;
|
||||
import com.keyman.engine.KeyboardPickerActivity;
|
||||
import com.keyman.engine.KMManager;
|
||||
import com.keyman.engine.ModelPickerActivity;
|
||||
|
|
@ -201,11 +203,19 @@ public final class LanguageSettingsActivity extends BaseActivity {
|
|||
if (KMManager.hasConnection(context)){
|
||||
// Scenario 1: Connection to keyman.com catalog
|
||||
// Pass the BCP47 language code to the KMPBrowserActivity
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String languageTag = prefs.getString(DisplayLanguages.displayLanguageKey, "");
|
||||
|
||||
Intent i = new Intent(context, KMPBrowserActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
i.putExtra("languageCode", lgCode);
|
||||
i.putExtra("languageName", lgName);
|
||||
if (languageTag != null && !languageTag.isEmpty()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("lang", languageTag);
|
||||
i.putExtras(bundle);
|
||||
}
|
||||
context.startActivity(i);
|
||||
/*
|
||||
} else if (KeyboardPickerActivity.hasKeyboardFromPackage()) {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ bool km::core::actions_normalize(
|
|||
boundary prior to the intersection of the cached_context and the output.
|
||||
*/
|
||||
if(!output.empty()) {
|
||||
while(n > 0 && !km::core::util::has_nfd_boundary_before(output[0])) {
|
||||
while(n > 0 && !km::core::util::has_nfc_boundary_before(output[0])) {
|
||||
// The output may interact with the context further in normalization. We
|
||||
// need to copy characters back further until we reach a normalization
|
||||
// boundary.
|
||||
|
|
@ -133,6 +133,7 @@ bool km::core::actions_normalize(
|
|||
To adjust, we remove one codepoint at a time from the app_context until
|
||||
its normalized form matches the cached_context normalized form.
|
||||
*/
|
||||
std::u32string context_final = U"";
|
||||
|
||||
while(!app_context_string.empty()) {
|
||||
auto app_context_nfd = app_context_string;
|
||||
|
|
@ -141,7 +142,16 @@ bool km::core::actions_normalize(
|
|||
return false;
|
||||
}
|
||||
|
||||
if(app_context_nfd == cached_context_string) {
|
||||
/*
|
||||
We are working in NFD as we backtrack in the context, but input variable
|
||||
app_context_string is NFC. The last character of the context may be part
|
||||
of a composed character, so we need to track decomposition. For example,
|
||||
'tä' + BKSP should result in 'ta', but delete back 1 in app_context_string
|
||||
will give us 't', so we need to remember 'a', and add it back afterwards
|
||||
(see output_nfc variable).
|
||||
*/
|
||||
if(app_context_nfd == cached_context_string.substr(0, app_context_nfd.length())) {
|
||||
context_final = cached_context_string.substr(app_context_nfd.length());
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +165,7 @@ bool km::core::actions_normalize(
|
|||
Normalize our output string
|
||||
*/
|
||||
|
||||
auto output_nfc = output;
|
||||
auto output_nfc = context_final + output;
|
||||
if(!km::core::util::normalize_nfc(output_nfc)) {
|
||||
DebugLog("nfc->normalize failed");
|
||||
return false;
|
||||
|
|
@ -197,6 +207,8 @@ bool km::core::actions_normalize(
|
|||
actions.output = new_output;
|
||||
actions.code_points_to_delete = nfu_to_delete;
|
||||
|
||||
// Outcome will be: <app_context><context_final><output>|
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -395,13 +395,18 @@ const char *Debug_ModifierName(KMX_UINT modifiers) {
|
|||
}
|
||||
|
||||
const char *Debug_VirtualKey(KMX_WORD vk) {
|
||||
if (!ShouldDebug()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return Debug_VirtualKey_Always(vk);
|
||||
}
|
||||
|
||||
const char *Debug_VirtualKey_Always(KMX_WORD vk) {
|
||||
#ifdef _MSC_VER
|
||||
__declspec(thread)
|
||||
#endif
|
||||
static char buf[256];
|
||||
if (!ShouldDebug()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (vk < 256) {
|
||||
snprintf(buf, 256, "['%s' 0x%x]", s_key_names[vk], vk);
|
||||
|
|
@ -411,7 +416,6 @@ const char *Debug_VirtualKey(KMX_WORD vk) {
|
|||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
const char *Debug_UnicodeString(PKMX_WCHAR s, int x) {
|
||||
if (!ShouldDebug()) {
|
||||
return "";
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ extern const char *s_key_names[];
|
|||
|
||||
int DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...);
|
||||
const char *Debug_VirtualKey(KMX_WORD vk);
|
||||
const char *Debug_VirtualKey_Always(KMX_WORD vk);
|
||||
/**
|
||||
* @param s PKMX_WCHAR to output
|
||||
* @param x temporary buffer (0 or 1) to write to
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ bool is_nfd(const std::u32string& str) {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool has_nfd_boundary_before(km_core_usv cp) {
|
||||
bool has_nfc_boundary_before(km_core_usv cp) {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// it's a negative table. entries in the table mean returning false. non-entries return true.
|
||||
for (auto i=0;i<(km_noBoundaryBefore_entries*2);i+=2) {
|
||||
|
|
@ -221,9 +221,9 @@ bool has_nfd_boundary_before(km_core_usv cp) {
|
|||
return true; // fallthrough
|
||||
#else
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
auto nfd = getNFD(status);
|
||||
if (nfd == nullptr) return false;
|
||||
return nfd->hasBoundaryBefore(cp);
|
||||
auto nfc = getNFC(status);
|
||||
if (nfc == nullptr) return false;
|
||||
return nfc->hasBoundaryBefore(cp);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ bool is_nfd(const std::u16string& str);
|
|||
bool is_nfd(const std::u32string& str);
|
||||
|
||||
/** @return true if cp can interacts with prior chars */
|
||||
bool has_nfd_boundary_before(km_core_usv cp);
|
||||
bool has_nfc_boundary_before(km_core_usv cp);
|
||||
|
||||
/** convenience function, caller owns storage */
|
||||
km_core_usv *string_to_usv(const std::u32string& src);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
|
||||
int
|
||||
write_nfd_table() {
|
||||
write_nfc_table() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::cerr << "Note: This is unusual - this generator is usually only run under emscripten!" << std::endl;
|
||||
#endif
|
||||
|
|
@ -49,13 +49,13 @@ write_nfd_table() {
|
|||
std::cout << std::endl;
|
||||
// we're going to need an NFD normalizer
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
|
||||
const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(status);
|
||||
assert(U_SUCCESS(status));
|
||||
|
||||
// collect the raw list of chars that do NOT have a boundary before them.
|
||||
std::vector<km_core_usv> noBoundary;
|
||||
for (km_core_usv ch = 0; ch < km::core::kmx::Uni_MAX_CODEPOINT; ch++) {
|
||||
bool bb = nfd->hasBoundaryBefore(ch);
|
||||
bool bb = nfc->hasBoundaryBefore(ch);
|
||||
assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator
|
||||
if (bb) continue; //only emit nonboundary
|
||||
noBoundary.push_back(ch);
|
||||
|
|
@ -102,6 +102,6 @@ write_nfd_table() {
|
|||
|
||||
int
|
||||
main(int /*argc*/, const char * /*argv*/[]) {
|
||||
write_nfd_table();
|
||||
write_nfc_table();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,6 +311,17 @@ void run_actions_normalize_tests() {
|
|||
/* app_context: */ u"abcệ"
|
||||
);
|
||||
|
||||
test_actions_normalize(
|
||||
"One backspace to delete last NFD character (#15487)",
|
||||
/* app context pre transform: */ u"abcê", // NFC
|
||||
/* cached context post transform: */ u"abce",
|
||||
/* cached context post transform: */ nullptr,
|
||||
/* action del, output: */ 1, U"", // NFD input; delete 1: \u0302
|
||||
// ---- results ----
|
||||
/* action del, output: */ 1, U"e", // NFC output; delete 1: e
|
||||
/* app_context: */ u"abce"
|
||||
);
|
||||
|
||||
test_actions_normalize(
|
||||
"One backspace for NFD converts into one char in NFC (ê) and recombine",
|
||||
/* app context pre transform: */ u"abcê",
|
||||
|
|
@ -347,6 +358,18 @@ void run_actions_normalize_tests() {
|
|||
/* app_context: */ u"\u0323\u0300\u0302"
|
||||
);
|
||||
|
||||
// #15505 - normalization of Bengali characters
|
||||
test_actions_normalize(
|
||||
"Bengali normalization of U+09C7 U+09D7 -> U+09CC",
|
||||
/* app context pre transform: */ u"\u0995\u09C7",
|
||||
/* cached context post transform: */ u"\u0995\u09C7\u09D7",
|
||||
/* cached context post transform: */ nullptr,
|
||||
/* action del, output: */ 0, U"\u09D7",
|
||||
// ---- results ----
|
||||
/* action del, output: */ 1, U"\u09CC",
|
||||
/* app_context: */ u"\u0995\u09CC"
|
||||
);
|
||||
|
||||
// Modifies the base as well as diacritic
|
||||
|
||||
test_actions_normalize(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
Note that the 'expected' lines are cumulative unless there's a reset event.
|
||||
|
||||
@@keys: [K_A][K_B][K_SLASH][K_BKSP]
|
||||
@@expected: ab
|
||||
Comment: bkspace should delete just the U+0301
|
||||
|
||||
@@keys: [K_C][K_E][K_SLASH][K_BKSP]
|
||||
@@expected: abce
|
||||
Comment: #15487: bksp will delete just acute (even though it will have combined to NFC, internally we do NFD bksp)
|
||||
|
||||
TODO: this seems to be working NFD on app context side also, do we need to set a flag? this may break other tests so care is needed
|
||||
|
||||
-->
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="en" conformsTo="45">
|
||||
<info author="Marc Durdin" name="Test BKSP"/>
|
||||
<version number="1.0.0"/>
|
||||
<keys>
|
||||
<import base="cldr" path="45/keys-Zyyy-punctuation.xml"/>
|
||||
<import base="cldr" path="45/keys-Zyyy-currency.xml"/>
|
||||
<key id="acute" output="\u{0301}"/>
|
||||
</keys>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none">
|
||||
<row keys="grave 1 2 3 4 5 6 7 8 9 0 hyphen equal"/>
|
||||
<row keys="q w e r t y u i o p open-square close-square backslash"/>
|
||||
<row keys="a s d f g h j k l semi-colon apos"/>
|
||||
<row keys="z x c v b n m comma period acute"/>
|
||||
<row keys="space"/>
|
||||
</layer>
|
||||
<layer modifiers="shift">
|
||||
<row keys="tilde bang at hash dollar percent caret amp asterisk open-paren close-paren underscore plus"/>
|
||||
<row keys="Q W E R T Y U I O P open-curly close-curly pipe"/>
|
||||
<row keys="A S D F G H J K L colon double-quote"/>
|
||||
<row keys="Z X C V B N M open-angle close-angle question"/>
|
||||
<row keys="space"/>
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
|
|
@ -30,6 +30,7 @@ tests_without_testdata = [
|
|||
'k_100_keytest',
|
||||
'k_101_keytest',
|
||||
'k_102_keytest',
|
||||
'k_213_backspace_decomposition',
|
||||
]
|
||||
|
||||
# These tests have a *-test.xml file as well.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@
|
|||
|
||||
namespace {
|
||||
|
||||
void print_context(std::u16string &text_store, km_core_state *&test_state, std::vector<km_core_context_item> &test_context);
|
||||
|
||||
bool g_beep_found = false;
|
||||
|
||||
km_core_option_item test_env_opts[] =
|
||||
|
|
@ -82,13 +84,15 @@ apply_action(
|
|||
std::vector<km_core_context_item> &context,
|
||||
km::tests::LdmlTestSource &test_source,
|
||||
std::vector<km_core_context_item> &test_context) {
|
||||
|
||||
// print_context(text_store, test_state, test_context);
|
||||
switch (act.type) {
|
||||
case KM_CORE_IT_END:
|
||||
test_assert(false);
|
||||
break;
|
||||
case KM_CORE_IT_ALERT:
|
||||
g_beep_found = true;
|
||||
// std::cout << "beep" << std::endl;
|
||||
std::cout << " + beep" << std::endl;
|
||||
break;
|
||||
case KM_CORE_IT_CHAR:
|
||||
context.push_back(km_core_context_item{
|
||||
|
|
@ -104,10 +108,10 @@ apply_action(
|
|||
text_store.push_back(buf.ch[i]);
|
||||
}
|
||||
}
|
||||
// std::cout << "char(" << act.character << ") size=" << cp->size() << std::endl;
|
||||
std::cout << " + char(" << act.character << ")" << std::endl;
|
||||
break;
|
||||
case KM_CORE_IT_MARKER:
|
||||
// std::cout << "deadkey(" << act.marker << ")" << std::endl;
|
||||
std::cout << " + deadkey(" << act.marker << ")" << std::endl;
|
||||
context.push_back(km_core_context_item{
|
||||
KM_CORE_CT_MARKER,
|
||||
{
|
||||
|
|
@ -117,6 +121,8 @@ apply_action(
|
|||
break;
|
||||
case KM_CORE_IT_BACK:
|
||||
{
|
||||
std::cout << " + back(" << act.backspace.expected_type << ")" << std::endl;
|
||||
|
||||
// single char removed in context
|
||||
km_core_usv ch = 0;
|
||||
bool matched_text = false;
|
||||
|
|
@ -167,10 +173,11 @@ apply_action(
|
|||
}
|
||||
break;
|
||||
case KM_CORE_IT_PERSIST_OPT:
|
||||
std::cout << " + TODO-LDML: persist_opt()" << std::endl;
|
||||
break;
|
||||
case KM_CORE_IT_INVALIDATE_CONTEXT:
|
||||
{
|
||||
std::cout << "action: context invalidated (markers cleared)" << std::endl;
|
||||
std::cout << " + context invalidated (markers cleared)" << std::endl;
|
||||
// TODO-LDML: We need the context for tests. So we will simulate recreating
|
||||
// the context from the context string.
|
||||
km_core_context_item* new_context_items = nullptr;
|
||||
|
|
@ -186,11 +193,11 @@ apply_action(
|
|||
}
|
||||
break;
|
||||
case KM_CORE_IT_EMIT_KEYSTROKE:
|
||||
std::cout << "action: emit keystroke" << std::endl;
|
||||
std::cout << " + emit keystroke" << std::endl;
|
||||
// TODO-LDML: For now, this is a no-op. We could handle enter, etc.
|
||||
break;
|
||||
case KM_CORE_IT_CAPSLOCK:
|
||||
std::cout << "action: capsLock " << act.capsLock << std::endl;
|
||||
std::cout << " + capsLock " << act.capsLock << std::endl;
|
||||
test_source.set_caps_lock_on(act.capsLock);
|
||||
break;
|
||||
default:
|
||||
|
|
@ -199,11 +206,46 @@ apply_action(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* verify the current context
|
||||
*/
|
||||
|
||||
void
|
||||
verify_context(std::u16string &text_store, km_core_state *&test_state, std::vector<km_core_context_item> &test_context) {
|
||||
apply_actions(
|
||||
km_core_state *test_state,
|
||||
km_core_actions const *actions,
|
||||
std::u16string &text_store,
|
||||
std::vector<km_core_context_item> &context,
|
||||
km::tests::LdmlTestSource &test_source,
|
||||
std::vector<km_core_context_item> &test_context
|
||||
) {
|
||||
|
||||
if(actions->do_alert) {
|
||||
apply_action(test_state, {KM_CORE_IT_ALERT}, text_store, context, test_source, test_context);
|
||||
}
|
||||
|
||||
if(actions->code_points_to_delete) {
|
||||
for(unsigned int i = 0; i < actions->code_points_to_delete; i++) {
|
||||
km_core_action_item act = {KM_CORE_IT_BACK};
|
||||
act.backspace = {KM_CORE_BT_CHAR, 0};
|
||||
apply_action(test_state, act, text_store, context, test_source, test_context);
|
||||
}
|
||||
}
|
||||
|
||||
if(actions->output) {
|
||||
for(auto ch = actions->output; *ch; ch++) {
|
||||
km_core_action_item act = {KM_CORE_IT_CHAR};
|
||||
act.character = *ch;
|
||||
apply_action(test_state, act, text_store, context, test_source, test_context);
|
||||
}
|
||||
}
|
||||
|
||||
if(actions->emit_keystroke) {
|
||||
apply_action(test_state, {KM_CORE_IT_EMIT_KEYSTROKE}, text_store, context, test_source, test_context);
|
||||
}
|
||||
|
||||
// TODO-LDML: other action types - persist, caps lock
|
||||
}
|
||||
|
||||
void
|
||||
print_context(std::u16string &text_store, km_core_state *&test_state, std::vector<km_core_context_item> &test_context) {
|
||||
// Compare context and text store at each step - should be identical
|
||||
size_t n = 0;
|
||||
km_core_context_item *citems = nullptr;
|
||||
|
|
@ -225,8 +267,18 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
|
|||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl;
|
||||
std::cout << "testcontext ";
|
||||
std::cout << "context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl;
|
||||
|
||||
delete[] buf;
|
||||
|
||||
km_core_context_item *citems_app = nullptr;
|
||||
try_status(km_core_context_get(km_core_state_app_context(test_state), &citems_app));
|
||||
try_status(context_items_to_utf16(citems_app, nullptr, &n));
|
||||
buf = new km_core_cu[n];
|
||||
try_status(context_items_to_utf16(citems_app, buf, &n));
|
||||
std::cout << "app context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl;
|
||||
|
||||
std::cout << "test_context : ";
|
||||
std::cout.fill('0');
|
||||
for (auto i = test_context.begin(); i < test_context.end(); i++) {
|
||||
switch (i->type) {
|
||||
|
|
@ -241,29 +293,60 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
|
|||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
km_core_context_items_dispose(citems);
|
||||
km_core_context_items_dispose(citems_app);
|
||||
delete[] buf;
|
||||
}
|
||||
|
||||
// Verify that both our local test_context and the core's test_state.context have
|
||||
// not diverged
|
||||
auto ci = citems;
|
||||
for (auto test_ci = test_context.begin();; ci++, test_ci++) {
|
||||
// skip over markers, they won't be in test_context
|
||||
while (ci->type == KM_CORE_CT_MARKER) {
|
||||
ci++;
|
||||
/**
|
||||
* verify the current context
|
||||
*/
|
||||
void
|
||||
verify_context(std::u16string &text_store, km_core_state *&test_state, std::vector<km_core_context_item> &test_context, bool fully_normalized_mode, bool normalization_enabled) {
|
||||
// Compare context and text store at each step - should be identical
|
||||
print_context(text_store, test_state, test_context);
|
||||
|
||||
size_t n = 0;
|
||||
km_core_context_item *citems = nullptr;
|
||||
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
|
||||
try_status(context_items_to_utf16(citems, nullptr, &n));
|
||||
km_core_cu *buf = new km_core_cu[n];
|
||||
try_status(context_items_to_utf16(citems, buf, &n));
|
||||
std::u16string buf_final(buf);
|
||||
|
||||
if(fully_normalized_mode) {
|
||||
if(normalization_enabled) {
|
||||
test_assert(km::core::util::normalize_nfc(buf_final));
|
||||
}
|
||||
// exit if BOTH are at end.
|
||||
if (ci->type == KM_CORE_CT_END && test_ci == test_context.end()) {
|
||||
break; // success
|
||||
// note: we don't compare test_context with citems because they have different
|
||||
// normalization forms in fully_normalized_mode
|
||||
} else {
|
||||
// Verify that both our local test_context and the core's test_state.context have
|
||||
// not diverged
|
||||
auto ci = citems;
|
||||
for (auto test_ci = test_context.begin();; ci++, test_ci++) {
|
||||
// skip over markers, they won't be in test_context
|
||||
while (ci->type == KM_CORE_CT_MARKER) {
|
||||
ci++;
|
||||
}
|
||||
// exit if BOTH are at end.
|
||||
if (ci->type == KM_CORE_CT_END && test_ci == test_context.end()) {
|
||||
break; // success
|
||||
}
|
||||
// fail if only ONE is at end
|
||||
test_assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end());
|
||||
// fail if type and marker don't match.
|
||||
test_assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
|
||||
}
|
||||
// fail if only ONE is at end
|
||||
test_assert(ci->type != KM_CORE_CT_END && test_ci != test_context.end());
|
||||
// fail if type and marker don't match.
|
||||
test_assert(test_ci->type == ci->type && test_ci->marker == ci->marker);
|
||||
}
|
||||
|
||||
km_core_context_items_dispose(citems);
|
||||
if (text_store != buf) {
|
||||
|
||||
if (text_store != buf_final) {
|
||||
std::cerr << "text store has diverged from buf" << std::endl;
|
||||
std::cerr << "text store: " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl;
|
||||
std::cerr << "buf_final : " << string_to_hex(buf_final) << " [" << buf << "]" << std::endl;
|
||||
std::cerr << "buf : " << string_to_hex(buf) << " [" << buf << "]" << std::endl;
|
||||
test_assert(false);
|
||||
}
|
||||
delete[] buf;
|
||||
|
|
@ -327,7 +410,7 @@ verify_key_list(const km_core_keyboard_key *actual_list, const std::u16string &e
|
|||
}
|
||||
|
||||
int
|
||||
run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source) {
|
||||
run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source, bool fully_normalized_mode) {
|
||||
km_core_keyboard * test_kb = nullptr;
|
||||
km_core_state * test_state = nullptr;
|
||||
|
||||
|
|
@ -341,18 +424,28 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
}
|
||||
|
||||
// setup normalization status
|
||||
const bool normalization_disabled = !test_kb->supports_normalization();
|
||||
test_source.set_normalization_disabled(normalization_disabled);
|
||||
const bool normalization_enabled = test_kb->supports_normalization();
|
||||
test_source.set_normalization_disabled(!normalization_enabled);
|
||||
std::wcout
|
||||
<< console_color::fg(console_color::BLUE) << "* normalization enabled = " << normalization_enabled
|
||||
<< console_color::reset() << std::endl << std::endl;
|
||||
|
||||
// Setup state, environment
|
||||
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
|
||||
|
||||
g_beep_found = false;
|
||||
|
||||
std::vector<km_core_context_item> test_context;
|
||||
|
||||
km_core_context_item *citems = nullptr;
|
||||
// setup test_context
|
||||
try_status(context_items_from_utf16(test_source.get_context().c_str(), &citems));
|
||||
try_status(km_core_context_set(km_core_state_context(test_state), citems));
|
||||
|
||||
if(fully_normalized_mode) {
|
||||
km_core_state_context_set_if_needed(test_state, test_source.get_context().c_str());
|
||||
} else {
|
||||
try_status(km_core_context_set(km_core_state_context(test_state), citems));
|
||||
}
|
||||
|
||||
// Make a copy of the setup context for the test
|
||||
copy_context_items_to_vector(citems, test_context);
|
||||
|
|
@ -364,7 +457,7 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
km::tests::ldml_action action;
|
||||
|
||||
// verify at beginning
|
||||
verify_context(text_store, test_state, test_context);
|
||||
verify_context(text_store, test_state, test_context, fully_normalized_mode, normalization_enabled);
|
||||
|
||||
int errorLine = 0; // nonzero if err.
|
||||
|
||||
|
|
@ -377,8 +470,8 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
break;
|
||||
case km::tests::LDML_ACTION_KEY_EVENT: {
|
||||
auto &p = action.k;
|
||||
std::cout << "- key action: " << km::core::kmx::Debug_VirtualKey(p.vk) << "/modifier "
|
||||
<< km::core::kmx::Debug_ModifierName(p.modifier_state) << " 0x" << p.modifier_state << std::dec << std::endl;
|
||||
std::cout << std::endl << "- key action: " << km::core::kmx::Debug_VirtualKey_Always(p.vk) << " modifier: ["
|
||||
<< km::core::kmx::Debug_ModifierName(p.modifier_state) << " 0x" << p.modifier_state << std::dec << "]" << std::endl;
|
||||
// Because a normal system tracks caps lock state itself,
|
||||
// we mimic that in the tests. We assume caps lock state is
|
||||
// updated on key_down before the processor receives the
|
||||
|
|
@ -388,6 +481,7 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
}
|
||||
|
||||
for (auto key_down = 1; key_down >= 0; key_down--) {
|
||||
std::cout << " - key_down = " << key_down << std::endl;
|
||||
// expected error only applies to key down
|
||||
try_status(km_core_process_event(
|
||||
test_state, p.vk, p.modifier_state | test_source.caps_lock_state(), key_down,
|
||||
|
|
@ -398,11 +492,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
test_context.clear();
|
||||
text_store.clear();
|
||||
}
|
||||
for (auto act = km_core_state_action_items(test_state, nullptr); act->type != KM_CORE_IT_END; act++) {
|
||||
apply_action(test_state, *act, text_store, test_context, test_source, test_context);
|
||||
|
||||
if(fully_normalized_mode) {
|
||||
auto actions = km_core_state_get_actions(test_state);
|
||||
apply_actions(test_state, actions, text_store, test_context, test_source, test_context);
|
||||
} else {
|
||||
for (auto act = km_core_state_action_items(test_state, nullptr); act->type != KM_CORE_IT_END; act++) {
|
||||
apply_action(test_state, *act, text_store, test_context, test_source, test_context);
|
||||
}
|
||||
}
|
||||
verify_context(text_store, test_state, test_context, fully_normalized_mode, normalization_enabled);
|
||||
}
|
||||
verify_context(text_store, test_state, test_context);
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_EMIT_STRING: {
|
||||
std::cout << "- string emit action: " << action.string << std::endl;
|
||||
|
|
@ -418,13 +518,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
}
|
||||
km_core_context_items_dispose(nitems);
|
||||
|
||||
verify_context(text_store, test_state, test_context);
|
||||
verify_context(text_store, test_state, test_context, fully_normalized_mode, normalization_enabled);
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_CHECK_EXPECTED: {
|
||||
if (!normalization_disabled) {
|
||||
test_assert(km::core::util::normalize_nfd(action.string)); // TODO-LDML: should be NFC
|
||||
if (normalization_enabled) {
|
||||
if (!fully_normalized_mode) {
|
||||
test_assert(km::core::util::normalize_nfd(action.string));
|
||||
} else {
|
||||
test_assert(km::core::util::normalize_nfc(action.string));
|
||||
}
|
||||
}
|
||||
std::cout << "- check expected" << std::endl;
|
||||
std::cout << std::endl << "# check expected" << std::endl;
|
||||
std::cout << "expected : " << string_to_hex(action.string) << " [" << action.string << "]" << std::endl;
|
||||
std::cout << "text store: " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl;
|
||||
// Compare internal context with expected result
|
||||
|
|
@ -433,7 +537,7 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
}
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_CHECK_KEYLIST: {
|
||||
std::cout << "- checking keylist" << std::endl;
|
||||
std::cout << std::endl << "# 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)) {
|
||||
|
|
@ -462,7 +566,7 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
|
||||
if (errorLine != 0) {
|
||||
// re-verify at end (if there wasn't already a failure)
|
||||
verify_context(text_store, test_state, test_context);
|
||||
verify_context(text_store, test_state, test_context, fully_normalized_mode, normalization_enabled);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
|
|
@ -476,15 +580,19 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
return __LINE__;
|
||||
}
|
||||
|
||||
if(errorLine != 0) {
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - error on line " << errorLine << console_color::reset() << std::endl;
|
||||
}
|
||||
|
||||
return errorLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all tests for this keyboard
|
||||
*/
|
||||
int run_all_tests(const km::core::path &source, const km::core::path &compiled, const std::string &filter) {
|
||||
std::wcout << console_color::fg(console_color::BLUE) << "source file = " << source << std::endl
|
||||
<< "compiled file = " << compiled << console_color::reset() << std::endl;
|
||||
int run_all_tests(const km::core::path &source, const km::core::path &compiled, const std::string &filter, bool fully_normalized_mode) {
|
||||
std::wcout << console_color::fg(console_color::BLUE) << "* source file = " << source << std::endl
|
||||
<< "* compiled file = " << compiled << console_color::reset() << std::endl;
|
||||
if(!filter.empty()) {
|
||||
std::wcout << "Running only tests matching (substring search): " << filter.c_str() << std::endl;
|
||||
}
|
||||
|
|
@ -497,14 +605,14 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
|
|||
|
||||
if (!filter.empty()) {
|
||||
// Always skip the embedded test if there's a filter.
|
||||
std::wcout << console_color::fg(console_color::YELLOW) << "SKIP: " << source.name() << " (embedded)" << console_color::reset()
|
||||
std::wcout << console_color::fg(console_color::YELLOW) << "* SKIP: " << source.name() << " (embedded)" << console_color::reset()
|
||||
<< std::endl;
|
||||
embedded_result = 0; // no error
|
||||
} else if (embedded_result == 0) {
|
||||
// embedded loaded OK, try it
|
||||
std::wcout << console_color::fg(console_color::BLUE) << console_color::bold() << "TEST: " << source.name() << " (embedded)"
|
||||
std::wcout << console_color::fg(console_color::BLUE) << console_color::bold() << "* TEST: " << source.name() << " (embedded)"
|
||||
<< console_color::reset() << std::endl;
|
||||
embedded_result = run_test(source, compiled, embedded_test_source);
|
||||
embedded_result = run_test(source, compiled, embedded_test_source, fully_normalized_mode);
|
||||
if (embedded_result != 0) {
|
||||
failures.push_back("in-XML (@@ comment) embedded test failed");
|
||||
}
|
||||
|
|
@ -533,7 +641,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
|
|||
continue;
|
||||
}
|
||||
std::wcout << console_color::fg(console_color::BLUE) << "TEST: " << json_path.stem().c_str() << "/" << console_color::bold() << n.first.c_str() << console_color::reset() << std::endl;
|
||||
int sub_test = run_test(source, compiled, *n.second);
|
||||
int sub_test = run_test(source, compiled, *n.second, fully_normalized_mode);
|
||||
if (sub_test != 0) {
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "FAIL: " << json_path.stem() << "/" << console_color::bold() << n.first.c_str()
|
||||
<< console_color::reset() << std::endl;
|
||||
|
|
@ -632,7 +740,15 @@ int main(int argc, char *argv[]) {
|
|||
filter = argv[first_arg++];
|
||||
}
|
||||
|
||||
int rc = run_all_tests(ldml_file, kmx_file, filter);
|
||||
std::cout << "1. Running tests in NFD mode" << std::endl;
|
||||
int rc = run_all_tests(ldml_file, kmx_file, filter, false);
|
||||
if (rc != EXIT_SUCCESS) {
|
||||
std::wcerr << console_color::fg(console_color::BRIGHT_RED) << "FAILED" << console_color::reset() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::cout << std::endl << "2. Running tests in fully normalized mode" << std::endl;
|
||||
rc = run_all_tests(ldml_file, kmx_file, filter, true);
|
||||
if (rc != EXIT_SUCCESS) {
|
||||
std::wcerr << console_color::fg(console_color::BRIGHT_RED) << "FAILED" << console_color::reset() << std::endl;
|
||||
rc = EXIT_FAILURE;
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ LdmlTestSource::get_modifier(std::string const &m) {
|
|||
|
||||
std::string key_event::dump() const {
|
||||
std::stringstream f;
|
||||
f << "Key: {" << km::core::kmx::Debug_VirtualKey(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}";
|
||||
f << "Key: {" << km::core::kmx::Debug_VirtualKey_Always(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}";
|
||||
return f.str();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -187,9 +187,20 @@ const std::string &block_unicode_ver) {
|
|||
inline const char *boolstr(bool b) {
|
||||
return b?"T":"f";
|
||||
}
|
||||
#endif
|
||||
|
||||
void test_has_boundary_before() {
|
||||
std::cout << "= " << __FUNCTION__ << std::endl;
|
||||
|
||||
// Latin - #15505
|
||||
test_assert(km::core::util::has_nfc_boundary_before(0x0065));
|
||||
test_assert(!km::core::util::has_nfc_boundary_before(0x0301));
|
||||
|
||||
// Bengali - #15505
|
||||
test_assert(km::core::util::has_nfc_boundary_before(0x0995));
|
||||
test_assert(!km::core::util::has_nfc_boundary_before(0x09d7));
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
std::cout << "I see we are on Emscripten / wasm! Now we will do some additional tests." << std::endl;
|
||||
std::string icu4c_unicode(U_UNICODE_VERSION), header_unicode(KM_HASBOUNDARYBEFORE_UNICODE_VERSION),
|
||||
icu4c_icu(U_ICU_VERSION), header_icu(KM_HASBOUNDARYBEFORE_ICU_VERSION);
|
||||
|
|
@ -198,26 +209,26 @@ void test_has_boundary_before() {
|
|||
assert_basic_equal(icu4c_unicode, header_unicode);
|
||||
assert_basic_equal(icu4c_icu, header_icu);
|
||||
|
||||
std::cout << std::endl << "Now, let's make sure has_nfd_boundary_before() matches ICU." << std::endl;
|
||||
std::cout << std::endl << "Now, let's make sure has_nfc_boundary_before() matches ICU." << std::endl;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
|
||||
const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(status);
|
||||
UASSERT_SUCCESS(status);
|
||||
|
||||
// now, test that hasBoundaryBefore is the same
|
||||
for (km_core_usv cp = 0; cp < km::core::kmx::Uni_MAX_CODEPOINT; cp++) {
|
||||
auto km_hbb = km::core::util::has_nfd_boundary_before(cp);
|
||||
auto icu_hbb = nfd->hasBoundaryBefore(cp);
|
||||
auto km_hbb = km::core::util::has_nfc_boundary_before(cp);
|
||||
auto icu_hbb = nfc->hasBoundaryBefore(cp);
|
||||
|
||||
if (km_hbb != icu_hbb) {
|
||||
std::cerr << "Error: util_normalize_table.h said " << boolstr(km_hbb) << " but ICU said " << boolstr(icu_hbb) << " for "
|
||||
<< "has_nfd_boundary_before(0x" << std::hex << cp << std::dec << ")" << std::endl;
|
||||
<< "has_nfc_boundary_before(0x" << std::hex << cp << std::dec << ")" << std::endl;
|
||||
}
|
||||
test_assert(km_hbb == icu_hbb);
|
||||
}
|
||||
#endif
|
||||
std::cout << "All OK!" << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
int test_all(const char *jsonpath, const char *packagepath, const char *blockspath) {
|
||||
std::cout << "= " << __FUNCTION__ << std::endl;
|
||||
|
|
@ -234,9 +245,7 @@ int test_all(const char *jsonpath, const char *packagepath, const char *blockspa
|
|||
|
||||
test_unicode_versions(versions, package, block_unicode_ver);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
test_has_boundary_before();
|
||||
#endif
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-xml-parser": "^5.2.2",
|
||||
"fast-xml-parser": "^5.3.4",
|
||||
"path-browserify": "^1.0.1",
|
||||
"restructure": "^3.0.1",
|
||||
"sax": ">=0.6.0",
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
|
||||
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
},
|
||||
|
|
@ -642,9 +642,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
|
||||
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
|
|
@ -723,9 +723,9 @@
|
|||
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="
|
||||
},
|
||||
"@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
|
||||
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
|
||||
"requires": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
}
|
||||
|
|
@ -1166,9 +1166,9 @@
|
|||
}
|
||||
},
|
||||
"tar": {
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
|
||||
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"requires": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
"chownr": "^3.0.0",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ https://help.keyman.com/developer/engine/android/latest-version/
|
|||
| KEYMAN_VERSION_ICU | 73.1 |
|
||||
| KEYMAN_VERSION_ISO639_3 | 2024-05-22 |
|
||||
| KEYMAN_VERSION_JAVA | 21 |
|
||||
| KEYMAN_VERSION_LANGTAGS | 2025-02-18 |
|
||||
| KEYMAN_VERSION_LANGTAGS | 2026-02-03 |
|
||||
| KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2025-03-10 |
|
||||
| KEYMAN_VERSION_UNICODE | 17.0.0 |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
keyman (18.0.246-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Eberhard Beilharz <eb1@sil.org> Wed, 04 Feb 2026 13:48:31 +0100
|
||||
|
||||
keyman (18.0.245-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Build-Depends:
|
|||
python3-xdg,
|
||||
xserver-xephyr,
|
||||
xvfb,
|
||||
Standards-Version: 4.7.2
|
||||
Standards-Version: 4.7.3
|
||||
Vcs-Git: https://github.com/keymanapp/keyman.git -b beta [linux/debian]
|
||||
Vcs-Browser: https://github.com/keymanapp/keyman/tree/beta/linux/debian
|
||||
Homepage: https://www.keyman.com
|
||||
|
|
|
|||
|
|
@ -6,24 +6,24 @@ Comment: Debian's licensecheck tool uses the term `Expat` for the `MIT` license.
|
|||
`Unicode-3.0` is identified `Unicode-DFS-2016`.
|
||||
|
||||
Files: *
|
||||
Copyright: 2018-2025 SIL Global
|
||||
Copyright: 2018-2026 SIL Global
|
||||
License: Expat
|
||||
|
||||
Files: linux/ibus-keyman/*
|
||||
Copyright: 2004-2025 SIL Global
|
||||
Copyright: 2004-2026 SIL Global
|
||||
License: GPL-2+
|
||||
|
||||
Files: linux/ibus-keyman/src/keymanutil.c
|
||||
linux/ibus-keyman/src/keymanutil.h
|
||||
linux/ibus-keyman/src/kmpdetails.c
|
||||
linux/ibus-keyman/src/kmpdetails.h
|
||||
Copyright: 2009-2025 SIL Global
|
||||
Copyright: 2009-2026 SIL Global
|
||||
License: GPL-2+ or Expat
|
||||
|
||||
Files: linux/ibus-keyman/src/keyman-service.c
|
||||
linux/ibus-keyman/src/keyman-service.h
|
||||
linux/keyman-config/buildtools/help2md
|
||||
Copyright: 2018-2025 SIL Global
|
||||
Copyright: 2018-2026 SIL Global
|
||||
License: GPL-3+
|
||||
|
||||
Files: linux/ibus-keyman/tests/ibusimcontext.c
|
||||
|
|
@ -33,7 +33,7 @@ Copyright: 2008-2010, Peng Huang <shawn.p.huang@gmail.com>
|
|||
2008-2013, Peng Huang <shawn.p.huang@gmail.com>
|
||||
2008-2021, Red Hat, Inc.
|
||||
2015-2021, Takao Fujiwara <takao.fujiwara1@gmail.com>
|
||||
2021-2025, SIL Global
|
||||
2021-2026, SIL Global
|
||||
License: LGPL-2.1+
|
||||
|
||||
Files: linux/keyman-config/buildtools/help2man
|
||||
|
|
@ -43,7 +43,7 @@ License: GPL-3+
|
|||
Files: debian/com.keyman.config.appdata.xml
|
||||
debian/com.keyman.ibus_keyman.metainfo.xml
|
||||
Copyright: 2019 Daniel Glassey <wdg@debian.org>
|
||||
2022-2025 SIL Global
|
||||
2022-2026 SIL Global
|
||||
License: Expat
|
||||
|
||||
Files: resources/standards-data/ldml-keyboards/*
|
||||
|
|
|
|||
|
|
@ -74,14 +74,18 @@ class ViewInstalledWindowBase(Gtk.Window):
|
|||
filter_text.set_name(_("KMP files"))
|
||||
filter_text.add_pattern("*.kmp")
|
||||
dlg.add_filter(filter_text)
|
||||
response = dlg.run()
|
||||
if response != Gtk.ResponseType.OK:
|
||||
dlg.destroy()
|
||||
return
|
||||
while True:
|
||||
response = dlg.run()
|
||||
if response != Gtk.ResponseType.OK:
|
||||
dlg.destroy()
|
||||
return
|
||||
|
||||
file = dlg.get_filename()
|
||||
dlg.destroy()
|
||||
self.restart(self.install_file(file))
|
||||
file = dlg.get_filename()
|
||||
if file and os.path.isfile(file) and os.path.splitext(file)[1] == '.kmp':
|
||||
dlg.destroy()
|
||||
self.restart(self.install_file(file))
|
||||
return
|
||||
# Loop until we have a valid file or the user cancels the dialog
|
||||
|
||||
def install_file(self, kmpfile, language=None):
|
||||
installDlg = InstallKmpWindow(kmpfile, viewkmp=self, language=language)
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ mkdir -p builddebs
|
|||
|
||||
# make the source packages
|
||||
cd builddebs
|
||||
vers=$(ls ../dist/keyman_*.orig.tar.gz)
|
||||
vers=$(ls ../dist/keyman_*.orig.tar.xz)
|
||||
#echo "vers1:${vers}"
|
||||
vers=${vers##*_}
|
||||
#echo "vers2:${vers}"
|
||||
vers=${vers%*.orig.tar.gz}
|
||||
vers=${vers%*.orig.tar.xz}
|
||||
#echo "vers3:${vers}"
|
||||
cp -a "../dist/keyman_${vers}.orig.tar.gz" .
|
||||
tar xfz "keyman_${vers}.orig.tar.gz"
|
||||
cp -a "../dist/keyman_${vers}.orig.tar.xz" .
|
||||
tar xfJ "keyman_${vers}.orig.tar.xz"
|
||||
cp -a ../../debian "keyman-${vers}"
|
||||
cd "keyman-${vers}"
|
||||
dch -v "${vers}-1" "local build"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# and put them in dist/
|
||||
|
||||
# parameters: ./dist.sh [origdist] [proj]
|
||||
# origdist = create Debian orig.tar.gz
|
||||
# origdist = create Debian orig.tar.xz
|
||||
# proj = only make tarball for this project
|
||||
|
||||
set -e
|
||||
|
|
@ -39,8 +39,9 @@ dch keyman --newversion "${KEYMAN_VERSION}" --force-bad-version --nomultimaint
|
|||
|
||||
# Create the tarball
|
||||
|
||||
# files and folders to include in the tarball
|
||||
# shellcheck disable=2034 # to_exclude appears to be unused
|
||||
# We always include these files which are the minimum files and
|
||||
# folder required for Ubuntu/Debian packaging
|
||||
# shellcheck disable=2034 # to_include appears to be unused
|
||||
to_include=(
|
||||
common/build.sh \
|
||||
common/cpp \
|
||||
|
|
@ -60,18 +61,61 @@ to_include=(
|
|||
|
||||
# files and subfolders to exclude from paths included in 'to_include',
|
||||
# i.e. the exceptions to 'to_include'.
|
||||
|
||||
# shellcheck disable=2034 # to_exclude appears to be unused
|
||||
to_exclude=(
|
||||
build \
|
||||
common/test/keyboards/baseline/kmcomp-*.zip \
|
||||
core/build \
|
||||
linux/build \
|
||||
linux/builddebs \
|
||||
linux/docs/help \
|
||||
linux/keyman-config/keyman_config/version.py \
|
||||
linux/keyman-config/buildtools/build-langtags.py \
|
||||
linux/keyman-system-service/build
|
||||
)
|
||||
|
||||
if [[ -z "${create_origdist+x}" ]]; then
|
||||
# If we build a full source tarball we include additional files
|
||||
# so that it's possible to run `${KEYMAN_ROOT}/build.sh` on Linux
|
||||
|
||||
# shellcheck disable=2034 # to_include appears to be unused
|
||||
to_include+=(
|
||||
common/tools/hextobin \
|
||||
common/web/keyman-version \
|
||||
common/web/langtags \
|
||||
common/web/types \
|
||||
common/windows/cpp \
|
||||
common/windows/include \
|
||||
developer/src/common/include \
|
||||
developer/src/common/web \
|
||||
developer/src/ext/json \
|
||||
developer/src/kmc \
|
||||
developer/src/kmc-analyze \
|
||||
developer/src/kmc-copy \
|
||||
developer/src/kmc-generate \
|
||||
developer/src/kmc-keyboard-info \
|
||||
developer/src/kmc-kmn \
|
||||
developer/src/kmc-ldml \
|
||||
developer/src/kmc-model \
|
||||
developer/src/kmc-model-info \
|
||||
developer/src/kmc-package \
|
||||
developer/src/kmcmplib \
|
||||
docs/minimum-versions.md.in
|
||||
resources/build \
|
||||
resources/standards-data \
|
||||
)
|
||||
|
||||
# additional files and subfolders to exclude from paths included in 'to_include',
|
||||
# i.e. the exceptions to 'to_include'.
|
||||
# shellcheck disable=2034 # to_exclude appears to be unused
|
||||
to_exclude+=(
|
||||
*.exe \
|
||||
resources/build/history \
|
||||
resources/build/l10n \
|
||||
resources/build/mac \
|
||||
resources/build/win \
|
||||
resources/build/*.lua \
|
||||
)
|
||||
fi
|
||||
|
||||
# array to store list of --tar-ignore parameters generated from to_include and to_exclude.
|
||||
ignored_files=()
|
||||
|
||||
|
|
@ -101,19 +145,19 @@ dpkg-source \
|
|||
\
|
||||
"${ignored_files[@]}" \
|
||||
\
|
||||
-Zgzip -b .
|
||||
-Zxz -b .
|
||||
|
||||
mv ../keyman_"${KEYMAN_VERSION}".tar.gz linux/dist/keyman-"${KEYMAN_VERSION}".tar.gz
|
||||
mv ../keyman_"${KEYMAN_VERSION}".tar.xz linux/dist/keyman-"${KEYMAN_VERSION}".tar.xz
|
||||
echo "3.0 (quilt)" > debian/source/format
|
||||
cd "${BASEDIR}"
|
||||
|
||||
# create orig.tar.gz
|
||||
# create orig.tar.xz
|
||||
if [[ ! -z "${create_origdist+x}" ]]; then
|
||||
cd dist
|
||||
cd "${KEYMAN_ROOT}/linux/dist"
|
||||
pkgvers="keyman-${KEYMAN_VERSION}"
|
||||
tar xfz keyman-"${KEYMAN_VERSION}".tar.gz
|
||||
tar xfJ keyman-"${KEYMAN_VERSION}".tar.xz
|
||||
mv -v keyman "${pkgvers}" 2>/dev/null || mv -v "$(find . -mindepth 1 -maxdepth 1 -type d)" "${pkgvers}"
|
||||
tar cfz "keyman_${KEYMAN_VERSION}.orig.tar.gz" "${pkgvers}"
|
||||
rm "keyman-${KEYMAN_VERSION}.tar.gz"
|
||||
tar cfJ "keyman_${KEYMAN_VERSION}.orig.tar.xz" "${pkgvers}"
|
||||
rm "keyman-${KEYMAN_VERSION}.tar.xz"
|
||||
rm -rf "${pkgvers}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ function downloadSource() {
|
|||
uscan || (echo "ERROR: No new version available for ${proj}" >&2 && exit 1)
|
||||
cd ..
|
||||
mv "${proj}-${version}" "${BASEDIR}/${packageDir}"
|
||||
mv "${proj}_${version}.orig.tar.gz" "${BASEDIR}/${packageDir}"
|
||||
mv "${proj}-${version}.tar.gz" "${BASEDIR}/${packageDir}"
|
||||
mv "${proj}_${version}.orig.tar.xz" "${BASEDIR}/${packageDir}"
|
||||
mv "${proj}-${version}.tar.xz" "${BASEDIR}/${packageDir}"
|
||||
mv "${proj}"*.asc "${BASEDIR}/${packageDir}"
|
||||
rm "${proj}"*.debian.tar.xz
|
||||
cd "${BASEDIR}/${packageDir}" || exit
|
||||
|
|
@ -91,78 +91,135 @@ function generate_tar_ignore_list() {
|
|||
local list_var="$4"
|
||||
local prefix="$5"
|
||||
local includes_array="${includes_var}[@]"
|
||||
# shellcheck disable=SC2034
|
||||
local includes=("${!includes_array}")
|
||||
local excludes_array="${excludes_var}[@]"
|
||||
local excludes=("${!excludes_array}")
|
||||
local dir all_dirs found_match inc
|
||||
|
||||
mapfile -t all_dirs < <(find "${directory}" -mindepth 1 -maxdepth 1 -type d | sort)
|
||||
for dir in "${all_dirs[@]}"; do
|
||||
found_match=false
|
||||
for inc in "${includes[@]}"; do
|
||||
if [[ "./${inc}" =~ ^${dir} ]]; then
|
||||
found_match=true
|
||||
if [[ "./${inc}" != "${dir}" ]]; then
|
||||
# check subdirectories
|
||||
generate_tar_ignore_list "${dir}" "${includes_var}" "${excludes_var}" "${list_var}" "${prefix}"
|
||||
fi
|
||||
# check if files/subdir in $dir are in excludes list
|
||||
_generate_excludes_for_dir "${dir}" "${includes_var}" "${excludes_var}" "${list_var}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if ! ${found_match}; then
|
||||
_add_to_list "${list_var}" "${dir}"
|
||||
# Loop through excludes and put all without path in single_excludes
|
||||
local single_excludes=()
|
||||
for item in "${excludes[@]}"; do
|
||||
if [[ ${item} != */* ]]; then
|
||||
single_excludes+=("${item}")
|
||||
fi
|
||||
done
|
||||
|
||||
local ignore_list=()
|
||||
_process_directory "${directory}" false
|
||||
|
||||
for item in "${ignore_list[@]}"; do
|
||||
eval "${list_var}+=(\"--tar-ignore=${item}\")"
|
||||
done
|
||||
}
|
||||
|
||||
function _generate_excludes_for_dir() {
|
||||
function _process_directory() {
|
||||
local directory="$1"
|
||||
local includes_var="$2"
|
||||
local excludes_var="$3"
|
||||
local list_var="$4"
|
||||
local includes_array="${includes_var}[@]"
|
||||
local includes=("${!includes_array}")
|
||||
local excludes_array="${excludes_var}[@]"
|
||||
local excludes=("${!excludes_array}")
|
||||
local file all_files excluded included is_match
|
||||
local isParentIncluded="$2"
|
||||
local all_items item
|
||||
|
||||
mapfile -t all_files < <(find "${directory}" -mindepth 1 -maxdepth 1 | sort)
|
||||
is_match=false
|
||||
for file in "${all_files[@]}"; do
|
||||
for included in "${includes[@]}"; do
|
||||
if [[ "${file}" == ./${included} ]]; then
|
||||
is_match=true
|
||||
break
|
||||
mapfile -t all_items < <(find "${directory}" -mindepth 1 -maxdepth 1 | sort)
|
||||
for item in "${all_items[@]}"; do
|
||||
debug "Checking item: ${item}"
|
||||
if _is_exact_match "includes" "${item}"; then
|
||||
debug " Including (full match): ${item}"
|
||||
if [[ -d "${item}" ]]; then
|
||||
_process_directory "${item}" true
|
||||
fi
|
||||
done
|
||||
if ${is_match} ; then
|
||||
if [[ "${file}" != ./${included} ]] && [[ -f "${file}" ]]; then
|
||||
_add_to_list "${list_var}" "${file}"
|
||||
elif _starts_with "includes" "${item}"; then
|
||||
debug " Including (partial match): ${item}"
|
||||
if [[ -d "${item}" ]]; then
|
||||
_process_directory "${item}" "${isParentIncluded}"
|
||||
fi
|
||||
elif _is_exact_match "excludes" "${item}"; then
|
||||
debug " Excluding (full match): ${item}"
|
||||
_add_to_list "${item}"
|
||||
elif _ends_with "single_excludes" "${item}" || _is_wildcard_match "single_excludes" "${item}"; then
|
||||
debug " Excluding (single exclude): ${item}"
|
||||
_add_to_list "${item}"
|
||||
elif [[ "${isParentIncluded}" == "false" ]]; then
|
||||
debug " Excluding (not included): ${item}"
|
||||
_add_to_list "${item}"
|
||||
else
|
||||
for excluded in "${excludes[@]}"; do
|
||||
if [[ "${file}" == ./${excluded} ]]; then
|
||||
_add_to_list "${list_var}" "${file}"
|
||||
break
|
||||
elif [[ "./${excluded}" =~ ^${file} ]]; then
|
||||
# check subdirectories
|
||||
_generate_excludes_for_dir "${file}" "${includes_var}" "${excludes_var}" "${list_var}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
debug " Including (parent included): ${item}"
|
||||
if [[ -d "${item}" ]]; then
|
||||
_process_directory "${item}" "${isParentIncluded}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function _add_to_list() {
|
||||
local list_var="$1"
|
||||
local filename="$2"
|
||||
|
||||
# Note: the files end up in subdirectories under `keyman` (or rather
|
||||
# the directory name of $KEYMAN_ROOT), so we can
|
||||
# include that when matching files and directories to ignore.
|
||||
# shellcheck disable=SC2154
|
||||
eval "${list_var}+=(\"--tar-ignore=${prefix}/${filename#./}\")"
|
||||
local item="$1"
|
||||
ignore_list+=("${prefix}/${item#./}")
|
||||
}
|
||||
|
||||
# Returns true if one of the values in the $1 array equals ${file} ($2)
|
||||
# Example: will return true for array=(path1/path2) file=./path1/path2
|
||||
function _is_exact_match() {
|
||||
local array_var="$1"
|
||||
local file="$2"
|
||||
local array_name="${array_var}[@]"
|
||||
local haystack=("${!array_name}")
|
||||
local item
|
||||
for item in "${haystack[@]}"; do
|
||||
if [[ "./${item#./}" == "${file}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns true if one of the values in the $1 array starts with ${file} ($2)
|
||||
# Example: will return true for array=(path1/path2) file=./path1
|
||||
function _starts_with() {
|
||||
local array_var="$1"
|
||||
local file="$2"
|
||||
local array_name="${array_var}[@]"
|
||||
local array_values=("${!array_name}")
|
||||
local array_item
|
||||
for array_item in "${array_values[@]}"; do
|
||||
if [[ "./${array_item#./}" == ${file}* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns true if ${file} ($2) ends with one of the values in the $1 array
|
||||
# Example: will return true for array=(path2) file=./path1/path2
|
||||
function _ends_with() {
|
||||
local array_var="$1"
|
||||
local file="$2"
|
||||
local array_name="${array_var}[@]"
|
||||
local array_values=("${!array_name}")
|
||||
local array_item
|
||||
for array_item in "${array_values[@]}"; do
|
||||
if [[ "${file}" == */${array_item#./} ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Returns true if ${file} ($2) matches one of the values of the $1 array.
|
||||
# These values may contain wildcards.
|
||||
# Example: will return true for array=(*.sh) file=./path1/build.sh
|
||||
function _is_wildcard_match() {
|
||||
local array_var="$1"
|
||||
local file="$2"
|
||||
local array_name="${array_var}[@]"
|
||||
local array_values=("${!array_name}")
|
||||
local array_item
|
||||
for array_item in "${array_values[@]}"; do
|
||||
array_item=${array_item/./\\.}
|
||||
if [[ "${file}" =~ /${array_item/\*/.\*}$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
function debug() {
|
||||
# echo "$@"
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,61 +16,105 @@ function setup_file() {
|
|||
# Create directory structure for testing
|
||||
# /
|
||||
# /subdir1
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /README.md
|
||||
# /path1
|
||||
# /abc.build
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /README.md
|
||||
# /x/z.txt
|
||||
# /xy/z.txt
|
||||
# /path2
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /path3
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /subdir2
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /path1
|
||||
# /build/foo
|
||||
# /subdir3
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /path1
|
||||
# /build/foo
|
||||
# /path2
|
||||
# /build/foo
|
||||
# /path3
|
||||
# /subdir4
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /path1
|
||||
# /build.sh
|
||||
# /path2
|
||||
# /subpath1
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /subpath2
|
||||
# /build/foo
|
||||
# /build.sh
|
||||
# /path3
|
||||
# /build.sh
|
||||
# /build.sh
|
||||
# subdir1/path2, subdir2, subdir4/path1, subdir4/path2/subpath1,
|
||||
# and subdir4/path3 will be ignored
|
||||
mkdir -p "${temp_dir}/subdir1/path1" # include
|
||||
touch "${temp_dir}/subdir1/build.sh"
|
||||
# all build/ directories will be ignored with the exception of
|
||||
# subdir3/build.
|
||||
mkdir -p "${temp_dir}/subdir1/build" # exclude
|
||||
touch "${temp_dir}/subdir1/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir1/path1/build"
|
||||
touch "${temp_dir}/subdir1/path1/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir1/path1/x"
|
||||
touch "${temp_dir}/subdir1/path1/x/z.txt"
|
||||
mkdir -p "${temp_dir}/subdir1/path1/xy"
|
||||
touch "${temp_dir}/subdir1/path1/xy/z.txt"
|
||||
touch "${temp_dir}/subdir1/build.sh" # include
|
||||
touch "${temp_dir}/subdir1/README.md" # exclude
|
||||
touch "${temp_dir}/subdir1/path1/abc.build"
|
||||
touch "${temp_dir}/subdir1/path1/build.sh" # include
|
||||
touch "${temp_dir}/subdir1/path1/README.md" # exclude
|
||||
mkdir -p "${temp_dir}/subdir1/path2" # exclude
|
||||
touch "${temp_dir}/subdir1/path2/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir1/path3" # include
|
||||
touch "${temp_dir}/subdir1/path3/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir2/path1" # exclude
|
||||
mkdir -p "${temp_dir}/subdir1/path2/build"
|
||||
touch "${temp_dir}/subdir1/path2/build/foo" # exclude
|
||||
touch "${temp_dir}/subdir1/path2/build.sh" # exclude
|
||||
mkdir -p "${temp_dir}/subdir1/path3/build"
|
||||
touch "${temp_dir}/subdir1/path2/build/foo" # exclude
|
||||
touch "${temp_dir}/subdir1/path3/build.sh" # include
|
||||
mkdir -p "${temp_dir}/subdir2/build" # exclude
|
||||
touch "${temp_dir}/subdir2/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir2/path1/build" # exclude
|
||||
touch "${temp_dir}/subdir2/path1/build/foo" # exclude
|
||||
touch "${temp_dir}/subdir2/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir3/build" # include
|
||||
touch "${temp_dir}/subdir3/build/foo" # include
|
||||
mkdir -p "${temp_dir}/subdir3/path1" # include
|
||||
touch "${temp_dir}/subdir3/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir3/path1/build" # exclude
|
||||
touch "${temp_dir}/subdir3/path1/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir3/path2" # include
|
||||
mkdir -p "${temp_dir}/subdir3/path2/build" # exclude
|
||||
touch "${temp_dir}/subdir3/path2/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir3/path3" # include
|
||||
mkdir -p "${temp_dir}/subdir4/build" # exclude
|
||||
touch "${temp_dir}/subdir4/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir4/path1" # exclude
|
||||
touch "${temp_dir}/subdir4/build.sh"
|
||||
touch "${temp_dir}/subdir4/path1/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir4/path2/subpath1" # exclude
|
||||
touch "${temp_dir}/subdir4/path2/subpath1/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir4/path2/subpath1/build" # exclude
|
||||
touch "${temp_dir}/subdir4/path2/subpath1/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir4/path2/subpath2" # include
|
||||
touch "${temp_dir}/subdir4/path2/subpath2/build.sh"
|
||||
mkdir -p "${temp_dir}/subdir4/path2/subpath2/build" # exclude
|
||||
touch "${temp_dir}/subdir4/path2/subpath2/build/foo" # exclude
|
||||
mkdir -p "${temp_dir}/subdir4/path3" # exclude
|
||||
touch "${temp_dir}/subdir4/path3/build.sh"
|
||||
touch "${temp_dir}/subdir4/path3/other.txt"
|
||||
touch "${temp_dir}/build.sh"
|
||||
}
|
||||
|
||||
function teardown_file() {
|
||||
|
|
@ -89,9 +133,14 @@ function test__generate_tar_ignore_list__basic() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=test1/build.sh
|
||||
--tar-ignore=test1/subdir1/build
|
||||
--tar-ignore=test1/subdir1/build.sh
|
||||
--tar-ignore=test1/subdir1/path2
|
||||
--tar-ignore=test1/subdir1/README.md
|
||||
--tar-ignore=test1/subdir2
|
||||
--tar-ignore=test1/subdir4/build
|
||||
--tar-ignore=test1/subdir4/build.sh
|
||||
--tar-ignore=test1/subdir4/path1
|
||||
--tar-ignore=test1/subdir4/path2/subpath1
|
||||
--tar-ignore=test1/subdir4/path3
|
||||
|
|
@ -113,6 +162,9 @@ function test__generate_tar_ignore_list__path_wildcard_in_exclude() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=foo/build.sh # not in include
|
||||
--tar-ignore=foo/subdir1/build # not in include
|
||||
--tar-ignore=foo/subdir1/build.sh # not in include
|
||||
--tar-ignore=foo/subdir1/path1/README.md # in exclude
|
||||
--tar-ignore=foo/subdir1/path2 # not in include
|
||||
--tar-ignore=foo/subdir1/README.md # not in include
|
||||
|
|
@ -136,10 +188,12 @@ function test__generate_tar_ignore_list__file_wildcard_in_exclude() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=keyman/build.sh # not in include
|
||||
--tar-ignore=keyman/subdir1/build # not in include
|
||||
--tar-ignore=keyman/subdir1/build.sh # not in include
|
||||
--tar-ignore=keyman/subdir1/path1/build.sh # in exclude
|
||||
--tar-ignore=keyman/subdir1/path2 # not in include
|
||||
--tar-ignore=keyman/subdir1/path3/build.sh # in exclude
|
||||
--tar-ignore=keyman/subdir1/build.sh # not in include
|
||||
--tar-ignore=keyman/subdir1/README.md # not in include
|
||||
--tar-ignore=keyman/subdir2 # not in include
|
||||
--tar-ignore=keyman/subdir3/build.sh # in exclude
|
||||
|
|
@ -162,13 +216,17 @@ function test__generate_tar_ignore_list__file_wildcard_in_include() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=xyz/build.sh # not in include
|
||||
--tar-ignore=xyz/subdir1/build # not in include
|
||||
--tar-ignore=xyz/subdir1/build.sh # not in include
|
||||
--tar-ignore=xyz/subdir1/path2 # not in include
|
||||
--tar-ignore=xyz/subdir1/README.md # in exclude
|
||||
--tar-ignore=xyz/subdir2 # not in include
|
||||
--tar-ignore=xyz/subdir4/build # not in include
|
||||
--tar-ignore=xyz/subdir4/build.sh # not in include
|
||||
--tar-ignore=xyz/subdir4/path1 # not in include
|
||||
--tar-ignore=xyz/subdir4/path2 # not in include
|
||||
--tar-ignore=xyz/subdir4/path3/other.txt # not in include
|
||||
--tar-ignore=xyz/subdir4/path2/subpath1 # not necessary, but in exclude
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
|
|
@ -187,9 +245,14 @@ function test__generate_tar_ignore_list__path_wildcard_in_include() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=bar/build.sh # not in include
|
||||
--tar-ignore=bar/subdir1/build # not in include
|
||||
--tar-ignore=bar/subdir1/build.sh # not in include
|
||||
--tar-ignore=bar/subdir1/path2 # not in include
|
||||
--tar-ignore=bar/subdir1/README.md # not in include
|
||||
--tar-ignore=bar/subdir2 # not in include
|
||||
--tar-ignore=bar/subdir4/build # not in include
|
||||
--tar-ignore=bar/subdir4/build.sh # not in include
|
||||
--tar-ignore=bar/subdir4/path1 # not in include
|
||||
--tar-ignore=bar/subdir4/path2/subpath1 # not in include
|
||||
--tar-ignore=bar/subdir4/path3 # not in include
|
||||
|
|
@ -210,15 +273,370 @@ function test__generate_tar_ignore_list__exclude_subsubdir() {
|
|||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=baz/subdir1
|
||||
--tar-ignore=baz/subdir2
|
||||
--tar-ignore=baz/subdir3
|
||||
--tar-ignore=baz/subdir4/path2/subpath2
|
||||
--tar-ignore=baz/build.sh # not in include
|
||||
--tar-ignore=baz/subdir1 # not in include
|
||||
--tar-ignore=baz/subdir2 # not in include
|
||||
--tar-ignore=baz/subdir3 # not in include
|
||||
--tar-ignore=baz/subdir4/path2/subpath2 # in exclude
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
}
|
||||
|
||||
function test__generate_tar_ignore_list__exclude_build_Dirs() {
|
||||
to_include=(subdir1/path1 subdir1/path3 subdir3 subdir3/build subdir4/path2)
|
||||
to_exclude=(subdir1/README.md subdir4/path2/subpath1 subdir4/path2/subpath3 build)
|
||||
|
||||
cd "${temp_dir}"
|
||||
ignored_files=()
|
||||
|
||||
# Execute
|
||||
generate_tar_ignore_list "./" to_include to_exclude ignored_files wildcard
|
||||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=wildcard/build.sh # not in include
|
||||
--tar-ignore=wildcard/subdir1/build # in exclude
|
||||
--tar-ignore=wildcard/subdir1/build.sh # not in include
|
||||
--tar-ignore=wildcard/subdir1/path1/build # in exclude
|
||||
--tar-ignore=wildcard/subdir1/path2 # not in include
|
||||
--tar-ignore=wildcard/subdir1/path3/build # in exclude
|
||||
--tar-ignore=wildcard/subdir1/README.md # in exclude
|
||||
--tar-ignore=wildcard/subdir2 # not in include
|
||||
--tar-ignore=wildcard/subdir3/path1/build # in exclude
|
||||
--tar-ignore=wildcard/subdir3/path2/build # in exclude
|
||||
--tar-ignore=wildcard/subdir4/build # in exclude
|
||||
--tar-ignore=wildcard/subdir4/build.sh # not in include
|
||||
--tar-ignore=wildcard/subdir4/path1 # not in include
|
||||
--tar-ignore=wildcard/subdir4/path2/subpath1 # in exclude
|
||||
--tar-ignore=wildcard/subdir4/path2/subpath2/build # in exclude
|
||||
--tar-ignore=wildcard/subdir4/path3 # not in include
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
}
|
||||
|
||||
function test__generate_tar_ignore_list__exclude_xy() {
|
||||
to_include=(subdir1 subdir2 subdir3 subdir4)
|
||||
to_exclude=(xy)
|
||||
|
||||
cd "${temp_dir}"
|
||||
ignored_files=()
|
||||
|
||||
# Execute
|
||||
generate_tar_ignore_list "./" to_include to_exclude ignored_files wildcard
|
||||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=wildcard/build.sh # not in include
|
||||
--tar-ignore=wildcard/subdir1/path1/xy # in exclude
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
}
|
||||
|
||||
function test__generate_tar_ignore_list__exclude_x_doesnot_exclude_xy() {
|
||||
to_include=(subdir1 subdir2 subdir3 subdir4)
|
||||
to_exclude=(x)
|
||||
|
||||
cd "${temp_dir}"
|
||||
ignored_files=()
|
||||
|
||||
# Execute
|
||||
generate_tar_ignore_list "./" to_include to_exclude ignored_files wildcard
|
||||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=wildcard/build.sh # not in include
|
||||
--tar-ignore=wildcard/subdir1/path1/x # in exclude
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
}
|
||||
|
||||
function test__generate_tar_ignore_list__can_include_toplevel_files() {
|
||||
to_include=(subdir1 subdir3 ./build.sh)
|
||||
to_exclude=(*.sh)
|
||||
|
||||
cd "${temp_dir}"
|
||||
ignored_files=()
|
||||
|
||||
# Execute
|
||||
generate_tar_ignore_list "./" to_include to_exclude ignored_files top
|
||||
|
||||
# Verify
|
||||
expected=(
|
||||
--tar-ignore=top/subdir1/build.sh # in exclude
|
||||
--tar-ignore=top/subdir1/path1/build.sh # in exclude
|
||||
--tar-ignore=top/subdir1/path2/build.sh # in exclude
|
||||
--tar-ignore=top/subdir1/path3/build.sh # in exclude
|
||||
--tar-ignore=top/subdir2 # not in include
|
||||
--tar-ignore=top/subdir3/build.sh # in exclude
|
||||
--tar-ignore=top/subdir4 # not in include
|
||||
)
|
||||
|
||||
assert-equal "${ignored_files[*]}" "${expected[*]}"
|
||||
}
|
||||
|
||||
# Tests for _starts_with function
|
||||
function test__starts_with__match_parent() {
|
||||
# Setup
|
||||
includes=(subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-true _starts_with includes ./subdir1
|
||||
}
|
||||
|
||||
function test__starts_with__exact_match() {
|
||||
# Setup
|
||||
includes=(subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-true _starts_with includes ./subdir1/path1
|
||||
}
|
||||
|
||||
function test__starts_with__exact_match_localdir() {
|
||||
# Setup
|
||||
includes=(./subdir1/path1 ./subdir3)
|
||||
|
||||
# Verify
|
||||
assert-true _starts_with includes ./subdir1/path1
|
||||
}
|
||||
|
||||
function test__starts_with__no_match() {
|
||||
# Setup
|
||||
includes=(subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-false _starts_with includes ./subdir2
|
||||
}
|
||||
|
||||
function test__starts_with__normalized_paths() {
|
||||
# Setup
|
||||
includes=(path1 path2)
|
||||
|
||||
# Verify
|
||||
assert-true _starts_with includes ./path1
|
||||
}
|
||||
|
||||
function test__starts_with__subdir() {
|
||||
# Setup
|
||||
includes=(path1 path2)
|
||||
|
||||
# Verify
|
||||
assert-false _starts_with includes ./path1/subdir
|
||||
}
|
||||
|
||||
function test__starts_with__no_match_different_name() {
|
||||
# Setup
|
||||
includes=(path1 path2)
|
||||
|
||||
# Verify
|
||||
assert-false _starts_with includes ./path3
|
||||
}
|
||||
|
||||
# Tests for _ends_with function
|
||||
function test__ends_with__exact_filename_first() {
|
||||
# Setup
|
||||
local array=(README.md test.txt)
|
||||
|
||||
# Verify
|
||||
assert-true _ends_with array ./subdir/README.md
|
||||
}
|
||||
|
||||
function test__ends_with__exact_filename_second() {
|
||||
# Setup
|
||||
local array=(README.md test.txt)
|
||||
|
||||
# Verify
|
||||
assert-true _ends_with array ./path/to/test.txt
|
||||
}
|
||||
|
||||
function test__ends_with__not_matching_similar_extension() {
|
||||
# Setup
|
||||
local array=(README.md test.txt)
|
||||
|
||||
# Verify
|
||||
assert-false _ends_with array ./README.md.bak
|
||||
}
|
||||
|
||||
function test__ends_with__no_match_other_filename() {
|
||||
# Setup
|
||||
local array=(README.md test.txt)
|
||||
|
||||
# Verify
|
||||
assert-false _ends_with array ./other.txt
|
||||
}
|
||||
|
||||
# Tests for _is_exact_match function
|
||||
function test__is_exact_match__full_path_first() {
|
||||
# Setup
|
||||
includes=(./subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-true _is_exact_match includes ./subdir1/path1
|
||||
}
|
||||
|
||||
function test__is_exact_match__full_path_second() {
|
||||
# Setup
|
||||
includes=(./subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-true _is_exact_match includes ./subdir3
|
||||
}
|
||||
|
||||
function test__is_exact_match__partial_path_no_match() {
|
||||
# Setup
|
||||
includes=(./subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-false _is_exact_match includes ./subdir1
|
||||
}
|
||||
|
||||
function test__is_exact_match__sibling_path_no_match() {
|
||||
# Setup
|
||||
includes=(./subdir1/path1 subdir3)
|
||||
|
||||
# Verify
|
||||
assert-false _is_exact_match includes ./subdir1/path2
|
||||
}
|
||||
|
||||
function test__is_exact_match__normalized_paths_first() {
|
||||
# Setup
|
||||
includes=(subdir1 subdir2)
|
||||
|
||||
# Verify
|
||||
assert-true _is_exact_match includes ./subdir1
|
||||
}
|
||||
|
||||
function test__is_exact_match__normalized_paths_second() {
|
||||
# Setup
|
||||
includes=(subdir1 subdir2)
|
||||
|
||||
# Verify
|
||||
assert-true _is_exact_match includes ./subdir2
|
||||
}
|
||||
|
||||
function test__is_exact_match__empty_array() {
|
||||
# Setup
|
||||
includes=()
|
||||
|
||||
# Verify
|
||||
assert-false _is_exact_match includes ./anything
|
||||
}
|
||||
|
||||
# Tests for _is_wildcard_match function
|
||||
function test__is_wildcard_match__file() {
|
||||
# Setup
|
||||
local array=(sh)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./config/sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__dot_file() {
|
||||
# Setup
|
||||
local array=(sh)
|
||||
|
||||
# Verify
|
||||
assert-false _is_wildcard_match array ./config/.sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__dot_files_nested() {
|
||||
# Setup
|
||||
local array=(.sh)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./deeply/nested/build/.sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__no_dot_prefix() {
|
||||
# Setup
|
||||
local array=(sh)
|
||||
|
||||
# Verify
|
||||
assert-false _is_wildcard_match array ./build.sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__toplevel_file() {
|
||||
# Setup
|
||||
local array=(sh)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__multiple_extensions_first() {
|
||||
# Setup
|
||||
local array=(tmp log)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/tmp
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__multiple_extensions_second() {
|
||||
# Setup
|
||||
local array=(tmp log)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/log
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__dot_in_wildcard_first() {
|
||||
# Setup
|
||||
local array=(.tmp .log)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/.tmp
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__dot_in_wildcard_second() {
|
||||
# Setup
|
||||
local array=(.tmp .log)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/.log
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__dot_in_wildcard_no_dotfile() {
|
||||
# Setup
|
||||
local array=(.tmp .log)
|
||||
|
||||
# Verify
|
||||
assert-false _is_wildcard_match array ./path/xtmp
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__wildcard_match_dotfile() {
|
||||
# Setup
|
||||
local array=(\*.sh)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/.sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__wildcard_match_file() {
|
||||
# Setup
|
||||
local array=(\*.sh)
|
||||
|
||||
# Verify
|
||||
assert-true _is_wildcard_match array ./path/subdir/build.sh
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__wildcard_no_matches_file() {
|
||||
# Setup
|
||||
local array=(\*.sh)
|
||||
|
||||
# Verify
|
||||
assert-false _is_wildcard_match array ./path/old
|
||||
}
|
||||
|
||||
function test__is_wildcard_match__wildcard_no_matches_path() {
|
||||
# Setup
|
||||
local array=(\*.sh)
|
||||
|
||||
# Verify
|
||||
assert-false _is_wildcard_match array ./path/build.sh/foo
|
||||
}
|
||||
|
||||
# shellcheck disable=2119
|
||||
run_tests
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Uncomment the next line to define a global platform for your project
|
||||
platform :osx, '10.13'
|
||||
platform :osx, '11.0'
|
||||
use_frameworks!
|
||||
|
||||
target 'Keyman' do
|
||||
|
|
@ -7,11 +7,11 @@ target 'Keyman' do
|
|||
# use_frameworks!
|
||||
|
||||
# Pods for Keyman
|
||||
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.38.0'
|
||||
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.57.3'
|
||||
|
||||
target 'KeymanTests' do
|
||||
inherit! :search_paths
|
||||
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.38.0'
|
||||
pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.57.3'
|
||||
use_frameworks!
|
||||
# Pods for testing
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
PODS:
|
||||
- Sentry (8.38.0-beta.1):
|
||||
- Sentry/Core (= 8.38.0-beta.1)
|
||||
- Sentry/Core (8.38.0-beta.1)
|
||||
- Sentry (8.57.3):
|
||||
- Sentry/Core (= 8.57.3)
|
||||
- Sentry/Core (8.57.3)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.38.0`)
|
||||
- Sentry (from `https://github.com/getsentry/sentry-cocoa.git`, tag `8.57.3`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Sentry:
|
||||
:git: https://github.com/getsentry/sentry-cocoa.git
|
||||
:tag: 8.38.0
|
||||
:tag: 8.57.3
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
Sentry:
|
||||
:git: https://github.com/getsentry/sentry-cocoa.git
|
||||
:tag: 8.38.0
|
||||
:tag: 8.57.3
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Sentry: 4d6027fbfde9ddc35e5c368292843097d039db5f
|
||||
Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
|
||||
|
||||
PODFILE CHECKSUM: 19b128c35d9c5e59f90d09522c053de65096696a
|
||||
PODFILE CHECKSUM: d45d4bde6c75c2c91314777c88c421e5e604d84d
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ fv,fv_ojibwa_rdot,ᐁᓂᔑᓇᐯᒧᐏᓐ (a-finals right w-dot),Eastern Subarc
|
|||
fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0.3,oj,Ojibwa
|
||||
fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0.3,oj,Ojibwa
|
||||
fv,fv_naskapi,ᓇᔅᑲᐱ (Naskapi),Eastern Subarctic,fv_naskapi_kmw-9.0.js,9.3.1,nsk-Cans,Naskapi (Unified Canadian Aboriginal Syllabics)
|
||||
sil,sil_euro_latin,English,European,european2-1.6.js,3.0.3,en,English
|
||||
sil,sil_euro_latin,English,European,european2-1.6.js,3.0.4,en,English
|
||||
basic,basic_kbdcan,Français,European,canadian_french-1.0.js,1.1.1,fr-CA,French (Canada)
|
||||
fv,fv_anishinaabemowin,Anishinaabemowin,Great Lakes - St. Lawrence,fv_anishinaabemowin_kmw-9.0.js,10.2,oj,Ojibwa
|
||||
fv,fv_bodewadminwen,Bodéwadminwen-Nishnabémwen,Great Lakes - St. Lawrence,fv_bodewadminwen_kmw-9.0.js,9.1.1,pot-Latn,Potawatomi (Latin)
|
||||
|
|
@ -102,5 +102,6 @@ fv,fv_shihgotine_yati,Shıhgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_shihgot
|
|||
fv,fv_southern_tutchone,Southern Tutchone,Western Subarctic,fv_southern_tutchone_kmw-9.0.js,9.3,tce-Latn,Southern Tutchone (Latin)
|
||||
fv,fv_tagizi_dene,Tāgizi Dene,Western Subarctic,fv_tagizi_dene_kmw-9.0.js,9.4,tgx-Latn,Tagish (Latin)
|
||||
fv,fv_tlicho_yatii,Tłı̨chǫ Yatıı̀,Western Subarctic,fv_tlicho_yatii_kmw-9.0.js,9.1.1,dgr-Latn,Dogrib (Latin)
|
||||
fv,fv_tlingityooxatangi,T’aku Tlingit Yóo X’átangí,Western Subarctic,fv_tlingityooxatangi-9.0.js,1.0,tli,T’aku Tlingit Yóo X’átangí
|
||||
fv,fv_dene_mb,ᑌᓀ ᔭᕠᐁ (Dene MB),Western Subarctic,fv_dene_mb_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)
|
||||
fv,fv_dene_nt,ᑌᓀ ᔭᕱᐁ (Dene NT),Western Subarctic,fv_dene_nt_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)
|
||||
|
|
|
|||
|
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -243,7 +243,7 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-xml-parser": "^5.2.2",
|
||||
"fast-xml-parser": "^5.3.4",
|
||||
"path-browserify": "^1.0.1",
|
||||
"restructure": "^3.0.1",
|
||||
"sax": ">=0.6.0",
|
||||
|
|
@ -7841,9 +7841,9 @@
|
|||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.2.tgz",
|
||||
"integrity": "sha512-ZaCmslH75Jkfowo/x44Uq8KT5SutC5BFxHmY61nmTXPccw11PVuIXKUqC2hembMkJ3nPwTkQESXiUlsKutCbMg==",
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz",
|
||||
"integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,6 @@ KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER=noble # Ubuntu 24.04 Noble
|
|||
# Data versions -- see resources/standards-data/readme.md
|
||||
KEYMAN_VERSION_CLDR=46 # LDML Keyboards version
|
||||
KEYMAN_VERSION_ISO639_3=2024-05-22 # Date of last import
|
||||
KEYMAN_VERSION_LANGTAGS=2025-02-18 # _version value
|
||||
KEYMAN_VERSION_LANGTAGS=2026-02-03 # _version value
|
||||
KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY=2025-03-10 # Date from first line of language-subtag-registry
|
||||
KEYMAN_VERSION_UNICODE=17.0.0 # UCD + related data
|
||||
|
|
|
|||
|
|
@ -77,6 +77,31 @@ assert-succeeded() {
|
|||
fi
|
||||
}
|
||||
|
||||
_callfunc() {
|
||||
local command="$1"
|
||||
shift
|
||||
|
||||
"${command}" "$@"
|
||||
}
|
||||
|
||||
assert-true() {
|
||||
# shellcheck disable=SC2310
|
||||
if ! _callfunc "$@"; then
|
||||
messages+=("- Expected test to return true but it returned false
|
||||
")
|
||||
((test_failures++))
|
||||
fi
|
||||
}
|
||||
|
||||
assert-false() {
|
||||
# shellcheck disable=SC2310
|
||||
if _callfunc "$@"; then
|
||||
messages+=("- Expected test to return false but it returned true
|
||||
")
|
||||
((test_failures++))
|
||||
fi
|
||||
}
|
||||
|
||||
# setup() will run before each test
|
||||
# can be overriden in test script
|
||||
setup() {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,74 @@ EOF
|
|||
assert-contains "${test_output_contents}" "PASS: test_succeeding1"
|
||||
}
|
||||
|
||||
# Helper functions for testing assert-true and assert-false
|
||||
function returns_true() {
|
||||
return 0
|
||||
}
|
||||
|
||||
function returns_false() {
|
||||
return 1
|
||||
}
|
||||
|
||||
## Tests for assert-true function
|
||||
|
||||
function test__assert_true__with_function_that_returns_true() {
|
||||
# Execute and Verify - should pass
|
||||
assert-true returns_true
|
||||
}
|
||||
|
||||
function test__assert_true__with_builtin_true() {
|
||||
# Execute and Verify - should pass
|
||||
assert-true true
|
||||
}
|
||||
|
||||
function test__assert_true__with_function_with_args_success() {
|
||||
# Execute and Verify - file exists
|
||||
assert-true test -f /etc/hostname
|
||||
}
|
||||
|
||||
function test__assert_true__increments_failure_on_false() {
|
||||
# Setup
|
||||
test_failures=0
|
||||
messages=()
|
||||
|
||||
# Execute function that returns false
|
||||
returns_false
|
||||
local result=$?
|
||||
|
||||
# Verify it failed
|
||||
assert-equal "${result}" "1"
|
||||
}
|
||||
|
||||
## Tests for assert-false function
|
||||
|
||||
function test__assert_false__with_function_that_returns_false() {
|
||||
# Execute and Verify - should pass
|
||||
assert-false returns_false
|
||||
}
|
||||
|
||||
function test__assert_false__with_builtin_false() {
|
||||
# Execute and Verify - should pass
|
||||
assert-false false
|
||||
}
|
||||
|
||||
function test__assert_false__with_function_with_args_success() {
|
||||
# Execute and Verify - file does not exist
|
||||
assert-false test -f /nonexistent/file/that/does/not/exist
|
||||
}
|
||||
|
||||
function test__assert_false__increments_failure_on_true() {
|
||||
# Setup
|
||||
test_failures=0
|
||||
messages=()
|
||||
|
||||
# Execute function that returns true
|
||||
returns_true
|
||||
local result=$?
|
||||
|
||||
# Verify it succeeded
|
||||
assert-equal "${result}" "0"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2031
|
||||
if [[ "${1:-}" != "--recursive" ]]; then
|
||||
|
|
@ -14,7 +14,7 @@ _utils_inc_sh=1
|
|||
# 1: UPLOAD_DIR Directory where artifact can be found
|
||||
# 2: ARTIFACT_FILENAME Filename (without path) of artifact
|
||||
# 3: ARTIFACT_NAME Descriptive name of artifact
|
||||
# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz)
|
||||
# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.xz)
|
||||
# 5: PLATFORM Target platform for artifact
|
||||
#
|
||||
# TODO: Move to CI include?
|
||||
|
|
|
|||
|
|
@ -135,6 +135,10 @@ wrap-signcode() {
|
|||
}
|
||||
|
||||
wrap-symstore() {
|
||||
local target="$1"
|
||||
local slasht="$2"
|
||||
local product="$3"
|
||||
|
||||
if builder_is_ci_build && builder_is_ci_build_level_build; then
|
||||
builder_echo "Skipping symstore - buildLevel=build"
|
||||
return 0
|
||||
|
|
@ -147,10 +151,10 @@ wrap-symstore() {
|
|||
|
||||
"$ProgramFilesx86/Windows Kits/10/Debuggers/x64/symstore.exe" \
|
||||
add \
|
||||
//s "$KEYMAN_SYMSTOREPATH" \
|
||||
//s "$(cygpath -w "$KEYMAN_SYMSTOREPATH")" \
|
||||
//v "$KEYMAN_VERSION_WIN" \
|
||||
//c "Version: $KEYMAN_VERSION_WITH_TAG" \
|
||||
//compress //f "$@"
|
||||
//compress //f "$(cygpath -w "$target")" "$slasht" "$product"
|
||||
}
|
||||
|
||||
wrap-mt() {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -60,10 +60,10 @@ function _make_release_source_tarball() {
|
|||
./scripts/reconf.sh
|
||||
PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" ./scripts/dist.sh
|
||||
mkdir -p "upload/${KEYMAN_VERSION}"
|
||||
cp -a dist/*.tar.gz "upload/${KEYMAN_VERSION}"
|
||||
cp -a dist/*.tar.xz "upload/${KEYMAN_VERSION}"
|
||||
(
|
||||
cd "upload/${KEYMAN_VERSION}"
|
||||
sha256sum ./*.tar.gz > SHA256SUMS
|
||||
sha256sum ./*.tar.xz > SHA256SUMS
|
||||
builder_echo end "make source tarball" success "Make source tarball"
|
||||
)
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ function _sign_source_tarball() {
|
|||
|
||||
eval "$(gpg-agent -vv --daemon --allow-preset-passphrase --debug-level 9)"
|
||||
/usr/lib/gnupg/gpg-preset-passphrase --passphrase "${GPGKEYPW}" --preset "${GPGKEYGRIP}"
|
||||
for f in ./*.tar.gz; do gpg --output "${f}.asc" -a --detach-sig "${f}"; done
|
||||
for f in ./*.tar.xz; do gpg --output "${f}.asc" -a --detach-sig "${f}"; done
|
||||
/usr/lib/gnupg/gpg-preset-passphrase --forget "${GPGKEYGRIP}" || true
|
||||
builder_echo end "sign source tarball" success "Sign source tarball"
|
||||
)
|
||||
|
|
@ -84,10 +84,10 @@ function _sign_source_tarball() {
|
|||
function _publish_to_downloads() {
|
||||
builder_echo start "publish to downloads" "Publish to downloads.keyman.com"
|
||||
|
||||
local UPLOAD_DIR KEYMAN_TGZ
|
||||
local UPLOAD_DIR KEYMAN_TXZ
|
||||
|
||||
UPLOAD_DIR="upload/${KEYMAN_VERSION}"
|
||||
KEYMAN_TGZ="keyman-${KEYMAN_VERSION}.tar.gz"
|
||||
KEYMAN_TXZ="keyman-${KEYMAN_VERSION}.tar.xz"
|
||||
|
||||
# Set permissions as required on download site
|
||||
builder_echo "Setting upload file permissions for downloads.keyman.com"
|
||||
|
|
@ -96,7 +96,7 @@ function _publish_to_downloads() {
|
|||
chmod g+w "${UPLOAD_DIR}"/*
|
||||
chmod a+r "${UPLOAD_DIR}"/*
|
||||
|
||||
write_download_info "${UPLOAD_DIR}" "${KEYMAN_TGZ}" "Keyman for Linux" tar.gz linux
|
||||
write_download_info "${UPLOAD_DIR}" "${KEYMAN_TXZ}" "Keyman for Linux" tar.xz linux
|
||||
tc_rsync_upload "${UPLOAD_DIR}" "linux/${KEYMAN_TIER}"
|
||||
|
||||
builder_echo end "publish to downloads" success "Publish to downloads.keyman.com"
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@
|
|||
},
|
||||
"imports": {
|
||||
"#gesture-tools": "./src/engine/osk/gesture-processor/build/tools/obj/index.js",
|
||||
"#recorder": "./build/tools/testing/recorder/obj/index.js"
|
||||
"#recorder": "./build/tools/testing/recorder/obj/index.js",
|
||||
"#test-resources/*.js": "./build/test/resources/*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import { assert } from 'chai'
|
||||
import sinon from 'sinon';
|
||||
|
||||
import * as PromiseStatusModule from 'promise-status-async';
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js';
|
||||
|
||||
import { InputSample, gestures, GestureDebugPath } from '@keymanapp/gesture-recognizer';
|
||||
|
||||
import { assertingPromiseStatus as promiseStatus } from '#test-resources/assertingPromiseStatus.js';
|
||||
import { TouchpathTurtle } from '#gesture-tools';
|
||||
|
||||
import { simulateMultiSourceMatcherInput } from "../../../../../resources/simulateMultiSourceInput.js";
|
||||
import { simulateMultiSourceMatcherInput } from "#test-resources/simulateMultiSourceInput.js";
|
||||
|
||||
import {
|
||||
FlickEndModel,
|
||||
|
|
@ -27,6 +24,8 @@ import {
|
|||
MainLongpressSourceModel
|
||||
} from './isolatedPathSpecs.js';
|
||||
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
|
||||
type PathInheritanceType = gestures.specs.ContactModel<string>['pathInheritance'];
|
||||
function dummyInheritanceMatcher(inheritanceType: PathInheritanceType): gestures.specs.GestureModel<string> {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import { assert } from 'chai'
|
||||
import sinon from 'sinon';
|
||||
|
||||
import * as PromiseStatusModule from 'promise-status-async';
|
||||
import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js';
|
||||
|
||||
import { GestureModelDefs, buildGestureMatchInspector, gestures } from '@keymanapp/gesture-recognizer';
|
||||
import { ManagedPromise, timedPromise } from '@keymanapp/web-utils';
|
||||
|
||||
import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools';
|
||||
import { assertingPromiseStatus as promiseStatus } from '#test-resources/assertingPromiseStatus.js';
|
||||
import { assertGestureSequence, SequenceAssertion } from "#test-resources/sequenceAssertions.js";
|
||||
|
||||
const { matchers } = gestures;
|
||||
|
||||
// Huh... gotta do BOTH here? One for constructor use, the other for generic-parameter use?
|
||||
|
|
@ -16,10 +20,7 @@ type MatcherSelection<Type> = gestures.matchers.MatcherSelection<Type>;
|
|||
const getGestureModelSet = gestures.specs.getGestureModelSet;
|
||||
const modelSetForAction = gestures.matchers.modelSetForAction;
|
||||
|
||||
import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools';
|
||||
import { ManagedPromise, timedPromise } from '@keymanapp/web-utils';
|
||||
|
||||
import { assertGestureSequence, SequenceAssertion } from "../../../../../resources/sequenceAssertions.js";
|
||||
|
||||
import {
|
||||
LongpressModel,
|
||||
|
|
|
|||
|
|
@ -2,18 +2,13 @@ import { assert } from 'chai'
|
|||
import sinon from 'sinon';
|
||||
|
||||
import * as PromiseStatusModule from 'promise-status-async';
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js';
|
||||
|
||||
import { simulateMultiSourceMatcherInput, simulateSelectorInput } from "../../../../../resources/simulateMultiSourceInput.js";
|
||||
|
||||
import { timedPromise } from '@keymanapp/web-utils';
|
||||
import { gestures } from '@keymanapp/gesture-recognizer';
|
||||
|
||||
import { TouchpathTurtle } from '#gesture-tools';
|
||||
|
||||
type MatcherSelection<Type> = gestures.matchers.MatcherSelection<Type>;
|
||||
type GestureModel<Type> = gestures.specs.GestureModel<Type>;
|
||||
import { assertingPromiseStatus as promiseStatus } from '#test-resources/assertingPromiseStatus.js';
|
||||
import { simulateMultiSourceMatcherInput, simulateSelectorInput } from "#test-resources/simulateMultiSourceInput.js";
|
||||
|
||||
import {
|
||||
LongpressModel,
|
||||
|
|
@ -26,6 +21,10 @@ import {
|
|||
LongpressDistanceThreshold
|
||||
} from './isolatedPathSpecs.js';
|
||||
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
type MatcherSelection<Type> = gestures.matchers.MatcherSelection<Type>;
|
||||
type GestureModel<Type> = gestures.specs.GestureModel<Type>;
|
||||
|
||||
describe("MatcherSelector", function () {
|
||||
beforeEach(function() {
|
||||
this.fakeClock = sinon.useFakeTimers();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { assert } from 'chai'
|
||||
import sinon from 'sinon';
|
||||
|
||||
import * as PromiseStatusModule from 'promise-status-async';
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js';
|
||||
|
||||
import { InputSample, GestureSource, gestures, CumulativePathStats } from '@keymanapp/gesture-recognizer';
|
||||
import { timedPromise } from '@keymanapp/web-utils';
|
||||
|
||||
import { assertingPromiseStatus as promiseStatus } from '#test-resources/assertingPromiseStatus.js';
|
||||
|
||||
import {
|
||||
InstantRejectionModel,
|
||||
InstantResolutionModel,
|
||||
|
|
@ -22,6 +21,8 @@ import {
|
|||
FlickEndThreshold
|
||||
} from './isolatedPathSpecs.js';
|
||||
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
|
||||
async function simulateSequence(
|
||||
samples: InputSample<string>[],
|
||||
fakeClock: sinon.SinonFakeTimers,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import { assert } from 'chai'
|
||||
import sinon from 'sinon';
|
||||
|
||||
import * as PromiseStatusModule from 'promise-status-async';
|
||||
import { assertingPromiseStatus as promiseStatus } from '../../../../../resources/assertingPromiseStatus.js';
|
||||
import { PROMISE_PENDING } from 'promise-status-async';
|
||||
|
||||
import { GestureModelDefs, GestureSource, gestures, TouchpointCoordinator } from '@keymanapp/gesture-recognizer';
|
||||
const { matchers } = gestures;
|
||||
|
||||
// Huh... gotta do BOTH here? One for constructor use, the other for generic-parameter use?
|
||||
const { GestureSequence } = matchers;
|
||||
type GestureSequence<Type> = gestures.matchers.GestureSequence<Type>;
|
||||
|
||||
import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools';
|
||||
import { ManagedPromise, timedPromise } from '@keymanapp/web-utils';
|
||||
|
||||
import { assertGestureSequence, SequenceAssertion } from "../../../../../resources/sequenceAssertions.js";
|
||||
import { HeadlessInputEngine, TouchpathTurtle } from '#gesture-tools';
|
||||
import { assertingPromiseStatus as promiseStatus } from '#test-resources/assertingPromiseStatus.js';
|
||||
import { assertGestureSequence, SequenceAssertion } from "#test-resources/sequenceAssertions.js";
|
||||
|
||||
import {
|
||||
LongpressModel,
|
||||
|
|
@ -25,9 +19,13 @@ import {
|
|||
SubkeySelectModel
|
||||
} from './isolatedGestureSpecs.js';
|
||||
|
||||
const LongpressDurationThreshold = LongpressModel.contacts[0].model.timer.duration;
|
||||
const { matchers } = gestures;
|
||||
|
||||
import { PROMISE_PENDING } from 'promise-status-async';
|
||||
// Huh... gotta do BOTH here? One for constructor use, the other for generic-parameter use?
|
||||
const { GestureSequence } = matchers;
|
||||
type GestureSequence<Type> = gestures.matchers.GestureSequence<Type>;
|
||||
|
||||
const LongpressDurationThreshold = LongpressModel.contacts[0].model.timer.duration;
|
||||
|
||||
const TestGestureModelDefinitions: GestureModelDefs<string> = {
|
||||
gestures: [
|
||||
|
|
|
|||
|
|
@ -774,7 +774,8 @@ end;
|
|||
procedure UpdateAvailableState.HandleInstallNow;
|
||||
begin
|
||||
bucStateContext.SetApplyNow(True);
|
||||
ChangeState(DownloadingState);
|
||||
// A new kmshell process will be used to download
|
||||
StartDownloadProcess;
|
||||
end;
|
||||
|
||||
{ DownloadingState }
|
||||
|
|
|
|||
|
|
@ -839,7 +839,12 @@ begin
|
|||
FResult := TUtilExecute.Shell(0, ShellPath, '', '-an');
|
||||
if not FResult then
|
||||
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
|
||||
'TrmfMain: Shell Execute Update_ApplyNow Failed');
|
||||
'TrmfMain: Shell Execute Update_ApplyNow Failed')
|
||||
else
|
||||
ModalResult := mrAbort;
|
||||
// If a splash screen is currently open when "Install Now" is executed,
|
||||
// setting mrAbort ensures the splash screen is closed on the
|
||||
// return of "Keyman Configuration".
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ uses
|
|||
Windows, Controls, SysUtils, Classes, ErrorControlledRegistry, Forms, MessageIdentifiers, MessageIdentifierConsts, keymanapi_TLB;
|
||||
|
||||
procedure Run;
|
||||
procedure Main(Owner: TComponent = nil);
|
||||
function Main(Owner: TComponent = nil): TModalResult;
|
||||
|
||||
|
||||
type
|
||||
|
|
@ -164,10 +164,11 @@ procedure ShowKeyboardWelcome(PackageName: WideString); forward; // I2569
|
|||
procedure PrintKeyboard(KeyboardName: WideString); forward; // I2329
|
||||
function ProcessBackgroundUpdate(FMode: TKMShellMode; FSilent: Boolean): Boolean; forward;
|
||||
|
||||
procedure Main(Owner: TComponent = nil);
|
||||
function Main(Owner: TComponent = nil): TModalResult;
|
||||
var
|
||||
frmMain: TfrmMain;
|
||||
begin
|
||||
Result := mrNone;
|
||||
if not Assigned(Owner) then
|
||||
begin
|
||||
UfrmWebContainer.CreateForm(TfrmMain, frmMain);
|
||||
|
|
@ -180,7 +181,7 @@ begin
|
|||
begin
|
||||
with TfrmMain.Create(Owner) do
|
||||
try
|
||||
ShowModal;
|
||||
Result := ShowModal;
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ type
|
|||
FShowConfigurationOnLoad: Boolean;
|
||||
procedure WMUser_FormShown(var Message: TMessage); message WM_USER_FormShown;
|
||||
procedure WMUser(var Message: TMessage); message WM_USER;
|
||||
procedure ShowConfiguration;
|
||||
protected
|
||||
procedure FireCommand(const command: WideString; params: TStringList);
|
||||
override;
|
||||
|
|
@ -112,6 +113,17 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmSplash.ShowConfiguration;
|
||||
var
|
||||
configFrmResult: Integer;
|
||||
begin
|
||||
configFrmResult := Main(Self);
|
||||
if configFrmResult = mrAbort then
|
||||
Command_Exit
|
||||
else
|
||||
Do_Content_Render;
|
||||
end;
|
||||
|
||||
procedure TfrmSplash.TntFormActivate(Sender: TObject);
|
||||
begin
|
||||
inherited;
|
||||
|
|
@ -152,15 +164,17 @@ begin
|
|||
|
||||
if FShowConfigurationOnLoad then
|
||||
begin
|
||||
Main(Self);
|
||||
Do_Content_Render;
|
||||
ShowConfiguration;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmSplash.FireCommand(const command: WideString; params: TStringList);
|
||||
begin
|
||||
if command = 'start' then Command_Start
|
||||
else if command = 'config' then begin Main(Self); Do_Content_Render; end // I4393 // I4396
|
||||
else if command = 'config' then
|
||||
begin
|
||||
ShowConfiguration;
|
||||
end // I4393 // I4396
|
||||
else if command = 'hidesplash' then FShouldDisplay := False
|
||||
else if command = 'showsplash' then FShouldDisplay := True
|
||||
else if command = 'exit' then Command_Exit
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue