Merge pull request #13090 from keymanapp/chore/merge-master-into-kmc-convert
chore: merge master into kmc-convert 😎
120
HISTORY.md
|
|
@ -1,5 +1,98 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 18.0.180 alpha 2025-01-29
|
||||
|
||||
* chore(common): Update crowdin strings for Portuguese (#13056)
|
||||
* chore(common): Update crowdin strings for Spanish (Latin America) (#13044)
|
||||
* chore(linux): Update debian changelog (#13063)
|
||||
* chore(linux): improve compatibility with Gentoo Linux (#13070)
|
||||
|
||||
## 18.0.179 alpha 2025-01-28
|
||||
|
||||
* fix(android): improve resource-update tool handling of host Activity's closure (#13004)
|
||||
* fix(windows): bool return value of KeymanIsTextSelected (#12841)
|
||||
* chore(mac): add breadcrumbs for Sentry (#12939)
|
||||
* fix(mac): show and hide OSK consistently for all keyboards (#12988)
|
||||
* fix(linux): remove unnecessary files from source tarball (#13053)
|
||||
* fix(windows): handle keyboard package not downloaded (#12948)
|
||||
|
||||
## 18.0.178 alpha 2025-01-27
|
||||
|
||||
* fix(web): fix handling of data: url fonts, fonts with single quotes in filename (#13032)
|
||||
* fix(android): disable early keyboard-loading when not feasible (#13001)
|
||||
* fix(web): add null guard for focusing next element via keystroke (#13013)
|
||||
* chore: adjust one bookmark link in windows doc (#13050)
|
||||
* chore: adjust bookmark links in developer docs (#13049)
|
||||
|
||||
## 18.0.177 alpha 2025-01-24
|
||||
|
||||
* fix(android): restores DOMRect polyfill for old Chrome support (#13012)
|
||||
* fix(developer): produce correct index parameter value for context in kmw compiler (#13003)
|
||||
* chore: add platform to what's new titles (#12989)
|
||||
* chore: add platforms to welcome titles (#12990)
|
||||
* chore: add platforms to version history titles (#13016)
|
||||
* chore: add platforms to troubleshooting titles and windows to help homepage (#13019)
|
||||
* chore: add platforms to system requirements and download-and-install docs (#13024)
|
||||
* chore: add mac platform to titles (#13027)
|
||||
* chore: add linux platform to titles (#13028)
|
||||
* chore: add ios platform to titles (#13031)
|
||||
* fix(developer): escape font facename in touch layout editor (#13020)
|
||||
* fix: use correct path for resources/build/version (#13029)
|
||||
* fix(developer): don't list .js keyboards for multi-keyboard packages in kmc-keyboard-info (#13034)
|
||||
* fix(web): do not simplify nul-prefixed contexts when rule-matching (#12998)
|
||||
* feat(android): add breadcrumbing during package install about package file, source (#13021)
|
||||
* fix(linux): correct permissions and paths in Linux docker image (#13008)
|
||||
|
||||
## 18.0.176 alpha 2025-01-23
|
||||
|
||||
* chore(developer): stub out icon support in kmc-generate (#12971)
|
||||
* chore(developer): remove '--rename' option from kmc-copy (#12973)
|
||||
* feat(developer): add 'verbose' and 'debug' log levels to kmc (#12976)
|
||||
* chore(developer): validate output folder name in kmc-copy (#12979)
|
||||
* chore(developer): report on filename collisions in kmc-copy (#12982)
|
||||
* feat(developer): support copying disorganized projects in kmc-copy (#12984)
|
||||
* chore(linux): docker build: update package index before installing packages in Dockerfiles (#12992)
|
||||
* feat(web): allow main manual-test page to load from KMP (#12977)
|
||||
* fix(android): Remove access to MEDIA storage permissions (#12999)
|
||||
|
||||
## 18.0.175 alpha 2025-01-22
|
||||
|
||||
* feat(windows): Fixes check now to use SM and LastUpdateCheckTime key on first run. (#12922)
|
||||
* chore(developer): validate keyboard and model IDs in kmc-generate (#12958)
|
||||
* fix(developer): use correct terms in kmc-generate lexical model help (#12960)
|
||||
* fix(developer): use 1.0.0 as default for ldml keyboards in kmc-generate (#12962)
|
||||
* chore: adds h1 tags (#12967)
|
||||
* fix(web): prevent warning that occurs when multitapping longpressable modifier keys (#12964)
|
||||
* chore(developer): add test for basic transform to kmc-generate (#12966)
|
||||
* fix(android/engine): Revert how keyboard picker menu launches (#12986)
|
||||
|
||||
## 18.0.174 alpha 2025-01-21
|
||||
|
||||
* chore(developer): validate targets parameter in kmc-generate (#12955)
|
||||
* fix(developer): support `&displayMap` font in web debugger (#12929)
|
||||
|
||||
## 18.0.173 alpha 2025-01-20
|
||||
|
||||
* feat(developer): rewrite font data in .kvk from package metadata (#12949)
|
||||
* docs(ios): document gestures within iOS app help (#12900)
|
||||
* fix(developer): include developer-utils deps in server build (#12951)
|
||||
|
||||
## 18.0.172 alpha 2025-01-19
|
||||
|
||||
* chore(common): Add 17.0.333 to version history (#12926)
|
||||
|
||||
## 18.0.171 alpha 2025-01-18
|
||||
|
||||
* fix(common/web): add StrsItem.isEqual() method (#12868)
|
||||
* fix(common/web): handle invalid order and tertiary arguments to ElementString.fromString() (#12882)
|
||||
|
||||
## 18.0.170 alpha 2025-01-17
|
||||
|
||||
* chore(linux): update copyright year (#12918)
|
||||
* fix(web): patch unit test with execution dependent on autocorrect state (#12940)
|
||||
* test(common/web): unit tests for element-string (#12811)
|
||||
* chore(linux): Update debian changelog (#12917)
|
||||
|
||||
## 18.0.169 alpha 2025-01-17
|
||||
|
||||
* chore(windows): remove `postinstall` state from mermaid diagram (#12923)
|
||||
|
|
@ -953,7 +1046,7 @@
|
|||
## 18.0.41 alpha 2024-05-22
|
||||
|
||||
* fix(developer): handle `KM_CORE_IT_INVALIDATE_CONTEXT` in debugger (#11488)
|
||||
* chore(linux): Trigger GHA packaging for stable builds :cherries: (#11495)
|
||||
* chore(linux): Trigger GHA packaging for stable builds (#11495)
|
||||
* chore(android,mac,windows): Update crowdin strings for DE (#11497)
|
||||
* feat(web): custom infrastructure for @web/test-runner use (#11403)
|
||||
* chore(web): conversion of lm-worker browser-test for @web/test-runner use (#11404)
|
||||
|
|
@ -1149,6 +1242,31 @@
|
|||
* chore(common): move to 18.0 alpha (#10713)
|
||||
* chore: move to 18.0 alpha
|
||||
|
||||
## 17.0.334 stable 2025-01-27
|
||||
|
||||
* chore(linux): update copyright year (#12919)
|
||||
* chore(linux): Update debian changelog (#12916)
|
||||
* fix(android): Remove access to MEDIA storage permissions (#13005)
|
||||
* fix(android): restores DOMRect polyfill for old Chrome support (#13014)
|
||||
* fix(web): add null guard for focusing next element via keystroke (#13035)
|
||||
* fix: use correct path for resources/build/version (#13030)
|
||||
* fix(web): fix handling of fonts with single quotes in filename (#13041)
|
||||
* fix(android): disable early keyboard-loading when not feasible (#13039)
|
||||
* fix(web): do not simplify nul-prefixed contexts when rule-matching (#13040)
|
||||
* feat(android): add breadcrumbing during package install about package file, source (#13038)
|
||||
* fix(web): prevent multitap warning that occurs multitapping longpressable modifier keys (#12974)
|
||||
|
||||
## 17.0.333 stable 2025-01-16
|
||||
|
||||
* fix(core): permanently disable logging (#12674)
|
||||
* fix(linux): pushing of updated changelog branch (#12819)
|
||||
* fix(linux): work around Lintian errors (#12817)
|
||||
* fix(core): implement ldml_processor::get_key_list() (#12816)
|
||||
* chore(linux): Update debian changelog (#12022)
|
||||
* fix(android): use main looper to dispatch key events when OSK is hidden (#12875)
|
||||
* chore: use GitHub PR titles when writing HISTORY.md (#12908)
|
||||
* fix(developer): filter incorrect fonts out of .keyboard_info (#12913)
|
||||
|
||||
## 17.0.332 stable 2024-11-06
|
||||
|
||||
* fix(developer): create Server config directory before options save (#12609)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
18.0.170
|
||||
18.0.181
|
||||
|
|
@ -7,10 +7,6 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- API 33 and above -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<!-- Devices running up to Android 12L
|
||||
Starting in API level 33, this permission has no effect:
|
||||
https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE -->
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ public class CheckPermissions {
|
|||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
// API 33+
|
||||
//https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions
|
||||
// Manifest.permission.READ_MEDIA_AUDIO doesn't seem to be needed
|
||||
// We had to remove these MEDIA permissions from AndroidManifest.xml so these will end up failing
|
||||
// https://support.google.com/googleplay/android-developer/answer/14115180?hl=en
|
||||
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_IMAGES);
|
||||
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_VIDEO);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
|
||||
import com.keyman.engine.BaseActivity;
|
||||
import com.keyman.engine.KMManager;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.KMPLink;
|
||||
import com.keyman.engine.util.KMString;
|
||||
import com.keyman.engine.util.WebViewUtils;
|
||||
|
|
@ -105,6 +106,7 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
// Create intent with keyboard download link for KMAPro main activity to handle
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.setData(downloadURI);
|
||||
KMLog.LogBreadcrumb(TAG, "Successful keyboard search now triggering package install", true);
|
||||
startActivity(intent);
|
||||
|
||||
// Finish activity
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
if (loadingIntentUri != null) {
|
||||
String scheme = loadingIntentUri.getScheme().toLowerCase();
|
||||
final String link = loadingIntentUri.toString();
|
||||
switch (scheme) {
|
||||
// content:// Android DownloadManager
|
||||
// file:// Chrome downloads and Filebrowsers
|
||||
|
|
@ -269,6 +270,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
case "file":
|
||||
requestPermissionIntentUri = loadingIntentUri;
|
||||
if (CheckPermissions.isPermissionOK(this)) {
|
||||
KMLog.LogBreadcrumb(TAG, "Installing local KMP: " + link, true);
|
||||
useLocalKMP(context, loadingIntentUri);
|
||||
} else {
|
||||
CheckPermissions.requestPermission(this, context);
|
||||
|
|
@ -277,14 +279,16 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
case "http" :
|
||||
case "https" :
|
||||
// Might need to modify link like KMPBrowserActivity
|
||||
String link = loadingIntentUri.toString();
|
||||
if (KMPLink.isKeymanInstallLink(link)) {
|
||||
loadingIntentUri = KMPLink.getKeyboardDownloadLink(link);
|
||||
}
|
||||
// Is usually triggered by KMPBrowserActivity
|
||||
KMLog.LogBreadcrumb(TAG, "Installing via HTTPS intent: " + link, true);
|
||||
downloadKMP(loadingIntentUri, KmpInstallMode.Full);
|
||||
break;
|
||||
case "keyman" :
|
||||
// TODO: Only accept download links from Keyman browser activities when universal links work
|
||||
KMLog.LogBreadcrumb(TAG, "Installing via keyman-link intent: " + link, true);
|
||||
if (KMPLink.isKeymanDownloadLink(loadingIntentUri.toString())) {
|
||||
|
||||
// Convert opaque URI to hierarchical URI so the query parameters can be parsed
|
||||
|
|
@ -296,7 +300,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
loadingIntentUri = Uri.parse(builder.build().toString());
|
||||
downloadKMP(loadingIntentUri, KmpInstallMode.Full);
|
||||
} else if (KMPLink.isLegacyKeymanDownloadLink(loadingIntentUri.toString())) {
|
||||
link = loadingIntentUri.toString();
|
||||
loadingIntentUri = KMPLink.getLegacyKeyboardDownloadLink(link);
|
||||
downloadKMP(loadingIntentUri, KmpInstallMode.Full);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages"> Para instalar los paquetes del teclado, permite que Keyman acceda al almacenamiento externo.</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted"> Se ha denegado la solicitud de permiso de almacenamiento. La instalación del paquete de teclado podría fallar</string>
|
||||
<string name="storage_permission_denied2" comment="Keyboard package installation failed. Recommend install from local file"> Falló la solicitud de permiso de almacenamiento. Seleccione Configuración de Keyman - Instalar desde archivo local</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="keyman_settings" comment="Settings menu title">Configuración</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
|
|
@ -67,6 +68,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Ajustar la altura del teclado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Adjust longpress delay</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Leyenda de barra espaciadora</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">Teclado</string>
|
||||
|
|
@ -86,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">No mostrar leyenda en la barra espaciadora</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">Vibrar al escribir</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">Mostrar siempre el banner</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">A implementar</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">Cuando está desactivado, solo se muestra cuando el texto predictivo está habilitado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">Permitir el envío de informes de fallas a través de la red</string>
|
||||
|
|
@ -122,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Rota el dispositivo para ajustar el modo vertical y horizontal</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Restaurar valores predeterminados</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Delay Time: %1$.1f seconds</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">Delay time longer</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Delay time shorter</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Longpress delay time slider</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">Buscar o escribir una dirección web</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Ajustar altura do teclado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Ajustar atraso do toque longo</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Barra de espaço</string>
|
||||
<!-- Context: Keyman Settings menu / Spacebar caption -->
|
||||
<string name="spacebar_caption_keyboard" comment="Spacebar caption option - keyboard">Teclado</string>
|
||||
|
|
@ -87,11 +89,11 @@
|
|||
<string name="spacebar_caption_hint_blank" comment="Spacebar caption hint - blank">Não mostrar legenda na barra de espaço</string>
|
||||
<!-- Context: Keyman Settings menu / Haptic feedback -->
|
||||
<string name="haptic_feedback" comment="haptic feedback on key press">Vibrar ao tocar</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner" comment="text suggestions banner">Sempre mostrar banner</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">A ser implementado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<!-- Context: Keyman Settings menu. Removed in Keyman 18 -->
|
||||
<string name="show_banner_off" comment="Description when toggle is off">Quando desativado, apenas mostrado quando o texto preditivo é ativado</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_send_crash_report" comment="permission for sending crash reports">Permitir o envio de relatórios de erro pela rede</string>
|
||||
|
|
@ -123,6 +125,14 @@
|
|||
<string name="rotate_the_device" comment="Second line of adjusting keyboard height">Gire o dispositivo para ajustar o retrato e a paisagem</string>
|
||||
<!-- Context: Adjust Keyboard Height menu -->
|
||||
<string name="reset_to_defaults" comment="Button to reset to default heights">Restaurar padrões</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Tempo de atraso: %1$.1f segundos</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_up" comment="Make longpress delay longer">Tempo de atraso maior</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Tempo de atraso menor</string>
|
||||
<!-- Context: Adjust Longpress Delay Time menu -->
|
||||
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Atraso do tempo com toque longo</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
<string name="hint_text" comment="Prompt to type in the browser search bar">Pesquise ou digite um endereço</string>
|
||||
<!-- Context: Keyman Web Browser -->
|
||||
|
|
|
|||
|
|
@ -37,7 +37,20 @@ function init() {
|
|||
keyman.beepKeyboard = beepKeyboard;
|
||||
|
||||
// Readies the keyboard stub for instant loading during the init process.
|
||||
KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard()));
|
||||
try {
|
||||
let starterStubStr = jsInterface.initialKeyboard();
|
||||
if(starterStubStr) {
|
||||
KeymanWeb.registerStub(JSON.parse(starterStubStr));
|
||||
}
|
||||
} catch (err) {
|
||||
// If a Java exception triggers during this time... okay, we can't
|
||||
// load a keyboard during startup. Rely on older loading techniques.
|
||||
//
|
||||
// Note: no information about the Java-side error is available here.
|
||||
if(err.message.indexOf("Java exception") < 0) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
keyman.init({
|
||||
'embeddingApp':device,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class DisplayLanguages {
|
|||
new DisplayLanguageType("ann", "Obolo"),
|
||||
new DisplayLanguageType("pl-PL", "Polski (Polish)"),
|
||||
new DisplayLanguageType("el", "Polytonic Greek"),
|
||||
new DisplayLanguageType("pt-PT", "Português do Portugal"),
|
||||
new DisplayLanguageType("pt-PT", "Português de Portugal"),
|
||||
new DisplayLanguageType("ff-ZA", "Pulaar-Fulfulde"), // or Fulah
|
||||
new DisplayLanguageType("ru-RU", "Pyccĸий (Russian)"),
|
||||
new DisplayLanguageType("shu-latn", "Shuwa (Latin)"),
|
||||
|
|
|
|||
|
|
@ -75,7 +75,10 @@ public class KMKeyboardJSHandler {
|
|||
Keyboard kbd = Keyboard.getDefaultKeyboard(context);
|
||||
int index = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, -1);
|
||||
if (index >= 0) {
|
||||
kbd = KMManager.getKeyboardInfo(this.context, index);
|
||||
// It is possible for this method to return `null` - especially
|
||||
// if the KeyboardController instance hasn't yet been initialized.
|
||||
Keyboard lastKbd = KMManager.getKeyboardInfo(this.context, index);
|
||||
kbd = lastKbd != null ? lastKbd : kbd;
|
||||
}
|
||||
return kbd.toStub(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -921,6 +921,9 @@ public final class KMManager {
|
|||
copyAsset(context, KMFilename_KmwCss, "", true);
|
||||
copyAsset(context, KMFilename_KmwGlobeHintCss, "", true);
|
||||
copyAsset(context, KMFilename_Osk_Ttf_Font, "", true);
|
||||
|
||||
// Needed until our minimum version of Chrome is 61.0+.
|
||||
copyAsset(context, KMFilename_JSPolyfill2, "", true);
|
||||
|
||||
// Copy default keyboard font
|
||||
copyAsset(context, KMDefault_KeyboardFont, "", true);
|
||||
|
|
@ -2267,6 +2270,7 @@ public final class KMManager {
|
|||
|
||||
if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
}
|
||||
|
||||
i.putExtra(KMKey_DisplayKeyboardSwitcher, kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM);
|
||||
|
|
|
|||
|
|
@ -520,10 +520,6 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
@Override
|
||||
public void onPackageInstalled(List<Map<String, String>> keyboardsInstalled) {
|
||||
if (!isContextAvailable()) {
|
||||
KMLog.LogError(TAG, "onPackageInstalled where context is null.");
|
||||
}
|
||||
|
||||
if (! openUpdates.isEmpty() && keyboardsInstalled != null) {
|
||||
for (Map<String, String> k : keyboardsInstalled) {
|
||||
String _langid = k.get(KMManager.KMKey_LanguageID);
|
||||
|
|
@ -537,10 +533,6 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
@Override
|
||||
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
|
||||
if (!isContextAvailable()) {
|
||||
KMLog.LogError(TAG, "onLexicalModelInstalled where context is null.");
|
||||
}
|
||||
|
||||
if (! openUpdates.isEmpty()) {
|
||||
for(Map<String,String> _lm:lexicalModelsInstalled) {
|
||||
String _langid = _lm.get(KMManager.KMKey_LanguageID);
|
||||
|
|
@ -566,8 +558,9 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
// TODO: make it smoother. Documented as #11097.
|
||||
KMManager.clearKeyboardCache();
|
||||
|
||||
Context appContext = currentContext.getApplicationContext();
|
||||
if (failedUpdateCount > 0) {
|
||||
BaseActivity.makeToast(currentContext, R.string.update_failed, Toast.LENGTH_SHORT);
|
||||
BaseActivity.makeToast(appContext, R.string.update_failed, Toast.LENGTH_SHORT);
|
||||
lastUpdateCheck = Calendar.getInstance();
|
||||
updateFailed = true;
|
||||
checkingUpdates = false;
|
||||
|
|
|
|||
|
|
@ -447,6 +447,9 @@ public class PackageProcessor {
|
|||
public String getPackageTarget(File kmpPath) {
|
||||
try {
|
||||
File tempPath = unzipKMP(kmpPath);
|
||||
String kmpFilename = kmpPath.getName();
|
||||
|
||||
KMLog.LogBreadcrumb(TAG, "Determining package type for " + kmpFilename, false);
|
||||
String target = getPackageTarget(loadPackageInfo(tempPath));
|
||||
FileUtils.deleteDirectory(tempPath);
|
||||
return target;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
<string name="dictionary_download_start_in_background" comment="Notification that a dictionary download has started">La descarga del diccionario inició en segundo plano</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">El diccionario seleccionado ya se está descargando; inténtalo de nuevo en un momento.</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<!-- Context: Background download messages. Removed in Keyman 17-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">La descarga del diccionario ha finalizado.</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">Error al descargar</string>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
<string name="update_check_current" comment="Notification that all resources are up to date">"¡Todos los recursos están actualizados!"</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">¡Uno o más recursos no se han actualizado!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<!-- Context: General Updates. Removed in Keyman 17 -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">¡Recursos actualizados correctamente!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">Versión del diccionario</string>
|
||||
|
|
@ -133,10 +133,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">Teclado %1$s instalado</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">Teclado borrado</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">Habilitar correcciones</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Habilitar predicciones</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Desactivar sugerencias</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">Sólo predicciones</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">Predicciones con correcciones</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">Predicciones con autocorrecciones</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Diccionario</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -133,10 +133,15 @@
|
|||
<string name="keyboard_install_toast" comment="Notification when a keyboard is installed">Teclado %1$s instalado</string>
|
||||
<!-- Context: Language Settings Keyboard uninstall strings -->
|
||||
<string name="keyboard_deleted_toast" comment="Notification when a keyboard is deleted">Teclado eliminado</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_corrections" comment="Enable corrections from the suggestion banner">Habilitar correções</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<!-- Context: Language Settings menu strings. Removed in Keyman 18 -->
|
||||
<string name="enable_predictions" comment="Enable predictions from the suggestion banner">Habilitar previsões</string>
|
||||
<!-- Context: Language Settings menu strings - radio buttons -->
|
||||
<string name="suggestions_radio_0" comment="Predictions and corrections disabled">Disable suggestions</string>
|
||||
<string name="suggestions_radio_1" comment="Suggestions Enabled: Predictions with no corrections">Predictions only</string>
|
||||
<string name="suggestions_radio_2" comment="Suggestions Enabled: Predictions with corrections">Predictions with corrections</string>
|
||||
<string name="suggestions_radio_3" comment="Suggestions Enabled: Predictions with auto-corrections">Predictions with auto-corrections</string>
|
||||
<!-- Context: Language Settings menu strings -->
|
||||
<string name="model_label">Dicionário</string>
|
||||
<plurals name="model_count">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: What's New
|
||||
title: What's New in Keyman Engine 18.0 for Android
|
||||
---
|
||||
|
||||
## Keyman Engine for Android Breaking Changes: ##
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Version History
|
||||
title: Version History of Keyman for Android
|
||||
---
|
||||
|
||||
A frequent updated changelog for Keyman is kept at [help.keyman.com/version-history](https://help.keyman.com/version-history/) on the Keyman Help website.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Welcome to Keyman
|
||||
title: Welcome to Keyman for Android
|
||||
---
|
||||
|
||||
Thank you for installing Keyman for Android. Whether you're a new or returning user, we believe you will appreciate the significant enhancements in this latest version of Keyman for Android.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: What's New
|
||||
title: What's New in Keyman 18.0 for Android
|
||||
---
|
||||
|
||||
Here are some of the new features we have added to Keyman 18.0 for Android:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: How To - Download and Install a Keyman Keyboard
|
||||
title: How To - Download and Install a Keyman Keyboard on Android
|
||||
---
|
||||
|
||||
## Adding New Keyboards
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ permission to access storage / "file and media". The screenshot below is from An
|
|||
|
||||
### Android 13.0+ Devices
|
||||
|
||||
Step 1)
|
||||
Go to Android Settings.
|
||||
Due to Google Play Store's "Photo and Video Permissions policy",
|
||||
Keyman for Android must use a system picker to browse for .kmp files from within the Keyman app.
|
||||
|
||||
Step 2)
|
||||
Depending on your device, click "Apps" and grant Keyman permission to the following:
|
||||
* Photos and videos --> Always allow all
|
||||
|
||||
The screenshot below is from Android 14.0.
|
||||
|
||||

|
||||
Follow the steps in [Installing Custom Keyboards/Dictionaries](../basic/installing-custom-packages).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Troubleshooting
|
||||
title: Keyman for Android Troubleshooting
|
||||
---
|
||||
|
||||
* [How To - Getting Keyboard Help](keyboard-help)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ export class ElemElement {
|
|||
order: number; // -128 to +127; used only by reorder element values
|
||||
tertiary: number; // -128 to +127; used only by reorder element values
|
||||
flags: ElemElementFlags;
|
||||
isEqual(a: ElemElement) {
|
||||
return a.value === this.value &&
|
||||
isEqual(a: ElemElement): boolean {
|
||||
return a.value.isEqual(this.value) &&
|
||||
a.order === this.order &&
|
||||
a.tertiary === this.tertiary &&
|
||||
a.flags === this.flags;
|
||||
|
|
@ -104,8 +104,8 @@ export class ElementString extends Array<ElemElement> {
|
|||
typeFlag |= constants.elem_flags_type_str;
|
||||
}
|
||||
}
|
||||
elem.order = orders.length ? parseInt(orders[i], 10) : 0;
|
||||
elem.tertiary = tertiaries.length ? parseInt(tertiaries[i], 10) : 0;
|
||||
elem.order = orders.length ? this.parseIntOrZero(orders[i]) : 0;
|
||||
elem.tertiary = tertiaries.length ? this.parseIntOrZero(tertiaries[i]) : 0;
|
||||
elem.flags = ElemElementFlags.none |
|
||||
(ElemElementFlags.type & typeFlag) |
|
||||
(tertiary_bases?.[i] == '1' /* TODO-LDML: or 'true'? */ ? ElemElementFlags.tertiary_base : 0) |
|
||||
|
|
@ -125,5 +125,9 @@ export class ElementString extends Array<ElemElement> {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
private static parseIntOrZero(str: string): number {
|
||||
const num = parseInt(str, 10);
|
||||
return !Number.isNaN(num) ? num : 0;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
|
|||
|
|
@ -131,6 +131,10 @@ export class StrsItem {
|
|||
get isOneChar() {
|
||||
return this.char !== undefined;
|
||||
}
|
||||
|
||||
isEqual(a: StrsItem): boolean {
|
||||
return a.value === this.value && a.char === this.char;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
498
common/web/types/tests/kmx/kmx-plus/element-string.tests.ts
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by Dr Mark C. Sinclair on 2024-12-09
|
||||
*
|
||||
* Test code for element-string.ts
|
||||
*/
|
||||
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { ElemElementFlags, ElemElement, ElementString } from '../../../src/kmx/kmx-plus/element-string.js';
|
||||
import { StrsItem, UsetItem, Strs, StrsOptions, DependencySections, CharStrsItem, Uset } from '../../../src/kmx/kmx-plus/kmx-plus.js';
|
||||
import { UnicodeSet, UnicodeSetParser } from '../../../src/ldml-keyboard/unicodeset-parser-api.js';
|
||||
import { ElementParser, ElementSegment, ElementType } from '../../../src/ldml-keyboard/pattern-parser.js';
|
||||
|
||||
const GOTHIC_A = new StrsItem("𐌰", 0x10330);
|
||||
const GOTHIC_B = new StrsItem("𐌱", 0x10331);
|
||||
const GOTHIC_C = new StrsItem("𐌲", 0x10332);
|
||||
const GOTHIC_D = new StrsItem("𐌳", 0x10333);
|
||||
const HI_GOTHIC_A = new StrsItem('\ud800', 0xd800);
|
||||
const LO_GOTHIC_A = new StrsItem('\udf30', 0xdf30);
|
||||
const GOTHIC_PATTERN = "[𐌰-𐍊]";
|
||||
const GOTHIC_STRSITEM = new StrsItem(GOTHIC_PATTERN);
|
||||
const GOTHIC_SET = new UnicodeSet(GOTHIC_PATTERN, [[0x10330,0x1034A]]);
|
||||
const GOTHIC_USETITEM = new UsetItem(GOTHIC_SET, GOTHIC_STRSITEM);
|
||||
const UGARITIC_PATTERN = "[𐎀-𐎟]";
|
||||
const UGARITIC_STRSITEM = new StrsItem(UGARITIC_PATTERN);
|
||||
const UGARITIC_SET = new UnicodeSet(UGARITIC_PATTERN, [[0x10380,0x1039F]]);
|
||||
const UGARITIC_USETITEM = new UsetItem(UGARITIC_SET, UGARITIC_STRSITEM);
|
||||
|
||||
class TestUnicodeSetParser implements UnicodeSetParser {
|
||||
parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => GOTHIC_SET;
|
||||
sizeUnicodeSet = (pattern: string) : number => 1;
|
||||
};
|
||||
|
||||
const origElementParserSegment = ElementParser.segment;
|
||||
let sections: DependencySections = null;
|
||||
|
||||
describe('Test of ElementString file', () => {
|
||||
describe('Test of ElemElement', () => {
|
||||
describe('Test of isEqual()', () => {
|
||||
it('returns true when elems identical', () => {
|
||||
const one = initElemElement();
|
||||
const two = initElemElement();
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns true when elems are clones', () => {
|
||||
const one = initElemElement(new StrsItem("𐌰", 0x10330));
|
||||
const two = initElemElement(new StrsItem("𐌰", 0x10330));
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns false when value differs', () => {
|
||||
const one = initElemElement(GOTHIC_A);
|
||||
const two = initElemElement(GOTHIC_B);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when order differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 1);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when tertiary differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 1);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when flags differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.none);
|
||||
const two = initElemElement(GOTHIC_A, GOTHIC_USETITEM, 0, 0, ElemElementFlags.type);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns true even though uset differs', () => {
|
||||
const one = initElemElement(GOTHIC_A, GOTHIC_USETITEM);
|
||||
const two = initElemElement(GOTHIC_A, UGARITIC_USETITEM);
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Test of ElementString class', () => {
|
||||
beforeEach(() => {
|
||||
sections = {
|
||||
strs: new Strs(),
|
||||
uset: new Uset(),
|
||||
usetparser: new TestUnicodeSetParser(),
|
||||
};
|
||||
ElementParser.segment = stubElementParserSegment_CodePoint;
|
||||
});
|
||||
afterEach(() => {
|
||||
ElementParser.segment = origElementParserSegment;
|
||||
});
|
||||
describe('Test of fromStrings()', () => {
|
||||
it('returns an empty ElementString if source is null', () => {
|
||||
const es = ElementString.fromStrings({}, null);
|
||||
assert.deepEqual(es, new ElementString());
|
||||
});
|
||||
it('can create an ElementString from a string array', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰", "𐌱", "𐌲"]);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can create an ElementString from a string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, "𐌰𐌱𐌲");
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply order string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single order to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 1),
|
||||
initElemElement(GOTHIC_C, undefined, 1),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle order string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle non-number in order string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
"1 A 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0),
|
||||
initElemElement(GOTHIC_C, undefined, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply tertiary string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single tertiary to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 1),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle tertiary string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 2),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle non-number in tertiary string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 A 3",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 1),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 3),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply tertiary_base string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1 0 1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single tertiary_base to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle tertiary_base string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
"1 0",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.tertiary_base),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply prebase string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1 0 1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can apply single prebase to all', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle prebase string that is too short', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"1 0",
|
||||
);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A, undefined, 0, 0, ElemElementFlags.prebase),
|
||||
initElemElement(GOTHIC_B, undefined, 0, 0, ElemElementFlags.none),
|
||||
initElemElement(GOTHIC_C, undefined, 0, 0, ElemElementFlags.none),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can create an ElementString from a uset string', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
sections.uset.allocUset = stubUsetAllocUset;
|
||||
const actual = ElementString.fromStrings(sections, "[𐌰-𐍊]");
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem(''),
|
||||
GOTHIC_USETITEM,
|
||||
0,
|
||||
0,
|
||||
constants.elem_flags_type_uset,
|
||||
),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('returns null for an invalid unicode set size', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.sizeUnicodeSet = (pattern: string) : number => -1;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('returns null if it cannot parse the unicode set', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => null;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('can handle quad strings', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
ElementParser.segment = stubElementParserSegment_Escaped;
|
||||
const actual = ElementString.fromStrings(sections, "\\ud800\\udf30");
|
||||
const expected = [
|
||||
initElemElement(HI_GOTHIC_A),
|
||||
initElemElement(LO_GOTHIC_A),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
it('can handle ElemElement of string type', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰𐌱𐌲",]);
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem("𐌰𐌱𐌲"),
|
||||
undefined,
|
||||
0,
|
||||
0,
|
||||
constants.elem_flags_type_str,
|
||||
),
|
||||
];
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
describe('Test of isEqual()', () => {
|
||||
it('returns true when ElementStrings are identical', () => {
|
||||
const es = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
assert.isTrue(es.isEqual(es));
|
||||
});
|
||||
it('returns true when ElementStrings are clones', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
assert.isTrue(one.isEqual(two));
|
||||
});
|
||||
it('returns false when ElementStrings are different lengths', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
]);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
it('returns false when ElementStrings have different ElemElements', () => {
|
||||
const one = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_C),
|
||||
]);
|
||||
const two = initElementString([
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
initElemElement(GOTHIC_D),
|
||||
]);
|
||||
assert.isFalse(one.isEqual(two));
|
||||
});
|
||||
});
|
||||
describe('Test of parseIntOrZero()', () => {
|
||||
it('returns a number for a valid string', () => {
|
||||
const num = ElementString['parseIntOrZero']('1');
|
||||
assert.equal(num, 1);
|
||||
});
|
||||
it('returns zero for an invalid string', () => {
|
||||
const num = ElementString['parseIntOrZero']('A');
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
it('returns zero for undefined', () => {
|
||||
const num = ElementString['parseIntOrZero'](undefined);
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
it('returns zero for a null string', () => {
|
||||
const num = ElementString['parseIntOrZero'](null);
|
||||
assert.equal(num, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function initElemElement(
|
||||
value: StrsItem = GOTHIC_A,
|
||||
uset: UsetItem = undefined,
|
||||
order: number = 0,
|
||||
tertiary: number = 0,
|
||||
flags: ElemElementFlags = ElemElementFlags.none,
|
||||
): ElemElement {
|
||||
const ee = new ElemElement();
|
||||
ee.value = value;
|
||||
ee.uset = uset;
|
||||
ee.order = order;
|
||||
ee.tertiary = tertiary;
|
||||
ee.flags = flags;
|
||||
return ee;
|
||||
};
|
||||
|
||||
function initElementString(elemElements: ElemElement[]): ElementString {
|
||||
const es: ElementString = new ElementString();
|
||||
elemElements.forEach((ee) => {es.push(ee)});
|
||||
return es;
|
||||
};
|
||||
|
||||
function stubStrsAllocString_Char(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
|
||||
return new CharStrsItem(s);
|
||||
};
|
||||
|
||||
function stubStrsAllocString_Str(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem {
|
||||
return new StrsItem(s);
|
||||
};
|
||||
|
||||
function stubElementParserSegment_CodePoint(str: string): ElementSegment[] {
|
||||
return [...str].map(s => new ElementSegment(s, ElementType.codepoint));
|
||||
};
|
||||
|
||||
function stubElementParserSegment_Uset(str: string): ElementSegment[] {
|
||||
return [new ElementSegment(str, ElementType.uset)];
|
||||
};
|
||||
|
||||
function stubElementParserSegment_Escaped(str: string): ElementSegment[] {
|
||||
const strs = str.match(/\\u[0-9a-fA-F]{4}/g);
|
||||
return strs.map((s) => new ElementSegment(s, ElementType.escaped));
|
||||
};
|
||||
|
||||
function stubUsetAllocUset(set: UnicodeSet, sections: DependencySections) : UsetItem {
|
||||
return new UsetItem(set, new StrsItem(set.pattern));
|
||||
};
|
||||
|
||||
|
|
@ -230,7 +230,8 @@ type
|
|||
ssPlatform = 31, ssBaseLayout = 32, ssLayer = 33, ssVKDictionary = 34, ssLayoutFile = 35, // I3438 // I3483
|
||||
ssKeyboardVersion = 36, ssKMW_EmbedCSS = 37, ssTargets = 38,
|
||||
ssCasedKeys = 39,
|
||||
ssBegin_NewContext = 40, ssBegin_PostKeystroke = 41, ssNewLayer = 42, ssOldLayer = 43); // I4140 // I4368 // I4504
|
||||
ssBegin_NewContext = 40, ssBegin_PostKeystroke = 41, ssNewLayer = 42, ssOldLayer = 43, // I4140 // I4368 // I4504
|
||||
ssDisplayMap = 44);
|
||||
|
||||
const
|
||||
SystemStoreNames: array[TSystemStore] of WideString = (
|
||||
|
|
@ -245,7 +246,8 @@ const
|
|||
'', '', '', '', 'LAYOUTFILE', 'KEYBOARDVERSION', 'KMW_EMBEDCSS',
|
||||
'TARGETS', //9.0 // I3483 // I4140 // I4368 // I4504
|
||||
'CASEDKEYS', //14.0,
|
||||
'', '', 'NEWLAYER', 'OLDLAYER'); //15.0
|
||||
'', '', 'NEWLAYER', 'OLDLAYER', //15.0
|
||||
'DISPLAYMAP'); //17.0
|
||||
|
||||
implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@ the specification itself.
|
|||
There are three ways - all of them optional - to extend and customize
|
||||
the word-breaking rules themselves:
|
||||
|
||||
* If you need to prevent splits in very specific scenarios and/or add splits in other specific scenarios, you may specify [context-based rules](#rules) to obtain the desired behavior.
|
||||
* If you need to prevent splits in very specific scenarios and/or add splits in other specific scenarios, you may specify [context-based rules](#toc-custom-word-breaking-rules) to obtain the desired behavior.
|
||||
* If certain characters are not handled appropriately for their role in
|
||||
your language, you may [map characters](#map) to different
|
||||
your language, you may [map characters](#toc-character-property-remapping) to different
|
||||
word-breaking character classes - including custom ones. This will
|
||||
override the default property they are assigned by the default
|
||||
implementation, with the new property applying for all word-breaking
|
||||
rules.
|
||||
* If the default word-breaking classes from the specification are
|
||||
too general for certain aspects of your language, it is possible to
|
||||
[define custom character classes](#define) for use in custom
|
||||
[define custom character classes](#toc-defining-and-using-new-word-breaking-properties) for use in custom
|
||||
rules.
|
||||
|
||||
## A first example
|
||||
|
|
@ -183,7 +183,7 @@ the potential boundary.
|
|||
### Word-breaking property names
|
||||
The names used in each array must be defined in one of the following places:
|
||||
* https://unicode.org/reports/tr29/#Table_Word_Break_Property_Values
|
||||
* `customProperties` - your [declaration of any custom property types](#define)
|
||||
* `customProperties` - your [declaration of any custom property types](#toc-defining-and-using-new-word-breaking-properties)
|
||||
* One of the special property types `"Other"`, `"sot"`, or `"eot"`:
|
||||
* `Other`: a character without an associated word-breaking property value
|
||||
* `sot`: "start of text" - a marker indicating the beginning of the string being word-broken
|
||||
|
|
@ -277,7 +277,7 @@ As noted at the top of the file:
|
|||
|
||||
### Redefining character properties
|
||||
|
||||
Of note from [our first example](#example):
|
||||
Of note from [our first example](#toc-a-first-example):
|
||||
|
||||
```typescript
|
||||
/*** Character class overrides for specific characters ***/
|
||||
|
|
@ -324,7 +324,7 @@ words (and/or names) in some languages. Default word-breaking behavior will spl
|
|||
hyphenated words and names apart, but by changing the property of hyphens, it is
|
||||
possible to disable this behavior.
|
||||
|
||||
Noting [rule WB6](#WB6) and WB7, the `MidLetter` class is designed to prevent
|
||||
Noting [rule WB6](#toc-custom-word-breaking-rules) and WB7, the `MidLetter` class is designed to prevent
|
||||
word-breaks from occurring when its characters lie directly between letters -
|
||||
hence the property name. Assigning hyphens to this class can provide the
|
||||
desired behavior.
|
||||
|
|
@ -364,7 +364,7 @@ the second example above. (After all, `can'` could be the end of a quoted
|
|||
phrase in English - `'sure you can'` - in which case we might want the split
|
||||
to occur.)
|
||||
|
||||
Revisiting [an earlier example](#example) and simplifying a little bit:
|
||||
Revisiting [an earlier example](#toc-redefining-character-properties) and simplifying a little bit:
|
||||
|
||||
```typescript
|
||||
/*** Definition of extra word-breaking rules ***/
|
||||
|
|
@ -440,7 +440,7 @@ included a couple of extra rules:
|
|||
}
|
||||
```
|
||||
|
||||
By replicating [WB6](#WB6) and WB7's structure and allowing `Hyphen` to match in the same
|
||||
By replicating [WB6](#toc-custom-word-breaking-rules) and WB7's structure and allowing `Hyphen` to match in the same
|
||||
position as `MidLetter` in the original rules, we can prevent word-breaking splits
|
||||
after additional text has been typed after a `Hyphen`-property character. This does not
|
||||
_replace_ the behavior of WB6 and WB7 - it merely _extends_ it to include the new property.
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ However, in languages written in other scripts — especially East Asian
|
|||
scripts like Chinese, Japanese, Khmer, Lao, and Thai — there are no obvious break in between words. For these languages, there must be special rules for determining when words start and stop. This is what a _word breaker function_ is responsible for. It is a little bit of code that looks at some text to determine where the words are.
|
||||
|
||||
You can customize the word breaker in three ways:
|
||||
- If your language uses its writing system in an unconventional way (e.g., use spaces to separate words in Thai, Lao, Burmese, or Khmer), you can [override the script's default behaviour](#overrides)
|
||||
- If the default word breaker creates **too many splits**, you can [choose which strings join words together](#join).
|
||||
- If the default word breaker creates **not enough splits**, you must [create your own word breaker function](#custom).
|
||||
- If your language uses its writing system in an unconventional way (e.g., use spaces to separate words in Thai, Lao, Burmese, or Khmer), you can [override the script's default behaviour](#toc-overriding-script-defaults)
|
||||
- If the default word breaker creates **too many splits**, you can [choose which strings join words together](#toc-customize-joining-rules).
|
||||
- If the default word breaker creates **not enough splits**, you must [create your own word breaker function](#toc-writing-a-custom-word-breaker-function).
|
||||
- Alternatively, you may choose to [customize and extend the wordbreaker's behavior](./unicode-breaker-extension) by adding extra rules and changing how it treats specific characters.
|
||||
|
||||
## Overriding script defaults
|
||||
|
|
|
|||
|
|
@ -38,25 +38,25 @@ The kmp.json file is a base object described as
|
|||
|
||||
: `Object`
|
||||
|
||||
[`System`](#obj-system) object.
|
||||
[`System`](#toc-the-system-object) object.
|
||||
|
||||
`options`
|
||||
|
||||
: `Object`
|
||||
|
||||
An [`Options`](#obj-options) object.
|
||||
An [`Options`](#toc-the-options-object) object.
|
||||
|
||||
`startMenu`
|
||||
|
||||
: `Object`
|
||||
|
||||
[`Start Menu`](#obj-startMenu) object.
|
||||
[`Start Menu`](#toc-the-start-menu-object) object.
|
||||
|
||||
`info`
|
||||
|
||||
: `Object`
|
||||
|
||||
[`Info`](#obj-info) object.
|
||||
[`Info`](#toc-the-info-object) object.
|
||||
|
||||
`files`
|
||||
|
||||
|
|
@ -69,13 +69,13 @@ The kmp.json file is a base object described as
|
|||
|
||||
: `Object`
|
||||
|
||||
Array of [`Keyboard`](#obj-keyboard) objects.
|
||||
Array of [`Keyboard`](#toc-the-keyboard-object) objects.
|
||||
|
||||
`lexicalModels`
|
||||
|
||||
: `Object`
|
||||
|
||||
Array of [`LexicalModel`](#obj-lexicalModel) objects.
|
||||
Array of [`LexicalModel`](#toc-the-lexicalmodel-object) objects.
|
||||
|
||||
### The System object
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ The `StartMenu` object is used by Keyman Desktop to install windows
|
|||
|
||||
: `Array`
|
||||
|
||||
An array of [Item](#obj-item) objects
|
||||
An array of [Item](#toc-the-item-object) objects
|
||||
|
||||
### The Item Object
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ The `Keyboard` object describes an individual keyboard in the Keyman package. A
|
|||
|
||||
: `Array`
|
||||
|
||||
An array of [`Language`](#obj-language) objects linked to the keyboard.
|
||||
An array of [`Language`](#toc-the-language-object) objects linked to the keyboard.
|
||||
|
||||
`displayFont`
|
||||
|
||||
|
|
@ -247,7 +247,7 @@ The `Keyboard` object describes an individual keyboard in the Keyman package. A
|
|||
|
||||
: `Array`
|
||||
|
||||
An array of [`Example`](#obj-example) objects linked to the keyboard.
|
||||
An array of [`Example`](#toc-the-example-object) objects linked to the keyboard.
|
||||
|
||||
### The Language object
|
||||
|
||||
|
|
@ -342,4 +342,4 @@ The `LexicalModel` object describes an individual model in the Keyman package. A
|
|||
|
||||
: `Array`
|
||||
|
||||
An array of [`Language`](#obj-language) objects linked to the model.
|
||||
An array of [`Language`](#toc-the-language-object) objects linked to the model.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Details:
|
|||
Spreadsheet programs such as Microsoft Excel and Google Sheets can
|
||||
export into TSV format. TSVs can also be programmatically generated
|
||||
from other data sources. For advanced users, see [File
|
||||
Format](#file-format) for more details.
|
||||
Format](#toc-file-format) for more details.
|
||||
|
||||
Distributed with lexical model:
|
||||
: No. This is a development file and should not be distributed.
|
||||
|
|
|
|||
|
|
@ -367,10 +367,13 @@ and the files in that folder will follow the
|
|||
|
||||
: The name of keyboard author (default: blank)
|
||||
|
||||
<!-- TODO-GENERATE: uncomment once supported
|
||||
|
||||
`-i, --icon`
|
||||
|
||||
: Include a generated icon. The icon will be a 16x16 pixel box with the first
|
||||
letters of the first language tag (default: true, include an icon)
|
||||
-->
|
||||
|
||||
`-d, --description <description>`
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ unit KeyboardFonts;
|
|||
interface
|
||||
|
||||
type
|
||||
TKeyboardFont = (kfontCode, kfontChar, kfontOSK, kfontTouchLayoutPhone, kfontTouchLayoutTablet, kfontTouchLayoutDesktop);
|
||||
TKeyboardFont = (kfontCode, kfontChar, kfontOSK, kfontTouchLayoutPhone, kfontTouchLayoutTablet, kfontTouchLayoutDesktop,
|
||||
kfontDisplayMap);
|
||||
|
||||
TKeyboardFontArray = array[TKeyboardFont] of string; // I4409
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ type
|
|||
|
||||
const
|
||||
KeyboardFontId: array[TKeyboardFont] of string = (
|
||||
'Code', 'Char', 'Osk', 'TouchLayoutPhone', 'TouchLayoutTablet', 'TouchLayoutDesktop'
|
||||
'Code', 'Char', 'Osk', 'TouchLayoutPhone', 'TouchLayoutTablet', 'TouchLayoutDesktop', 'DisplayMap'
|
||||
);
|
||||
implementation
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,13 @@
|
|||
namespace CompilerErrorSeverity {
|
||||
// We use a namespace to stop the enum names leaking out into global namespace scope
|
||||
enum {
|
||||
Info = 0x000000, // Informational, not necessarily a problem
|
||||
Hint = 0x100000, // Something the user might want to be aware of
|
||||
Warn = 0x200000, // Warning: Not great, but we can keep going.
|
||||
Error = 0x300000, // Severe error where we can't continue
|
||||
Fatal = 0x400000, // OOM or should-not-happen internal problem
|
||||
Debug = 0x000000, // log everything including internal debug
|
||||
Verbose = 0x100000, // log everything, except debug
|
||||
Info = 0x200000, // Informational, not necessarily a problem
|
||||
Hint = 0x300000, // Something the user might want to be aware of
|
||||
Warn = 0x400000, // Warning: Not great, but we can keep going.
|
||||
Error = 0x500000, // Severe error where we can't continue
|
||||
Fatal = 0x600000, // OOM or should-not-happen internal problem
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks,
|
||||
CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks,
|
||||
CompilerFileSystemAsyncCallbacks } from '@keymanapp/developer-utils';
|
||||
CompilerFileSystemAsyncCallbacks,
|
||||
CompilerErrorSeverity} from '@keymanapp/developer-utils';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const { TEST_SAVE_FIXTURES } = process.env;
|
||||
|
|
@ -27,6 +28,10 @@ export class TestCompilerCallbacks implements CompilerCallbacks {
|
|||
this.messages = [];
|
||||
}
|
||||
|
||||
filteredMessages(severity: CompilerErrorSeverity = CompilerErrorSeverity.Info) {
|
||||
return this.messages.filter(m => CompilerError.severity(m.code) >= severity);
|
||||
}
|
||||
|
||||
printMessages() {
|
||||
if(this.messages.length) {
|
||||
process.stdout.write(CompilerError.formatEvent(this.messages));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function verifyCompilerMessagesObject(source: Record<string,any>, namespa
|
|||
// Verify each object member matches the pattern we expect
|
||||
|
||||
if(typeof m[key] == 'function') {
|
||||
const o = /^(Info|Hint|Warn|Error|Fatal)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
const o = /^(Debug|Verbose|Info|Hint|Warn|Error|Fatal)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
expect(o).to.be.instanceOf(Array, `Expected member ${key} to be a valid message function name`);
|
||||
|
||||
const c = o[1].toUpperCase() + '_' + o[2];
|
||||
|
|
@ -41,7 +41,7 @@ export function verifyCompilerMessagesObject(source: Record<string,any>, namespa
|
|||
expect(v.code).to.equal(m[c], `Function ${key} returns the wrong code`);
|
||||
}
|
||||
else if(typeof m[key] == 'number') {
|
||||
const o = /^(INFO|HINT|WARN|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
const o = /^(DEBUG|VERBOSE|INFO|HINT|WARN|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
expect(o).to.be.instanceOf(Array);
|
||||
|
||||
const f = toTitleCase(o[1]) + '_' + o[2];
|
||||
|
|
@ -53,7 +53,7 @@ export function verifyCompilerMessagesObject(source: Record<string,any>, namespa
|
|||
// Verify severify masks
|
||||
|
||||
if(typeof m[key] == 'number') {
|
||||
const o = /^(INFO|HINT|WARN|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
const o = /^(DEBUG|VERBOSE|INFO|HINT|WARN|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key);
|
||||
expect(o).to.be.instanceOf(Array);
|
||||
|
||||
const mask = CompilerError.formatSeverity(m[key]).toUpperCase();
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ export interface CompilerEvent {
|
|||
};
|
||||
|
||||
export enum CompilerErrorSeverity {
|
||||
Info = 0x000000, // Informational, not necessarily a problem
|
||||
Hint = 0x100000, // Something the user might want to be aware of
|
||||
Warn = 0x200000, // Warning: Not great, but we can keep going.
|
||||
Error = 0x300000, // Severe error where we can't continue
|
||||
Fatal = 0x400000, // OOM or should-not-happen internal problem
|
||||
Debug = 0x000000, // log everything including internal debug
|
||||
Verbose = 0x100000, // log everything, except debug
|
||||
Info = 0x200000, // Informational, not necessarily a problem
|
||||
Hint = 0x300000, // Something the user might want to be aware of
|
||||
Warn = 0x400000, // Warning: Not great, but we can keep going.
|
||||
Error = 0x500000, // Severe error where we can't continue
|
||||
Fatal = 0x600000, // OOM or should-not-happen internal problem
|
||||
};
|
||||
|
||||
export const CompilerErrorSeverityValues = [
|
||||
CompilerErrorSeverity.Debug,
|
||||
CompilerErrorSeverity.Verbose,
|
||||
CompilerErrorSeverity.Info,
|
||||
CompilerErrorSeverity.Hint,
|
||||
CompilerErrorSeverity.Warn,
|
||||
|
|
@ -48,6 +52,8 @@ export enum CompilerErrorMask {
|
|||
};
|
||||
|
||||
const errorSeverityName = {
|
||||
[CompilerErrorSeverity.Debug]: 'debug',
|
||||
[CompilerErrorSeverity.Verbose]: 'verbose',
|
||||
[CompilerErrorSeverity.Info]: 'info',
|
||||
[CompilerErrorSeverity.Hint]: 'hint',
|
||||
[CompilerErrorSeverity.Warn]: 'warn',
|
||||
|
|
@ -419,8 +425,9 @@ export const ALL_COMPILER_LOG_LEVELS = [
|
|||
'error', /// Only errors emitted
|
||||
'warn', /// Errors + warnings
|
||||
'hint', /// Errors + warnings + hints
|
||||
'info', /// All messages: errors + warnings + hints + info
|
||||
'debug' /// All messages: errors + warnings + hints + info, plus debug logs
|
||||
'info', /// All normal messages: errors + warnings + hints + info
|
||||
'verbose', /// All messages + verbose logging
|
||||
'debug', /// All messages + verbose + internal debug
|
||||
] as const;
|
||||
|
||||
type CompilerLogLevelTuple = typeof ALL_COMPILER_LOG_LEVELS;
|
||||
|
|
@ -432,7 +439,8 @@ export const compilerLogLevelToSeverity: {[index in CompilerLogLevel]: number} =
|
|||
'warn': CompilerErrorSeverity.Warn,
|
||||
'hint': CompilerErrorSeverity.Hint,
|
||||
'info': CompilerErrorSeverity.Info,
|
||||
'debug': CompilerErrorSeverity.Info
|
||||
'verbose': CompilerErrorSeverity.Verbose,
|
||||
'debug': CompilerErrorSeverity.Debug,
|
||||
};
|
||||
|
||||
export const ALL_COMPILER_LOG_FORMATS = [
|
||||
|
|
|
|||
|
|
@ -24,5 +24,32 @@ export async function getFontFamily(source: Uint8Array) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return font.meta.property.find(prop => prop.name == 'font-family')?.text ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the font-family from an in-memory TTF or WOFF blob in `source`
|
||||
* parameter.
|
||||
*
|
||||
* @param source In-memory TTF or WOFF font blob
|
||||
*
|
||||
* @throws Uncaught exceptions from ttfMeta.promise if the font file is invalid.
|
||||
*
|
||||
* @returns If the file is invalid or cannot be parsed, returns `null`,
|
||||
* otherwise returns the font family as a string.
|
||||
*/
|
||||
export function getFontFamilySync(source: Uint8Array) {
|
||||
/* c8 ignore next 3 */
|
||||
if(!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(source);
|
||||
const font = ttfMeta.ttfInfoSync(buffer);
|
||||
/* c8 ignore next 3 */
|
||||
if(!font) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return font.meta.property.find(prop => prop.name == 'font-family')?.text ?? null;
|
||||
}
|
||||
|
|
@ -68,4 +68,8 @@ export * as SourceFilenamePatterns from './source-filename-patterns.js';
|
|||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js';
|
||||
|
||||
export * as GitHubUrls from './github-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
|
||||
export { getFontFamily, getFontFamilySync } from './font-family.js';
|
||||
|
||||
export * as ValidIds from './valid-ids.js';
|
||||
|
|
|
|||
|
|
@ -31,6 +31,20 @@ export function ttfInfo(pathOrData, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
* @param {string | number | Buffer | URL} pathOrData
|
||||
* @returns {typeof ttf.result}
|
||||
*/
|
||||
export function ttfInfoSync(pathOrData) {
|
||||
if (pathOrData instanceof Buffer) {
|
||||
return ttf.ttfInfoSync(ttf.view(pathOrData));
|
||||
} else {
|
||||
const data = fs.readFileSync(pathOrData);
|
||||
return ttf.ttfInfoSync(ttf.view(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number | Buffer | URL} pathOrData
|
||||
* @returns {Promise<typeof ttf.result>}
|
||||
|
|
@ -48,5 +62,5 @@ export function promise(pathOrData) {
|
|||
}
|
||||
|
||||
/** @namespace */
|
||||
export const ttfMeta = { ttfInfo, promise };
|
||||
export const ttfMeta = { ttfInfo, ttfInfoSync, promise };
|
||||
export default ttfMeta;
|
||||
|
|
@ -300,6 +300,16 @@ export function ttfInfo(data, callback) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
* @param {*} data
|
||||
* @returns {typeof result}
|
||||
*/
|
||||
export function ttfInfoSync(data) {
|
||||
resultTables(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number | Buffer | URL | DataView} pathOrData
|
||||
* @returns {Promise<typeof result>}
|
||||
38
developer/src/common/web/utils/src/valid-ids.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for a Keyman .kmn
|
||||
* keyboard: starts with alpha or underscore and remainder is alphanumeric
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidKeymanKeyboardId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for an LDML xml
|
||||
* keyboard: starts with alpha or underscore and remainder is alphanumeric.
|
||||
* Note that this test is not the same as the CLDR test for valid LDML xml
|
||||
* filename, as this test has a narrower validity policy to match the Keyman
|
||||
* implementation requirements.
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidLdmlKeyboardId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that an id passed in is valid as an identifier for a Keyman
|
||||
* lexical model: starts with alpha or underscore and remainder is alphanumeric,
|
||||
* in three components -- author.bcp47.uniq
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export function isValidLexicalModelId(id: string) {
|
||||
return /^[a-z_][a-z0-9_]*\.[a-z][a-z0-9_]+\.[a-z_][a-z0-9_]*$/.test(id);
|
||||
}
|
||||
76
developer/src/common/web/utils/test/valid-ids.tests.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import 'mocha';
|
||||
import { isValidKeymanKeyboardId, isValidLdmlKeyboardId, isValidLexicalModelId } from '../src/valid-ids.js';
|
||||
|
||||
describe('valid-ids', function () {
|
||||
it('should accept a valid Keyman keyboard ID', function() {
|
||||
[
|
||||
'khmer_angkor',
|
||||
'm',
|
||||
'fancypants1_2',
|
||||
'keyboard___',
|
||||
'_100',
|
||||
].forEach(id => assert.isTrue(isValidKeymanKeyboardId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid Keyman keyboard IDs', function() {
|
||||
[
|
||||
'khmer_angkor@example.com',
|
||||
'keyboard ',
|
||||
'Khmer',
|
||||
' keyboard',
|
||||
'111',
|
||||
'?',
|
||||
'keyboard.version',
|
||||
].forEach(id => assert.isFalse(isValidKeymanKeyboardId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
|
||||
it('should accept a valid LDML keyboard ID', function() {
|
||||
[
|
||||
'khmer_angkor',
|
||||
'm',
|
||||
'fancypants1_2',
|
||||
'keyboard___',
|
||||
'_100',
|
||||
].forEach(id => assert.isTrue(isValidLdmlKeyboardId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid LDML keyboard IDs', function() {
|
||||
[
|
||||
'khmer_angkor@example.com',
|
||||
'keyboard ',
|
||||
' keyboard',
|
||||
'Khmer',
|
||||
'111',
|
||||
'?',
|
||||
'keyboard.version',
|
||||
].forEach(id => assert.isFalse(isValidLdmlKeyboardId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
|
||||
it('should accept a valid lexical model ID', function() {
|
||||
[
|
||||
'khmer.km.khmer_angkor',
|
||||
'nrc.en.mtnt',
|
||||
].forEach(id => assert.isTrue(isValidLexicalModelId(id), `expected '${id}' to be valid`));
|
||||
});
|
||||
|
||||
it('should reject invalid lexical model IDs', function() {
|
||||
[
|
||||
'khmer',
|
||||
'en-us',
|
||||
'khmer.km-KM.angkor',
|
||||
'Khmer.km_km.angkor',
|
||||
'model ',
|
||||
' model',
|
||||
'111',
|
||||
'?',
|
||||
'lexical.model',
|
||||
'lexical..model',
|
||||
'.en.model',
|
||||
].forEach(id => assert.isFalse(isValidLexicalModelId(id), `expected '${id}' to be invalid`));
|
||||
});
|
||||
});
|
||||
|
|
@ -5,8 +5,12 @@
|
|||
"rootDir": ".",
|
||||
"outDir": "./build/",
|
||||
"baseUrl": ".",
|
||||
|
||||
/** for ttfmeta */
|
||||
"allowJs": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/ttfmeta/lib/*.js",
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,6 @@ builder_run_action api api-extractor run --local --verbose
|
|||
# note: `export TEST_SAVE_ARTIFACTS=1` to save a copy of artifacts to temp path
|
||||
# note: `export TEST_SAVE_FIXTURES=1` to get a copy of cloud-based fixtures saved to online/
|
||||
# TODO: -skip-full
|
||||
builder_run_action test builder_do_typescript_tests 70
|
||||
builder_run_action test builder_do_typescript_tests 75
|
||||
|
||||
builder_run_action publish builder_publish_npm
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Copy a keyboard or lexical model project
|
||||
*/
|
||||
|
||||
import { CloudUrls, GitHubUrls, CompilerCallbacks, CompilerLogLevel, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanDeveloperProject, KeymanDeveloperProjectOptions, KPJFileReader, KPJFileWriter, KpsFileReader, KpsFileWriter } from "@keymanapp/developer-utils";
|
||||
import { CloudUrls, GitHubUrls, CompilerCallbacks, CompilerLogLevel, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanDeveloperProject, KeymanDeveloperProjectOptions, KPJFileReader, KPJFileWriter, KpsFileReader, KpsFileWriter, ValidIds } from "@keymanapp/developer-utils";
|
||||
import { KeymanFileTypes } from "@keymanapp/common-types";
|
||||
|
||||
import { CopierMessages } from "./copier-messages.js";
|
||||
|
|
@ -29,11 +29,6 @@ export interface CopierOptions /* not inheriting from CompilerBaseOptions */ {
|
|||
* output path where project folder will be created
|
||||
*/
|
||||
outPath: string;
|
||||
/*
|
||||
* TODO-COPY rename: rename existing files rather than copy
|
||||
|
||||
rename: boolean;
|
||||
*/
|
||||
/**
|
||||
* dryRun: show what would happen
|
||||
*/
|
||||
|
|
@ -125,7 +120,6 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
|
||||
this.asyncCallbacks = new CopierAsyncCallbacks(this.callbacks, this.githubRef);
|
||||
|
||||
// TODO-COPY: validate outputId is valid for the project type?
|
||||
this.outputId = this.callbacks.path.basename(this.outPath);
|
||||
this.sourceId = this.callbacks.path.basename(projectSource, KeymanFileTypes.Source.Project);
|
||||
|
||||
|
|
@ -136,6 +130,20 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
return null;
|
||||
}
|
||||
|
||||
if(project.isKeyboardProject()) {
|
||||
// LDML and .kmn keyboards have the same id validity requirements, so
|
||||
// we'll just test against .kmn
|
||||
if(!ValidIds.isValidKeymanKeyboardId(this.outputId)) {
|
||||
this.callbacks.reportMessage(CopierMessages.Error_InvalidKeyboardId({id: this.outputId}));
|
||||
return null;
|
||||
}
|
||||
} else if(project.isLexicalModelProject()) {
|
||||
if(!ValidIds.isValidLexicalModelId(this.outputId)) {
|
||||
this.callbacks.reportMessage(CopierMessages.Error_InvalidLexicalModelId({id: this.outputId}));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const result: CopierResult = { artifacts: {
|
||||
'kmc-copy:outputPath': {
|
||||
data: null,
|
||||
|
|
@ -302,7 +310,6 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
};
|
||||
|
||||
private async copyFolder(inputPath: string, outputPath: string, ignorePatterns: (string | RegExp)[], result: CopierResult) {
|
||||
// TODO-COPY: watch out for file collisions when copying project files -- after renames
|
||||
const files = await this.asyncCallbacks.fsAsync.readdir(inputPath);
|
||||
for(const {filename,type} of files) {
|
||||
const fullPath = this.normalizePath(this.callbacks.path.join(inputPath, filename));
|
||||
|
|
@ -312,9 +319,17 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
if(type == 'dir') {
|
||||
await this.copyFolder(fullPath, this.callbacks.path.join(outputPath, filename), ignorePatterns, result);
|
||||
} else if(!result.artifacts[fullPath]) {
|
||||
const filename = this.generateNewFilename(fullPath, outputPath);
|
||||
|
||||
for(const artifact of Object.keys(result.artifacts)) {
|
||||
if(result.artifacts[artifact].filename == filename) {
|
||||
this.callbacks.reportMessage(CopierMessages.Warn_FilenameCollides({filename}));
|
||||
}
|
||||
}
|
||||
|
||||
result.artifacts[fullPath] = {
|
||||
data: await this.asyncCallbacks.fsAsync.readFile(fullPath),
|
||||
filename: this.generateNewFilename(fullPath, outputPath)
|
||||
filename
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -365,7 +380,7 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
return true;
|
||||
}
|
||||
|
||||
private async copyGenericFile(_project: KeymanDeveloperProject, filename: string, outputPath: string, _source: string, result: CopierResult): Promise<boolean> {
|
||||
private async copyGenericFile(project: KeymanDeveloperProject, filename: string, outputPath: string, _source: string, result: CopierResult): Promise<boolean> {
|
||||
if(result.artifacts[filename]) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -388,6 +403,10 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
return false;
|
||||
}
|
||||
|
||||
if(KeymanFileTypes.binaryTypeFromFilename(filename) != null) {
|
||||
outputPath = this.callbacks.path.join(this.outPath, 'build');
|
||||
}
|
||||
|
||||
result.artifacts[filename] = {
|
||||
data,
|
||||
filename: this.generateNewFilename(filename, outputPath)
|
||||
|
|
@ -578,6 +597,7 @@ export class KeymanProjectCopier implements KeymanCompiler {
|
|||
*/
|
||||
private generateNewFilename(filename: string, outputPath: string): string {
|
||||
const {ext, base} = this.splitFilename(filename);
|
||||
|
||||
const newFilename = base == this.sourceId
|
||||
? this.callbacks.path.join(outputPath, this.outputId + ext)
|
||||
: this.callbacks.path.join(outputPath, base + ext);
|
||||
|
|
|
|||
|
|
@ -63,9 +63,10 @@ export class KeymanCloudSource {
|
|||
}
|
||||
|
||||
public async downloadFolderFromGitHub(ref: GitHubRef): Promise<{filename:string,type:'dir'|'file'}[]> {
|
||||
// this.callbacks.reportMessage(CopierMessages.Info_DownloadingFile({ref})); // TODO-COPY: verbose mode support
|
||||
|
||||
const url = `https://api.github.com/repos/${ref.owner}/${ref.repo}/contents/${ref.path}?ref=${ref.branch}`;
|
||||
this.callbacks.reportMessage(CopierMessages.Verbose_DownloadingFolder({path:ref.path, url}));
|
||||
|
||||
let folder: any;
|
||||
try {
|
||||
folder = await this.callbacks.net.fetchJSON(url);
|
||||
|
|
@ -94,6 +95,7 @@ export class KeymanCloudSource {
|
|||
}
|
||||
|
||||
const url = `https://raw.githubusercontent.com/${ref.owner}/${ref.repo}/refs/heads/${ref.branch}${ref.path}`;
|
||||
this.callbacks.reportMessage(CopierMessages.Verbose_DownloadingFile({filename:this.callbacks.path.basename(ref.path), url}));
|
||||
|
||||
try {
|
||||
return await this.callbacks.net.fetchBlob(url);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.Copier;
|
||||
const SevVerbose = CompilerErrorSeverity.Verbose | Namespace;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | Namespace;
|
||||
|
|
@ -198,4 +199,36 @@ export class CopierMessages {
|
|||
the provided error details for more details.`
|
||||
);
|
||||
|
||||
static VERBOSE_DownloadingFile = SevVerbose | 0x001C;
|
||||
static Verbose_DownloadingFile = (o:{filename: string, url: string}) => m(
|
||||
this.VERBOSE_DownloadingFile,
|
||||
`Downloading '${def(o.filename)}' from '${def(o.url)}'`,
|
||||
);
|
||||
|
||||
static VERBOSE_DownloadingFolder = SevVerbose | 0x001D;
|
||||
static Verbose_DownloadingFolder = (o:{path: string, url: string}) => m(
|
||||
this.VERBOSE_DownloadingFolder,
|
||||
`Downloading folder '${def(o.path)}' from '${def(o.url)}'`,
|
||||
);
|
||||
|
||||
// See also generator-messages.ERROR_InvalidKeymanKeyboardId
|
||||
static ERROR_InvalidKeyboardId = SevError | 0x001E;
|
||||
static Error_InvalidKeyboardId = (o:{id: string}) => m(
|
||||
this.ERROR_InvalidKeyboardId,
|
||||
`The specified keyboard id '${def(o.id)}' contains characters that are not permitted for a keyboard id or filename.`,
|
||||
);
|
||||
|
||||
// See also generator-messages.ERROR_InvalidLexicalModelId
|
||||
static ERROR_InvalidLexicalModelId = SevError | 0x001F;
|
||||
static Error_InvalidLexicalModelId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidLexicalModelId,
|
||||
`The specified lexical model id '${def(o.id)}' contains characters that are not permitted or does not match the required pattern of 'author.bcp47.uniq'.`,
|
||||
);
|
||||
|
||||
static WARN_FilenameCollides = SevWarn | 0x0020;
|
||||
static Warn_FilenameCollides = (o:{filename:string}) => m(
|
||||
this.WARN_FilenameCollides,
|
||||
`The output file '${def(o.filename)}' has two different possible source files.`,
|
||||
);
|
||||
|
||||
};
|
||||
|
|
|
|||
54
developer/src/kmc-copy/test/copier-messages.tests.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import 'mocha';
|
||||
// import * as path from 'path';
|
||||
// import * as fs from 'fs';
|
||||
// import { fileURLToPath } from 'url';
|
||||
import { assert } from 'chai';
|
||||
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
import { CopierMessages } from '../src/copier-messages.js';
|
||||
import { KeymanProjectCopier } from '../src/KeymanProjectCopier.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
|
||||
describe('CopierMessages', function () {
|
||||
const callbacks = new TestCompilerCallbacks(makePathToFixture('online'));
|
||||
|
||||
this.beforeEach(function() {
|
||||
callbacks.clear();
|
||||
});
|
||||
|
||||
this.afterEach(function() {
|
||||
if(this.currentTest?.isFailed()) {
|
||||
callbacks.printMessages();
|
||||
}
|
||||
});
|
||||
|
||||
it('should have a valid CopierMessages object', function() {
|
||||
return verifyCompilerMessagesObject(CopierMessages, CompilerErrorNamespace.Copier);
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidKeyboardId if the output keyboard id is invalid', async function() {
|
||||
const copier = new KeymanProjectCopier();
|
||||
assert.isTrue(await copier.init(callbacks, {dryRun: true, outPath: '0invalid'}))
|
||||
assert.isNull(await copier.run('cloud:jawa'));
|
||||
assert.isTrue(callbacks.hasMessage(CopierMessages.ERROR_InvalidKeyboardId));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidLexicalModelId if the output lexical model id is invalid', async function() {
|
||||
const copier = new KeymanProjectCopier();
|
||||
assert.isTrue(await copier.init(callbacks, {dryRun: true, outPath: 'nrc.str-x-sencoten.sencoten'}))
|
||||
assert.isNull(await copier.run('cloud:nrc.str.sencoten'));
|
||||
assert.isTrue(callbacks.hasMessage(CopierMessages.ERROR_InvalidLexicalModelId));
|
||||
});
|
||||
|
||||
it('should generate WARN_FilenameCollision if a copied file will collide', async function() {
|
||||
const copier = new KeymanProjectCopier();
|
||||
assert.isTrue(await copier.init(callbacks, {dryRun: true, outPath: 'collided'}))
|
||||
const result = await copier.run(makePathToFixture('projects/collision/collision.kpj'));
|
||||
assert.isNotNull(result);
|
||||
assert.isTrue(callbacks.hasMessage(CopierMessages.WARN_FilenameCollides));
|
||||
});
|
||||
});
|
||||
|
|
@ -306,7 +306,6 @@ describe('KeymanProjectCopier', function() {
|
|||
assert.isTrue(await copier.init(callbacks, {
|
||||
dryRun: false,
|
||||
outPath,
|
||||
// TODO-COPY rename: false,
|
||||
}));
|
||||
|
||||
const result = await copier.run(fullSource);
|
||||
|
|
@ -316,7 +315,7 @@ describe('KeymanProjectCopier', function() {
|
|||
//
|
||||
|
||||
assert.isOk(result);
|
||||
assert.isEmpty(callbacks.messages);
|
||||
assert.isEmpty(callbacks.filteredMessages());
|
||||
|
||||
if(TEST_SAVE_ARTIFACTS) {
|
||||
assert.isTrue(await copier.write(result.artifacts));
|
||||
|
|
@ -370,7 +369,6 @@ describe('KeymanProjectCopier', function() {
|
|||
assert.isTrue(await copier.init(callbacks, {
|
||||
dryRun: false,
|
||||
outPath,
|
||||
// TODO-COPY rename: false,
|
||||
}));
|
||||
|
||||
// armenian_mnemonic selected because (a) small, and (b) has v2.0 project, so
|
||||
|
|
@ -379,7 +377,7 @@ describe('KeymanProjectCopier', function() {
|
|||
|
||||
// We should have no messages and a successful result
|
||||
assert.isOk(result);
|
||||
assert.isEmpty(callbacks.messages);
|
||||
assert.isEmpty(callbacks.filteredMessages());
|
||||
|
||||
// TODO-COPY: verify outcome using pattern above
|
||||
|
||||
|
|
@ -400,14 +398,13 @@ describe('KeymanProjectCopier', function() {
|
|||
assert.isTrue(await copier.init(callbacks, {
|
||||
dryRun: false,
|
||||
outPath,
|
||||
// TODO-COPY rename: false,
|
||||
}));
|
||||
|
||||
const result = await copier.run('cloud:'+id);
|
||||
|
||||
// We should have no messages and a successful result
|
||||
assert.isOk(result);
|
||||
assert.isEmpty(callbacks.messages);
|
||||
assert.isEmpty(callbacks.filteredMessages());
|
||||
|
||||
// TODO-COPY: verify outcome using pattern above
|
||||
|
||||
|
|
@ -492,8 +489,41 @@ describe('KeymanProjectCopier', function() {
|
|||
});
|
||||
}));
|
||||
|
||||
it('should copy a disorganized project into current structure', async function() {
|
||||
const copier = new KeymanProjectCopier();
|
||||
assert.isTrue(await copier.init(callbacks, {
|
||||
dryRun: false,
|
||||
outPath: 'organized',
|
||||
}));
|
||||
|
||||
const result = await copier.run(makePathToFixture('projects','disorganized','alephwithbeth.kpj'));
|
||||
assert.sameMembers(Object.keys(result.artifacts).map(k => result.artifacts[k].filename), [
|
||||
'organized', // kmc-copy:outputPath
|
||||
'organized/organized.kpj',
|
||||
'organized/HISTORY.md',
|
||||
'organized/LICENSE.md',
|
||||
'organized/README.md',
|
||||
// primary source files get moved to 'source/'
|
||||
'organized/source/organized.kps',
|
||||
'organized/source/organized.kmn',
|
||||
'organized/source/organized.ico',
|
||||
'organized/source/organized.kvks',
|
||||
// files in 'other/' get moved to 'source/other/'
|
||||
'organized/source/other/organized.keyman-touch-layout',
|
||||
'organized/source/other/readme.htm',
|
||||
'organized/source/other/welcome/welcome.htm',
|
||||
'organized/source/other/welcome/KeyboardLayout.png',
|
||||
'organized/source/other/welcome/KeyboardLayoutAlt.png',
|
||||
'organized/source/other/welcome/KeyboardLayoutShift.png',
|
||||
// build artifact files get moved to build/
|
||||
'organized/build/organized.js',
|
||||
'organized/build/organized.kvk',
|
||||
'organized/build/organized.kmx',
|
||||
// 'organized/build/organized.kmp', // TODO-COPY: do we need to copy this file as well?
|
||||
]);
|
||||
});
|
||||
|
||||
// TODO-COPY: additional tests
|
||||
it.skip('should copy a disorganized project into current structure', async function() {});
|
||||
it.skip('should copy a standalone .kmn into a new project', async function() {});
|
||||
it.skip('should copy a standalone .kmn and .kps into a new project', async function() {});
|
||||
});
|
||||
|
|
|
|||
7
developer/src/kmc-copy/test/fixtures/projects/collision/collision.kpj
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<ProjectType>keyboard</ProjectType>
|
||||
<Version>2.0</Version>
|
||||
</Options>
|
||||
</KeymanDeveloperProject>
|
||||
BIN
developer/src/kmc-copy/test/fixtures/projects/collision/source/collided.ico
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
developer/src/kmc-copy/test/fixtures/projects/collision/source/collision.ico
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
7
developer/src/kmc-copy/test/fixtures/projects/collision/source/collision.kmn
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
store(&TARGETS) 'any'
|
||||
store(&NAME) 'Collision'
|
||||
store(&BITMAP) 'collision.ico'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
35
developer/src/kmc-copy/test/fixtures/projects/disorganized/HISTORY.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
alephwithbeth Change History
|
||||
====================
|
||||
|
||||
1.0.6 (2024-06-11)
|
||||
------------------
|
||||
* Remove source files from package
|
||||
|
||||
1.0.5 (2023-02-28)
|
||||
------------------
|
||||
* replaced hyphen with maqef
|
||||
* added a touch-optimized keyboard layout for phone
|
||||
|
||||
1.0.4 (2023-02-13)
|
||||
------------------
|
||||
* Removed unnecessary version numbers.
|
||||
* added description
|
||||
|
||||
1.0.3 (2023-02-11)
|
||||
------------------
|
||||
* Removed the faulty auto correct feature.
|
||||
* Captialized all words of the title.
|
||||
* Added title to .kmn
|
||||
|
||||
1.0.2 (2023-02-09)
|
||||
------------------
|
||||
* Renamed keyboard to "Aleph with Beth"
|
||||
|
||||
1.0.1 (2023-02-08)
|
||||
----------------
|
||||
* Updated author and website (in kps)
|
||||
* Added copyrights where needed.
|
||||
|
||||
1.0 (2023-01-28)
|
||||
----------------
|
||||
* Created by Cameron Briggs
|
||||
21
developer/src/kmc-copy/test/fixtures/projects/disorganized/LICENSE.md
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2023-2024 Cameron Briggs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
25
developer/src/kmc-copy/test/fixtures/projects/disorganized/README.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
alephwithbeth keyboard
|
||||
==============
|
||||
|
||||
Description
|
||||
-----------
|
||||
A Hebrew keyboard designed to intuitively match the standard QWERTY layout.
|
||||
|
||||
Copyright
|
||||
---------
|
||||
See [LICENSE.md](LICENSE.md)
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
* Windows
|
||||
* macOS
|
||||
* Linux
|
||||
* Web
|
||||
* iPhone
|
||||
* iPad
|
||||
* Android phone
|
||||
* Android tablet
|
||||
* Mobile devices
|
||||
* Desktop devices
|
||||
* Tablet devices
|
||||
|
||||
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.ico
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
1
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.js
vendored
Normal file
215
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kmn
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
store(&TARGETS) 'any'
|
||||
store(&NAME) 'Aleph With Beth'
|
||||
store(&BITMAP) 'alephwithbeth.ico'
|
||||
store(&VISUALKEYBOARD) 'alephwithbeth.kvks'
|
||||
|
||||
store(letters-with-final-forms) "כמנפצ"
|
||||
store(sofit-forms-of-letters) "ךםןףץ"
|
||||
store(presentation-dageshed-letters-with-final-forms) "כּפּ"
|
||||
store(presentation-dageshed-sofit-forms-of-letters) "ךּףּ"
|
||||
store(dagesh) 'ּ'
|
||||
c store(vowels) "ְֱֲֳִֵֶַׇׇָֹֻׅ"
|
||||
|
||||
store(normal-dageshed-letters-with-final-forms) "מּנּצּ"
|
||||
store(normal-undageshed-sofit-forms-of-letters) "םןץ"
|
||||
|
||||
store(punctuation) U+0591 .. U+05AF U+05BD .. U+05BF U+05C0 U+05C4
|
||||
store(whitespace) [K_SPACE] [K_ENTER] [K_TAB]
|
||||
|
||||
store(&KMW_RTL) '1'
|
||||
store(&LAYOUTFILE) 'other\alephwithbeth.keyman-touch-layout'
|
||||
store(©RIGHT) '© Cameron Briggs'
|
||||
store(&KEYBOARDVERSION) '1.0.6'
|
||||
store(&KMW_HELPTEXT) 'Aleph With Beth'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
+ [SHIFT K_I] > nul
|
||||
+ [ALT K_BKQUOTE] > nul
|
||||
+ [ALT K_B] > nul
|
||||
+ [ALT K_V] > nul
|
||||
+ [ALT K_C] > nul
|
||||
+ [ALT K_Z] > nul
|
||||
+ [ALT K_L] > nul
|
||||
+ [ALT K_J] > nul
|
||||
+ [ALT K_F] > nul
|
||||
+ [ALT K_D] > nul
|
||||
+ [ALT K_S] > nul
|
||||
+ [ALT K_U] > nul
|
||||
+ [ALT K_Y] > nul
|
||||
+ [ALT K_T] > nul
|
||||
+ [ALT K_R] > nul
|
||||
+ [ALT K_W] > nul
|
||||
+ [ALT K_Q] > nul
|
||||
+ [SHIFT K_BKSLASH] > '|'
|
||||
+ [SHIFT K_EQUAL] > '+'
|
||||
+ [SHIFT K_HYPHEN] > '_'
|
||||
+ [SHIFT K_0] > ')'
|
||||
+ [SHIFT K_9] > '('
|
||||
+ [SHIFT K_SLASH] > '?'
|
||||
+ [SHIFT K_QUOTE] > '"'
|
||||
+ [SHIFT K_BKQUOTE] > nul
|
||||
+ [K_EQUAL] > '='
|
||||
+ [K_HYPHEN] > '־'
|
||||
+ [K_0] > '0'
|
||||
+ [K_9] > '9'
|
||||
+ [K_8] > '8'
|
||||
+ [K_7] > '7'
|
||||
+ [K_6] > '6'
|
||||
+ [K_5] > '5'
|
||||
+ [K_4] > '4'
|
||||
+ [K_3] > '3'
|
||||
+ [K_2] > '2'
|
||||
+ [K_1] > '1'
|
||||
+ [K_PERIOD] > '.'
|
||||
+ [K_SLASH] > '/'
|
||||
+ [K_QUOTE] > U+0027
|
||||
+ [SHIFT K_COLON] > '״'
|
||||
+ [ALT K_A] > 'ֲ'
|
||||
+ [SHIFT K_RBRKT] > '}'
|
||||
+ [SHIFT K_LBRKT] > '{'
|
||||
+ [K_BKSLASH] > '\'
|
||||
+ [K_RBRKT] > ']'
|
||||
+ [K_LBRKT] > '['
|
||||
+ [SHIFT K_R] > 'רּ'
|
||||
+ [SHIFT CTRL K_SPACE] > ' '
|
||||
+ [SHIFT K_SPACE] > ' '
|
||||
+ [K_SPACE] > ' '
|
||||
+ [ALT K_SPACE] > ' '
|
||||
+ [SHIFT K_8] > '֝'
|
||||
+ [SHIFT K_7] > '֬'
|
||||
+ [SHIFT K_6] > '֔'
|
||||
+ [SHIFT K_5] > '֞'
|
||||
+ [SHIFT K_4] > '֜'
|
||||
+ [SHIFT K_3] > '֨'
|
||||
+ [SHIFT K_2] > '֘'
|
||||
+ [SHIFT K_1] > '!'
|
||||
+ [ALT K_SLASH] > '״'
|
||||
+ [ALT K_PERIOD] > '̈'
|
||||
+ [ALT K_COMMA] > '֫'
|
||||
+ [ALT K_QUOTE] > 'ׄ'
|
||||
+ [ALT K_COLON] > '׃'
|
||||
+ [ALT K_H] > '̊'
|
||||
+ [ALT K_G] > '•'
|
||||
+ [ALT K_BKSLASH] > '֫'
|
||||
+ [ALT K_RBRKT] > '֒'
|
||||
+ [ALT K_LBRKT] > '֔'
|
||||
+ [ALT K_I] > 'ֽ'
|
||||
+ [ALT K_O] > 'ֳ'
|
||||
+ [ALT K_E] > 'ֱ'
|
||||
+ [ALT K_EQUAL] > '◌'
|
||||
+ [ALT K_HYPHEN] > 'ֿ'
|
||||
+ [ALT K_0] > '֯'
|
||||
+ [ALT K_9] > '֓'
|
||||
+ [ALT K_8] > '֕'
|
||||
+ [ALT K_7] > '֡'
|
||||
+ [ALT K_6] > '֟'
|
||||
+ [ALT K_5] > '֩'
|
||||
+ [ALT K_4] > '֠'
|
||||
+ [ALT K_3] > '֙'
|
||||
+ [ALT K_2] > '֮'
|
||||
+ [ALT K_1] > '֗'
|
||||
+ [ALT K_N] > 'ן'
|
||||
+ [ALT K_P] > 'ף'
|
||||
+ [ALT K_K] > 'ך'
|
||||
+ [ALT K_M] > 'ם'
|
||||
+ [ALT K_X] > 'ץ'
|
||||
+ [SHIFT K_PERIOD] > 'ּ'
|
||||
+ [SHIFT K_COMMA] > nul
|
||||
+ [SHIFT K_M] > 'מּ'
|
||||
+ [SHIFT K_N] > 'נּ'
|
||||
+ [SHIFT K_B] > 'בּ'
|
||||
+ [SHIFT K_V] > 'שּׁ'
|
||||
+ [SHIFT K_C] > 'שּׂ'
|
||||
+ [SHIFT K_X] > 'צּ'
|
||||
+ [SHIFT K_Z] > 'זּ'
|
||||
+ [SHIFT K_L] > 'לּ'
|
||||
+ [SHIFT K_K] > 'כּ'
|
||||
+ [SHIFT K_J] > 'וַיּ'
|
||||
+ [SHIFT K_H] > 'הּ'
|
||||
+ [SHIFT K_G] > 'גּ'
|
||||
+ [SHIFT K_F] > 'טּ'
|
||||
+ [SHIFT K_D] > 'דּ'
|
||||
+ [SHIFT K_S] > 'סּ'
|
||||
+ [SHIFT K_A] > 'ָ'
|
||||
+ [SHIFT K_P] > 'פּ'
|
||||
+ [SHIFT K_O] > 'וֹ'
|
||||
+ [SHIFT K_U] > 'ֻ'
|
||||
+ [SHIFT K_Y] > 'יּ'
|
||||
+ [SHIFT K_T] > 'תּ'
|
||||
+ [SHIFT K_E] > 'ֵ'
|
||||
+ [SHIFT K_W] > 'וּ'
|
||||
+ [SHIFT K_Q] > 'קּ'
|
||||
+ [K_BKQUOTE] > '֑'
|
||||
+ [K_COMMA] > 'א'
|
||||
+ [K_M] > 'מ'
|
||||
+ [K_N] > 'נ'
|
||||
+ [K_B] > 'ב'
|
||||
+ [K_V] > 'שׁ'
|
||||
+ [K_C] > 'שׂ'
|
||||
+ [K_X] > 'צ'
|
||||
+ [K_Z] > 'ז'
|
||||
+ [K_COLON] > 'ְ'
|
||||
+ [K_L] > 'ל'
|
||||
+ [K_K] > 'כ'
|
||||
+ [K_J] > 'ח'
|
||||
+ [K_H] > 'ה'
|
||||
+ [K_G] > 'ג'
|
||||
+ [K_F] > 'ט'
|
||||
+ [K_D] > 'ד'
|
||||
+ [K_S] > 'ס'
|
||||
+ [K_A] > 'ַ'
|
||||
+ [K_P] > 'פ'
|
||||
+ [K_O] > 'ֹ'
|
||||
+ [K_I] > 'ִ'
|
||||
+ [K_U] > 'ע'
|
||||
+ [K_Y] > 'י'
|
||||
+ [K_T] > 'ת'
|
||||
+ [K_R] > 'ר'
|
||||
+ [K_E] > 'ֶ'
|
||||
+ [K_W] > 'ו'
|
||||
+ [K_Q] > 'ק'
|
||||
c Transform final letters
|
||||
any(letters-with-final-forms) + any(whitespace) > index(sofit-forms-of-letters,1)
|
||||
|
||||
c Transform final letters with a dagesh
|
||||
any(letters-with-final-forms) $dagesh + any(whitespace) > index(sofit-forms-of-letters,1) $dagesh
|
||||
any(presentation-dageshed-letters-with-final-forms) + any(whitespace) > index(presentation-dageshed-sofit-forms-of-letters,1)
|
||||
any(normal-dageshed-letters-with-final-forms) + any(whitespace) > index(normal-undageshed-sofit-forms-of-letters,1) $dagesh
|
||||
|
||||
c Transform final letters with a vowel
|
||||
c any(letters-with-final-forms) any(vowels) + any(whitespace) > index(sofit-forms-of-letters,1) index(vowels,2)
|
||||
|
||||
c Transform final letters with a vowel and a dagesh
|
||||
c any(letters-with-final-forms) any(vowels) $dagesh + any(whitespace) > index(sofit-forms-of-letters,1) index(vowels,2) $dagesh
|
||||
c any(letters-with-final-forms) $dagesh any(vowels) + any(whitespace) > index(sofit-forms-of-letters,1) $dagesh index(vowels,3)
|
||||
c any(presentation-dageshed-letters-with-final-forms) any(vowels) + any(whitespace) > index(presentation-dageshed-sofit-forms-of-letters,1) index(vowels,2)
|
||||
c any(normal-dageshed-letters-with-final-forms) any(vowels) + any(whitespace) > index(normal-undageshed-sofit-forms-of-letters,1) $dagesh index(vowels,2)
|
||||
|
||||
c Transform final letters with punctuation
|
||||
any(letters-with-final-forms) any(punctuation) + any(whitespace) > index(sofit-forms-of-letters,1) index(punctuation,2)
|
||||
|
||||
c Transform final letters with punctuation and a dagesh
|
||||
any(letters-with-final-forms) any(punctuation) $dagesh + any(whitespace) > index(sofit-forms-of-letters,1) index(punctuation,2) $dagesh
|
||||
any(letters-with-final-forms) $dagesh any(punctuation) + any(whitespace) > index(sofit-forms-of-letters,1) $dagesh index(punctuation,3)
|
||||
any(presentation-dageshed-letters-with-final-forms) any(punctuation) + any(whitespace) > index(presentation-dageshed-sofit-forms-of-letters,1) index(punctuation,2)
|
||||
any(normal-dageshed-letters-with-final-forms) any(punctuation) + any(whitespace) > index(normal-undageshed-sofit-forms-of-letters,1) $dagesh index(punctuation,2)
|
||||
|
||||
c Transfrom final letters with a punctuation and a vowel
|
||||
c any(letters-with-final-forms) any(punctuation) any(vowels) + any(whitespace) > index(sofit-forms-of-letters,1) index(punctuation,2) index(vowels,3)
|
||||
c any(letters-with-final-forms) any(vowels) any(punctuation) + any(whitespace) > index(sofit-forms-of-letters,1) index(vowels,2) index(punctuation,3)
|
||||
|
||||
c Transform final letters with punctuation, a vowel, and a dagesh (phew!)
|
||||
c any(letters-with-final-forms) any(punctuation) any(vowels) $dagesh + any(whitespace) > index(sofit-forms-of-letters,1) index(punctuation,2) index(vowels,3) $dagesh
|
||||
c any(letters-with-final-forms) any(punctuation) $dagesh any(vowels) + any(whitespace) > index(sofit-forms-of-letters,1) index(punctuation,2) $dagesh index(vowels,4)
|
||||
c any(letters-with-final-forms) any(vowels) any(punctuation) $dagesh + any(whitespace) > index(sofit-forms-of-letters,1) index(vowels,2) index(punctuation,3) $dagesh
|
||||
c any(letters-with-final-forms) any(vowels) $dagesh any(punctuation) + any(whitespace) > index(sofit-forms-of-letters,1) index(vowels,2) $dagesh index(punctuation,4)
|
||||
c any(letters-with-final-forms) $dagesh any(punctuation) any(vowels) + any(whitespace) > index(sofit-forms-of-letters,1) $dagesh index(punctuation,3) index(vowels,4)
|
||||
c any(letters-with-final-forms) $dagesh any(vowels) any(punctuation) + any(whitespace) > index(sofit-forms-of-letters,1) $dagesh index(vowels,3) index(punctuation,4)
|
||||
|
||||
|
||||
c any(presentation-dageshed-letters-with-final-forms) any(punctuation) any(vowels) + any(whitespace) > index(presentation-dageshed-sofit-forms-of-letters,1) index(punctuation,2) index(vowels,3)
|
||||
c any(presentation-dageshed-letters-with-final-forms) any(vowels) any(punctuation) + any(whitespace) > index(presentation-dageshed-sofit-forms-of-letters,1) index(vowels,2) index(punctuation,3)
|
||||
c any(normal-dageshed-letters-with-final-forms) any(punctuation) any(vowels) + any(whitespace) > index(normal-undageshed-sofit-forms-of-letters,1) $dagesh index(punctuation,2) index(vowels,3)
|
||||
c any(normal-dageshed-letters-with-final-forms) any(vowels) any(punctuation) + any(whitespace) > index(normal-undageshed-sofit-forms-of-letters,1) $dagesh index(vowels,2) index(punctuation,3)
|
||||
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kmp
vendored
Normal file
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kmx
vendored
Normal file
133
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kpj
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<BuildPath>$PROJECTPATH</BuildPath>
|
||||
<CompilerWarningsAsErrors>True</CompilerWarningsAsErrors>
|
||||
<CheckFilenameConventions>True</CheckFilenameConventions>
|
||||
</Options>
|
||||
<Files>
|
||||
<File>
|
||||
<ID>id_ede98e4633e239f933cbfd1f4e1b766c</ID>
|
||||
<Filename>HISTORY.md</Filename>
|
||||
<Filepath>HISTORY.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_53e892b8b41cc4caece1cfd5ef21d6e7</ID>
|
||||
<Filename>LICENSE.md</Filename>
|
||||
<Filepath>LICENSE.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_0730bb7c2e8f9ea2438b52e419dd86c9</ID>
|
||||
<Filename>README.md</Filename>
|
||||
<Filepath>README.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_195752eb7ec06cfb1f90ed508d2c5b54</ID>
|
||||
<Filename>alephwithbeth.kps</Filename>
|
||||
<Filepath>alephwithbeth.kps</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kps</FileType>
|
||||
<Details>
|
||||
<Name>Aleph With Beth</Name>
|
||||
<Copyright>© Cameron Briggs</Copyright>
|
||||
</Details>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_e72bb7d9956f6973ce3de0362e47b0d3</ID>
|
||||
<Filename>alephwithbeth.kmn</Filename>
|
||||
<Filepath>alephwithbeth.kmn</Filepath>
|
||||
<FileVersion>1.0.6</FileVersion>
|
||||
<FileType>.kmn</FileType>
|
||||
<Details>
|
||||
<Name>Aleph With Beth</Name>
|
||||
<Copyright>© Cameron Briggs</Copyright>
|
||||
</Details>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_dc799c273ed24ff1ca06a2429e5af8b4</ID>
|
||||
<Filename>alephwithbeth.ico</Filename>
|
||||
<Filepath>alephwithbeth.ico</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.ico</FileType>
|
||||
<ParentFileID>id_e72bb7d9956f6973ce3de0362e47b0d3</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_8da344c4cea6f467013357fe099006f5</ID>
|
||||
<Filename>readme.htm</Filename>
|
||||
<Filepath>other\readme.htm</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.htm</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_724e5b4c63f10bc0abf7077f7c3172fc</ID>
|
||||
<Filename>welcome.htm</Filename>
|
||||
<Filepath>other\welcome\welcome.htm</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.htm</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_101067b24e270db6fdd8b52ac6c77297</ID>
|
||||
<Filename>alephwithbeth.js</Filename>
|
||||
<Filepath>alephwithbeth.js</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.js</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_cf57b9fb0e29a5e6b4ff4b9a55fc7ef3</ID>
|
||||
<Filename>alephwithbeth.kvk</Filename>
|
||||
<Filepath>alephwithbeth.kvk</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kvk</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_dfa1b7bb444da6106e7ffcce1b6ae59d</ID>
|
||||
<Filename>alephwithbeth.kmx</Filename>
|
||||
<Filepath>alephwithbeth.kmx</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kmx</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_92d25e21c1bb82cab07440032ea15ca1</ID>
|
||||
<Filename>KeyboardLayout.png</Filename>
|
||||
<Filepath>other\welcome\KeyboardLayout.png</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.png</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_a751525f488287b36c872b48e3c385b8</ID>
|
||||
<Filename>KeyboardLayoutAlt.png</Filename>
|
||||
<Filepath>other\welcome\KeyboardLayoutAlt.png</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.png</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_a260e1bca7e99f6955c706b3697f842c</ID>
|
||||
<Filename>KeyboardLayoutShift.png</Filename>
|
||||
<Filepath>other\welcome\KeyboardLayoutShift.png</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.png</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_c296b5df3dc126e219e0173fcbc1baec</ID>
|
||||
<Filename>LICENSE.md</Filename>
|
||||
<Filepath>LICENSE.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
<ParentFileID>id_195752eb7ec06cfb1f90ed508d2c5b54</ParentFileID>
|
||||
</File>
|
||||
</Files>
|
||||
</KeymanDeveloperProject>
|
||||
95
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kps
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package>
|
||||
<System>
|
||||
<KeymanDeveloperVersion>17.0.325.0</KeymanDeveloperVersion>
|
||||
<FileVersion>7.0</FileVersion>
|
||||
</System>
|
||||
<Options>
|
||||
<ExecuteProgram></ExecuteProgram>
|
||||
<ReadMeFile>other\readme.htm</ReadMeFile>
|
||||
<LicenseFile>LICENSE.md</LicenseFile>
|
||||
<WelcomeFile>other\welcome\welcome.htm</WelcomeFile>
|
||||
<MSIFileName></MSIFileName>
|
||||
<MSIOptions></MSIOptions>
|
||||
<FollowKeyboardVersion/>
|
||||
</Options>
|
||||
<StartMenu>
|
||||
<Folder></Folder>
|
||||
<Items/>
|
||||
</StartMenu>
|
||||
<Info>
|
||||
<Version URL=""></Version>
|
||||
<Name URL="">Aleph With Beth</Name>
|
||||
<Copyright URL="">© Cameron Briggs</Copyright>
|
||||
<Author URL="">Cameron Briggs</Author>
|
||||
<WebSite URL="https://freehebrew.online">https://freehebrew.online</WebSite>
|
||||
<Description URL="">alephwithbeth keyboard</Description>
|
||||
</Info>
|
||||
<Files>
|
||||
<File>
|
||||
<Name>other\readme.htm</Name>
|
||||
<Description>File readme.htm</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.htm</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>other\welcome\welcome.htm</Name>
|
||||
<Description>File welcome.htm</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.htm</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>alephwithbeth.js</Name>
|
||||
<Description>File alephwithbeth.js</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.js</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>alephwithbeth.kvk</Name>
|
||||
<Description>File alephwithbeth.kvk</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kvk</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>alephwithbeth.kmx</Name>
|
||||
<Description>Keyboard alephwithbeth</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kmx</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>other\welcome\KeyboardLayout.png</Name>
|
||||
<Description>File KeyboardLayout.png</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.png</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>other\welcome\KeyboardLayoutAlt.png</Name>
|
||||
<Description>File KeyboardLayoutAlt.png</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.png</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>other\welcome\KeyboardLayoutShift.png</Name>
|
||||
<Description>File KeyboardLayoutShift.png</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.png</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>LICENSE.md</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
</Files>
|
||||
<Keyboards>
|
||||
<Keyboard>
|
||||
<Name>Aleph With Beth</Name>
|
||||
<ID>alephwithbeth</ID>
|
||||
<Version>1.0.4</Version>
|
||||
<Languages>
|
||||
<Language ID="he">Hebrew</Language>
|
||||
</Languages>
|
||||
</Keyboard>
|
||||
</Keyboards>
|
||||
<Strings/>
|
||||
</Package>
|
||||
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kvk
vendored
Normal file
138
developer/src/kmc-copy/test/fixtures/projects/disorganized/alephwithbeth.kvks
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<visualkeyboard>
|
||||
<header>
|
||||
<version>10.0</version>
|
||||
<kbdname>alephwithbeth</kbdname>
|
||||
<flags/>
|
||||
</header>
|
||||
<encoding name="unicode" fontname="Arial" fontsize="-12">
|
||||
<layer shift="S">
|
||||
<key vkey="K_1">!</key>
|
||||
<key vkey="K_QUOTE">"</key>
|
||||
<key vkey="K_3">֨</key>
|
||||
<key vkey="K_4">֜</key>
|
||||
<key vkey="K_5">֞</key>
|
||||
<key vkey="K_7">֬</key>
|
||||
<key vkey="K_9">(</key>
|
||||
<key vkey="K_0">)</key>
|
||||
<key vkey="K_8">֝</key>
|
||||
<key vkey="K_EQUAL">+</key>
|
||||
<key vkey="K_COLON">״</key>
|
||||
<key vkey="K_PERIOD">ּ</key>
|
||||
<key vkey="K_SLASH">?</key>
|
||||
<key vkey="K_2">֘</key>
|
||||
<key vkey="K_A">ָ</key>
|
||||
<key vkey="K_B">בּ</key>
|
||||
<key vkey="K_C">שּׂ</key>
|
||||
<key vkey="K_D">דּ</key>
|
||||
<key vkey="K_E">ֵ</key>
|
||||
<key vkey="K_F">טּ</key>
|
||||
<key vkey="K_G">גּ</key>
|
||||
<key vkey="K_H">הּ</key>
|
||||
<key vkey="K_J">וַיּ</key>
|
||||
<key vkey="K_K">כּ</key>
|
||||
<key vkey="K_L">לּ</key>
|
||||
<key vkey="K_M">מּ</key>
|
||||
<key vkey="K_N">נּ</key>
|
||||
<key vkey="K_O">וֹ</key>
|
||||
<key vkey="K_P">פּ</key>
|
||||
<key vkey="K_Q">קּ</key>
|
||||
<key vkey="K_R">רּ</key>
|
||||
<key vkey="K_S">סּ</key>
|
||||
<key vkey="K_T">תּ</key>
|
||||
<key vkey="K_U">ֻ</key>
|
||||
<key vkey="K_V">שּׁ</key>
|
||||
<key vkey="K_W">וּ</key>
|
||||
<key vkey="K_X">צּ</key>
|
||||
<key vkey="K_Y">יּ</key>
|
||||
<key vkey="K_Z">זּ</key>
|
||||
<key vkey="K_6">֔</key>
|
||||
<key vkey="K_HYPHEN">_</key>
|
||||
<key vkey="K_LBRKT">{</key>
|
||||
<key vkey="K_BKSLASH">|</key>
|
||||
<key vkey="K_RBRKT">}</key>
|
||||
</layer>
|
||||
<layer shift="">
|
||||
<key vkey="K_QUOTE">'</key>
|
||||
<key vkey="K_COMMA">א</key>
|
||||
<key vkey="K_PERIOD">.</key>
|
||||
<key vkey="K_SLASH">/</key>
|
||||
<key vkey="K_1">1</key>
|
||||
<key vkey="K_2">2</key>
|
||||
<key vkey="K_3">3</key>
|
||||
<key vkey="K_4">4</key>
|
||||
<key vkey="K_5">5</key>
|
||||
<key vkey="K_6">6</key>
|
||||
<key vkey="K_7">7</key>
|
||||
<key vkey="K_8">8</key>
|
||||
<key vkey="K_9">9</key>
|
||||
<key vkey="K_COLON">ְ</key>
|
||||
<key vkey="K_EQUAL">=</key>
|
||||
<key vkey="K_LBRKT">[</key>
|
||||
<key vkey="K_BKSLASH">\</key>
|
||||
<key vkey="K_RBRKT">]</key>
|
||||
<key vkey="K_BKQUOTE">֑</key>
|
||||
<key vkey="K_A">ַ</key>
|
||||
<key vkey="K_B">ב</key>
|
||||
<key vkey="K_C">שׂ</key>
|
||||
<key vkey="K_D">ד</key>
|
||||
<key vkey="K_E">ֶ</key>
|
||||
<key vkey="K_F">ט</key>
|
||||
<key vkey="K_G">ג</key>
|
||||
<key vkey="K_H">ה</key>
|
||||
<key vkey="K_I">ִ</key>
|
||||
<key vkey="K_J">ח</key>
|
||||
<key vkey="K_K">כ</key>
|
||||
<key vkey="K_L">ל</key>
|
||||
<key vkey="K_M">מ</key>
|
||||
<key vkey="K_N">נ</key>
|
||||
<key vkey="K_O">ֹ</key>
|
||||
<key vkey="K_P">פ</key>
|
||||
<key vkey="K_Q">ק</key>
|
||||
<key vkey="K_R">ר</key>
|
||||
<key vkey="K_S">ס</key>
|
||||
<key vkey="K_T">ת</key>
|
||||
<key vkey="K_U">ע</key>
|
||||
<key vkey="K_V">שׁ</key>
|
||||
<key vkey="K_W">ו</key>
|
||||
<key vkey="K_X">צ</key>
|
||||
<key vkey="K_Y">י</key>
|
||||
<key vkey="K_Z">ז</key>
|
||||
<key vkey="K_HYPHEN">־</key>
|
||||
<key vkey="K_0">0</key>
|
||||
</layer>
|
||||
<layer shift="A">
|
||||
<key vkey="K_QUOTE">ׄ</key>
|
||||
<key vkey="K_COMMA">֫</key>
|
||||
<key vkey="K_HYPHEN">ֿ</key>
|
||||
<key vkey="K_PERIOD">̈</key>
|
||||
<key vkey="K_SLASH">״</key>
|
||||
<key vkey="K_0">֯</key>
|
||||
<key vkey="K_1">֗</key>
|
||||
<key vkey="K_2">֮</key>
|
||||
<key vkey="K_3">֙</key>
|
||||
<key vkey="K_4">֠</key>
|
||||
<key vkey="K_5">֩</key>
|
||||
<key vkey="K_6">֟</key>
|
||||
<key vkey="K_7">֡</key>
|
||||
<key vkey="K_8">֕</key>
|
||||
<key vkey="K_9">֓</key>
|
||||
<key vkey="K_COLON">׃</key>
|
||||
<key vkey="K_EQUAL">◌</key>
|
||||
<key vkey="K_LBRKT">֔</key>
|
||||
<key vkey="K_BKSLASH">֫</key>
|
||||
<key vkey="K_RBRKT">֒</key>
|
||||
<key vkey="K_A">ֲ</key>
|
||||
<key vkey="K_E">ֱ</key>
|
||||
<key vkey="K_G">•</key>
|
||||
<key vkey="K_H">̊</key>
|
||||
<key vkey="K_I">ֽ</key>
|
||||
<key vkey="K_K">ך</key>
|
||||
<key vkey="K_M">ם</key>
|
||||
<key vkey="K_N">ן</key>
|
||||
<key vkey="K_O">ֳ</key>
|
||||
<key vkey="K_P">ף</key>
|
||||
<key vkey="K_X">ץ</key>
|
||||
</layer>
|
||||
</encoding>
|
||||
</visualkeyboard>
|
||||
1646
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/alephwithbeth.keyman-touch-layout
vendored
Normal file
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/help/KeyboardLayout.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/help/KeyboardLayoutAlt.png
vendored
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/help/KeyboardLayoutShift.png
vendored
Normal file
|
After Width: | Height: | Size: 83 KiB |
22
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/help/alephwithbeth.php
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
$pagename = 'Aleph with Beth Keyboard Help';
|
||||
$pagetitle = $pagename;
|
||||
$pagestyle = <<<END
|
||||
.center { display: block; margin-left: auto; margin-right: auto; width: 75%; }
|
||||
END;
|
||||
|
||||
require_once('header.php');
|
||||
?>
|
||||
<body>
|
||||
|
||||
<h1>Start Using Aleph with Beth</h1>
|
||||
|
||||
<h1>Desktop Layout</h1>
|
||||
|
||||
<h2>Base layout</h2>
|
||||
<img src="KeyboardLayout.png" alt="Keyboard layout" class="center">
|
||||
<h2>Layout when shift is pressed</h2>
|
||||
<img src="KeyboardLayoutShift.png" alt="Keyboard layout (when shift key is pressed)" class="center">
|
||||
<h2>Layout when alt is pressed</h2>
|
||||
<img src="KeyboardLayoutAlt.png" alt="Keyboard layout (when alt key is pressed)" class="center">
|
||||
|
||||
22
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/readme.htm
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>alephwithbeth</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Aleph With Beth</h1>
|
||||
|
||||
<p>A Hebrew keyboard designed to intuitively match the standard QWERTY layout.</p>
|
||||
|
||||
<p>© Cameron Briggs</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/welcome/KeyboardLayout.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/welcome/KeyboardLayoutAlt.png
vendored
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/welcome/KeyboardLayoutShift.png
vendored
Normal file
|
After Width: | Height: | Size: 83 KiB |
28
developer/src/kmc-copy/test/fixtures/projects/disorganized/other/welcome/welcome.htm
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Start Using alephwithbeth</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
.center { display: block; margin-left: auto; margin-right: auto; width: 75%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Start Using Aleph With Beth</h1>
|
||||
|
||||
<h1>Keyboard Layouts</h1>
|
||||
|
||||
<h2>Base layout</h2>
|
||||
<img src="KeyboardLayout.png" alt="Keyboard layout" class="center">
|
||||
<h2>Layout when shift is pressed</h2>
|
||||
<img src="KeyboardLayoutShift.png" alt="Keyboard layout (when shift key is pressed)" class="center">
|
||||
<h2>Layout when alt is pressed</h2>
|
||||
<img src="KeyboardLayoutAlt.png" alt="Keyboard layout (when alt key is pressed)" class="center">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import 'mocha';
|
||||
// import * as path from 'path';
|
||||
// import * as fs from 'fs';
|
||||
// import { fileURLToPath } from 'url';
|
||||
// import { assert } from 'chai';
|
||||
import { CopierMessages } from '../src/copier-messages.js';
|
||||
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
|
||||
describe('CopierMessages', function () {
|
||||
it('should have a valid CopierMessages object', function() {
|
||||
return verifyCompilerMessagesObject(CopierMessages, CompilerErrorNamespace.Copier);
|
||||
});
|
||||
|
||||
// it('should generate ERROR_OutputPathAlreadyExists if output path already exists', async function () {
|
||||
// const ag = new AbstractGenerator();
|
||||
// const callbacks = new TestCompilerCallbacks();
|
||||
// const options: GeneratorOptions = {
|
||||
// id: 'ERROR_OutputPathAlreadyExists',
|
||||
// outPath: path.dirname(fileURLToPath(import.meta.url)),
|
||||
// };
|
||||
// assert(await ag.init(callbacks, options));
|
||||
// const dir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'ERROR_OutputPathAlreadyExists');
|
||||
// if(!fs.existsSync(dir))
|
||||
// fs.mkdirSync(dir);
|
||||
// assert.isFalse(await ag.write({}));
|
||||
// assert.isTrue(callbacks.hasMessage(CopierMessages.ERROR_OutputPathAlreadyExists),
|
||||
// `messageId ERROR_OutputPathAlreadyExists not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
|
||||
// fs.rmdirSync(dir);
|
||||
// });
|
||||
|
||||
// it('should generate ERROR_CannotWriteOutputFile if it cannot create a folder, e.g. invalid filename', async function () {
|
||||
// const ag = new AbstractGenerator();
|
||||
// const callbacks = new TestCompilerCallbacks();
|
||||
// const options: GeneratorOptions = {
|
||||
// id: 'ERROR_CannotWriteOutputFile',
|
||||
// outPath: path.dirname(fileURLToPath(import.meta.url)),
|
||||
// };
|
||||
// assert(await ag.init(callbacks, options));
|
||||
// assert.isFalse(await ag.write({
|
||||
// '.': {filename: '.', data: new Uint8Array([1,2,3])}
|
||||
// }));
|
||||
// assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_CannotWriteOutputFile),
|
||||
// `messageId ERROR_CannotWriteOutputFile not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
// });
|
||||
});
|
||||
|
|
@ -54,7 +54,8 @@
|
|||
"exclude-after-remap": true,
|
||||
"exclude": [
|
||||
"test/",
|
||||
"src/template/"
|
||||
"src/template/",
|
||||
"src/main.ts"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
|||
|
|
@ -53,11 +53,14 @@ export interface GeneratorOptions /* not inheriting from CompilerBaseOptions */
|
|||
* name of the author of the keyboard
|
||||
*/
|
||||
author?: string;
|
||||
/**
|
||||
|
||||
// TODO-GENERATE: icon support
|
||||
/* *
|
||||
* set to true to generate an icon for a Keyman keyboard with the first
|
||||
* characters of the first specified BCP 47 tag
|
||||
*/
|
||||
icon?: boolean;
|
||||
// icon?: boolean;
|
||||
|
||||
/**
|
||||
* description of the keyboard, Markdown permissible
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { KeymanTargets } from "@keymanapp/common-types";
|
||||
import KEYMAN_VERSION from "@keymanapp/keyman-version";
|
||||
import { AbstractGenerator, GeneratorArtifacts } from "./abstract-generator.js";
|
||||
import { GeneratorMessages } from "./generator-messages.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
|
@ -28,7 +29,19 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
this.languageTags = this.options.languageTags.length
|
||||
? this.options.languageTags.map(tag => new Intl.Locale(tag).minimize().toString())
|
||||
: [this.DEFAULT_LOCALE];
|
||||
//TODO-GENERATE: validate targets
|
||||
|
||||
// Verify that targets passed in are valid
|
||||
let failed = false;
|
||||
for(const target of this.options.targets) {
|
||||
if(!KeymanTargets.AllKeymanTargets.includes(target)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidTarget({target}));
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
if(failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.options.targets.includes(KeymanTargets.KeymanTarget.any) || this.options.targets.length == 0) {
|
||||
this.resolvedTargets = KeymanTargets.AllKeymanTargets.filter(target => target != KeymanTargets.KeymanTarget.any);
|
||||
} else {
|
||||
|
|
@ -50,6 +63,8 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
dt.getDate().toString().padStart(2, '0');
|
||||
this.tokenMap['$PACKAGE_LANGUAGES'] = this.generateLanguageListForPackage();
|
||||
this.tokenMap['$PLATFORMS_DOTLIST_README'] = this.getPlatformDotListForReadme();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected generate(artifacts: GeneratorArtifacts): boolean {
|
||||
|
|
@ -89,6 +104,8 @@ export class BasicGenerator extends AbstractGenerator {
|
|||
sourceFile = this.callbacks.path.join(this.templateBasePath, this.templatePath, sourceFile);
|
||||
|
||||
const sourceData = this.callbacks.loadFile(sourceFile);
|
||||
|
||||
/* c8 ignore next 4 */
|
||||
if(sourceData == null) {
|
||||
// internal error -- source file should be readable
|
||||
throw new Error(`source file ${sourceFile} does not exist`);
|
||||
|
|
|
|||
|
|
@ -17,30 +17,67 @@ const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
|||
* @internal
|
||||
*/
|
||||
export class GeneratorMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(
|
||||
this.FATAL_UnexpectedException, null, o.e ?? 'unknown error'
|
||||
);
|
||||
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`);
|
||||
static INFO_GeneratingProject = SevInfo | 0x0002;
|
||||
static Info_GeneratingProject = (o:{type: string, id: string}) => m(
|
||||
this.INFO_GeneratingProject,
|
||||
`Generating project of type ${def(o.type)} with id ${def(o.id)}`
|
||||
);
|
||||
|
||||
static ERROR_CannotCreateFolder = SevError | 0x0003;
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(this.ERROR_CannotCreateFolder, null,
|
||||
`Unable to create folder ${def(o.folderName)}: ${o.e ?? 'unknown error'}`);
|
||||
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
|
||||
this.ERROR_CannotCreateFolder, null,
|
||||
`Unable to create folder ${def(o.folderName)}: ${o.e ?? 'unknown error'}`
|
||||
);
|
||||
|
||||
static ERROR_OutputPathAlreadyExists = SevError | 0x0004;
|
||||
static Error_OutputPathAlreadyExists = (o:{outPath:string}) => m(
|
||||
this.ERROR_OutputPathAlreadyExists,
|
||||
`Output path ${def(o.outPath)} already exists, not overwriting`);
|
||||
`Output path ${def(o.outPath)} already exists, not overwriting`
|
||||
);
|
||||
|
||||
static ERROR_CannotWriteOutputFile = SevError | 0x0005;
|
||||
static Error_CannotWriteOutputFile = (o:{filename:string, e: any}) => CompilerMessageSpecWithException(this.ERROR_CannotWriteOutputFile, null,
|
||||
`Unable to write file ${def(o.filename)}: ${o.e ?? 'unknown error'}`);
|
||||
static Error_CannotWriteOutputFile = (o:{filename:string, e: any}) => CompilerMessageSpecWithException(
|
||||
this.ERROR_CannotWriteOutputFile, null,
|
||||
`Unable to write file ${def(o.filename)}: ${o.e ?? 'unknown error'}`
|
||||
);
|
||||
|
||||
// See also PackageCompilerMessages.WARN_PackageNameDoesNotFollowLexicalModelConventions
|
||||
static WARN_ModelIdDoesNotFollowLexicalModelConventions = SevWarn | 0x0006;
|
||||
static Warn_ModelIdDoesNotFollowLexicalModelConventions = (o:{id: string}) => m(this.WARN_ModelIdDoesNotFollowLexicalModelConventions,
|
||||
static Warn_ModelIdDoesNotFollowLexicalModelConventions = (o:{id: string}) => m(
|
||||
this.WARN_ModelIdDoesNotFollowLexicalModelConventions,
|
||||
`The id ${def(o.id)} does not follow the recommended model id conventions. The id should be all lower case, `+
|
||||
`include only alphanumeric characters and underscore (_), not start with a digit, and should have the structure `+
|
||||
`<author>.<bcp47>.<uniq>`);
|
||||
`<author>.<bcp47>.<uniq>`
|
||||
);
|
||||
|
||||
static ERROR_InvalidTarget = SevError | 0x0007;
|
||||
static Error_InvalidTarget = (o:{target:string}) => m(
|
||||
this.ERROR_InvalidTarget,
|
||||
`The specified target platform '${def(o.target)}' is not a valid target.`
|
||||
);
|
||||
|
||||
// See also copier-messages.ERROR_InvalidKeyboardId
|
||||
static ERROR_InvalidKeymanKeyboardId = SevError | 0x0008;
|
||||
static Error_InvalidKeymanKeyboardId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidKeymanKeyboardId,
|
||||
`The specified keyboard id '${def(o.id)}' contains characters that are not permitted for a .kmn keyboard id or filename.`
|
||||
);
|
||||
|
||||
static ERROR_InvalidLdmlKeyboardId = SevError | 0x0009;
|
||||
static Error_InvalidLdmlKeyboardId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidLdmlKeyboardId,
|
||||
`The specified keyboard id '${def(o.id)}' contains characters that are not permitted for an LDML .xml keyboard id or filename.`
|
||||
);
|
||||
|
||||
// See also copier-messages.ERROR_InvalidLexicalModelId
|
||||
static ERROR_InvalidLexicalModelId = SevError | 0x000A;
|
||||
static Error_InvalidLexicalModelId = (o:{id:string}) => m(
|
||||
this.ERROR_InvalidLexicalModelId,
|
||||
`The specified lexical model id '${def(o.id)}' contains characters that are not permitted or does not match the required pattern of 'author.bcp47.uniq'.`
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes, KeymanTargets } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, ValidIds, } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
@ -39,7 +40,15 @@ export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCom
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidKeymanKeyboardId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidKeymanKeyboardId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
|
||||
|
|
@ -63,9 +72,10 @@ export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCom
|
|||
KeymanKeyboardGenerator.SPath_Source+this.options.id+KeymanFileTypes.Source.TouchLayout;
|
||||
}
|
||||
|
||||
if(this.hasIcon()) {
|
||||
this.includedPrefixes.push('Icon');
|
||||
}
|
||||
// TODO-GENERATE: icon support
|
||||
// if(this.hasIcon()) {
|
||||
// this.includedPrefixes.push('Icon');
|
||||
// }
|
||||
|
||||
if(this.hasKMX()) {
|
||||
this.includedPrefixes.push('KMX');
|
||||
|
|
@ -82,9 +92,9 @@ export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCom
|
|||
|
||||
// Special case for creating icon, run after successful creation of other
|
||||
// project bits and pieces
|
||||
if(this.hasIcon()) {
|
||||
this.writeIcon(artifacts);
|
||||
}
|
||||
// if(this.hasIcon()) {
|
||||
// this.writeIcon(artifacts);
|
||||
// }
|
||||
|
||||
return {artifacts};
|
||||
}
|
||||
|
|
@ -98,13 +108,13 @@ export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCom
|
|||
private readonly hasKMX = () => this.targetIncludes(KeymanTargets.KMXKeymanTargets);
|
||||
private readonly hasTouchLayout = () => this.targetIncludes(KeymanTargets.TouchKeymanTargets);
|
||||
|
||||
// TODO-GENERATE, once writeIcon is implemented:
|
||||
// TODO-GENERATE: icon support
|
||||
// hasIcon = () => this.options.icon && this.targetIncludes(KeymanTargets.KMXKeymanTargets);
|
||||
private readonly hasIcon = () => false;
|
||||
// private readonly hasIcon = () => false;
|
||||
|
||||
private writeIcon(artifacts: GeneratorArtifacts) {
|
||||
// TODO-GENERATE: this will require some effort
|
||||
// proposal: generate 16x16 icon with 2-3 letters. Following TKeyboardIconGenerator.GenerateIcon
|
||||
// research for .ico writer in node
|
||||
}
|
||||
// TODO-GENERATE: icon support
|
||||
// private writeIcon(artifacts: GeneratorArtifacts) {
|
||||
// proposal: generate 16x16 icon with 2-3 letters. Following TKeyboardIconGenerator.GenerateIcon
|
||||
// research for .ico writer in node
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, ValidIds, } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
|
@ -28,7 +29,15 @@ export class LdmlKeyboardGenerator extends BasicGenerator implements KeymanCompi
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidLdmlKeyboardId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidLdmlKeyboardId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
this.templatePath = 'ldml-keyboard';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { KeymanCompiler, SourceFilenamePatterns } from '@keymanapp/developer-utils';
|
||||
import { KeymanCompiler, SourceFilenamePatterns, ValidIds } from '@keymanapp/developer-utils';
|
||||
import { GeneratorArtifacts, GeneratorResult } from './abstract-generator.js';
|
||||
import { BasicGenerator } from './basic-generator.js';
|
||||
import { GeneratorMessages } from './generator-messages.js';
|
||||
|
|
@ -31,7 +31,15 @@ export class LexicalModelGenerator extends BasicGenerator implements KeymanCompi
|
|||
* @returns Binary artifacts on success, null on failure.
|
||||
*/
|
||||
async run(): Promise<GeneratorResult> {
|
||||
this.preGenerate();
|
||||
if(!ValidIds.isValidLexicalModelId(this.options.id)) {
|
||||
this.callbacks.reportMessage(GeneratorMessages.Error_InvalidLexicalModelId({id:this.options.id}));
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!this.preGenerate()) {
|
||||
// errors will have been reported in preGenerate
|
||||
return null;
|
||||
}
|
||||
|
||||
const artifacts: GeneratorArtifacts = this.defaultArtifacts();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,22 +2,71 @@
|
|||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { AbstractGenerator, GeneratorArtifacts } from '../src/abstract-generator.js';
|
||||
import { options } from './shared-options.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
|
||||
// this class is here to make it possible to access protected members
|
||||
class BasicGeneratorTest extends BasicGenerator {
|
||||
static files() {
|
||||
return [
|
||||
'sample/' + AbstractGenerator.SFile_WelcomeHTM,
|
||||
'sample/' + AbstractGenerator.SFile_ReadmeHTM,
|
||||
'sample/' + AbstractGenerator.SFile_HistoryMD,
|
||||
'sample/' + AbstractGenerator.SFile_LicenseMD,
|
||||
'sample/' + AbstractGenerator.SFile_ReadmeMD,
|
||||
'sample/' + AbstractGenerator.SFile_GitIgnore,
|
||||
];
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.templatePath = 'kmn-keyboard';
|
||||
}
|
||||
}
|
||||
|
||||
describe('BasicGenerator', function () {
|
||||
it('should configure default settings', async function() {
|
||||
const bg = new BasicGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert(await bg.init(callbacks, options));
|
||||
bg.test_preGenerate();
|
||||
assert.isTrue(await bg.init(callbacks, options));
|
||||
assert.isTrue(bg.test_preGenerate());
|
||||
|
||||
assert.equal(bg.test_tokenMap['$NAME'], 'Sample Project');
|
||||
});
|
||||
|
||||
// TODO-GENERATE: test transform
|
||||
it('should successfully transform common project files', async function() {
|
||||
const bg = new BasicGeneratorTest();
|
||||
bg.setup();
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await bg.init(callbacks, options));
|
||||
assert.isTrue(bg.test_preGenerate());
|
||||
|
||||
const artifacts: GeneratorArtifacts = { "kmc-generate:outputPath": { data: null, filename: '' } };
|
||||
assert.isTrue(bg.test_generate(artifacts));
|
||||
|
||||
// standard artifacts are listed in AbstractGenerator
|
||||
const keys = [].concat(
|
||||
'kmc-generate:outputPath',
|
||||
BasicGeneratorTest.files().map(f => f.replaceAll('/', path.sep))
|
||||
);
|
||||
|
||||
assert.hasAllKeys(artifacts, keys);
|
||||
|
||||
// pick one file and compare it
|
||||
const filename = BasicGeneratorTest.files()[0].replaceAll('/', path.sep); // welcome.htm
|
||||
assert.equal(artifacts[filename].filename, filename);
|
||||
const fixturePath = makePathToFixture('basic', 'welcome.htm');
|
||||
const fixture = fs.readFileSync(fixturePath, 'utf-8').replace(/\r\n/g, '\n');
|
||||
const actual = new TextDecoder().decode(artifacts[filename].data);
|
||||
assert.equal(actual, fixture);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
26
developer/src/kmc-generate/test/fixtures/basic/welcome.htm
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Start Using Sample Project</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Start Using Sample Project</h1>
|
||||
|
||||
<p>
|
||||
# A mighty description
|
||||
</p>
|
||||
|
||||
<h1>Keyboard Layout</h1>
|
||||
|
||||
<!-- Insert Keyboard Layout Images or HTML here -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,15 +2,24 @@
|
|||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { assert } from 'chai';
|
||||
import { GeneratorMessages } from '../src/generator-messages.js';
|
||||
|
||||
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
|
||||
import { GeneratorMessages } from '../src/generator-messages.js';
|
||||
import { AbstractGenerator, GeneratorOptions } from '../src/abstract-generator.js';
|
||||
import { BasicGenerator } from '../src/basic-generator.js';
|
||||
import { KeymanKeyboardGenerator } from '../src/keyman-keyboard-generator.js';
|
||||
import { LdmlKeyboardGenerator } from '../src/ldml-keyboard-generator.js';
|
||||
import { LexicalModelGenerator } from '../src/lexical-model-generator.js';
|
||||
|
||||
import { options } from './shared-options.js';
|
||||
|
||||
describe('GeneratorMessages', function () {
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
|
@ -62,4 +71,49 @@ describe('GeneratorMessages', function () {
|
|||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_CannotWriteOutputFile),
|
||||
`messageId ERROR_CannotWriteOutputFile not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidTarget if an invalid target is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
// @ts-ignore ('invalid' could be passed by a user, even if would be a compile error here)
|
||||
targets: ['invalid']
|
||||
};
|
||||
const bg = new BasicGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert(await bg.init(callbacks, testOptions));
|
||||
assert.isFalse(bg.test_preGenerate());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidTarget));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidKeymanKeyboardId if an invalid keyboard id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: '???invalid',
|
||||
};
|
||||
const kkg = new KeymanKeyboardGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await kkg.init(callbacks, testOptions));
|
||||
assert.isNull(await kkg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidKeymanKeyboardId));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidLdmlKeyboardId if an invalid keyboard id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: '???invalid',
|
||||
};
|
||||
const lkg = new LdmlKeyboardGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await lkg.init(callbacks, testOptions));
|
||||
assert.isNull(await lkg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLdmlKeyboardId));
|
||||
});
|
||||
|
||||
it('should generate ERROR_InvalidLexicalModelId if an invalid lexical model id is passed in', async function () {
|
||||
const testOptions: GeneratorOptions = { ...options,
|
||||
id: 'example.???.invalid',
|
||||
};
|
||||
const lmg = new LexicalModelGenerator();
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
assert.isTrue(await lmg.init(callbacks, testOptions));
|
||||
assert.isNull(await lmg.run());
|
||||
assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLexicalModelId));
|
||||
});
|
||||
});
|
||||
|
|
@ -6,7 +6,8 @@ import { KeymanTargets } from "@keymanapp/common-types";
|
|||
import { GeneratorOptions } from "../src/abstract-generator.js";
|
||||
|
||||
export const options: GeneratorOptions = {
|
||||
icon: false,
|
||||
// TODO-GENERATE: icon support
|
||||
// icon: false,
|
||||
id: 'sample',
|
||||
languageTags: ['en'],
|
||||
name: 'Sample Project',
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { CompilerCallbacks, KeymanCompiler, CompilerOptions, KeymanCompilerResul
|
|||
import { KmpCompiler } from "@keymanapp/kmc-package";
|
||||
|
||||
import { SchemaValidators } from "@keymanapp/common-types";
|
||||
import { getFontFamily } from "./font-family.js";
|
||||
import { getFontFamily } from "@keymanapp/developer-utils";
|
||||
|
||||
const regionNames = new Intl.DisplayNames(['en'], { type: "region" });
|
||||
const scriptNames = new Intl.DisplayNames(['en'], { type: "script" });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|||
import { assert } from 'chai';
|
||||
import 'mocha';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { getFontFamily } from '../src/font-family.js';
|
||||
import { getFontFamily } from '@keymanapp/developer-utils';
|
||||
|
||||
const AFGHAN_TURKMEN_DISPLAY_FONT = makePathToFixture('afghan_turkmen', "Lateef-Regular.ttf");
|
||||
const AFGHAN_TURKMEN_OSK_FONT = makePathToFixture('afghan_turkmen', "Lateef-Bold.ttf");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/ttfmeta/lib/*.js",
|
||||
"src/imports/langtags.js",
|
||||
],
|
||||
"references": [
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
|
||||
switch(recContext.Code) {
|
||||
case KMX.KMXFile.CODE_ANY:
|
||||
Index = AdjustIndex(fkp.dpContext, ContextIndex) + 1; // I3910 // I4611
|
||||
Index = AdjustIndex(fkp.dpContext, ContextIndex); // I3910 // I4611
|
||||
Result += nlt + `k.KIO(${len},this.s${JavaScript_Name(recContext.Any.StoreIndex, recContext.Any.Store.dpName)},${Index},t);`; // I4611
|
||||
break;
|
||||
case KMX.KMXFile.CODE_DEADKEY:
|
||||
|
|
@ -681,7 +681,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
// Note that this is checked in compiler.cpp as well, so this error can probably never occur
|
||||
throw new Error('Unexpected: notany() encountered with invalid version');
|
||||
}
|
||||
Result += nlt + `k.KCXO(${len},t,${AdjustIndex(fkp.dpContext, xstrlen(fkp.dpContext))},${AdjustIndex(fkp.dpContext, ContextIndex)+1});`;
|
||||
Result += nlt + `k.KCXO(${len},t,${AdjustIndex(fkp.dpContext, xstrlen(fkp.dpContext))},${AdjustIndex(fkp.dpContext, ContextIndex)});`;
|
||||
break;
|
||||
case KMX.KMXFile.CODE_IFOPT:
|
||||
case KMX.KMXFile.CODE_IFSYSTEMSTORE:
|
||||
|
|
@ -759,7 +759,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
case KMX.KMXFile.CODE_CONTEXT:
|
||||
if (x > 0 || len == -1) {
|
||||
let xContext = 0;
|
||||
let n = 0;
|
||||
let n = 1;
|
||||
while(xContext < fkp.dpContext.length) { // I4611
|
||||
if(!isGroupReadOnly(fk, fgp)) {
|
||||
Result += ContextChar(n, fkp.dpContext, xContext);
|
||||
|
|
@ -767,7 +767,6 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
n++;
|
||||
xContext = incxstr(fkp.dpContext, xContext);
|
||||
}
|
||||
//Result := Result + Format('k.KO(%d,t,k.KC(%d,%d,t));', [len, xstrlen_printing(fkp.dpContext), xstrlen_printing(fkp.dpContext)]);
|
||||
}
|
||||
// else, we don't need to output anything - just don't delete the context
|
||||
len = -1;
|
||||
|
|
@ -779,7 +778,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
|
|||
}
|
||||
|
||||
if(!isGroupReadOnly(fk, fgp)) {
|
||||
Result += ContextChar(rec.ContextEx.Index, fkp.dpContext, xContext); // I4611
|
||||
Result += ContextChar(rec.ContextEx.Index + 1, fkp.dpContext, xContext); // I4611
|
||||
}
|
||||
len = -1;
|
||||
break;
|
||||
|
|
|
|||
29
developer/src/kmc-kmn/test/fixtures/kmw/test_index_12980.kmn
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
c test_index_12980 generated from template at 2025-01-22 13:25:35
|
||||
c Validates index() offsets when used with if() - see #12980
|
||||
|
||||
store(&VERSION) '14.0'
|
||||
store(&NAME) 'test_index_12980'
|
||||
store(&TARGETS) 'web'
|
||||
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
||||
store(a) '1'
|
||||
store(b) '2'
|
||||
store(c) '3'
|
||||
store(d) '4'
|
||||
store(e) '5'
|
||||
store(f) '6'
|
||||
store(g) '7'
|
||||
store(h) '8'
|
||||
|
||||
if(&platform = 'web') any(a) + 'a' > context 'A'
|
||||
if(&platform = 'web') any(b) + 'b' > context(2) 'B'
|
||||
if(&platform = 'web') notany(c) + 'c' > context 'C'
|
||||
if(&platform = 'web') notany(d) + 'd' > context(2) 'D'
|
||||
nul any(e) + 'e' > context 'E'
|
||||
nul any(f) + 'f' > context(2) 'F'
|
||||
nul notany(g) + 'g' > context 'G'
|
||||
nul notany(h) + 'h' > context(2) 'H'
|
||||
|
|
@ -218,6 +218,30 @@ describe('KeymanWeb Compiler', function() {
|
|||
assert.lengthOf(callbacks.messages, 2);
|
||||
});
|
||||
|
||||
it('should generate correct index offsets (#12980) for context and context(n) when building a KMW keyboard', async function() {
|
||||
const filenames = generateTestFilenames('test_index_12980');
|
||||
|
||||
let result = await kmnCompiler.run(filenames.source, null);
|
||||
assert.isNotNull(result);
|
||||
const data = new TextDecoder('utf-8').decode(result.artifacts.js.data);
|
||||
|
||||
function doMatchKIO(s: string) {
|
||||
const regex = new RegExp('k\\.KIO\\(-1,this\\.'+s+',(.+?),t\\);');
|
||||
const m = regex.exec(data);
|
||||
assert.isNotNull(m, `Could not find match for ${s}`);
|
||||
assert.equal(m[1], '1', `Expected '${s}' usage to have an index parameter == 1`);
|
||||
}
|
||||
|
||||
doMatchKIO('s_a_6');
|
||||
doMatchKIO('s_b_7');
|
||||
|
||||
// matches the 4 notany() rules
|
||||
const matches = [...data.matchAll(/k\.KCXO\(-1,t,1,1\);/g)];
|
||||
assert.lengthOf(matches, 4);
|
||||
|
||||
doMatchKIO('s_e_10');
|
||||
doMatchKIO('s_f_11');
|
||||
});
|
||||
});
|
||||
|
||||
async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import { PackageKeyboardTargetValidator } from './package-keyboard-target-valida
|
|||
import { PackageMetadataUpdater } from './package-metadata-updater.js';
|
||||
import { markdownToHTML } from './markdown.js';
|
||||
import { PackageValidation } from './package-validation.js';
|
||||
import { getFontFamilySync } from "@keymanapp/developer-utils";
|
||||
import { setKvkFontData } from './kvk-font-facename.js';
|
||||
|
||||
const KMP_JSON_FILENAME = 'kmp.json';
|
||||
const KMP_INF_FILENAME = 'kmp.inf';
|
||||
|
|
@ -483,36 +485,36 @@ export class KmpCompiler implements KeymanCompiler {
|
|||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_AbsolutePath({filename: filename}));
|
||||
}
|
||||
|
||||
let memberFileData = this.getMemberFileData(kpsFilename, filename);
|
||||
if(!memberFileData) {
|
||||
failed = true;
|
||||
return;
|
||||
}
|
||||
filename = this.callbacks.resolveFilename(kpsFilename, filename);
|
||||
const basename = this.callbacks.path.basename(filename);
|
||||
|
||||
if(!this.callbacks.fs.existsSync(filename)) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileDoesNotExist({filename: filename}));
|
||||
failed = true;
|
||||
return;
|
||||
if(KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Binary.VisualKeyboard)) {
|
||||
if(!this.isKvkFileBinary(memberFileData)) {
|
||||
// warn the few users who are still doing this -- non-binary .kvk should not be included in package!
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename}));
|
||||
} else {
|
||||
memberFileData = this.setKvkFontData(kpsFilename, data, filename, memberFileData);
|
||||
}
|
||||
}
|
||||
|
||||
let memberFileData;
|
||||
try {
|
||||
memberFileData = this.callbacks.loadFile(filename);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileCouldNotBeRead({filename: filename, e: e}));
|
||||
failed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.warnIfKvkFileIsNotBinary(filename, memberFileData);
|
||||
|
||||
zip.file(basename, memberFileData);
|
||||
|
||||
// Remove path data from files before JSON save
|
||||
value.name = basename;
|
||||
});
|
||||
|
||||
if(failed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
data.files.forEach((value) => {
|
||||
const basename = this.callbacks.path.basename(value.name);
|
||||
// Remove path data from files before JSON save
|
||||
value.name = basename;
|
||||
});
|
||||
|
||||
// TODO #9477: transform .md to .htm
|
||||
|
||||
// Remove path data from file references in options
|
||||
|
|
@ -557,26 +559,87 @@ export class KmpCompiler implements KeymanCompiler {
|
|||
return transcodeToCP1252(s);
|
||||
}
|
||||
|
||||
private getMemberFileData(kpsFilename: string, filename: string) {
|
||||
filename = this.callbacks.resolveFilename(kpsFilename, filename);
|
||||
|
||||
if(!this.callbacks.fs.existsSync(filename)) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileDoesNotExist({filename: filename}));
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.callbacks.loadFile(filename);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Error_FileCouldNotBeRead({filename: filename, e: e}));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private setKvkFontData(kpsFilename: string, data: KmpJsonFile.KmpJsonFile, filename: string, kvk: Uint8Array) {
|
||||
// find the appropriate font to set
|
||||
const kvkId = this.callbacks.path.basename(filename.toLowerCase(), '.kvk');
|
||||
const kbd = data.keyboards.find(k => k.id == kvkId);
|
||||
if(!kbd) {
|
||||
// cannot find a matching keyboard
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindMatchingKeyboardForVisualKeyboard({filename}));
|
||||
return kvk;
|
||||
}
|
||||
const fontFilename = kbd.oskFont || kbd.displayFont;
|
||||
if(!fontFilename) {
|
||||
// no font data to set
|
||||
return kvk;
|
||||
}
|
||||
|
||||
// Look up the full font filename
|
||||
const fontFile = data.files.find(file => this.callbacks.path.basename(file.name.toLowerCase()) == fontFilename.toLowerCase());
|
||||
if(!fontFile) {
|
||||
// font cannot be found
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindFontForKeyboard({id: kbd.id, fontFilename}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
// the font is a filename, included in the .kps
|
||||
const fontData = this.getMemberFileData(kpsFilename, fontFile.name);
|
||||
if(!fontData) {
|
||||
// cannot find source font
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotFindFontForKeyboard({id: kbd.id, fontFilename: fontFile.name}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
const fontFacename = getFontFamilySync(fontData);
|
||||
if(!fontFacename) {
|
||||
// Font facename could not be extracted from ttf
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_CannotReadFont({fontFilename: fontFile.name}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
const result = setKvkFontData(kvk, fontFacename);
|
||||
if(!result) {
|
||||
// KVK is invalid
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_VisualKeyboardFileIsInvalid({filename}));
|
||||
return kvk;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy .kmp compiler would transform xml-format .kvk files into a binary .kvk file; now
|
||||
* we want that to remain the responsibility of the keyboard compiler, so we'll warn the
|
||||
* few users who are still doing this
|
||||
* we want that to remain the responsibility of the keyboard compiler.
|
||||
*/
|
||||
private warnIfKvkFileIsNotBinary(filename: string, data: Uint8Array) {
|
||||
if(!KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Binary.VisualKeyboard)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private isKvkFileBinary(data: Uint8Array) {
|
||||
if(data.byteLength < 4) {
|
||||
// TODO: Not a valid .kvk file; should we be reporting this?
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must start with 'KVKF'
|
||||
if(data[0] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[0] ||
|
||||
data[1] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[1] ||
|
||||
data[2] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[2] ||
|
||||
data[3] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[3]) {
|
||||
this.callbacks.reportMessage(PackageCompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||