From aa9788ad46fc3976cfdc59f453e5e9a19efa27fe Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 11 Aug 2023 10:34:38 +0700 Subject: [PATCH 001/207] chore(common): remove deprecated fields from keyboard_info Removes the following fields: * legacyId * documentationFilename * documentationFileSize * links * related[].note None of these fields are needed any longer; see #9351 for steps to remove data. Eliminates difference between keyboard_info source and distribution, renaming to keyboard_info.schema.json, and updating the required members of the json accordingly. Will have corresponding commits in keyman.com, api.keyman.com, help.keyman.com, and keyboards repositories. --- common/schemas/keyboard_info/README.md | 9 ++++---- .../keyboard_info.distribution.json | 8 ------- ....source.json => keyboard_info.schema.json} | 21 ++----------------- developer/src/inst/download.in.mak | 5 ++--- developer/src/inst/kmdev.wxs | 6 +----- developer/src/kmc-keyboard-info/src/index.ts | 7 +++---- .../src/keyboard-info-file.ts | 14 ++----------- .../src/tike/compile/MergeKeyboardInfo.pas | 4 +--- .../src/tike/compile/ValidateKeyboardInfo.pas | 2 ++ 9 files changed, 18 insertions(+), 58 deletions(-) delete mode 100644 common/schemas/keyboard_info/keyboard_info.distribution.json rename common/schemas/keyboard_info/{keyboard_info.source.json => keyboard_info.schema.json} (91%) diff --git a/common/schemas/keyboard_info/README.md b/common/schemas/keyboard_info/README.md index 3222a465a8c..d59febe3732 100644 --- a/common/schemas/keyboard_info/README.md +++ b/common/schemas/keyboard_info/README.md @@ -1,18 +1,19 @@ # keyboard_info -* **keyboard_info.source.json** -* **keyboard_info.distribution.json** +* **keyboard_info.schema.json** Documentation at https://help.keyman.com/developer/cloud/keyboard_info * Primary version: - * https://github.com/keymanapp/api.keyman.com/tree/master/schemas/keyboard_info.source - * https://github.com/keymanapp/api.keyman.com/tree/master/schemas/keyboard_info.distribution + * https://github.com/keymanapp/api.keyman.com/tree/master/schemas/keyboard_info * Synchronized copies at: * https://github.com/keymanapp/keyman/tree/master/common/schemas/keyboard_info # .keyboard_info version history +## 2023-08-11 2.0 stable +* Removed legacyId, documentationFilename, documentationFileSize. Source vs distribution keyboard_info distinction is removed. + ## 2019-09-06 1.0.6 stable * No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). diff --git a/common/schemas/keyboard_info/keyboard_info.distribution.json b/common/schemas/keyboard_info/keyboard_info.distribution.json deleted file mode 100644 index 3d3d4ee0c86..00000000000 --- a/common/schemas/keyboard_info/keyboard_info.distribution.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "allOf": [ - { "$ref": "/keyboard_info.source.json#/definitions/KeyboardInfo" }, - { "required": [ "id", "name", "license", "languages", "lastModifiedDate", "platformSupport" ] } - ] -} diff --git a/common/schemas/keyboard_info/keyboard_info.source.json b/common/schemas/keyboard_info/keyboard_info.schema.json similarity index 91% rename from common/schemas/keyboard_info/keyboard_info.source.json rename to common/schemas/keyboard_info/keyboard_info.schema.json index ac2687701ff..4ad6f67a292 100644 --- a/common/schemas/keyboard_info/keyboard_info.source.json +++ b/common/schemas/keyboard_info/keyboard_info.schema.json @@ -17,13 +17,10 @@ { "$ref": "#/definitions/KeyboardLanguageInfo" } ]}, "lastModifiedDate": { "type": "string", "format": "date-time" }, - "links": { "type": "array", "items": { "$ref": "#/definitions/KeyboardLinkInfo" } }, "packageFilename": { "type": "string", "pattern": "\\.km[xp]$" }, "packageFileSize": { "type": "number" }, "jsFilename": { "type": "string", "pattern": "\\.js$" }, "jsFileSize": { "type": "number" }, - "documentationFilename": { "type": "string", "pattern": "\\.(rtf|html?|pdf|txt)$" }, - "documentationFileSize": { "type": "number" }, "isRTL": { "type": "boolean" }, "encodings": { "type": "array", "items": { "type": "string", "enum": ["ansi", "unicode"] }, "additionalItems": false }, "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["welcome", "documentation", "fonts", "visualKeyboard"] }, "additionalItems": false }, @@ -31,7 +28,6 @@ "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.\\d$" }, "helpLink": { "type": "string", "pattern": "^https://help\\.keyman\\.com/keyboard/" }, "platformSupport": { "$ref": "#/definitions/KeyboardPlatformInfo" }, - "legacyId": { "type": "number" }, "sourcePath": { "type": "string", "pattern": "^(release|legacy|experimental)/.+/.+$" }, "related": { "type": "object", "patternProperties": { ".": { "$ref": "#/definitions/KeyboardRelatedInfo" } @@ -40,9 +36,7 @@ }, "deprecated": { "type": "boolean" } }, - "required": [ - "license", "languages" - ] + "required": [ "id", "name", "license", "languages", "lastModifiedDate", "platformSupport" ] }, "KeyboardLanguageInfo": { @@ -140,16 +134,6 @@ "additionalProperties": false }, - "KeyboardLinkInfo": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "url": { "type": "string" } - }, - "required": ["name", "url"], - "additionalProperties": false - }, - "KeyboardPlatformInfo": { "type": "object", "patternProperties": { @@ -163,8 +147,7 @@ "type": "object", "properties": { "deprecates": { "type": "boolean" }, - "deprecatedBy": { "type": "boolean" }, - "note": { "type": "string" } + "deprecatedBy": { "type": "boolean" } }, "required": [], "additionalProperties": false diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index bed37b413ff..2bda1ac4bbd 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -119,7 +119,7 @@ make-kmcomp-install-zip: copy-schemas kmconvert.exe \ sentry.dll sentry.x64.dll \ kmdecomp.exe \ - keyboard_info.source.json keyboard_info.distribution.json \ + keyboard_info.schema.json \ keyman-touch-layout.spec.json keyman-touch-layout.clean.spec.json \ xml\layoutbuilder\*.keyman-touch-layout \ projects\* \ @@ -130,8 +130,7 @@ make-kmcomp-install-zip: copy-schemas # ldml-keyboard.schema.json ldml-keyboardtest.schema.json \ copy-schemas: - copy $(KEYMAN_ROOT)\common\schemas\keyboard_info\keyboard_info.source.json $(DEVELOPER_ROOT)\bin - copy $(KEYMAN_ROOT)\common\schemas\keyboard_info\keyboard_info.distribution.json $(DEVELOPER_ROOT)\bin + copy $(KEYMAN_ROOT)\common\schemas\keyboard_info\keyboard_info.schema.json $(DEVELOPER_ROOT)\bin copy $(KEYMAN_ROOT)\common\schemas\keyman-touch-layout\keyman-touch-layout.spec.json $(DEVELOPER_ROOT)\bin copy $(KEYMAN_ROOT)\common\schemas\keyman-touch-layout\keyman-touch-layout.clean.spec.json $(DEVELOPER_ROOT)\bin copy $(KEYMAN_ROOT)\common\schemas\displaymap\displaymap.schema.json $(DEVELOPER_ROOT)\bin diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index d43183fd27c..436d532f4f5 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -184,11 +184,7 @@ - - - - - + diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index ca9e8150d9c..ed3e7a1734d 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -126,10 +126,9 @@ export class KeyboardInfoCompiler { }[] = this.loadKmxFiles(sources.kpsFileName, sources.kmpJsonData); // - // Build merged .keyboard_info file - // https://api.keyman.com/schemas/keyboard_info.source.json and - // https://api.keyman.com/schemas/keyboard_info.distribution.json - // https://help.keyman.com/developer/cloud/keyboard_info/1.0 + // Build .keyboard_info file + // https://api.keyman.com/schemas/keyboard_info.schema.json + // https://help.keyman.com/developer/cloud/keyboard_info/2.0 // keyboard_info.isRTL = keyboard_info.isRTL ?? !!jsFile?.match(/this\.KRTL=1/); diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts index b221452bc4e..cd15bff6f50 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts @@ -12,13 +12,10 @@ export interface KeyboardInfoFile { license?: "freeware" | "shareware" | "commercial" | "mit" | "other"; languages?: string[] | {[bcp47: string]: KeyboardInfoFileLanguage}; lastModifiedDate?: string; - links?: KeyboardInfoFileLink[]; packageFilename?: string; packageFileSize?: number; jsFilename?: string; jsFileSize?: number; - documentationFilename?: string; - documentationFileSize?: number; isRTL?: boolean; encodings: KeyboardInfoFileEncodings[]; packageIncludes?: KeyboardInfoFileIncludes[]; @@ -26,21 +23,14 @@ export interface KeyboardInfoFile { minKeymanVersion?: string; helpLink?: string; platformSupport?: {[id in KeyboardInfoFilePlatform]?: KeyboardInfoFilePlatformSupport}; - legacyId?: number; sourcePath?: string; related?: {[id: string]: KeyboardInfoFileRelated}; deprecated?: boolean; } -export interface KeyboardInfoFileLink { - name: string; - url: string; -} - export interface KeyboardInfoFileRelated { - deprecates?: string; - deprecatedBy?: string; - note?: string; + deprecates?: boolean; + deprecatedBy?: boolean; } export interface KeyboardInfoFileLanguage { diff --git a/developer/src/tike/compile/MergeKeyboardInfo.pas b/developer/src/tike/compile/MergeKeyboardInfo.pas index af0bc242f71..aa5360e6bfc 100644 --- a/developer/src/tike/compile/MergeKeyboardInfo.pas +++ b/developer/src/tike/compile/MergeKeyboardInfo.pas @@ -19,7 +19,6 @@ # packageFileSize -- get from the size of the file # jsFilename -- from $keyboard_info_jsFilename # jsFileSize -- get from the size of the file - # documentationFileSize -- get from the size of the file # isRTL -- from .js, KRTL\s*=\s*1 # encodings -- from .kmx (existence of .js implies unicode) # packageIncludes -- from kmp.inf? @@ -727,7 +726,7 @@ procedure TMergeKeyboardInfo.CheckOrAddEncodings; end; // -// packageFileSize, jsFileSize, documentationFileSize, all from the actual files +// packageFileSize, jsFileSize, all from the actual files // procedure TMergeKeyboardInfo.CheckOrAddFileSizes; procedure DoFileSize(prefix: string); @@ -756,7 +755,6 @@ procedure TMergeKeyboardInfo.CheckOrAddFileSizes; begin DoFileSize('js'); DoFileSize('package'); - DoFileSize('documentation'); end; // diff --git a/developer/src/tike/compile/ValidateKeyboardInfo.pas b/developer/src/tike/compile/ValidateKeyboardInfo.pas index 0f0d141020d..5dc3a6a41ad 100644 --- a/developer/src/tike/compile/ValidateKeyboardInfo.pas +++ b/developer/src/tike/compile/ValidateKeyboardInfo.pas @@ -1,5 +1,7 @@ unit ValidateKeyboardInfo; +// TODO: this unit is deprecated + interface uses From 25717d1d1cde15c8751f6c375d1d9c863efc1791 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 11 Aug 2023 14:06:22 +0700 Subject: [PATCH 002/207] chore: fix unit test --- developer/src/test/auto/keyboard-info/test-valid.keyboard_info | 1 + 1 file changed, 1 insertion(+) diff --git a/developer/src/test/auto/keyboard-info/test-valid.keyboard_info b/developer/src/test/auto/keyboard-info/test-valid.keyboard_info index 6fe56a7abb8..0997d5c4ad8 100644 --- a/developer/src/test/auto/keyboard-info/test-valid.keyboard_info +++ b/developer/src/test/auto/keyboard-info/test-valid.keyboard_info @@ -1,4 +1,5 @@ { + "id": "test-valid", "name": "Balochi Phonetic", "jsFilename": "balochi_phonetic-1.1.js", "packageFilename": "balochi_phonetic.kmp", From 675f6bc91efe6762ff3c2990fb9cafdbdecded5f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 09:23:20 +0700 Subject: [PATCH 003/207] feat(developer): description, font and example metadata in packages Adds info.description, options.fonts, keyboard.examples metadata to .kps and kmp.json files, updates schemas, and Typescript compiler. --- common/schemas/kps/kps.xsd | 15 ++ common/web/types/src/package/kmp-json-file.ts | 30 ++++ common/web/types/src/package/kps-file.ts | 33 ++++ .../kmc-package/src/compiler/kmp-compiler.ts | 41 +++-- .../test/fixtures/kmp_2.0/khmer_angkor.js | 1 + .../test/fixtures/kmp_2.0/khmer_angkor.kmx | Bin 0 -> 26280 bytes .../test/fixtures/kmp_2.0/khmer_angkor.kps | 143 ++++++++++++++++++ .../test/fixtures/kmp_2.0/kmp.json | 131 ++++++++++++++++ .../kmc-package/test/test-package-compiler.ts | 28 ++++ 9 files changed, 401 insertions(+), 21 deletions(-) create mode 100644 developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.js create mode 100644 developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kmx create mode 100644 developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps create mode 100644 developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd index e6ee424e28d..e951c3369ff 100644 --- a/common/schemas/kps/kps.xsd +++ b/common/schemas/kps/kps.xsd @@ -74,6 +74,7 @@ + @@ -110,6 +111,7 @@ + @@ -209,4 +211,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 913bc98f06e..6c56b500124 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -19,6 +19,11 @@ export interface KmpJsonFileOptions { executeProgram?: string; msiFilename?: string; msiOptions?: string; + /** + * List of all font files, grouped by font family + * Compiler extracts font metadata from .ttf,.otf,.woff,.woff2 + */ + fonts?: {[family:string]: string[]}; } export interface KmpJsonFileInfo { @@ -27,6 +32,11 @@ export interface KmpJsonFileInfo { name?: KmpJsonFileInfoItem; copyright?: KmpJsonFileInfoItem; author?: KmpJsonFileInfoItem; + /** + * A Markdown description of the keyboard, intended for use in websites + * referencing the keyboard + */ + description?: KmpJsonFileInfoItem; } export interface KmpJsonFileInfoItem { @@ -59,6 +69,7 @@ export interface KmpJsonFileKeyboard { displayFont?: string; rtl?: boolean; languages?: KmpJsonFileLanguage[]; + examples?: KmpJsonFileExample[]; } export interface KmpJsonFileStartMenu { @@ -74,3 +85,22 @@ export interface KmpJsonFileStartMenuItem { icon?: string; location?: string; } + +export interface KmpJsonFileExample { + /** + * BCP 47 identifier for the example + */ + id: string; + /** + * A space-separated list of keys, modifiers indicated with "+", spacebar is "space", plus key is "shift+=" or "plus" + */ + keys: string; + /** + * The text that would be generated by typing those keys + */ + text?: string; + /** + * A short description of what the text means or represents + */ + note?: string; +} \ No newline at end of file diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 7ff77d09846..ca854888db3 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -51,6 +51,7 @@ export interface KpsFileInfo { author?: KpsFileInfoItem; webSite?: KpsFileInfoItem; version?: KpsFileInfoItem; + description?: KpsFileInfoItem; } export interface KpsFileInfoItem { @@ -64,8 +65,11 @@ export interface KpsFileContentFiles { export interface KpsFileContentFile { name: string; + /** @deprecated */ description: string; + /** @deprecated */ copyLocation: string; + /** @deprecated */ fileType: string; } @@ -96,6 +100,7 @@ export interface KpsFileKeyboard { displayFont?: string; rTL?: string; languages?: KpsFileLanguages; + examples?: KpsFileLanguageExamples; } export interface KpsFileKeyboards { @@ -124,3 +129,31 @@ export interface KpsFileStrings { //TODO: validate this structure string: string[] | string; } + +export interface KpsFileLanguageExamples { + example: KpsFileLanguageExample | KpsFileLanguageExample[]; +} + +/** + * An example key sequence intended to demonstrate how the keyboard works + */ +export interface KpsFileLanguageExample { + $: { + /** + * BCP 47 identifier for the example + */ + ID: string; + /** + * A space-separated list of keys, modifiers indicated with "+", spacebar is "space", plus key is "shift+=" or "plus" + */ + keys: string; + /** + * The text that would be generated by typing those keys + */ + text?: string; + /** + * A short description of what the text means or represents + */ + note?: string; + } +} diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index dbce6b09578..cf006d313b4 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -67,25 +67,20 @@ export class KmpCompiler { // Fill in additional fields // - let keys: [keyof KpsFile.KpsFileOptions, keyof KmpJsonFile.KmpJsonFileOptions][] = [ - ['executeProgram','executeProgram'], - ['graphicFile', 'graphicFile'], - ['msiFileName','msiFilename'], - ['msiOptions', 'msiOptions'], - ['readMeFile', 'readmeFile'] - ]; if(kps.options) { - for (let [src,dst] of keys) { - if (kps.options[src]) { - if(dst == 'graphicFile' || dst == 'readmeFile') { - kmp.options[dst] = /[/\\]?([^/\\]*)$/.exec(kps.options[src])[1]; - } else { - kmp.options[dst] = kps.options[src]; - } - } + kmp.options.executeProgram = kps.options?.executeProgram || undefined; + if(kps.options.graphicFile) { + kmp.options.graphicFile = /[/\\]?([^/\\]*)$/.exec(kps.options.graphicFile)[1]; + } + kmp.options.msiFilename = kps.options.msiFileName; + kmp.options.msiOptions = kps.options.msiOptions; + if(kps.options.readMeFile) { + kmp.options.readmeFile = /[/\\]?([^/\\]*)$/.exec(kps.options.readMeFile)[1]; } } + // TODO: this.addFontMetadata(); + // // Add basic metadata // @@ -139,7 +134,12 @@ export class KmpCompiler { rtl:keyboard.rTL == 'True' ? true : undefined, languages: keyboard.languages ? this.kpsLanguagesToKmpLanguages(this.arrayWrap(keyboard.languages.language) as KpsFile.KpsFileLanguage[]) : - [] + [], + examples: keyboard.examples ? + (this.arrayWrap(keyboard.examples.example) as KpsFile.KpsFileLanguageExample[]).map( + e => ({id: e.$.ID, keys: e.$.keys, text: e.$.text, note: e.$.note}) + ) as KmpJsonFile.KmpJsonFileExample[] : + undefined })); } @@ -237,13 +237,14 @@ export class KmpCompiler { ['copyright','copyright'], ['name','name'], ['version','version'], - ['webSite','website'] + ['webSite','website'], + ['description','description'], ]; for (let [src,dst] of keys) { if (info[src]) { - ni[dst] = {description: info[src]._ ?? (typeof info[src] == 'string' ? info[src].toString() : '')}; - if(info[src].$ && info[src].$.URL) ni[dst].url = info[src].$.URL; + ni[dst] = {description: (info[src]._ ?? (typeof info[src] == 'string' ? info[src].toString() : '').trim())}; + if(info[src].$ && info[src].$.URL) ni[dst].url = info[src].$.URL.trim(); } } @@ -264,8 +265,6 @@ export class KmpCompiler { return language.map((element) => { return { name: element._, id: element.$.ID } }); }; - - private stripUndefined(o: any) { for(const key in o) { if(o[key] === undefined) { diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.js b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.js new file mode 100644 index 00000000000..4ee51e4d3e6 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.js @@ -0,0 +1 @@ +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_khmer_angkor());}function Keyboard_khmer_angkor(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_khmer_angkor";this.KN="Khmer Angkor";this.KMINVER="10.0";this.KV={F:' 1em "Khmer Busra Kbd"',K102:0};this.KV.KLS={"rightalt": ["‍","‌","@","","$","€","៙","៚","*","{","}","≈","","","","","ៜ","","ឯ","ឫ","ឨ","[","]","ឦ","ឱ","ឰ","ឩ","ឳ","\\","","","","+","-","×","÷",":","‘","’","ឝ","៘","៖","ៈ","","","","","","","<",">","#","&","ឞ",";","",",",".","/","","","","",""," "],"rightalt-shift": ["","៱","៲","៳","៴","៵","៶","៷","៸","៹","៰","","","","","","᧠","᧡","᧢","᧣","᧤","᧥","᧦","᧧","᧨","᧩","᧪","᧫","","","","","᧬","᧭","᧮","᧯","᧰","᧱","᧲","᧳","᧴","᧵","᧶","","","","","","","᧷","᧸","᧹","᧺","᧻","᧼","᧽","᧾","᧿","","","","","","",""],"default": ["«","១","២","៣","៤","៥","៦","៧","៨","៩","០","ឥ","ឲ","","","","ឆ","","","រ","ត","យ","","","","ផ","","ឪ","ឮ","","","","","ស","ដ","ថ","ង","ហ","","ក","ល","","","","","","","","","ឋ","ខ","ច","វ","ប","ន","ម","","។","","","","","","","​"],"shift": ["»","!","ៗ","\"","៛","%","","","","(",")","","=","","","","ឈ","","","ឬ","ទ","","","","","ភ","","ឧ","ឭ","","","","","","ឌ","ធ","អ","ះ","ញ","គ","ឡ","","","","","","","","","ឍ","ឃ","ជ","","ព","ណ","","","៕","?","","","","","",""]};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.3";this.KMBM=0x0018;this.KVKD="T_17D2_1780 T_17D2_1781 T_17D2_1782 T_17D2_1783 T_17D2_1784 T_17D2_1785 T_17D2_1786 T_17D2_1787 T_17D2_1788 T_17D2_1789 T_17D2_178A T_17D2_178B T_17D2_178C T_17D2_178D T_17D2_178E T_17D2_178F T_17D2_1790 T_17D2_1791 T_17D2_1792 T_17D2_1793 T_17D2_1794 T_17D2_1795 T_17D2_1796 T_17D2_1797 T_17D2_1798 T_17D2_1799 T_17D2_179A T_17D2_179B T_17D2_179C T_17D2_179D T_17D2_179E T_17D2_179F T_17D2_17A0 T_17D2_17A1 T_17D2_17A2 U_0030 U_0031 U_0032 U_0033 U_0034 U_0035 U_0036 U_0037 U_0038 U_0039";this.KVKL={"phone":{"font":"Khmer Busra Kbd","fontsize":"0.8em","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_Q","text":"\u1786","sk":[{"layer":"shift","id":"K_Q","text":"\u1788"},{"id":"T_17D2_1786","text":"\uEF86"},{"id":"T_17D2_1788","text":"\uEF88"}]},{"id":"K_W","text":"\uEFB9","sk":[{"layer":"shift","id":"K_W","text":"\uEFBA"}]},{"id":"K_E","text":"\uEFC1","sk":[{"layer":"shift","id":"K_E","text":"\uEFC2"},{"layer":"shift","id":"K_S","text":"\uEFC3"},{"layer":"shift","id":"K_V","text":"\uEF12"},{"id":"U_17AF","text":"\u17AF"},{"id":"U_17B0","text":"\u17B0"}]},{"id":"K_R","text":"\u179A","sk":[{"id":"T_17D2_179A","text":"\uEF9A"},{"id":"U_17AB","text":"\u17AB"},{"id":"U_17AC","text":"\u17AC"}]},{"id":"K_T","text":"\u178F","sk":[{"layer":"shift","id":"K_T","text":"\u1791"},{"id":"T_17D2_178F","text":"\uEF8F"},{"layer":"default","id":"T_17D2_1791","text":"\uEF91"}]},{"id":"K_Y","text":"\u1799","sk":[{"id":"T_17D2_1799","text":"\uEF99"}]},{"id":"K_U","text":"\uEFBB","sk":[{"layer":"shift","id":"K_U","text":"\uEFBC"},{"layer":"shift","id":"K_Y","text":"\uEFBD"},{"id":"U_17A7","text":"\u17A7"},{"layer":"shift","id":"U_17AA","text":"\u17AA"},{"layer":"shift","id":"U_17A9","text":"\u17A9"},{"id":"U_17A8","text":"\u17A8"}]},{"id":"K_I","text":"\uEFB7","sk":[{"layer":"shift","id":"K_I","text":"\uEFB8"},{"id":"U_17A5","text":"\u17A5"},{"layer":"shift","id":"U_17A6","text":"\u17A6"}]},{"id":"K_O","text":"\uEFC4","sk":[{"layer":"shift","id":"K_O","text":"\uEFC5"},{"id":"K_LBRKT","text":"\uEFC0"},{"layer":"shift","id":"K_LBRKT","text":"\uEFBF"},{"layer":"shift","id":"K_COLON","text":"\uEF14"},{"id":"U_17B1","text":"\u17B1"},{"id":"U_17B2","text":"\u17B2"},{"layer":"shift","id":"U_17B3","text":"\u17B3"}]},{"id":"K_P","text":"\u1795","sk":[{"layer":"shift","id":"K_P","text":"\u1797"},{"id":"T_17D2_1795","text":"\uEF95"},{"layer":"default","id":"T_17D2_1797","text":"\uEF97"}]}]},{"id":"2","key":[{"width":"100","id":"K_A","text":"\uEFB6","sk":[{"layer":"shift","id":"K_A","text":"\uEF11"}]},{"id":"K_S","text":"\u179F","sk":[{"id":"T_17D2_179F","text":"\uEF9F"},{"id":"U_179D","text":"\u179D"},{"id":"U_179E","text":"\u179E"}]},{"id":"K_D","text":"\u178A","sk":[{"layer":"shift","id":"K_D","text":"\u178C"},{"id":"T_17D2_178A","text":"\uEF8A"},{"layer":"default","id":"T_17D2_178C","text":"\uEF8C"}]},{"id":"K_F","text":"\u1790","sk":[{"layer":"shift","id":"K_F","text":"\u1792"},{"id":"T_17D2_1790","text":"\uEF90"},{"layer":"default","id":"T_17D2_1792","text":"\uEF92"}]},{"id":"K_G","text":"\u1784","sk":[{"layer":"shift","id":"K_G","text":"\u17A2"},{"id":"T_17D2_1784","text":"\uEF84"},{"layer":"default","id":"T_17D2_17A2","text":"\uEFA2"}]},{"id":"K_H","text":"\u17A0","sk":[{"id":"T_17D2_17A0","text":"\uEFA0"},{"layer":"shift","id":"K_H","text":"\u17C7"},{"id":"U_17C8","text":"\u17C8"}]},{"layer":"shift","id":"K_J","text":"\u1789","sk":[{"id":"T_17D2_1789","text":"\uEF89"}]},{"id":"K_K","text":"\u1780","sk":[{"layer":"shift","id":"K_K","text":"\u1782"},{"id":"T_17D2_1780","text":"\uEF80"},{"id":"T_17D2_1782","text":"\uEF82"}]},{"id":"K_L","text":"\u179B","sk":[{"layer":"shift","id":"K_L","text":"\u17A1"},{"id":"T_17D2_179B","text":"\uEF9B"},{"id":"U_17AD","text":"\u17AD"},{"id":"U_17AE","text":"\u17AE"}]},{"id":"K_COLON","text":"\uEFBE"}]},{"id":"3","key":[{"id":"K_Z","text":"\u178B","sk":[{"layer":"shift","id":"K_Z","text":"\u178D"},{"id":"T_17D2_178B","text":"\uEF8B"},{"layer":"default","id":"T_17D2_178D","text":"\uEF8D"}]},{"id":"K_X","text":"\u1781","sk":[{"layer":"shift","id":"K_X","text":"\u1783"},{"id":"T_17D2_1781","text":"\uEF81"},{"layer":"default","id":"T_17D2_1783","text":"\uEF83"}]},{"id":"K_C","text":"\u1785","sk":[{"layer":"shift","id":"K_C","text":"\u1787"},{"id":"T_17D2_1785","text":"\uEF85"},{"layer":"default","id":"T_17D2_1787","text":"\uEF87"}]},{"id":"K_V","text":"\u179C","sk":[{"id":"T_17D2_179C","text":"\uEF9C"}]},{"id":"K_B","text":"\u1794","sk":[{"layer":"shift","id":"K_B","text":"\u1796"},{"id":"T_17D2_1794","text":"\uEF94"},{"layer":"default","id":"T_17D2_1796","text":"\uEF96"}]},{"id":"K_N","text":"\u1793","sk":[{"layer":"shift","id":"K_N","text":"\u178E"},{"id":"T_17D2_1793","text":"\uEF93"},{"layer":"default","id":"T_17D2_178E","text":"\uEF8E"}]},{"id":"K_M","text":"\u1798","sk":[{"id":"T_17D2_1798","text":"\uEF98"},{"layer":"shift","id":"K_M","text":"\uEFC6"}]},{"id":"K_COMMA","text":"\uEF10","sk":[{"layer":"shift","id":"K_COMMA","text":"\uEF13"},{"layer":"shift","id":"K_6","text":"\uEFCD"},{"layer":"shift","id":"K_7","text":"\uEFD0"},{"layer":"shift","id":"K_8","text":"\uEFCF"},{"layer":"shift","id":"K_HYPHEN","text":"\uEFCC"},{"layer":"shift","id":"U_17D1","text":"\uEFD1"},{"layer":"shift","id":"U_17DD","text":"\uEFDD"},{"layer":"shift","id":"U_17CE","text":"\uEFCE"}]},{"width":"100","id":"K_QUOTE","text":"\uEFCB","sk":[{"layer":"shift","id":"K_QUOTE","text":"\uEFC9"},{"id":"K_SLASH","text":"\uEFCA"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"\u17E1\u17E2\u17E3"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"555","id":"K_SPACE","text":"\u200B","sk":[{"layer":"default","id":"U_0020","text":" "}]},{"width":"120","id":"K_PERIOD","text":"\u17D4","sk":[{"layer":"shift","id":"K_PERIOD","text":"\u17D5"},{"id":"U_0021","text":"!"},{"id":"U_003F","text":"?"}]},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"\u17E1","sk":[{"id":"U_0031","text":"1"}]},{"id":"K_2","text":"\u17E2","sk":[{"id":"U_0032","text":"2"}]},{"id":"K_3","text":"\u17E3","sk":[{"id":"U_0033","text":"3"}]},{"id":"K_4","text":"\u17E4","sk":[{"id":"U_0034","text":"4"}]},{"id":"K_5","text":"\u17E5","sk":[{"id":"U_0035","text":"5"}]},{"id":"K_6","text":"\u17E6","sk":[{"id":"U_0036","text":"6"}]},{"id":"K_7","text":"\u17E7","sk":[{"id":"U_0037","text":"7"}]},{"id":"K_8","text":"\u17E8","sk":[{"id":"U_0038","text":"8"}]},{"id":"K_9","text":"\u17E9","sk":[{"id":"U_0039","text":"9"}]},{"id":"K_0","text":"\u17E0","sk":[{"id":"U_0030","text":"0"},{"id":"U_17D3","text":"\uEFD3"}]}]},{"id":"2","key":[{"id":"U_0040","text":"@","sk":[{"id":"U_00A9","text":"\u00A9"},{"id":"U_00AE","text":"\u00AE"}]},{"id":"U_0023","text":"#","sk":[{"id":"U_2116","text":"\u2116"},{"id":"U_007E","text":"~"}]},{"id":"U_17DB","text":"\u17DB","sk":[{"id":"U_0024","text":"$"},{"id":"U_0E3F","text":"\u0E3F"},{"id":"U_00A2","text":"\u00A2"},{"id":"U_00A3","text":"\u00A3"},{"id":"U_00A5","text":"\u00A5"}]},{"id":"U_0026","text":"&"},{"id":"U_0025","text":"%","sk":[{"id":"U_2030","text":"\u2030"},{"id":"U_2031","text":"\u2031"}]},{"id":"U_002B","text":"+","sk":[{"id":"U_002D","text":"-"},{"id":"U_00D7","text":"\u00D7"},{"id":"U_00F7","text":"\u00F7"},{"id":"U_00B1","text":"\u00B1"}]},{"id":"U_003D","text":"=","sk":[{"id":"U_005F","text":"_"},{"id":"U_2260","text":"\u2260"}]},{"id":"U_002A","text":"*","sk":[{"id":"U_005E","text":"^"}]},{"id":"U_003F","text":"?","sk":[{"id":"U_00BF","text":"\u00BF"}]},{"id":"U_0021","text":"!","sk":[{"id":"U_00A1","text":"\u00A1"}]}]},{"id":"3","key":[{"id":"U_2018","text":"\u2018","sk":[{"id":"U_2019","text":"\u2019"}]},{"id":"U_201C","text":"\u201C","sk":[{"id":"U_201D","text":"\u201D"}]},{"id":"U_00AB","text":"\u00AB","sk":[{"id":"U_00BB","text":"\u00BB"}]},{"id":"U_002F","text":"\/","sk":[{"id":"U_005C","text":"\\"},{"id":"U_007C","text":"|"},{"id":"U_00A6","text":"\u00A6"}]},{"id":"U_0028","text":"(","sk":[{"id":"U_0029","text":")"},{"id":"U_005B","text":"["},{"id":"U_005D","text":"]"},{"id":"U_007B","text":"{"},{"id":"U_007D","text":"}"}]},{"id":"U_17D9","text":"\u17D9","sk":[{"id":"U_17DA","text":"\u17DA"},{"id":"U_17DC","text":"\u17DC"},{"id":"U_00A7","text":"\u00A7"},{"id":"U_00D8","text":"\u00D8"}]},{"id":"U_17D7","text":"\u17D7"},{"id":"U_003C","text":"<","sk":[{"id":"U_2264","text":"\u2264"},{"id":"U_003E","text":">"},{"id":"U_2265","text":"\u2265"}]},{"id":"U_17D6","text":"\u17D6","sk":[{"id":"U_003A","text":":"},{"id":"U_003B","text":";"},{"id":"U_2026","text":"\u2026"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"140","id":"K_LCONTROL","sp":"2","text":"\u17E1\u17E2\u17E3"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"layer":"shift","width":"555","id":"K_SPACE","text":"\u200B"},{"width":"120","id":"K_PERIOD","text":"\u17D4","sk":[{"layer":"shift","id":"K_PERIOD","text":"\u17D5"},{"id":"U_0021","text":"!"},{"id":"U_003F","text":"?"}]},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]},"tablet":{"font":"Khmer Busra Kbd","fontsize":"0.8em","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_1","text":"\u17E1"},{"id":"K_2","text":"\u17E2"},{"id":"K_3","text":"\u17E3"},{"id":"K_4","text":"\u17E4"},{"id":"K_5","text":"\u17E5"},{"id":"K_6","text":"\u17E6"},{"id":"K_7","text":"\u17E7"},{"id":"K_8","text":"\u17E8"},{"id":"K_9","text":"\u17E9"},{"id":"K_0","text":"\u17E0"},{"id":"K_HYPHEN","text":"\u17A5"},{"id":"K_EQUAL","text":"\u17B2"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"2","key":[{"id":"K_Q","pad":"75","text":"\u1786"},{"id":"K_W","text":"\uEFB9"},{"id":"K_E","text":"\uEFC1"},{"id":"K_R","text":"\u179A"},{"id":"K_T","text":"\u178F"},{"id":"K_Y","text":"\u1799"},{"id":"K_U","text":"\uEFBB"},{"id":"K_I","text":"\uEFB7"},{"id":"K_O","text":"\uEFC4"},{"id":"K_P","text":"\u1795"},{"id":"K_LBRKT","text":"\uEFC0"},{"id":"K_RBRKT","text":"\u17AA"},{"width":"10","id":"T_new_138","sp":"10"}]},{"id":"3","key":[{"id":"K_BKQUOTE","text":"\u00AB"},{"id":"K_A","text":"\uEFB6"},{"id":"K_S","text":"\u179F"},{"id":"K_D","text":"\u178A"},{"id":"K_F","text":"\u1790"},{"id":"K_G","text":"\u1784"},{"id":"K_H","text":"\u17A0"},{"id":"K_J","text":"\uEFD2"},{"id":"K_K","text":"\u1780"},{"id":"K_L","text":"\u179B"},{"id":"K_COLON","text":"\uEFBE"},{"id":"K_QUOTE","text":"\uEFCB"},{"id":"K_BKSLASH","text":"\u17AE"}]},{"id":"4","key":[{"nextlayer":"shift","width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_oE2"},{"id":"K_Z","text":"\u178B"},{"id":"K_X","text":"\u1781"},{"id":"K_C","text":"\u1785"},{"id":"K_V","text":"\u179C"},{"id":"K_B","text":"\u1794"},{"id":"K_N","text":"\u1793"},{"id":"K_M","text":"\u1798"},{"id":"K_COMMA","text":"\uEF10"},{"id":"K_PERIOD","text":"\u17D4"},{"id":"K_SLASH","text":"\uEFCA"},{"width":"10","id":"T_new_164","sp":"10"}]},{"id":"5","key":[{"nextlayer":"rightalt","width":"160","id":"K_LCONTROL","sp":"1","text":"*AltGr*"},{"width":"160","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"930","id":"K_SPACE","text":"\u200B"},{"width":"160","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"rightalt","row":[{"id":"1","key":[{"id":"K_1","text":"\u200C"},{"id":"K_2","text":"@"},{"id":"K_3","text":"\uEFD1"},{"id":"K_4","text":"$"},{"id":"K_5","text":"\u20AC"},{"id":"K_6","text":"\u17D9"},{"id":"K_7","text":"\u17DA"},{"id":"K_8","text":"*"},{"id":"K_9","text":"{"},{"id":"K_0","text":"}"},{"id":"K_HYPHEN","text":"\u2248"},{"id":"K_EQUAL","text":"\uEFCE"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"2","key":[{"id":"K_Q","pad":"75","text":"\u17DC"},{"id":"K_W","text":"\uEFDD"},{"id":"K_E","text":"\u17AF"},{"id":"K_R","text":"\u17AB"},{"id":"K_T","text":"\u17A8"},{"id":"K_Y","text":"["},{"id":"K_U","text":"]"},{"id":"K_I","text":"\u17A6"},{"id":"K_O","text":"\u17B1"},{"id":"K_P","text":"\u17B0"},{"id":"K_LBRKT","text":"\u17A9"},{"id":"K_RBRKT","text":"\u17B3"},{"width":"10","id":"T_new_307","sp":"10"}]},{"id":"3","key":[{"id":"K_BKQUOTE","text":"\u200D"},{"id":"K_A","text":"+"},{"id":"K_S","text":"-"},{"id":"K_D","text":"\u00D7"},{"id":"K_F","text":"\u00F7"},{"id":"K_G","text":":"},{"id":"K_H","text":"\u2018"},{"id":"K_J","text":"\u2019"},{"id":"K_K","text":"\u179D"},{"id":"K_L","text":"\u17D8"},{"id":"K_COLON","text":"\u17D6"},{"id":"K_QUOTE","text":"\u17C8"},{"id":"K_BKSLASH","text":"\\"}]},{"id":"4","key":[{"nextlayer":"shift","width":"160","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_oE2"},{"id":"K_Z","text":"<"},{"id":"K_X","text":">"},{"id":"K_C","text":"#"},{"id":"K_V","text":"&"},{"id":"K_B","text":"\u179E"},{"id":"K_N","text":";"},{"id":"K_M","text":"\uEFD3"},{"id":"K_COMMA","text":","},{"id":"K_PERIOD","text":"."},{"id":"K_SLASH","text":"\/"},{"width":"10","id":"T_new_333","sp":"10"}]},{"id":"5","key":[{"nextlayer":"default","width":"160","id":"K_LCONTROL","sp":"2","text":"*AltGr*"},{"width":"160","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"930","id":"K_SPACE","text":"\u00A0"},{"width":"160","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_1","text":"!"},{"id":"K_2","text":"\u17D7"},{"id":"K_3","text":"\""},{"id":"K_4","text":"\u17DB"},{"id":"K_5","text":"%"},{"id":"K_6","text":"\uEFCD"},{"id":"K_7","text":"\uEFD0"},{"id":"K_8","text":"\uEFCF"},{"id":"K_9","text":"("},{"id":"K_0","text":")"},{"id":"K_HYPHEN","text":"\uEFCC"},{"id":"K_EQUAL","text":"="},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"2","key":[{"id":"K_Q","pad":"75","text":"\u1788"},{"id":"K_W","text":"\uEFBA"},{"id":"K_E","text":"\uEFC2"},{"id":"K_R","text":"\u17AC"},{"id":"K_T","text":"\u1791"},{"id":"K_Y","text":"\uEFBD"},{"id":"K_U","text":"\uEFBC"},{"id":"K_I","text":"\uEFB8"},{"id":"K_O","text":"\uEFC5"},{"id":"K_P","text":"\u1797"},{"id":"K_LBRKT","text":"\uEFBF"},{"id":"K_RBRKT","text":"\u17A7"},{"width":"10","id":"T_new_364","sp":"10"}]},{"id":"3","key":[{"id":"K_BKQUOTE","text":"\u00BB"},{"id":"K_A","text":"\uEF11"},{"id":"K_S","text":"\uEFC3"},{"id":"K_D","text":"\u178C"},{"id":"K_F","text":"\u1792"},{"id":"K_G","text":"\u17A2"},{"id":"K_H","text":"\u17C7"},{"id":"K_J","text":"\u1789"},{"id":"K_K","text":"\u1782"},{"id":"K_L","text":"\u17A1"},{"id":"K_COLON","text":"\uEF14"},{"id":"K_QUOTE","text":"\uEFC9"},{"id":"K_BKSLASH","text":"\u17AD"}]},{"id":"4","key":[{"nextlayer":"default","width":"160","id":"K_SHIFT","sp":"2","text":"*Shift*"},{"id":"K_oE2"},{"id":"K_Z","text":"\u178D"},{"id":"K_X","text":"\u1783"},{"id":"K_C","text":"\u1787"},{"id":"K_V","text":"\uEF12"},{"id":"K_B","text":"\u1796"},{"id":"K_N","text":"\u178E"},{"id":"K_M","text":"\uEFC6"},{"id":"K_COMMA","text":"\uEF13"},{"id":"K_PERIOD","text":"\u17D5"},{"id":"K_SLASH","text":"?"},{"width":"10","id":"T_new_390","sp":"10"}]},{"id":"5","key":[{"nextlayer":"rightalt","width":"160","id":"K_LCONTROL","sp":"1","text":"*AltGr*"},{"width":"160","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"930","id":"K_SPACE"},{"width":"160","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.s12=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',''];this.s13="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវសហឡអឝឞ";this.s14=['','','','','','','','','','','','','','','',''];this.s15="ាិីឹឺុូួើឿៀេែៃោៅ";this.s16=['','',''];this.s17="ំះៈ";this.s18=['','','','','','','','','','','','','','','','','','',''];this.s19="ាិីឹឺុូួើឿៀេែៃោៅំះៈ";this.s20="ាិីឹឺុូួើឿៀេែៃោៅំះៈ";this.s21="េោុិីឹែ";this.s22="ាុ";this.s23="េោុិីឹែាុ";this.s24=['','','','','','','','','','','','','','',''];this.s25="ឥឦឧឨឩឪឫឬឭឮឯឰឱឲឳ";this.s26=['','','','','','','','','','',''];this.s27="់័៌៏៍ៈ៎៑៝ៜ្";this.s28=['',''];this.s29="៉៊";this.s30=['','','','','','','',''];this.s31="។៕៖ៗ៘៙៚៓";this.s32=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',''];this.s33="«»()!\"%=?{}\\@*,×./[]‍‌+-÷:≈‘’;<>#&";this.s34=['','',''];this.s35="​  ";this.s36=['','',''];this.s37="៛$€";this.s38=['','','','','','','','','',''];this.s39="០១២៣៤៥៦៧៨៩";this.s40=['','','','','','','','','',''];this.s41="៰៱៲៳៴៵៶៷៸៹";this.s42=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',''];this.s43="᧬᧻᧹᧮᧢᧯᧰᧱᧧᧲᧳᧴᧽᧼᧨᧩᧠᧣᧭᧤᧦᧺᧡᧸᧥᧷᧵᧾᧿᧪᧫᧶";this.s44=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','',''];this.s45="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវឝឞសហឡអ";this.s46=['','','','','','','','','',''];this.s47="0123456789";this.s48="ិីឹឺើ័";this.s49="សហអ";this.s50="ប";this.s51="ងញមយរវនល";this.s52="ងញមយរវនលប";this.s53="យមងបវ";this.s54="យលងរ";this.s55="បហអ";this.s56="ហសអ";this.s57="បយលមនញងរវអ";this.s58="ឆឈបផតទ";this.s59="វឣ";this.s63="touch";this.KVER="15.0.270.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,8)) {if(k.KFCM(2,t,['្',{t:'a',a:this.s13}])&&k.KIFS(31,this.s63,t)){r=m=1;k.KDC(2,t);}else if(k.KFCM(2,t,[{t:'a',a:this.s22},'ំ'])){r=m=1;k.KDC(2,t);}else if(k.KFCM(2,t,[{t:'a',a:this.s21},'ះ'])){r=m=1;k.KDC(2,t);}}else if(k.KKM(e,16392,75)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឝ");}}else if(k.KKM(e,16392,66)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឞ");}}else if(k.KKM(e,16392,222)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៈ");}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៈ");}}else if(k.KKM(e,16392,221)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឳ");}}else if(k.KKM(e,16392,80)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឰ");}}else if(k.KKM(e,16392,73)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឦ");}}else if(k.KKM(e,16392,84)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឨ");}}else if(k.KKM(e,16392,79)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឱ");}}else if(k.KKM(e,16392,219)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឩ");}}else if(k.KKM(e,16392,82)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឫ");}}else if(k.KKM(e,16392,69)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឯ");}}else if(k.KKM(e,16392,87)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៝");}}else if(k.KKM(e,16392,51)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៑");}}else if(k.KKM(e,16392,187)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៎");}}else if(k.KKM(e,16392,81)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៜ");}}else if(k.KKM(e,16392,186)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៖");}}else if(k.KKM(e,16392,77)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៓");}}else if(k.KKM(e,16392,76)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៘");}}else if(k.KKM(e,16392,54)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៙");}}else if(k.KKM(e,16392,55)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៚");}}else if(k.KKM(e,16392,192)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‍");}}else if(k.KKM(e,16392,49)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16392,85)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"]");}}else if(k.KKM(e,16392,89)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"[");}}else if(k.KKM(e,16392,191)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"/");}}else if(k.KKM(e,16392,65)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"+");}}else if(k.KKM(e,16392,83)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"-");}}else if(k.KKM(e,16392,70)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"÷");}}else if(k.KKM(e,16392,190)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,".");}}else if(k.KKM(e,16392,220)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"\\");}}else if(k.KKM(e,16392,48)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"}");}}else if(k.KKM(e,16392,71)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,":");}}else if(k.KKM(e,16392,68)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"×");}}else if(k.KKM(e,16392,188)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,",");}}else if(k.KKM(e,16392,189)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"≈");}}else if(k.KKM(e,16392,56)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"*");}}else if(k.KKM(e,16392,72)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‘");}}else if(k.KKM(e,16392,50)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"@");}}else if(k.KKM(e,16392,74)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"’");}}else if(k.KKM(e,16392,78)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,";");}}else if(k.KKM(e,16392,90)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"<");}}else if(k.KKM(e,16392,88)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,">");}}else if(k.KKM(e,16392,57)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"{");}}else if(k.KKM(e,16392,86)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"&");}}else if(k.KKM(e,16392,67)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"#");}}else if(k.KKM(e,16392,53)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"€");}}else if(k.KKM(e,16392,52)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"$");}}else if(k.KKM(e,16408,52)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៴");}}else if(k.KKM(e,16408,56)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៸");}}else if(k.KKM(e,16408,48)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៰");}}else if(k.KKM(e,16408,49)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៱");}}else if(k.KKM(e,16408,50)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៲");}}else if(k.KKM(e,16408,51)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៳");}}else if(k.KKM(e,16408,57)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៹");}}else if(k.KKM(e,16408,53)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៵");}}else if(k.KKM(e,16408,54)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៶");}}else if(k.KKM(e,16408,55)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៷");}}else if(k.KKM(e,16408,221)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧫");}}else if(k.KKM(e,16408,219)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧪");}}else if(k.KKM(e,16408,65)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧬");}}else if(k.KKM(e,16408,66)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧻");}}else if(k.KKM(e,16408,67)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧹");}}else if(k.KKM(e,16408,68)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧮");}}else if(k.KKM(e,16408,69)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧢");}}else if(k.KKM(e,16408,70)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧯");}}else if(k.KKM(e,16408,71)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧰");}}else if(k.KKM(e,16408,72)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧱");}}else if(k.KKM(e,16408,222)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧶");}}else if(k.KKM(e,16408,74)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧲");}}else if(k.KKM(e,16408,75)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧳");}}else if(k.KKM(e,16408,76)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧴");}}else if(k.KKM(e,16408,77)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧽");}}else if(k.KKM(e,16408,78)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧼");}}else if(k.KKM(e,16408,79)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧨");}}else if(k.KKM(e,16408,80)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧩");}}else if(k.KKM(e,16408,81)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧠");}}else if(k.KKM(e,16408,82)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧣");}}else if(k.KKM(e,16408,83)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧭");}}else if(k.KKM(e,16408,84)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧤");}}else if(k.KKM(e,16408,85)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧦");}}else if(k.KKM(e,16408,86)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧺");}}else if(k.KKM(e,16408,87)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧡");}}else if(k.KKM(e,16408,88)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧸");}}else if(k.KKM(e,16408,89)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧥");}}else if(k.KKM(e,16408,190)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧿");}}else if(k.KKM(e,16408,90)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧷");}}else if(k.KKM(e,16408,186)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧵");}}else if(k.KKM(e,16408,188)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧾");}}else if(k.KKM(e,16408,73)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"᧧");}}else if(k.KKM(e,16392,32)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t," ");}}else if(k.KKM(e,16384,261)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ច");}}else if(k.KKM(e,16384,260)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ង");}}else if(k.KKM(e,16384,264)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឈ");}}else if(k.KKM(e,16384,265)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ញ");}}else if(k.KKM(e,16384,262)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឆ");}}else if(k.KKM(e,16384,259)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឃ");}}else if(k.KKM(e,16384,266)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ដ");}}else if(k.KKM(e,16384,267)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឋ");}}else if(k.KKM(e,16384,268)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឌ");}}if(m) {}else if(k.KKM(e,16384,290)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្អ");}}else if(k.KKM(e,16384,289)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឡ");}}else if(k.KKM(e,16384,288)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ហ");}}else if(k.KKM(e,16384,287)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ស");}}else if(k.KKM(e,16384,286)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឞ");}}else if(k.KKM(e,16384,285)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឝ");}}else if(k.KKM(e,16384,284)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្វ");}}else if(k.KKM(e,16384,283)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ល");}}else if(k.KKM(e,16384,282)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្រ");}}else if(k.KKM(e,16384,281)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្យ");}}else if(k.KKM(e,16384,280)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ម");}}else if(k.KKM(e,16384,279)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ភ");}}else if(k.KKM(e,16384,278)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ព");}}else if(k.KKM(e,16384,277)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ផ");}}else if(k.KKM(e,16384,276)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ប");}}else if(k.KKM(e,16384,275)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ន");}}else if(k.KKM(e,16384,274)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ធ");}}else if(k.KKM(e,16384,273)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ទ");}}else if(k.KKM(e,16384,272)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ថ");}}else if(k.KKM(e,16384,271)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ត");}}else if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ក");}}else if(k.KKM(e,16384,270)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ណ");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ខ");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្គ");}}else if(k.KKM(e,16384,269)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ឍ");}}else if(k.KKM(e,16384,263)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្ជ");}}else if(k.KKM(e,16384,106)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"*");}}else if(k.KKM(e,16400,106)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"*");}}else if(k.KKM(e,16384,107)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"+");}}else if(k.KKM(e,16400,107)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"+");}}else if(k.KKM(e,16384,109)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"-");}}else if(k.KKM(e,16400,109)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"-");}}else if(k.KKM(e,16384,110)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,".");}}else if(k.KKM(e,16400,110)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,".");}}else if(k.KKM(e,16384,111)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"/");}}else if(k.KKM(e,16400,111)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"/");}}else if(k.KKM(e,16384,32)) {if(k.KFCM(1,t,['​'])){r=m=1;k.KDC(1,t);k.KO(-1,t," ");}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"​");}}else if(k.KKM(e,16400,32)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t," ");}}else if(k.KKM(e,16400,49)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"!");}}else if(k.KKM(e,16400,222)) {if(k.KFCM(3,t,[{t:'a',a:this.s58},'្','អ'])){r=m=1;k.KDC(3,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្អ៉");k.KB(t);}else if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s55}])){r=m=1;k.KDC(3,t);k.KO(-1,t,"ល្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៉");k.KB(t);}else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s56}])){r=m=1;k.KDC(3,t);k.KO(-1,t,"ម្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៉");k.KB(t);}else if(k.KFCM(3,t,['ស','្',{t:'a',a:this.s57}])){r=m=1;k.KDC(3,t);k.KO(-1,t,"ស្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៉");k.KB(t);}else if(k.KFCM(3,t,[{t:'a',a:this.s59},'្','ហ'])){r=m=1;k.KDC(3,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្ហ៉");k.KB(t);}else if(k.KFCM(3,t,['អ','្','ង'])){r=m=1;k.KDC(3,t);k.KO(-1,t,"អ្ង៉");k.KB(t);}else if(k.KFCM(3,t,['អ','្','វ'])){r=m=1;k.KDC(3,t);k.KO(-1,t,"អ្វ៉");k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s29}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s29,1,t);k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s49}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៉");k.KB(t);}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៉");}}else if(k.KKM(e,16400,51)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"\"");}}else if(k.KKM(e,16400,52)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៛");}}else if(k.KKM(e,16400,53)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"%");}}else if(k.KKM(e,16400,55)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"័");}}else if(k.KKM(e,16384,222)) {if(k.KFCM(2,t,['្',{t:'a',a:this.s13}])){r=m=1;k.KDC(2,t);k.KO(-1,t,"្");k.KIO(-1,this.s13,2,t);k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s15}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s15,1,t);k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s17}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s17,1,t);k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s29}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s29,1,t);k.KB(t);}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"់");}}else if(k.KKM(e,16400,57)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"(");}}else if(k.KKM(e,16400,48)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,")");}}else if(k.KKM(e,16400,56)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៏");}}else if(k.KKM(e,16400,187)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"=");}}else if(k.KKM(e,16384,188)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ុំ");}}else if(k.KKM(e,16384,189)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឥ");}}else if(k.KKM(e,16384,190)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"។");}}else if(k.KKM(e,16384,191)) {if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s53}])){r=m=1;k.KDC(3,t);k.KO(-1,t,"ល្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៊");k.KB(t);}else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s54}])){r=m=1;k.KDC(3,t);k.KO(-1,t,"ម្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៊");k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s29}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s29,1,t);k.KB(t);}else if(k.KFCM(1,t,[{t:'a',a:this.s52}])){r=m=1;k.KDC(1,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៊");k.KB(t);}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៊");}}else if(k.KKM(e,16384,48)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"០");}}else if(k.KKM(e,16384,49)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"១");}}else if(k.KKM(e,16384,50)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"២");}}else if(k.KKM(e,16384,51)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៣");}}else if(k.KKM(e,16384,52)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៤");}}else if(k.KKM(e,16384,53)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៥");}}else if(k.KKM(e,16384,54)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៦");}}else if(k.KKM(e,16384,55)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៧");}}else if(k.KKM(e,16384,56)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៨");}}else if(k.KKM(e,16384,57)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៩");}}else if(k.KKM(e,16400,186)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ោះ");}}else if(k.KKM(e,16384,186)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ើ");}}else if(k.KKM(e,16400,188)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ុះ");}}else if(k.KKM(e,16384,187)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឲ");}}else if(k.KKM(e,16400,190)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៕");}}else if(k.KKM(e,16400,191)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"?");}}else if(k.KKM(e,16400,50)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៗ");}}else if(k.KKM(e,16400,65)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ាំ");}}else if(k.KKM(e,16400,66)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ព");}}else if(k.KKM(e,16400,67)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ជ");}}else if(k.KKM(e,16400,68)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឌ");}}else if(k.KKM(e,16400,69)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ែ");}}else if(k.KKM(e,16400,70)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ធ");}}else if(k.KKM(e,16400,71)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"អ");}}else if(k.KKM(e,16400,72)) {if(k.KFCM(1,t,['ះ'])){r=m=1;k.KDC(1,t);k.KO(-1,t,"ៈ");}else if(k.KFCM(1,t,['ៈ'])){r=m=1;k.KDC(1,t);k.KO(-1,t,"ះ");}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ះ");}}else if(k.KKM(e,16400,73)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ី");}}else if(k.KKM(e,16400,74)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ញ");}}else if(k.KKM(e,16400,75)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"គ");}}else if(k.KKM(e,16400,76)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឡ");}}else if(k.KKM(e,16400,77)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ំ");}}else if(k.KKM(e,16400,78)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ណ");}}else if(k.KKM(e,16400,79)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៅ");}}else if(k.KKM(e,16400,80)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ភ");}}else if(k.KKM(e,16400,81)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឈ");}}else if(k.KKM(e,16400,82)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឬ");}}else if(k.KKM(e,16400,83)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៃ");}}else if(k.KKM(e,16400,84)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ទ");}}else if(k.KKM(e,16400,85)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ូ");}}else if(k.KKM(e,16400,86)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"េះ");}}else if(k.KKM(e,16400,87)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឺ");}}else if(k.KKM(e,16400,88)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឃ");}}else if(k.KKM(e,16400,89)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ួ");}}else if(k.KKM(e,16400,90)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឍ");}}else if(k.KKM(e,16384,219)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ៀ");}}else if(k.KKM(e,16384,220)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឮ");}}else if(k.KKM(e,16384,221)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឪ");}}else if(k.KKM(e,16400,54)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៍");}}if(m) {}else if(k.KKM(e,16400,189)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"៌");}}else if(k.KKM(e,16384,192)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"«");}}else if(k.KKM(e,16384,65)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ា");}}else if(k.KKM(e,16384,66)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ប");}}else if(k.KKM(e,16384,67)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ច");}}else if(k.KKM(e,16384,68)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ដ");}}else if(k.KKM(e,16384,69)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"េ");}}else if(k.KKM(e,16384,70)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ថ");}}else if(k.KKM(e,16384,71)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ង");}}else if(k.KKM(e,16384,72)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ហ");}}else if(k.KKM(e,16384,73)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ិ");}}else if(k.KKM(e,16384,74)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"្");}}else if(k.KKM(e,16384,75)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ក");}}else if(k.KKM(e,16384,76)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ល");}}else if(k.KKM(e,16384,77)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ម");}}else if(k.KKM(e,16384,78)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ន");}}else if(k.KKM(e,16384,79)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ោ");}}else if(k.KKM(e,16384,80)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ផ");}}else if(k.KKM(e,16384,81)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឆ");}}else if(k.KKM(e,16384,82)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"រ");}}else if(k.KKM(e,16384,83)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ស");}}else if(k.KKM(e,16384,84)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ត");}}else if(k.KKM(e,16384,85)) {if(k.KFCM(3,t,[{t:'a',a:this.s49},'ា','ំ'])){r=m=1;k.KDC(3,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(3,t,[{t:'a',a:this.s52},'ា','ំ'])){r=m=1;k.KDC(3,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ុ");}}else if(k.KKM(e,16384,86)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"វ");}}else if(k.KKM(e,16384,87)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឹ");}}else if(k.KKM(e,16384,88)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ខ");}}else if(k.KKM(e,16384,89)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"យ");}}else if(k.KKM(e,16384,90)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឋ");}}else if(k.KKM(e,16400,219)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឿ");}}else if(k.KKM(e,16400,220)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឭ");}}else if(k.KKM(e,16400,221)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"ឧ");}}else if(k.KKM(e,16400,192)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"»");}}if(m==1) {k.KDC(-1,t);r=this.g1(t,e);m=2;}return r;};this.g1=function(t,e) {var k=KeymanWeb,r=1,m=0;if(k.KFCM(7,t,[{t:'a',a:this.s58},'្','អ','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s55},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s56},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ស','្','ប','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ស','្',{t:'a',a:this.s57},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,[{t:'a',a:this.s59},'្','ហ','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['អ','្','ង','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['អ','្','វ','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ហ','្','ប','ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ហ','្',{t:'a',a:this.s52},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KIO(-1,this.s52,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s53},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s54},'ុ','ំ','ា','ំ'])){m=1;k.KDC(7,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s22},'ំ','្','រ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s22,3,t);k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s21},'ះ','្','រ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s21,3,t);k.KO(-1,t,"ះ");}else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s22},'ំ','្','ដ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s22,3,t);k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s21},'ះ','្','ដ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s21,3,t);k.KO(-1,t,"ះ");}else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s22},'ំ','្',{t:'a',a:this.s45}])){m=1;k.KDC(6,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,6,t);k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s22,3,t);k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s21},'ះ','្',{t:'a',a:this.s45}])){m=1;k.KDC(6,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,6,t);k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s21,3,t);k.KO(-1,t,"ះ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្','ប','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','ង','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','វ','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ហ','្','ប','ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KO(-1,t,"ប");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ហ','្',{t:'a',a:this.s52},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KIO(-1,this.s52,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s52,3,t);k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'ុ','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'៊',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s15,5,t);k.KIO(-1,this.s17,6,t);}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'៊',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s15,5,t);k.KIO(-1,this.s17,6,t);}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','៉','ា','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្','ប','៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','៉','ា','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','ង','៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','វ','៉','ា','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','ង','ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','ង','ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','វ','ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['អ','្','វ','ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KO(-1,t,"ុ");k.KO(-1,t,"ា");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'ា','ុ','ំ'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'ុ','ំ','ា'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','េ','ុ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊ើ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','ុ','េ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊ើ");}else if(k.KFCM(6,t,[{t:'a',a:this.s58},'្','អ','៉','េ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s55},'៉','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s56},'៉','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s57},'៉','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','េ','ុ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊ើ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','ុ','េ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊ើ");}else if(k.KFCM(6,t,[{t:'a',a:this.s59},'្','ហ','៉','េ','ី'])){m=1;k.KDC(6,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊ើ");}else if(k.KFCM(6,t,['អ','្','ង','េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊ើ");}else if(k.KFCM(6,t,['អ','្','ង','ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊ើ");}else if(k.KFCM(6,t,['អ','្','ង','៉','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊ើ");}else if(k.KFCM(6,t,['អ','្','វ','េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊ើ");}else if(k.KFCM(6,t,['អ','្','វ','ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊ើ");}else if(k.KFCM(6,t,['អ','្','វ','៉','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s53},'៊','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'េ','ុ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'ុ','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s54},'៊','េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s13},'េ','ឺ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្");k.KIO(-1,this.s13,4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s13},'េ','ឹ'])){m=1;k.KDC(6,t);k.KO(-1,t,"្");k.KIO(-1,this.s13,4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s13},'េ','ី'])){m=1;k.KDC(6,t);k.KO(-1,t,"្");k.KIO(-1,this.s13,4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(5,t,[{t:'a',a:this.s29},{t:'a',a:this.s22},'ំ','្',{t:'a',a:this.s45}])){m=1;k.KDC(5,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,5,t);k.KIO(-1,this.s29,1,t);k.KIO(-1,this.s22,2,t);k.KO(-1,t,"ំ");}else if(k.KFCM(5,t,[{t:'a',a:this.s29},{t:'a',a:this.s21},'ះ','្',{t:'a',a:this.s45}])){m=1;k.KDC(5,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,5,t);k.KIO(-1,this.s29,1,t);k.KIO(-1,this.s21,2,t);k.KO(-1,t,"ះ");}else if(k.KFCM(5,t,['្','ដ',{t:'a',a:this.s20},'្','រ'])){m=1;k.KDC(5,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s20,3,t);}else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s20},'្','ដ'])){m=1;k.KDC(5,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s20,3,t);}else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s29},'្',{t:'a',a:this.s45}])){m=1;k.KDC(5,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,5,t);k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s29,3,t);}else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s20},'្',{t:'a',a:this.s45}])){m=1;k.KDC(5,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,5,t);k.KO(-1,t,"្");k.KO(-1,t,"រ");k.KIO(-1,this.s20,3,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s58},'្','អ','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s58},'្','អ',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s55},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s55},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s56},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s56},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,4,t);}if(m) {}else if(k.KFCM(5,t,['ស','្','ប','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ស','្','ប',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s57},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s57},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s59},'្','ហ','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s59},'្','ហ',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['អ','្','ង','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['អ','្','ង',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['អ','្','វ','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['អ','្','វ',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ហ','្','ប','ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ហ','្','ប',{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s52},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KIO(-1,this.s52,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s52},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ហ");k.KO(-1,t,"្");k.KIO(-1,this.s52,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s53},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s53},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s54},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s54},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s49},'ុ','ំ','ា','ំ'])){m=1;k.KDC(5,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(5,t,[{t:'a',a:this.s52},'ុ','ំ','ា','ំ'])){m=1;k.KDC(5,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s53},'៊',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s53,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s54},'៊',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s54,3,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['្',{t:'a',a:this.s51},'៊',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(5,t);k.KO(-1,t,"្");k.KIO(-1,this.s51,2,t);k.KO(-1,t,"៊");k.KIO(-1,this.s15,4,t);k.KIO(-1,this.s17,5,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s58},'្','អ','៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KIO(-1,this.s58,1,t);k.KO(-1,t,"្");k.KO(-1,t,"អ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s55},'៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ល");k.KO(-1,t,"្");k.KIO(-1,this.s55,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s56},'៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ម");k.KO(-1,t,"្");k.KIO(-1,this.s56,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ស','្','ប','៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KO(-1,t,"ប៉");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s57},'៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"ស");k.KO(-1,t,"្");k.KIO(-1,this.s57,3,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,[{t:'a',a:this.s59},'្','ហ','៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KIO(-1,this.s59,1,t);k.KO(-1,t,"្");k.KO(-1,t,"ហ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['អ','្','ង','៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"ង៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['អ','្','វ','៉',{t:'a',a:this.s48}])){m=1;k.KDC(5,t);k.KO(-1,t,"អ");k.KO(-1,t,"្");k.KO(-1,t,"វ៊");k.KIO(-1,this.s48,5,t);}else if(k.KFCM(5,t,['ព','័','ន','្','ឋ'])){m=1;k.KDC(5,t);k.KO(-1,t,"ព");k.KO(-1,t,"័");k.KO(-1,t,"ន");k.KO(-1,t,"្ធ");}else if(k.KFCM(4,t,[{t:'a',a:this.s15},{t:'a',a:this.s17},{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(4,t);k.KIO(-1,this.s15,3,t);k.KIO(-1,this.s17,4,t);}else if(k.KFCM(4,t,[{t:'a',a:this.s22},'ំ','្',{t:'a',a:this.s45}])){m=1;k.KDC(4,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,4,t);k.KIO(-1,this.s22,1,t);k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s21},'ះ','្',{t:'a',a:this.s45}])){m=1;k.KDC(4,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,4,t);k.KIO(-1,this.s21,1,t);k.KO(-1,t,"ះ");}else if(k.KFCM(4,t,[{t:'a',a:this.s29},{t:'a',a:this.s20},'្',{t:'a',a:this.s45}])){m=1;k.KDC(4,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,4,t);k.KIO(-1,this.s29,1,t);k.KIO(-1,this.s20,2,t);}else if(k.KFCM(4,t,['្','ដ','្','រ'])){m=1;k.KDC(4,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");}else if(k.KFCM(4,t,['្','រ','្','ដ'])){m=1;k.KDC(4,t);k.KO(-1,t,"្ត");k.KO(-1,t,"្");k.KO(-1,t,"រ");}else if(k.KFCM(4,t,['្','រ','្',{t:'a',a:this.s45}])){m=1;k.KDC(4,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,4,t);k.KO(-1,t,"្");k.KO(-1,t,"រ");}else if(k.KFCM(4,t,[{t:'a',a:this.s29},{t:'a',a:this.s15},{t:'a',a:this.s17},{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KIO(-1,this.s15,2,t);k.KIO(-1,this.s17,3,t);k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'ុ','ា','ំ'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s52},'ុ','ា','ំ'])){m=1;k.KDC(4,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s51},'៊',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(4,t);k.KIO(-1,this.s51,1,t);k.KO(-1,t,"៉");k.KIO(-1,this.s15,3,t);k.KIO(-1,this.s17,4,t);}else if(k.KFCM(4,t,['្',{t:'a',a:this.s51},'៊',{t:'a',a:this.s48}])){m=1;k.KDC(4,t);k.KO(-1,t,"្");k.KIO(-1,this.s51,2,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,4,t);}else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ប្យ");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ស្ប");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ឆ្ប");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ប្យ");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ស្ប");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s29}])){m=1;k.KDC(4,t);k.KO(-1,t,"ឆ្ប");k.KIO(-1,this.s29,4,t);}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'៉',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KIO(-1,this.s15,3,t);k.KIO(-1,this.s17,4,t);}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'ា','ុ','ំ'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'ុ','ំ','ា'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s52},'ា','ុ','ំ'])){m=1;k.KDC(4,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s52},'ុ','ំ','ា'])){m=1;k.KDC(4,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KO(-1,t,"ា");k.KO(-1,t,"ំ");}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'េ','ុ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'ុ','េ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(4,t,[{t:'a',a:this.s49},'៉','េ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊ើ");}else if(k.KFCM(4,t,[{t:'a',a:this.s51},'េ','ុ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s51,1,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(4,t,[{t:'a',a:this.s51},'ុ','េ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s51,1,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(4,t,[{t:'a',a:this.s51},'៊','េ','ី'])){m=1;k.KDC(4,t);k.KIO(-1,this.s51,1,t);k.KO(-1,t,"៉ើ");}else if(k.KFCM(4,t,['ព','ន','្','ឋ'])){m=1;k.KDC(4,t);k.KO(-1,t,"ព");k.KO(-1,t,"ន");k.KO(-1,t,"្ធ");}else if(k.KFCM(4,t,['្','យ','េ','ឺ'])){m=1;k.KDC(4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(4,t,['្','យ','េ','ឹ'])){m=1;k.KDC(4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(4,t,['្','យ','េ','ី'])){m=1;k.KDC(4,t);k.KO(-1,t,"ឿ");}else if(k.KFCM(3,t,[{t:'a',a:this.s15},{t:'a',a:this.s17},{t:'a',a:this.s15}])){m=1;k.KDC(3,t);k.KIO(-1,this.s15,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s15},{t:'a',a:this.s17},{t:'a',a:this.s17}])){m=1;k.KDC(3,t);k.KIO(-1,this.s17,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s15},{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(3,t);k.KIO(-1,this.s15,2,t);k.KIO(-1,this.s17,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s17},{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(3,t);k.KIO(-1,this.s15,2,t);k.KIO(-1,this.s17,3,t);}else if(k.KFCM(3,t,['្',{t:'a',a:this.s15},{t:'a',a:this.s17}])){m=1;k.KDC(3,t);k.KIO(-1,this.s15,2,t);k.KIO(-1,this.s17,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s20},'្',{t:'a',a:this.s45}])){m=1;k.KDC(3,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,3,t);k.KIO(-1,this.s20,1,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s29},'្',{t:'a',a:this.s45}])){m=1;k.KDC(3,t);k.KO(-1,t,"្");k.KIO(-1,this.s45,3,t);k.KIO(-1,this.s29,1,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s15},{t:'a',a:this.s17},{t:'a',a:this.s29}])){m=1;k.KDC(3,t);k.KIO(-1,this.s29,3,t);k.KIO(-1,this.s15,1,t);k.KIO(-1,this.s17,2,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s29},{t:'a',a:this.s20},{t:'a',a:this.s29}])){m=1;k.KDC(3,t);k.KIO(-1,this.s29,3,t);k.KIO(-1,this.s20,2,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s49},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(3,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s49},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(3,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,2,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s52},'ុ',{t:'a',a:this.s48}])){m=1;k.KDC(3,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s52},{t:'a',a:this.s48},'ុ'])){m=1;k.KDC(3,t);k.KIO(-1,this.s52,1,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,2,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s51},'៊',{t:'a',a:this.s48}])){m=1;k.KDC(3,t);k.KIO(-1,this.s51,1,t);k.KO(-1,t,"៉");k.KIO(-1,this.s48,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s49},'៉',{t:'a',a:this.s48}])){m=1;k.KDC(3,t);k.KIO(-1,this.s49,1,t);k.KO(-1,t,"៊");k.KIO(-1,this.s48,3,t);}else if(k.KFCM(3,t,[{t:'a',a:this.s13},{t:'a',a:this.s15},'៌'])){m=1;k.KDC(3,t);k.KIO(-1,this.s13,1,t);k.KO(-1,t,"៌");k.KIO(-1,this.s15,2,t);}else if(k.KFCM(3,t,['ណ','្','ត'])){m=1;k.KDC(3,t);k.KO(-1,t,"ណ");k.KO(-1,t,"្ដ");}else if(k.KFCM(3,t,['ន','្','ដ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ន");k.KO(-1,t,"្ត");}else if(k.KFCM(3,t,['ទ','្','ប'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឡ");}else if(k.KFCM(3,t,['ប','្','ញ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឫ");}else if(k.KFCM(3,t,['ព','្','ញ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឭ");}else if(k.KFCM(3,t,['ព','្','ឋ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឰ");}else if(k.KFCM(3,t,['ដ','្','ធ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ដ្ឋ");}else if(k.KFCM(3,t,['ទ','្','ឋ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ទ្ធ");}else if(k.KFCM(3,t,['ឪ','្','យ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឱ");k.KO(-1,t,"្");k.KO(-1,t,"យ");}else if(k.KFCM(3,t,['ឳ','្','យ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ឱ");k.KO(-1,t,"្");k.KO(-1,t,"យ");}else if(k.KFCM(3,t,['ញ','្','វ'])){m=1;k.KDC(3,t);k.KO(-1,t,"ព");k.KO(-1,t,"្");k.KO(-1,t,"វា");}else if(k.KFCM(2,t,['េ','ា'])){m=1;k.KDC(2,t);k.KO(-1,t,"ោ");}else if(k.KFCM(2,t,['ា','េ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ោ");}else if(k.KFCM(2,t,['េ','ី'])){m=1;k.KDC(2,t);k.KO(-1,t,"ើ");}else if(k.KFCM(2,t,['ី','េ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ើ");}else if(k.KFCM(2,t,['ំ','ុ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ុំ");}else if(k.KFCM(2,t,['ំ','ា'])){m=1;k.KDC(2,t);k.KO(-1,t,"ាំ");}else if(k.KFCM(2,t,[{t:'a',a:this.s15},{t:'a',a:this.s15}])){m=1;k.KDC(2,t);k.KIO(-1,this.s15,2,t);}else if(k.KFCM(2,t,[{t:'a',a:this.s17},{t:'a',a:this.s17}])){m=1;k.KDC(2,t);k.KIO(-1,this.s17,2,t);}if(m) {}else if(k.KFCM(2,t,['្','្'])){m=1;k.KDC(2,t);k.KO(-1,t,"្");}else if(k.KFCM(2,t,['្',{t:'a',a:this.s20}])){m=1;k.KDC(2,t);k.KIO(-1,this.s20,2,t);}else if(k.KFCM(2,t,[{t:'a',a:this.s20},{t:'a',a:this.s29}])){m=1;k.KDC(2,t);k.KIO(-1,this.s29,2,t);k.KIO(-1,this.s20,1,t);}else if(k.KFCM(2,t,['ឫ','ុ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ឬ");}else if(k.KFCM(2,t,['ឭ','ា'])){m=1;k.KDC(2,t);k.KO(-1,t,"ញ");}else if(k.KFCM(2,t,['ឮ','ា'])){m=1;k.KDC(2,t);k.KO(-1,t,"ញ");}else if(k.KFCM(2,t,['ឭ','ុ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ឮ");}else if(k.KFCM(2,t,['ឧ','ិ'])){m=1;k.KDC(2,t);k.KO(-1,t,"ឱ");}else if(k.KFCM(2,t,['ឧ','៌'])){m=1;k.KDC(2,t);k.KO(-1,t,"ឱ");}else if(k.KFCM(2,t,['ឧ','៍'])){m=1;k.KDC(2,t);k.KO(-1,t,"ឱ");}return r;};} \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kmx b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kmx new file mode 100644 index 0000000000000000000000000000000000000000..c3d0289f4863c7c4a998c348eb1d425caadca79d GIT binary patch literal 26280 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 oDtl0`t=LoFCZ2P9{7X|R#ncIv*OjNe@{%3 + + + 15.0.266.0 + 7.0 + + + + readme.htm + splash.gif + + + + + + + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + # Khmer Angkor + +Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including: + +* Using wrong vowel combination +* Typing clusters out of order +* Typing wrong mark for consonant shifters +* And more! + + + + khmer_angkor.js + File khmer_angkor.js + 0 + .js + + + khmer_angkor.kvk + File khmer_angkor.kvk + 0 + .kvk + + + khmer_angkor.kmx + Keyboard Khmer Angkor + 0 + .kmx + + + welcome\keyboard_layout.png + File keyboard_layout.png + 0 + .png + + + welcome\welcome.htm + File welcome.htm + 0 + .htm + + + ..\shared\fonts\khmer\mondulkiri\FONTLOG.txt + File FONTLOG.txt + 0 + .txt + + + ..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + Font Khmer Mondulkiri + 0 + .ttf + + + ..\shared\fonts\khmer\mondulkiri\OFL.txt + File OFL.txt + 0 + .txt + + + ..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt + File OFL-FAQ.txt + 0 + .txt + + + welcome\KAK_Documentation_EN.pdf + File KAK_Documentation_EN.pdf + 0 + .pdf + + + welcome\KAK_Documentation_KH.pdf + File KAK_Documentation_KH.pdf + 0 + .pdf + + + readme.htm + File readme.htm + 0 + .htm + + + welcome\image002.png + File image002.png + 0 + .png + + + ..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + Font KhmerBusraKbd + 0 + .ttf + + + splash.gif + File splash.gif + 0 + .gif + + + + + Khmer Angkor + khmer_angkor + 1.3 + ..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + ..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + + Central Khmer (Khmer, Cambodia) + + + + + + + + + diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json new file mode 100644 index 00000000000..bd4d99c08bd --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json @@ -0,0 +1,131 @@ +{ + "system": { + "keymanDeveloperVersion": "16.0.60.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "readme.htm", + "graphicFile": "splash.gif" + }, + "info": { + "name": { + "description": "Khmer Angkor" + }, + "copyright": { + "description": "\u00A9 2015-2022 SIL International" + }, + "author": { + "description": "Makara Sok", + "url": "mailto:makara_sok@sil.org" + }, + "version": { + "description": "1.3" + }, + "website": { + "description": "https://keyman.com/keyboards/khmer_angkor", + "url": "https://keyman.com/keyboards/khmer_angkor" + }, + "description": { + "description": "# Khmer Angkor\r\n\r\nKhmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:\r\n\r\n* Using wrong vowel combination\r\n* Typing clusters out of order\r\n* Typing wrong mark for consonant shifters\r\n* And more!" + } + }, + "files": [ + { + "name": "khmer_angkor.js", + "description": "File khmer_angkor.js" + }, + { + "name": "khmer_angkor.kvk", + "description": "File khmer_angkor.kvk" + }, + { + "name": "khmer_angkor.kmx", + "description": "Keyboard Khmer Angkor" + }, + { + "name": "keyboard_layout.png", + "description": "File keyboard_layout.png" + }, + { + "name": "welcome.htm", + "description": "File welcome.htm" + }, + { + "name": "FONTLOG.txt", + "description": "File FONTLOG.txt" + }, + { + "name": "Mondulkiri-R.ttf", + "description": "Font Khmer Mondulkiri" + }, + { + "name": "OFL.txt", + "description": "File OFL.txt" + }, + { + "name": "OFL-FAQ.txt", + "description": "File OFL-FAQ.txt" + }, + { + "name": "KAK_Documentation_EN.pdf", + "description": "File KAK_Documentation_EN.pdf" + }, + { + "name": "KAK_Documentation_KH.pdf", + "description": "File KAK_Documentation_KH.pdf" + }, + { + "name": "readme.htm", + "description": "File readme.htm" + }, + { + "name": "image002.png", + "description": "File image002.png" + }, + { + "name": "khmer_busra_kbd.ttf", + "description": "Font KhmerBusraKbd" + }, + { + "name": "splash.gif", + "description": "File splash.gif" + }, + { + "name": "kmp.inf", + "description": "Package information" + }, + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + { + "name": "Khmer Angkor", + "id": "khmer_angkor", + "version": "1.3", + "oskFont": "khmer_busra_kbd.ttf", + "displayFont": "Mondulkiri-R.ttf", + "languages": [ + { + "name": "Central Khmer (Khmer, Cambodia)", + "id": "km" + } + ], + "examples": [ + { + "id": "km", + "keys": "x j m E r", + "text": "ខ្មែរ", + "note": "Name of language" + }, + { + "id": "km", + "keys": "B j r H r a C a N a c k j r k m j B u C a", + "text": "ព្រះរាជាណាចក្រកម្ពុជា", + "note": "'Kingdom of Cambodia'" + } + ] + } + ] +} diff --git a/developer/src/kmc-package/test/test-package-compiler.ts b/developer/src/kmc-package/test/test-package-compiler.ts index d33d6803e1d..433d730c181 100644 --- a/developer/src/kmc-package/test/test-package-compiler.ts +++ b/developer/src/kmc-package/test/test-package-compiler.ts @@ -155,6 +155,34 @@ describe('KmpCompiler', function () { assert.deepEqual(kmpJsonActual, kmpJsonFixture); }); + it(`should support .kps 17.0 metadata correctly`, function () { + callbacks.clear(); + + const kpsPath = makePathToFixture('kmp_2.0', 'khmer_angkor.kps'); + const kmpJsonRefPath = makePathToFixture('kmp_2.0', 'kmp.json'); + + debugger; + + let kmpJsonActual = kmpCompiler.transformKpsToKmpObject(kpsPath); + if(kmpJsonActual == null) { + callbacks.printMessages(); + assert.isNotNull(kmpJsonActual); + } + let kmpJsonFixture = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8')); + assert.isNotNull(kmpJsonFixture); + + // Blank out system.keymanDeveloperVersion which will vary + kmpJsonActual.system.keymanDeveloperVersion = '-'; + kmpJsonFixture.system.keymanDeveloperVersion = '-'; + + // Strip file paths to basename for the actual (this is currently part of the + // zip phase and not unit testable without refactoring) + for(const file of kmpJsonActual.files) { + file.name = path.basename(file.name); + } + + assert.deepEqual(kmpJsonActual, kmpJsonFixture); + }); /* * Testing Warnings and Errors */ From 4d71048daf02a3495617186578f0a800a5f7efe7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 10:06:17 +0700 Subject: [PATCH 004/207] chore(developer): fix casing --- common/web/types/src/package/kps-file.ts | 6 +++--- developer/src/kmc-package/src/compiler/kmp-compiler.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index ca854888db3..fdd7f1583a0 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -146,14 +146,14 @@ export interface KpsFileLanguageExample { /** * A space-separated list of keys, modifiers indicated with "+", spacebar is "space", plus key is "shift+=" or "plus" */ - keys: string; + Keys: string; /** * The text that would be generated by typing those keys */ - text?: string; + Text?: string; /** * A short description of what the text means or represents */ - note?: string; + Note?: string; } } diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index cf006d313b4..17b0668cf7f 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -137,7 +137,7 @@ export class KmpCompiler { [], examples: keyboard.examples ? (this.arrayWrap(keyboard.examples.example) as KpsFile.KpsFileLanguageExample[]).map( - e => ({id: e.$.ID, keys: e.$.keys, text: e.$.text, note: e.$.note}) + e => ({id: e.$.ID, keys: e.$.Keys, text: e.$.Text, note: e.$.Note}) ) as KmpJsonFile.KmpJsonFileExample[] : undefined })); From bf1d5b0f7ae3782a1cb37e66e9e5ba75be8ea0f4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 10:06:32 +0700 Subject: [PATCH 005/207] chore(developer): fix casing --- .../src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps index ecb7ac2b873..da135703b67 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps @@ -134,8 +134,8 @@ Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically c Central Khmer (Khmer, Cambodia) - - + + From d838df1c00130150722152079862fe39fc231a70 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 12:12:08 +0700 Subject: [PATCH 006/207] feat(developer): add editor of Package Description and Examples --- .../windows/delphi/packages/PackageInfo.pas | 197 ++++++++++++++++- .../src/tike/child/UfrmPackageEditor.dfm | 107 +++++++-- .../src/tike/child/UfrmPackageEditor.pas | 206 +++++++++++++++++- ...n.Developer.UI.UfrmEditLanguageExample.dfm | 96 ++++++++ ...n.Developer.UI.UfrmEditLanguageExample.pas | 139 ++++++++++++ .../Keyman.Developer.System.HelpTopics.pas | 1 + developer/src/tike/tike.dpr | 5 +- developer/src/tike/tike.dproj | 18 +- 8 files changed, 726 insertions(+), 43 deletions(-) create mode 100644 developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.dfm create mode 100644 developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index e468a9ceb74..f310c2d2464 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -41,6 +41,7 @@ interface System.Generics.Collections, System.IniFiles, System.JSON, + System.StrUtils, System.Sysutils, Winapi.Windows, Xml.XMLDoc, @@ -55,7 +56,15 @@ function GetJsonValueString(o: TJSONObject; const n: string): string; function GetJsonValueBool(o: TJSONObject; const n: string): Boolean; type - TPackageInfoEntryType = (pietName, pietVersion, pietCopyright, pietAuthor, pietWebsite, pietOther); + TPackageInfoEntryType = ( + pietName, + pietVersion, + pietCopyright, + pietAuthor, + pietWebsite, + pietDescription, + pietOther + ); const PackageInfo_Name = 'Name'; @@ -63,9 +72,17 @@ function GetJsonValueBool(o: TJSONObject; const n: string): Boolean; PackageInfo_Copyright = 'Copyright'; PackageInfo_Author = 'Author'; PackageInfo_Website = 'Website'; - - PackageInfoEntryTypeNames: array[TPackageInfoEntryType] of WideString = - (PackageInfo_Name, PackageInfo_Version, PackageInfo_Copyright, PackageInfo_Author, PackageInfo_Website, ''); + PackageInfo_Description = 'Description'; + + PackageInfoEntryTypeNames: array[TPackageInfoEntryType] of string = ( + PackageInfo_Name, + PackageInfo_Version, + PackageInfo_Copyright, + PackageInfo_Author, + PackageInfo_Website, + PackageInfo_Description, + '' + ); type EPackageInfo = class(Exception); @@ -282,6 +299,8 @@ TPackageKeyboard = class; TPackageKeyboardList = class; TPackageKeyboardLanguage = class; TPackageKeyboardLanguageList = class; + TPackageKeyboardExample = class; + TPackageKeyboardExampleList = class; TPackageKeyboard = class(TPackageBaseObject) private @@ -291,6 +310,7 @@ TPackageKeyboard = class(TPackageBaseObject) FRTL: Boolean; FDisplayFont: TPackageContentFile; FLanguages: TPackageKeyboardLanguageList; + FExamples: TPackageKeyboardExampleList; FVersion: string; FMinKeymanVersion: string; procedure SetDisplayFont(const Value: TPackageContentFile); @@ -308,6 +328,7 @@ TPackageKeyboard = class(TPackageBaseObject) property RTL: Boolean read FRTL write FRTL; property Version: string read FVersion write FVersion; property Languages: TPackageKeyboardLanguageList read FLanguages; + property Examples: TPackageKeyboardExampleList read FExamples; property OSKFont: TPackageContentFile read FOSKFont write SetOSKFont; property DisplayFont: TPackageContentFile read FDisplayFont write SetDisplayFont; // The following properties are used only in memory and never streamed in or out @@ -341,6 +362,23 @@ TPackageKeyboardLanguageList = class(TPackageObjectList) + public + procedure LoadJSON(ARoot: TJSONObject); virtual; + procedure SaveJSON(ARoot: TJSONObject); virtual; + procedure LoadXML(ARoot: IXMLNode); virtual; + procedure SaveXML(ARoot: IXMLNode); virtual; + function ContainsID(const id: string): Boolean; + function IndexOfID(const id: string; from: Integer = 0): Integer; + end; + TPackageLexicalModel = class(TPackageBaseObject) private FName: string; @@ -450,11 +488,18 @@ implementation SXML_PackageKeyboard_OSKFont = 'OSKFont'; SXML_PackageKeyboard_DisplayFont = 'DisplayFont'; SXML_PackageKeyboard_Languages = 'Languages'; + SXML_PackageKeyboard_Examples = 'Examples'; SXML_PackageKeyboard_Language = 'Language'; SXML_PackageKeyboard_Language_ID = 'ID'; SXML_PackageKeyboard_Language_Name = 'Name'; + SXML_PackageKeyboard_Example = 'Example'; + SXML_PackageKeyboard_Example_ID = 'ID'; + SXML_PackageKeyboard_Example_Keys = 'Keys'; + SXML_PackageKeyboard_Example_Text = 'Text'; + SXML_PackageKeyboard_Example_Note = 'Note'; + SXML_PackageLexicalModels = 'LexicalModels'; SXML_PackageLexicalModel = 'LexicalModel'; SXML_PackageLexicalModel_Name = 'Name'; @@ -493,14 +538,22 @@ implementation SJSON_Info__Description = 'description'; SJSON_Info__URL = 'url'; - SJSON_Info_Website = 'website'; - SJSON_Info_Version = 'version'; SJSON_Info_Name = 'name'; + SJSON_Info_Version = 'version'; SJSON_Info_Copyright = 'copyright'; SJSON_Info_Author = 'author'; - - SJSON_PackageInfoEntryTypeNames: array[TPackageInfoEntryType] of string = - (SJSON_Info_Name, SJSON_Info_Version, SJSON_Info_Copyright, SJSON_Info_Author, SJSON_Info_Website, ''); + SJSON_Info_Website = 'website'; + SJSON_Info_Description = 'description'; + + SJSON_PackageInfoEntryTypeNames: array[TPackageInfoEntryType] of string = ( + SJSON_Info_Name, + SJSON_Info_Version, + SJSON_Info_Copyright, + SJSON_Info_Author, + SJSON_Info_Website, + SJSON_Info_Description, + '' + ); SJSON_Files = 'files'; SJSON_Files_Name = 'name'; @@ -519,6 +572,12 @@ implementation SJSON_Keyboard_Language_ID = 'id'; SJSON_Keyboard_Language_Name = 'name'; + SJSON_Keyboard_Examples = 'examples'; + SJSON_Keyboard_Example_ID = 'id'; + SJSON_Keyboard_Example_Keys = 'keys'; + SJSON_Keyboard_Example_Text = 'text'; + SJSON_Keyboard_Example_Note = 'note'; + SJSON_LexicalModels = 'lexicalModels'; SJSON_LexicalModel_Name = 'name'; SJSON_LexicalModel_ID = 'id'; @@ -527,7 +586,7 @@ implementation function XmlVarToStr(v: OleVariant): string; begin - Result := Trim(VarToStr(v)); + Result := ReplaceStr(ReplaceStr(Trim(VarToStr(v)), #$D#$A, #$A), #$A, #$D#$A); end; {------------------------------------------------------------------------------- @@ -1822,6 +1881,7 @@ procedure TPackageKeyboard.Assign(Source: TPackageKeyboard); var i: Integer; FLanguage: TPackageKeyboardLanguage; + FExample: TPackageKeyboardExample; begin FName := Source.Name; FID := Source.ID; @@ -1842,6 +1902,16 @@ procedure TPackageKeyboard.Assign(Source: TPackageKeyboard); FLanguage.Name := Source.Languages[i].Name; FLanguages.Add(FLanguage); end; + FExamples.Clear; + for i := 0 to Source.Examples.Count - 1 do + begin + FExample := TPackageKeyboardExample.Create(Package); + FExample.ID := Source.Examples[i].ID; + FExample.Keys := Source.Examples[i].Keys; + FExample.Text := Source.Examples[i].Text; + FExample.Note := Source.Examples[i].Note; + FExamples.Add(FExample); + end; end; procedure TPackageKeyboard.SetDisplayFont(const Value: TPackageContentFile); @@ -1861,11 +1931,13 @@ constructor TPackageKeyboard.Create(APackage: TPackage); begin inherited Create(APackage); FLanguages := TPackageKeyboardLanguageList.Create(APackage); + FExamples := TPackageKeyboardExampleList.Create(APackage); end; destructor TPackageKeyboard.Destroy; begin FreeAndNil(FLanguages); + FreeAndNil(FExamples); inherited Destroy; end; @@ -1990,6 +2062,7 @@ procedure TPackageKeyboardList.LoadJSON(ARoot: TJSONObject); keyboard.OSKFont := Package.Files.FromFileNameEx(GetJsonValueString(AKeyboard, SJSON_Keyboard_OSKFont)); keyboard.DisplayFont := Package.Files.FromFileNameEx(GetJsonValueString(AKeyboard, SJSON_Keyboard_DisplayFont)); keyboard.Languages.LoadJSON(AKeyboard); + keyboard.Examples.LoadJSON(AKeyboard); Add(keyboard); end; end; @@ -2016,6 +2089,7 @@ procedure TPackageKeyboardList.LoadXML(ARoot: IXMLNode); keyboard.DisplayFont := Package.Files.FromFileNameEx(XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_DisplayFont])); keyboard.Languages.LoadXML(AKeyboard); + keyboard.Examples.LoadXML(AKeyboard); Add(keyboard); end; end; @@ -2071,6 +2145,7 @@ procedure TPackageKeyboardList.SaveJSON(ARoot: TJSONObject); AKeyboard.AddPair(SJSON_Keyboard_DisplayFont, Items[i].DisplayFont.RelativeFileName); Items[i].Languages.SaveJSON(AKeyboard); + Items[i].Examples.SaveJSON(AKeyboard); end; end; @@ -2095,6 +2170,7 @@ procedure TPackageKeyboardList.SaveXML(ARoot: IXMLNode); AKeyboard.ChildNodes[SXML_PackageKeyboard_DisplayFont].NodeValue := Items[i].DisplayFont.RelativeFileName; Items[i].Languages.SaveXML(AKeyboard); + Items[i].Examples.SaveXML(AKeyboard); end; end; @@ -2353,5 +2429,106 @@ procedure TPackageKeyboardLanguageList.SaveXML(ARoot: IXMLNode); end; end; +{ TPackageKeyboardExampleList } + +function TPackageKeyboardExampleList.ContainsID(const id: string): Boolean; +begin + Result := IndexOfID(id) >= 0; +end; + +function TPackageKeyboardExampleList.IndexOfID(const id: string; + from: Integer): Integer; +var + i: Integer; +begin + for i := from to Count - 1 do + if SameText(Items[i].ID, id) then + Exit(i); + Result := -1; +end; + +procedure TPackageKeyboardExampleList.LoadJSON(ARoot: TJSONObject); +var + j: Integer; + AExample: TJSONObject; + FExample: TPackageKeyboardExample; + AExamples: TJSONArray; +begin + AExamples := ARoot.Values[SJSON_Keyboard_Examples] as TJSONArray; + if not Assigned(AExamples) then + Exit; + + for j := 0 to AExamples.Count - 1 do + begin + AExample := AExamples.Items[j] as TJSONObject; + + FExample := TPackageKeyboardExample.Create(Package); + FExample.ID := GetJsonValueString(AExample, SJSON_Keyboard_Example_ID); + FExample.Keys := GetJsonValueString(AExample, SJSON_Keyboard_Example_Keys); + FExample.Text := GetJsonValueString(AExample, SJSON_Keyboard_Example_Text); + FExample.Note := GetJsonValueString(AExample, SJSON_Keyboard_Example_Note); + Self.Add(FExample); + end; +end; + +procedure TPackageKeyboardExampleList.LoadXML(ARoot: IXMLNode); +var + j: Integer; + AExamples, AExample: IXMLNode; + FExample: TPackageKeyboardExample; +begin + AExamples := ARoot.ChildNodes[SXML_PackageKeyboard_Examples]; + if not Assigned(AExamples) then + Exit; + + for j := 0 to AExamples.ChildNodes.Count - 1 do + begin + AExample := AExamples.ChildNodes[j]; + + FExample := TPackageKeyboardExample.Create(Package); + FExample.ID := VarToStr(AExample.Attributes[SXML_PackageKeyboard_Example_ID]); + FExample.Keys := VarToStr(AExample.Attributes[SXML_PackageKeyboard_Example_Keys]); + FExample.Text := VarToStr(AExample.Attributes[SXML_PackageKeyboard_Example_Text]); + FExample.Note := VarToStr(AExample.Attributes[SXML_PackageKeyboard_Example_Note]); + Self.Add(FExample); + end; +end; + +procedure TPackageKeyboardExampleList.SaveJSON(ARoot: TJSONObject); +var + AExamples: TJSONArray; + j: Integer; + AExample: TJSONObject; +begin + AExamples := TJSONArray.Create; + ARoot.AddPair(SJSON_Keyboard_Examples, AExamples); + for j := 0 to Count - 1 do + begin + AExample := TJSONObject.Create; + AExamples.Add(AExample); + AExample.AddPair(SJSON_Keyboard_Example_ID, Items[j].ID); + AExample.AddPair(SJSON_Keyboard_Example_Keys, Items[j].Keys); + AExample.AddPair(SJSON_Keyboard_Example_Text, Items[j].Text); + AExample.AddPair(SJSON_Keyboard_Example_Note, Items[j].Note); + end; +end; + +procedure TPackageKeyboardExampleList.SaveXML(ARoot: IXMLNode); +var + AExamples: IXMLNode; + j: Integer; + AExample: IXMLNode; +begin + AExamples := ARoot.AddChild(SXML_PackageKeyboard_Examples); + for j := 0 to Count - 1 do + begin + AExample := AExamples.AddChild(SXML_PackageKeyboard_Example); + AExample.Attributes[SXML_PackageKeyboard_Example_ID] := Items[j].ID; + AExample.Attributes[SXML_PackageKeyboard_Example_Keys] := Items[j].Keys; + AExample.Attributes[SXML_PackageKeyboard_Example_Text] := Items[j].Text; + AExample.Attributes[SXML_PackageKeyboard_Example_Note] := Items[j].Note; + end; +end; + end. diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index 4808af032f3..fe44e3de9bc 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -174,6 +174,7 @@ inherited frmPackageEditor: TfrmPackageEditor 1FFFFFFF9FFF} KeyPreview = True OnKeyDown = FormKeyDown + OnResize = FormResize ExplicitWidth = 965 ExplicitHeight = 622 PixelsPerInch = 96 @@ -183,7 +184,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 0 Width = 965 Height = 622 - ActivePage = pageFiles + ActivePage = pageKeyboards Align = alClient Images = modActionsMain.ilEditorPages MultiLine = True @@ -235,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 546 Height = 32 AutoSize = False - Caption = + Caption = 'A typical package will need keyboards, fonts, and documentation.' + ' You shouldn'#39't typically add source files. Also, don'#39't add any s' + 'tandard Keyman files (such as keyman.exe) here.' @@ -434,6 +435,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 56 Height = 13 Caption = 'Languages' + FocusControl = gridKeyboardLanguages end object lblKeyboardRTL: TLabel Left = 15 @@ -444,6 +446,14 @@ inherited frmPackageEditor: TfrmPackageEditor Caption = 'Is right-to-left:' ExplicitTop = 420 end + object lblKeyboardExamples: TLabel + Left = 260 + Top = 363 + Width = 47 + Height = 13 + Caption = 'Examples' + FocusControl = gridKeyboardExamples + end object lbKeyboards: TListBox Left = 15 Top = 72 @@ -509,8 +519,8 @@ inherited frmPackageEditor: TfrmPackageEditor Left = 260 Top = 144 Width = 593 - Height = 432 - Anchors = [akLeft, akTop, akRight, akBottom] + Height = 177 + Anchors = [akLeft, akTop, akRight] ColCount = 2 DefaultRowHeight = 16 FixedCols = 0 @@ -525,22 +535,20 @@ inherited frmPackageEditor: TfrmPackageEditor end object cmdKeyboardAddLanguage: TButton Left = 260 - Top = 587 + Top = 327 Width = 73 Height = 25 - Anchors = [akLeft, akBottom] Caption = '&Add...' TabOrder = 8 OnClick = cmdKeyboardAddLanguageClick end object cmdKeyboardRemoveLanguage: TButton Left = 418 - Top = 587 + Top = 327 Width = 72 Height = 25 - Anchors = [akLeft, akBottom] Caption = '&Remove' - TabOrder = 9 + TabOrder = 10 OnClick = cmdKeyboardRemoveLanguageClick end object editKeyboardRTL: TEdit @@ -556,14 +564,63 @@ inherited frmPackageEditor: TfrmPackageEditor end object cmdKeyboardEditLanguage: TButton Left = 339 - Top = 587 + Top = 327 Width = 73 Height = 25 - Anchors = [akLeft, akBottom] Caption = 'Ed&it...' - TabOrder = 10 + TabOrder = 9 OnClick = cmdKeyboardEditLanguageClick end + object gridKeyboardExamples: TStringGrid + Left = 260 + Top = 382 + Width = 593 + Height = 177 + Anchors = [akLeft, akTop, akRight, akBottom] + ColCount = 4 + DefaultRowHeight = 16 + FixedCols = 0 + RowCount = 9 + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] + TabOrder = 11 + OnClick = gridKeyboardExamplesClick + OnDblClick = gridKeyboardExamplesDblClick + ColWidths = ( + 78 + 64 + 64 + 64) + end + object cmdKeyboardAddExample: TButton + Left = 260 + Top = 565 + Width = 73 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = 'Add...' + TabOrder = 12 + OnClick = cmdKeyboardAddExampleClick + end + object cmdKeyboardEditExample: TButton + Left = 339 + Top = 565 + Width = 73 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = 'Ed&it...' + TabOrder = 13 + OnClick = cmdKeyboardEditExampleClick + end + object cmdKeyboardRemoveExample: TButton + Left = 418 + Top = 565 + Width = 72 + Height = 25 + Anchors = [akLeft, akBottom] + Caption = '&Remove' + TabOrder = 14 + OnClick = cmdKeyboardRemoveExampleClick + end end end object pageLexicalModels: TTabSheet @@ -845,6 +902,22 @@ inherited frmPackageEditor: TfrmPackageEditor Caption = 'e.g. 1.0.2' ExplicitLeft = 317 end + object lblDescription: TLabel + Left = 15 + Top = 379 + Width = 62 + Height = 13 + Caption = 'Description:' + FocusControl = memoInfoDescription + end + object lblDescriptionMarkdown: TLabel + Left = 108 + Top = 524 + Width = 215 + Height = 13 + Caption = 'Markdown accepted, no embedded HTML' + Transparent = True + end object cbReadMe: TComboBox Left = 108 Top = 116 @@ -954,6 +1027,14 @@ inherited frmPackageEditor: TfrmPackageEditor TabOrder = 3 OnClick = chkFollowKeyboardVersionClick end + object memoInfoDescription: TMemo + Left = 108 + Top = 376 + Width = 499 + Height = 145 + TabOrder = 11 + OnChange = memoInfoDescriptionChange + end end end object pageShortcuts: TTabSheet @@ -1144,7 +1225,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 48 Width = 551 Height = 13 - Caption = + Caption = 'Compiling the package takes all the files you have selected and ' + 'compresses them into a single package file.' end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index 4b36d67170b..8ec1c3e607d 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -207,6 +207,14 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 cmdConfigureWebDebugger: TButton; cmdSendURLsToEmail: TButton; cmdCopyDebuggerLink: TButton; + lblDescription: TLabel; + memoInfoDescription: TMemo; + lblDescriptionMarkdown: TLabel; + gridKeyboardExamples: TStringGrid; + lblKeyboardExamples: TLabel; + cmdKeyboardAddExample: TButton; + cmdKeyboardEditExample: TButton; + cmdKeyboardRemoveExample: TButton; procedure cmdCloseClick(Sender: TObject); procedure cmdAddFileClick(Sender: TObject); procedure cmdRemoveFileClick(Sender: TObject); @@ -271,6 +279,13 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure cmdCopyDebuggerLinkClick(Sender: TObject); procedure sbDetailsMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); + procedure memoInfoDescriptionChange(Sender: TObject); + procedure cmdKeyboardAddExampleClick(Sender: TObject); + procedure cmdKeyboardEditExampleClick(Sender: TObject); + procedure cmdKeyboardRemoveExampleClick(Sender: TObject); + procedure FormResize(Sender: TObject); + procedure gridKeyboardExamplesDblClick(Sender: TObject); + procedure gridKeyboardExamplesClick(Sender: TObject); private pack: TKPSFile; FSetup: Integer; @@ -317,6 +332,11 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure ShowAddLanguageForm(grid: TStringGrid; langs: TPackageKeyboardLanguageList); procedure RefreshLexicalModelList; procedure UpdateQRCode; + procedure RefreshKeyboardExampleList(k: TPackageKeyboard); + procedure ShowAddExampleForm(k: TPackageKeyboard); + procedure ShowEditExampleForm(k: TPackageKeyboard; example: TPackageKeyboardExample); + function SelectedKeyboardExample: TPackageKeyboardExample; + procedure ResizeGridColumns; protected function GetHelpTopic: string; override; @@ -345,6 +365,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 implementation uses + System.Math, Vcl.Clipbrd, Vcl.Imaging.GifImg, @@ -373,6 +394,7 @@ implementation UfrmMessages, UfrmSendURLsToEmail, utilexecute, + Keyman.Developer.UI.UfrmEditLanguageExample, Keyman.Developer.UI.UfrmSelectBCP47Language, xmldoc; @@ -478,6 +500,19 @@ procedure TfrmPackageEditor.FormKeyDown(Sender: TObject; var Key: Word; end; end; +procedure TfrmPackageEditor.FormResize(Sender: TObject); +begin + inherited; + ResizeGridColumns; +end; + +procedure TfrmPackageEditor.ResizeGridColumns; +begin + gridKeyboardLanguages.ColWidths[1] := System.Math.Max(gridKeyboardLanguages.ClientWidth - 120 - 1, 80); + gridLexicalModelLanguages.ColWidths[1] := System.Math.Max(gridLexicalModelLanguages.ClientWidth - 120 - 1, 80); + gridKeyboardExamples.ColWidths[3] := System.Math.Max(gridKeyboardExamples.ClientWidth - 120 - 200 - 120 - 3, 80); +end; + procedure TfrmPackageEditor.FormShow(Sender: TObject); begin inherited; @@ -1014,6 +1049,13 @@ procedure TfrmPackageEditor.lbStartMenuEntriesClick(Sender: TObject); end; end; +procedure TfrmPackageEditor.memoInfoDescriptionChange(Sender: TObject); +begin + if FSetup > 0 then Exit; + pack.Info.Desc['Description'] := Trim(memoInfoDescription.Text); + Modified := True; +end; + procedure TfrmPackageEditor.pagesChange(Sender: TObject); begin if FSetup > 0 then Exit; @@ -1290,6 +1332,7 @@ procedure TfrmPackageEditor.UpdateInfo(nm, desc, url: WideString); else if nmlc = 'version' then editInfoVersion.Text := desc // less 'mailto:' else if nmlc = 'author' then begin editInfoAuthor.Text := desc; editInfoEmail.Text := Copy(url,8,1024); end else if nmlc = 'website' then editInfoWebSite.Text := desc + else if nmlc = 'description' then memoInfoDescription.Text := desc finally Dec(FSetup); end; @@ -1510,6 +1553,20 @@ function TfrmPackageEditor.SelectedKeyboardLanguage: TPackageKeyboardLanguage; Result := gridKeyboardLanguages.Objects[0, gridKeyboardLanguages.Row] as TPackageKeyboardLanguage; end; +function TfrmPackageEditor.SelectedKeyboardExample: TPackageKeyboardExample; +var + k: TPackageKeyboard; +begin + k := SelectedKeyboard; + if not Assigned(k) then + Exit(nil); + + if gridKeyboardExamples.Row = 0 then + Exit(nil); + + Result := gridKeyboardExamples.Objects[0, gridKeyboardExamples.Row] as TPackageKeyboardExample; +end; + procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); var k: TPackageKeyboard; @@ -1522,6 +1579,15 @@ procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); gridKeyboardLanguages.ColWidths[0] := 120; gridKeyboardLanguages.ColWidths[1] := 10; + gridKeyboardExamples.Cells[0, 0] := 'BCP 47 tag'; + gridKeyboardExamples.Cells[1, 0] := 'Keys'; + gridKeyboardExamples.Cells[2, 0] := 'Text'; + gridKeyboardExamples.Cells[3, 0] := 'Note'; + gridKeyboardExamples.ColWidths[0] := 120; + gridKeyboardExamples.ColWidths[1] := 200; + gridKeyboardExamples.ColWidths[2] := 120; + gridKeyboardExamples.ColWidths[3] := 10; + k := SelectedKeyboard; if not Assigned(k) then begin @@ -1532,6 +1598,7 @@ procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); cbKeyboardOSKFont.ItemIndex := -1; cbKeyboardDisplayFont.ItemIndex := -1; gridKeyboardLanguages.RowCount := 1; + gridKeyboardExamples.RowCount := 1; EnableKeyboardTabControls; Exit; end; @@ -1560,38 +1627,47 @@ procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); // Languages RefreshKeyboardLanguageList(k); + RefreshKeyboardExampleList(k); EnableKeyboardTabControls; finally Dec(FSetup); end; end; -procedure TfrmPackageEditor.RefreshKeyboardLanguageList(k: TPackageKeyboard); +procedure TfrmPackageEditor.RefreshKeyboardExampleList(k: TPackageKeyboard); var i: Integer; begin Inc(FSetup); try - gridKeyboardLanguages.RowCount := k.Languages.Count + 1; - gridKeyboardLanguages.ColWidths[1] := gridKeyboardLanguages.ClientWidth - 120 - 1; + gridKeyboardExamples.RowCount := k.Examples.Count + 1; + ResizeGridColumns; - if k.Languages.Count > 0 then + if k.Examples.Count > 0 then begin - gridKeyboardLanguages.FixedRows := 1; + gridKeyboardExamples.FixedRows := 1; end else - gridKeyboardLanguages.Enabled := False; - for i := 0 to k.Languages.Count - 1 do + gridKeyboardExamples.Enabled := False; + + for i := 0 to k.Examples.Count - 1 do begin - gridKeyboardLanguages.Objects[0, i+1] := k.Languages[i]; - gridKeyboardLanguages.Cells[0, i+1] := k.Languages[i].ID; - gridKeyboardLanguages.Cells[1, i+1] := k.Languages[i].Name; + gridKeyboardExamples.Objects[0, i+1] := k.Examples[i]; + gridKeyboardExamples.Cells[0, i+1] := k.Examples[i].ID; + gridKeyboardExamples.Cells[1, i+1] := k.Examples[i].Keys; + gridKeyboardExamples.Cells[2, i+1] := k.Examples[i].Text; + gridKeyboardExamples.Cells[3, i+1] := k.Examples[i].Note; end; finally Dec(FSetup); end; end; +procedure TfrmPackageEditor.RefreshKeyboardLanguageList(k: TPackageKeyboard); +begin + RefreshLanguageList(gridKeyboardLanguages, k.Languages); +end; + procedure TfrmPackageEditor.cbKeyboardDisplayFontClick(Sender: TObject); var k: TPackageKeyboard; @@ -1666,11 +1742,30 @@ procedure TfrmPackageEditor.EnableKeyboardTabControls; cbKeyboardDisplayFont.Enabled := e; lblKeyboardLanguages.Enabled := e; cmdKeyboardAddLanguage.Enabled := e; + lblKeyboardExamples.Enabled := e; + cmdKeyboardAddExample.Enabled := e; e := e and (gridKeyboardLanguages.Row > 0); gridKeyboardLanguages.Enabled := e; cmdKeyboardRemoveLanguage.Enabled := e; cmdKeyboardEditLanguage.Enabled := e; + + e := (lbKeyboards.ItemIndex >= 0) and (gridKeyboardLanguages.Row > 0); + gridKeyboardExamples.Enabled := e; + cmdKeyboardRemoveExample.Enabled := e; + cmdKeyboardEditExample.Enabled := e; + +end; + +procedure TfrmPackageEditor.gridKeyboardExamplesClick(Sender: TObject); +begin + EnableKeyboardTabControls; +end; + +procedure TfrmPackageEditor.gridKeyboardExamplesDblClick(Sender: TObject); +begin + if SelectedKeyboardExample <> nil then + cmdKeyboardEditExample.Click; end; procedure TfrmPackageEditor.gridKeyboardLanguagesClick(Sender: TObject); @@ -1684,6 +1779,15 @@ procedure TfrmPackageEditor.gridKeyboardLanguagesDblClick(Sender: TObject); cmdKeyboardEditLanguage.Click; end; +procedure TfrmPackageEditor.cmdKeyboardAddExampleClick(Sender: TObject); +var + k: TPackageKeyboard; +begin + k := SelectedKeyboard; + Assert(Assigned(k)); + ShowAddExampleForm(k); +end; + procedure TfrmPackageEditor.cmdKeyboardAddLanguageClick(Sender: TObject); var k: TPackageKeyboard; @@ -1693,6 +1797,20 @@ procedure TfrmPackageEditor.cmdKeyboardAddLanguageClick(Sender: TObject); ShowAddLanguageForm(gridKeyboardLanguages, k.Languages); end; +procedure TfrmPackageEditor.cmdKeyboardEditExampleClick(Sender: TObject); +var + k: TPackageKeyboard; + example: TPackageKeyboardExample; +begin + k := SelectedKeyboard; + Assert(Assigned(k)); + + example := SelectedKeyboardExample; + Assert(Assigned(example)); + + ShowEditExampleForm(k, example); +end; + procedure TfrmPackageEditor.cmdKeyboardEditLanguageClick(Sender: TObject); var k: TPackageKeyboard; @@ -1707,6 +1825,22 @@ procedure TfrmPackageEditor.cmdKeyboardEditLanguageClick(Sender: TObject); ShowEditLanguageForm(gridKeyboardLanguages, k.Languages, lang); end; +procedure TfrmPackageEditor.cmdKeyboardRemoveExampleClick(Sender: TObject); +var + k: TPackageKeyboard; + example: TPackageKeyboardExample; +begin + k := SelectedKeyboard; + Assert(Assigned(k)); + example := SelectedKeyboardExample; + Assert(Assigned(example)); + + k.Examples.Remove(example); + RefreshKeyboardExampleList(k); + EnableKeyboardTabControls; + Modified := True; +end; + procedure TfrmPackageEditor.cmdKeyboardRemoveLanguageClick(Sender: TObject); var k: TPackageKeyboard; @@ -1768,7 +1902,7 @@ procedure TfrmPackageEditor.RefreshLanguageList(grid: TStringGrid; langs: TPacka Inc(FSetup); try grid.RowCount := langs.Count + 1; - grid.ColWidths[1] := grid.ClientWidth - 120 - 1; + ResizeGridColumns; if langs.Count > 0 then begin @@ -1863,6 +1997,56 @@ procedure TfrmPackageEditor.ShowEditLanguageForm(grid: TStringGrid; langs: TPack end; end; +procedure TfrmPackageEditor.ShowAddExampleForm(k: TPackageKeyboard); +var + example: TPackageKeyboardExample; + frm: TfrmEditLanguageExample; +begin + frm := TfrmEditLanguageExample.Create(Application.MainForm); + try + if frm.ShowModal = mrOk then + begin + example := TPackageKeyboardExample.Create(pack); + example.ID := frm.LanguageID; + example.Keys := frm.ExampleKeys; + example.Text := frm.ExampleText; + example.Note := frm.ExampleNote; + k.Examples.Add(example); + RefreshKeyboardExampleList(k); + gridKeyboardExamples.Row := gridKeyboardExamples.RowCount - 1; + Modified := True; + EnableControls; + end; + finally + frm.Free; + end; +end; + +procedure TfrmPackageEditor.ShowEditExampleForm(k: TPackageKeyboard; example: TPackageKeyboardExample); +var + frm: TfrmEditLanguageExample; +begin + frm := TfrmEditLanguageExample.Create(Application.MainForm); + try + frm.LanguageID := example.ID; + frm.ExampleKeys := example.Keys; + frm.ExampleText := example.Text; + frm.ExampleNote := example.Note; + if frm.ShowModal = mrOk then + begin + example.ID := frm.LanguageID; + example.Keys := frm.ExampleKeys; + example.Text := frm.ExampleText; + example.Note := frm.ExampleNote; + RefreshKeyboardExampleList(k); + EnableControls; + Modified := True; + end; + finally + frm.Free; + end; +end; + {------------------------------------------------------------------------------- - Lexical Models tab -------------------------------------------------------------------------------} diff --git a/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.dfm b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.dfm new file mode 100644 index 00000000000..d4aa0a9bf64 --- /dev/null +++ b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.dfm @@ -0,0 +1,96 @@ +inherited frmEditLanguageExample: TfrmEditLanguageExample + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'Edit Language Example' + ClientHeight = 163 + ClientWidth = 289 + Position = poScreenCenter + ExplicitWidth = 295 + ExplicitHeight = 192 + PixelsPerInch = 96 + TextHeight = 13 + object lblLanguageTag: TLabel + Left = 8 + Top = 11 + Width = 70 + Height = 13 + Caption = '&Language tag:' + FocusControl = editLanguageID + end + object lblExampleNote: TLabel + Left = 8 + Top = 92 + Width = 27 + Height = 13 + Caption = '&Note:' + FocusControl = editExampleNote + end + object lblExampleKeys: TLabel + Left = 8 + Top = 38 + Width = 71 + Height = 13 + Caption = 'Key &sequence:' + FocusControl = editExampleKeys + end + object lblExampleText: TLabel + Left = 8 + Top = 65 + Width = 72 + Height = 13 + Caption = 'Expected &text:' + FocusControl = editExampleText + end + object cmdOK: TButton + Left = 124 + Top = 127 + Width = 75 + Height = 25 + Caption = 'OK' + Default = True + ModalResult = 1 + TabOrder = 4 + end + object cmdCancel: TButton + Left = 205 + Top = 127 + Width = 75 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 5 + end + object editExampleNote: TEdit + Left = 94 + Top = 89 + Width = 186 + Height = 21 + TabOrder = 3 + OnChange = FieldChange + end + object editLanguageID: TEdit + Left = 94 + Top = 8 + Width = 131 + Height = 21 + TabOrder = 0 + OnChange = FieldChange + end + object editExampleKeys: TEdit + Left = 94 + Top = 35 + Width = 186 + Height = 21 + TabOrder = 1 + OnChange = FieldChange + end + object editExampleText: TEdit + Left = 94 + Top = 62 + Width = 186 + Height = 21 + TabOrder = 2 + OnChange = FieldChange + end +end diff --git a/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas new file mode 100644 index 00000000000..4abdbc4cfce --- /dev/null +++ b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas @@ -0,0 +1,139 @@ +(* + Name: UfrmEditLanguageExample + Copyright: Copyright (C) SIL International. + Date: 14 Aug 2023 + Authors: mcdurdin +*) +unit Keyman.Developer.UI.UfrmEditLanguageExample; + +interface + +uses + System.Classes, + System.SysUtils, + System.Variants, + Winapi.Messages, + Winapi.Windows, + Vcl.Controls, + Vcl.Dialogs, + Vcl.Forms, + Vcl.Graphics, + Vcl.StdCtrls, + + UfrmTike; + +type + TfrmEditLanguageExample = class(TTikeForm) + cmdOK: TButton; + cmdCancel: TButton; + lblLanguageTag: TLabel; + lblExampleNote: TLabel; + editExampleNote: TEdit; + editLanguageID: TEdit; + lblExampleKeys: TLabel; + editExampleKeys: TEdit; + lblExampleText: TLabel; + editExampleText: TEdit; + procedure FormCreate(Sender: TObject); + procedure FieldChange(Sender: TObject); + private + procedure EnableControls; + function GetExampleKeys: string; + function GetExampleNote: string; + function GetExampleText: string; + function GetLanguageID: string; + procedure SetExampleKeys(const Value: string); + procedure SetExampleNote(const Value: string); + procedure SetExampleText(const Value: string); + procedure SetLanguageID(const Value: string); + protected + function GetHelpTopic: string; override; + public + { Public declarations } + property LanguageID: string read GetLanguageID write SetLanguageID; + property ExampleKeys: string read GetExampleKeys write SetExampleKeys; + property ExampleText: string read GetExampleText write SetExampleText; + property ExampleNote: string read GetExampleNote write SetExampleNote; + end; + +implementation + +uses + System.Generics.Collections, + + Keyman.Developer.System.HelpTopics; + +{$R *.dfm} + +{ TfrmEditLanguageExample } + +procedure TfrmEditLanguageExample.FieldChange(Sender: TObject); +begin + inherited; + EnableControls; +end; + +procedure TfrmEditLanguageExample.FormCreate(Sender: TObject); +begin + inherited; + EnableControls; +end; + +function TfrmEditLanguageExample.GetExampleKeys: string; +begin + Result := Trim(editExampleKeys.Text); +end; + +function TfrmEditLanguageExample.GetExampleNote: string; +begin + Result := Trim(editExampleNote.Text); +end; + +function TfrmEditLanguageExample.GetExampleText: string; +begin + Result := Trim(editExampleText.Text); +end; + +function TfrmEditLanguageExample.GetHelpTopic: string; +begin + Result := SHelpTopic_Context_EditLanguageExample; +end; + +function TfrmEditLanguageExample.GetLanguageID: string; +begin + Result := Trim(editLanguageID.Text); +end; + +procedure TfrmEditLanguageExample.EnableControls; +begin + cmdOK.Enabled := + (LanguageID <> '') and + (ExampleKeys <> '') and + (ExampleText <> ''); +end; + +procedure TfrmEditLanguageExample.SetExampleKeys(const Value: string); +begin + editExampleKeys.Text := Value.Trim; + EnableControls; +end; + +procedure TfrmEditLanguageExample.SetExampleNote(const Value: string); +begin + editExampleNote.Text := Value.Trim; + EnableControls; +end; + +procedure TfrmEditLanguageExample.SetExampleText(const Value: string); +begin + editExampleText.Text := Value.Trim; + EnableControls; +end; + +procedure TfrmEditLanguageExample.SetLanguageID(const Value: string); +begin + editLanguageID.Text := Value.Trim; + EnableControls; +end; + +end. diff --git a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas index fcc955a8dc0..4f998160c33 100644 --- a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas +++ b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas @@ -47,6 +47,7 @@ interface SHelpTopic_Context_TouchLayoutBuilder = 'context/keyboard-editor#toc-touch-layout-tab'; SHelpTopic_Context_TestKeyboard = 'context/debug#toc-test-mode'; SHelpTopic_Context_WordlistEditor = 'context/wordlist-editor'; + SHelpTopic_Context_EditLanguageExample = 'context/edit-language-example'; // For all .kmn language reference topics, prefix with this path: SHelpTopic_LanguageReference_Prefix = 'language/reference/'; diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index 1a1b1a07fed..aca7985c636 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -284,7 +284,8 @@ uses Keyman.Developer.System.ServerAPI in 'http\Keyman.Developer.System.ServerAPI.pas', Keyman.System.FontLoadUtil in 'main\Keyman.System.FontLoadUtil.pas', Keyman.Developer.UI.ServerUI in 'http\Keyman.Developer.UI.ServerUI.pas', - Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas'; + Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', + Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}; {$R *.RES} {$R ICONS.RES} @@ -314,7 +315,7 @@ begin Application.Title := 'Keyman Developer'; if TikeActive then Exit; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - try + try Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); Application.Run; finally diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index 46661e39ce1..01655c3fab2 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -553,6 +553,10 @@ + +
frmEditLanguageExample
+ dfm +
Cfg_2 @@ -614,9 +618,9 @@ False - + - tike.rsm + .\ true @@ -626,21 +630,21 @@ true - + tike.rsm true - + - tike.exe + tike.rsm true - + - .\ + tike.exe true From 38c3415eed81f23716bb58ab30976cf610c3ca26 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 14:18:01 +0700 Subject: [PATCH 007/207] feat(developer): add relatedPackages metadata Adds relatedPackages support to: * .kps schema * .kps file format for Typescript * kmp.json file format for Typescript * kmc-package compiler * Package Editor --- common/schemas/kps/kps.xsd | 22 ++- common/web/types/src/package/kmp-json-file.ts | 8 +- common/web/types/src/package/kps-file.ts | 15 ++ .../windows/delphi/packages/PackageInfo.pas | 152 +++++++++++++++++- .../kmc-package/src/compiler/kmp-compiler.ts | 10 ++ .../test/fixtures/kmp_2.0/khmer_angkor.kps | 4 + .../test/fixtures/kmp_2.0/kmp.json | 4 + .../src/tike/child/UfrmPackageEditor.dfm | 88 +++++++--- .../src/tike/child/UfrmPackageEditor.pas | 142 ++++++++++++++++ ...n.Developer.UI.UfrmEditLanguageExample.pas | 7 + ...an.Developer.UI.UfrmEditRelatedPackage.dfm | 57 +++++++ ...an.Developer.UI.UfrmEditRelatedPackage.pas | 103 ++++++++++++ .../Keyman.Developer.System.HelpTopics.pas | 1 + developer/src/tike/tike.dpr | 3 +- developer/src/tike/tike.dproj | 4 + 15 files changed, 592 insertions(+), 28 deletions(-) create mode 100644 developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.dfm create mode 100644 developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.pas diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd index e951c3369ff..b53ce6b2339 100644 --- a/common/schemas/kps/kps.xsd +++ b/common/schemas/kps/kps.xsd @@ -97,6 +97,19 @@
+ + + + + + + + + + + + + @@ -137,6 +150,7 @@ + @@ -151,8 +165,6 @@ - - @@ -191,6 +203,12 @@ + + + + + + diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 6c56b500124..87aaaf63048 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -6,6 +6,7 @@ export interface KmpJsonFile { lexicalModels?: KmpJsonFileLexicalModel[]; startMenu?: KmpJsonFileStartMenu; keyboards?: KmpJsonFileKeyboard[]; + relatedPackages?: KmpJsonRelatedPackage[]; } export interface KmpJsonFileSystem { @@ -103,4 +104,9 @@ export interface KmpJsonFileExample { * A short description of what the text means or represents */ note?: string; -} \ No newline at end of file +} + +export interface KmpJsonRelatedPackage { + id: string; + relationship: "deprecates" | "related"; +} diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index fdd7f1583a0..43f2097b7a9 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -29,6 +29,7 @@ export interface KpsFile { lexicalModels?: KpsFileLexicalModels; startMenu?: KpsFileStartMenu; strings?: KpsFileStrings; + relatedPackages?: KpsFileRelatedPackages; } export interface KpsFileSystem { @@ -92,6 +93,20 @@ export interface KpsFileLanguage { $: { ID: string } } +export interface KpsFileRelatedPackages { + relatedPackage: KpsFileRelatedPackage | KpsFileRelatedPackage[]; +} + +export interface KpsFileRelatedPackage { + $: { + ID: string; + /** + * relationship between this package and the related package, "related" is default if not specified + */ + Relationship?: "deprecates"; + } +} + export interface KpsFileKeyboard { name: string; /// the descriptive name of the keyboard iD: string; /// the keyboard identifier, equal to the basename of the keyboard file sans extension diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index f310c2d2464..d7fadc409a8 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -404,6 +404,24 @@ TPackageLexicalModelList = class(TPackageObjectList) function ItemByID(id: string): TPackageLexicalModel; end; + TPackageRelatedPackage = class(TPackageBaseObject) + private + FID: string; + FRelationship: string; + public + procedure Assign(Source: TPackageRelatedPackage); virtual; + property ID: string read FID write FID; + property Relationship: string read FRelationship write FRelationship; + end; + + TPackageRelatedPackageList = class(TPackageObjectList) + procedure Assign(Source: TPackageRelatedPackageList); virtual; + procedure LoadXML(ARoot: IXMLNode); virtual; + procedure SaveXML(ARoot: IXMLNode); virtual; + procedure LoadJSON(ARoot: TJSONObject); virtual; + procedure SaveJSON(ARoot: TJSONObject); virtual; + end; + { TPackage } TPackage = class @@ -428,6 +446,7 @@ TPackage = class Info: TPackageInfoEntryList; Keyboards: TPackageKeyboardList; LexicalModels: TPackageLexicalModelList; + RelatedPackages: TPackageRelatedPackageList; property FileName: WideString read FFileName write FFileName; procedure Assign(Source: TPackage); virtual; @@ -455,6 +474,9 @@ TPackage = class const PackageStartMenuEntryLocationName: array[TPackageStartMenuEntryLocation] of WideString = ('Start Menu', 'Desktop'); //, 'Quick Launch Toolbar'); +const + S_RelatedPackage_Deprecates = 'deprecates'; + implementation uses @@ -475,9 +497,6 @@ implementation SDisplayFontNotOwnedCorrectly = 'The display font file ''%s'' referred to is not part of the package.'; SOSKFontNotOwnedCorrectly = 'The OSK font file ''%s'' referred to is not part of the package.'; - - - const SXML_PackageKeyboards = 'Keyboards'; SXML_PackageKeyboard = 'Keyboard'; @@ -507,6 +526,11 @@ implementation SXML_PackageLexicalModel_RTL = 'RTL'; SXML_PackageLexicalModel_Languages = 'Languages'; + SXML_PackageRelatedPackages = 'RelatedPackages'; + SXML_PackageRelatedPackage = 'RelatedPackage'; + SXML_PackageRelatedPackage_ID = 'ID'; + SXML_PackageRelatedPackage_Relationship = 'Relationship'; + const SJSON_System = 'system'; SJSON_System_KeymanDeveloperVersion = 'keymanDeveloperVersion'; @@ -584,6 +608,10 @@ implementation SJSON_LexicalModel_RTL = 'rtl'; SJSON_LexicalModel_Languages = 'languages'; + SJSON_RelatedPackages = 'relatedPackages'; + SJSON_RelatedPackage_ID = 'id'; + SJSON_RelatedPackage_Relationship = 'relationship'; + function XmlVarToStr(v: OleVariant): string; begin Result := ReplaceStr(ReplaceStr(Trim(VarToStr(v)), #$D#$A, #$A), #$A, #$D#$A); @@ -1479,6 +1507,7 @@ procedure TPackage.Assign(Source: TPackage); Info.Assign(Source.Info); Keyboards.Assign(Source.Keyboards); LexicalModels.Assign(Source.LexicalModels); + RelatedPackages.Assign(Source.RelatedPackages); end; constructor TPackage.Create; @@ -1491,6 +1520,7 @@ constructor TPackage.Create; if not Assigned(Info) then Info := TPackageInfoEntryList.Create(Self); if not Assigned(Keyboards) then Keyboards := TPackageKeyboardList.Create(Self); if not Assigned(LexicalModels) then LexicalModels := TPackageLexicalModelList.Create(Self); + if not Assigned(RelatedPackages) then RelatedPackages := TPackageRelatedPackageList.Create(Self); end; destructor TPackage.Destroy; @@ -1501,6 +1531,7 @@ destructor TPackage.Destroy; Info.Free; Keyboards.Free; LexicalModels.Free; + RelatedPackages.Free; inherited Destroy; end; @@ -1534,6 +1565,7 @@ procedure TPackage.DoLoadIni(ini: TIniFile); Options.LoadIni(ini); Keyboards.LoadIni(ini); //LexicalModels not supported in ini + //RelatedPackages not supported in ini end; procedure TPackage.LoadJSON; @@ -1652,8 +1684,9 @@ procedure TPackage.DoLoadJSON(ARoot: TJSONObject); Files.LoadJSON(ARoot); Options.LoadJSON(ARoot); Keyboards.LoadJSON(ARoot); - if Options.FileVersion = SKeymanVersion120 then + if CompareVersions(Options.FileVersion, SKeymanVersion120) >= 0 then LexicalModels.LoadJSON(ARoot); + RelatedPackages.LoadJSON(ARoot); end; procedure TPackage.DoLoadXML(ARoot: IXMLNode); @@ -1672,8 +1705,9 @@ procedure TPackage.DoLoadXML(ARoot: IXMLNode); Files.LoadXML(ARoot); Options.LoadXML(ARoot); Keyboards.LoadXML(ARoot); - if FVersion = SKeymanVersion120 then + if CompareVersions(FVersion, SKeymanVersion120) >= 0 then LexicalModels.LoadXML(ARoot); + RelatedPackages.LoadXML(ARoot); end; procedure TPackage.DoSaveIni(ini: TIniFile); @@ -1685,6 +1719,7 @@ procedure TPackage.DoSaveIni(ini: TIniFile); Files.SaveIni(ini); Keyboards.SaveIni(ini); // Lexical models not supported in ini + // RelatedPackages not supported in ini end; procedure TPackage.FixupFileVersion; @@ -1706,6 +1741,7 @@ procedure TPackage.DoSaveJSON(ARoot: TJSONObject); Keyboards.SaveJSON(ARoot); if LexicalModels.Count > 0 then LexicalModels.SaveJSON(ARoot); + RelatedPackages.SaveJSON(ARoot); end; procedure TPackage.DoSaveXML(ARoot: IXMLNode); @@ -1719,6 +1755,7 @@ procedure TPackage.DoSaveXML(ARoot: IXMLNode); Keyboards.SaveXML(ARoot); if LexicalModels.Count > 0 then LexicalModels.SaveXML(ARoot); + RelatedPackages.SaveXML(ARoot); end; procedure TPackage.SaveIni; @@ -2530,5 +2567,110 @@ procedure TPackageKeyboardExampleList.SaveXML(ARoot: IXMLNode); end; end; +{ TPackageRelatedPackage } + +procedure TPackageRelatedPackage.Assign(Source: TPackageRelatedPackage); +begin + FID := Source.ID; + FRelationship := Source.Relationship; +end; + +{ TPackageRelatedPackageList } + +procedure TPackageRelatedPackageList.Assign(Source: TPackageRelatedPackageList); +var + i: Integer; + rp: TPackageRelatedPackage; +begin + Clear; + for i := 0 to Source.Count - 1 do + begin + rp := TPackageRelatedPackage.Create(Package); + rp.Assign(Source[i]); + Add(rp); + end; +end; + +procedure TPackageRelatedPackageList.LoadJSON(ARoot: TJSONObject); +var + rp: TPackageRelatedPackage; + i: Integer; + ANode: TJSONArray; + ARelatedPackage: TJSONObject; +begin + Clear; + + ANode := ARoot.Values[SJSON_RelatedPackages] as TJSONArray; + if not Assigned(ANode) then + Exit; + + for i := 0 to ANode.Count - 1 do + begin + ARelatedPackage := ANode.Items[i] as TJSONObject; + + rp := TPackageRelatedPackage.Create(Package); + rp.ID := GetJsonValueString(ARelatedPackage,SJSON_RelatedPackage_ID); + rp.Relationship := GetJsonValueString(ARelatedPackage, SJSON_RelatedPackage_Relationship); + Add(rp); + end; +end; + +procedure TPackageRelatedPackageList.LoadXML(ARoot: IXMLNode); +var + rp: TPackageRelatedPackage; + i: Integer; + ARelatedPackage, ANode: IXMLNode; +begin + Clear; + + ANode := ARoot.ChildNodes[SXML_PackageRelatedPackages]; + for i := 0 to ANode.ChildNodes.Count - 1 do + begin + ARelatedPackage := ANode.ChildNodes[i]; + + rp := TPackageRelatedPackage.Create(Package); + rp.ID := XmlVarToStr(ARelatedPackage.Attributes[SXML_PackageRelatedPackage_ID]); + rp.Relationship := XmlVarToStr(ARelatedPackage.Attributes[SXML_PackageRelatedPackage_Relationship]); + Add(rp); + end; +end; + +procedure TPackageRelatedPackageList.SaveJSON(ARoot: TJSONObject); +var + i: Integer; + ARelatedPackage: TJSONObject; + ARelatedPackages: TJSONArray; +begin + if Count = 0 then + Exit; + + ARelatedPackages := TJSONArray.Create; + ARoot.AddPair(SJSON_RelatedPackages, ARelatedPackages); + + for i := 0 to Count - 1 do + begin + ARelatedPackage := TJSONObject.Create; + ARelatedPackages.Add(ARelatedPackage); + + ARelatedPackage.AddPair(SJSON_RelatedPackage_ID, Items[i].ID); + ARelatedPackage.AddPair(SJSON_RelatedPackage_Relationship, Items[i].Relationship); + end; +end; + +procedure TPackageRelatedPackageList.SaveXML(ARoot: IXMLNode); +var + i: Integer; + ARelatedPackage, ANode: IXMLNode; +begin + ANode := ARoot.AddChild(SXML_PackageRelatedPackages); + for i := 0 to Count - 1 do + begin + ARelatedPackage := ANode.AddChild(SXML_PackageRelatedPackage); + + ARelatedPackage.Attributes[SXML_PackageRelatedPackage_ID] := Items[i].ID; + ARelatedPackage.Attributes[SXML_PackageRelatedPackage_Relationship] := Items[i].Relationship; + end; +end; + end. diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 17b0668cf7f..3c88064e6d5 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -89,6 +89,16 @@ export class KmpCompiler { kmp.info = this.kpsInfoToKmpInfo(kps.info); } + // + // Add related package metadata + // + + if(kps.relatedPackages) { + kmp.relatedPackages = (this.arrayWrap(kps.relatedPackages.relatedPackage) as KpsFile.KpsFileRelatedPackage[]).map(p => + ({id: p.$.ID, relationship: p.$.Relationship || "related"}) + ); + } + // // Add file metadata // diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps index da135703b67..4f747b58893 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps @@ -123,6 +123,10 @@ Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically c .gif + + + + Khmer Angkor diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json index bd4d99c08bd..df70b4c97a4 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json @@ -127,5 +127,9 @@ } ] } + ], + "relatedPackages": [ + { "id": "modern_khmer", "relationship": "deprecates" }, + { "id": "khmer_nida", "relationship": "related" } ] } diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index fe44e3de9bc..d23089ab1c1 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -236,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 546 Height = 32 AutoSize = False - Caption = + Caption = 'A typical package will need keyboards, fonts, and documentation.' + ' You shouldn'#39't typically add source files. Also, don'#39't add any s' + 'tandard Keyman files (such as keyman.exe) here.' @@ -798,7 +798,7 @@ inherited frmPackageEditor: TfrmPackageEditor Transparent = True end object lblKMPImageSize: TLabel - Left = 108 + Left = 114 Top = 348 Width = 92 Height = 13 @@ -894,13 +894,12 @@ inherited frmPackageEditor: TfrmPackageEditor ParentFont = False end object lblVersionHint: TLabel - Left = 616 + Left = 622 Top = 157 Width = 46 Height = 13 Anchors = [akTop, akRight] Caption = 'e.g. 1.0.2' - ExplicitLeft = 317 end object lblDescription: TLabel Left = 15 @@ -911,15 +910,23 @@ inherited frmPackageEditor: TfrmPackageEditor FocusControl = memoInfoDescription end object lblDescriptionMarkdown: TLabel - Left = 108 - Top = 524 + Left = 114 + Top = 479 Width = 215 Height = 13 Caption = 'Markdown accepted, no embedded HTML' Transparent = True end + object lblRelatedPackages: TLabel + Left = 16 + Top = 515 + Width = 93 + Height = 13 + Caption = 'Related packages:' + FocusControl = gridRelatedPackages + end object cbReadMe: TComboBox - Left = 108 + Left = 114 Top = 116 Width = 499 Height = 21 @@ -929,7 +936,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnClick = cbReadMeClick end object editInfoName: TEdit - Left = 108 + Left = 114 Top = 64 Width = 499 Height = 21 @@ -938,7 +945,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoNameChange end object editInfoVersion: TEdit - Left = 108 + Left = 114 Top = 152 Width = 499 Height = 21 @@ -947,7 +954,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoVersionChange end object editInfoCopyright: TEdit - Left = 108 + Left = 114 Top = 200 Width = 499 Height = 21 @@ -957,7 +964,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoCopyrightChange end object editInfoAuthor: TEdit - Left = 108 + Left = 114 Top = 232 Width = 499 Height = 21 @@ -966,7 +973,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoAuthorChange end object editInfoEmail: TEdit - Left = 108 + Left = 114 Top = 256 Width = 499 Height = 21 @@ -975,7 +982,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoEmailChange end object editInfoWebSite: TEdit - Left = 108 + Left = 114 Top = 280 Width = 499 Height = 21 @@ -984,7 +991,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnChange = editInfoWebSiteChange end object cmdInsertCopyright: TButton - Left = 614 + Left = 620 Top = 200 Width = 49 Height = 21 @@ -994,7 +1001,7 @@ inherited frmPackageEditor: TfrmPackageEditor OnClick = cmdInsertCopyrightClick end object cbKMPImageFile: TComboBox - Left = 108 + Left = 114 Top = 324 Width = 499 Height = 21 @@ -1019,7 +1026,7 @@ inherited frmPackageEditor: TfrmPackageEditor end end object chkFollowKeyboardVersion: TCheckBox - Left = 108 + Left = 114 Top = 178 Width = 237 Height = 17 @@ -1028,13 +1035,56 @@ inherited frmPackageEditor: TfrmPackageEditor OnClick = chkFollowKeyboardVersionClick end object memoInfoDescription: TMemo - Left = 108 + Left = 114 Top = 376 Width = 499 - Height = 145 + Height = 97 TabOrder = 11 OnChange = memoInfoDescriptionChange end + object gridRelatedPackages: TStringGrid + Left = 114 + Top = 512 + Width = 415 + Height = 89 + ColCount = 2 + DefaultRowHeight = 16 + FixedCols = 0 + RowCount = 9 + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] + TabOrder = 12 + OnDblClick = gridRelatedPackagesDblClick + ColWidths = ( + 78 + 64) + end + object cmdAddRelatedPackage: TButton + Left = 541 + Top = 512 + Width = 73 + Height = 25 + Caption = '&Add...' + TabOrder = 13 + OnClick = cmdAddRelatedPackageClick + end + object cmdEditRelatedPackage: TButton + Left = 540 + Top = 544 + Width = 73 + Height = 25 + Caption = 'Ed&it...' + TabOrder = 14 + OnClick = cmdEditRelatedPackageClick + end + object cmdRemoveRelatedPackage: TButton + Left = 541 + Top = 576 + Width = 72 + Height = 25 + Caption = '&Remove' + TabOrder = 15 + OnClick = cmdRemoveRelatedPackageClick + end end end object pageShortcuts: TTabSheet @@ -1225,7 +1275,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 48 Width = 551 Height = 13 - Caption = + Caption = 'Compiling the package takes all the files you have selected and ' + 'compresses them into a single package file.' end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index 8ec1c3e607d..7b7ee3d949c 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -215,6 +215,11 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 cmdKeyboardAddExample: TButton; cmdKeyboardEditExample: TButton; cmdKeyboardRemoveExample: TButton; + lblRelatedPackages: TLabel; + gridRelatedPackages: TStringGrid; + cmdAddRelatedPackage: TButton; + cmdEditRelatedPackage: TButton; + cmdRemoveRelatedPackage: TButton; procedure cmdCloseClick(Sender: TObject); procedure cmdAddFileClick(Sender: TObject); procedure cmdRemoveFileClick(Sender: TObject); @@ -286,6 +291,10 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure FormResize(Sender: TObject); procedure gridKeyboardExamplesDblClick(Sender: TObject); procedure gridKeyboardExamplesClick(Sender: TObject); + procedure gridRelatedPackagesDblClick(Sender: TObject); + procedure cmdAddRelatedPackageClick(Sender: TObject); + procedure cmdEditRelatedPackageClick(Sender: TObject); + procedure cmdRemoveRelatedPackageClick(Sender: TObject); private pack: TKPSFile; FSetup: Integer; @@ -337,6 +346,8 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure ShowEditExampleForm(k: TPackageKeyboard; example: TPackageKeyboardExample); function SelectedKeyboardExample: TPackageKeyboardExample; procedure ResizeGridColumns; + procedure RefreshRelatedPackagesList; + function SelectedRelatedPackage: TPackageRelatedPackage; protected function GetHelpTopic: string; override; @@ -395,6 +406,7 @@ implementation UfrmSendURLsToEmail, utilexecute, Keyman.Developer.UI.UfrmEditLanguageExample, + Keyman.Developer.UI.UfrmEditRelatedPackage, Keyman.Developer.UI.UfrmSelectBCP47Language, xmldoc; @@ -431,12 +443,17 @@ procedure TfrmPackageEditor.FormCreate(Sender: TObject); EnableControls; + gridRelatedPackages.ColWidths[0] := 240; + gridRelatedPackages.Cells[0, 0] := 'ID'; + gridRelatedPackages.Cells[1, 0] := 'Relationship'; + UpdateStartMenuPrograms; UpdateReadme; UpdateImageFiles; UpdateImagePreviews; RefreshKeyboardList; RefreshLexicalModelList; + RefreshRelatedPackagesList; frameSource := TframeTextEditor.Create(Self); frameSource.Parent := pageSource; @@ -511,6 +528,7 @@ procedure TfrmPackageEditor.ResizeGridColumns; gridKeyboardLanguages.ColWidths[1] := System.Math.Max(gridKeyboardLanguages.ClientWidth - 120 - 1, 80); gridLexicalModelLanguages.ColWidths[1] := System.Math.Max(gridLexicalModelLanguages.ClientWidth - 120 - 1, 80); gridKeyboardExamples.ColWidths[3] := System.Math.Max(gridKeyboardExamples.ClientWidth - 120 - 200 - 120 - 3, 80); + gridRelatedPackages.ColWidths[1] := System.Math.Max(gridRelatedPackages.ClientWidth - 240 - 1, 80); end; procedure TfrmPackageEditor.FormShow(Sender: TObject); @@ -1313,6 +1331,7 @@ procedure TfrmPackageEditor.UpdateData; RefreshKeyboardList; RefreshLexicalModelList; + RefreshRelatedPackagesList; EnableControls; finally @@ -1723,6 +1742,13 @@ procedure TfrmPackageEditor.EnableDetailsTabControls; editInfoVersion.Enabled := e; lblStep2C.Enabled := e; lblVersionHint.Enabled := e; + + cmdAddRelatedPackage.Enabled := True; + + e := gridRelatedPackages.Row > 0; + gridRelatedPackages.Enabled := e; + cmdEditRelatedPackage.Enabled := e; + cmdRemoveRelatedPackage.Enabled := e; end; procedure TfrmPackageEditor.EnableKeyboardTabControls; @@ -2004,6 +2030,7 @@ procedure TfrmPackageEditor.ShowAddExampleForm(k: TPackageKeyboard); begin frm := TfrmEditLanguageExample.Create(Application.MainForm); try + frm.SetTitle(True); if frm.ShowModal = mrOk then begin example := TPackageKeyboardExample.Create(pack); @@ -2028,6 +2055,7 @@ procedure TfrmPackageEditor.ShowEditExampleForm(k: TPackageKeyboard; example: TP begin frm := TfrmEditLanguageExample.Create(Application.MainForm); try + frm.SetTitle(False); frm.LanguageID := example.ID; frm.ExampleKeys := example.Keys; frm.ExampleText := example.Text; @@ -2047,6 +2075,120 @@ procedure TfrmPackageEditor.ShowEditExampleForm(k: TPackageKeyboard; example: TP end; end; +{------------------------------------------------------------------------------- + - Related Packages + -------------------------------------------------------------------------------} + +procedure TfrmPackageEditor.RefreshRelatedPackagesList; +var + i: Integer; +begin + Inc(FSetup); + try + gridRelatedPackages.RowCount := pack.RelatedPackages.Count + 1; + ResizeGridColumns; + + if pack.RelatedPackages.Count > 0 then + begin + gridRelatedPackages.FixedRows := 1; + end; + + for i := 0 to pack.RelatedPackages.Count - 1 do + begin + gridRelatedPackages.Objects[0, i+1] := pack.RelatedPackages[i]; + gridRelatedPackages.Cells[0, i+1] := pack.RelatedPackages[i].ID; + if pack.RelatedPackages[i].Relationship = '' + then gridRelatedPackages.Cells[1, i+1] := 'related' // UI string + else gridRelatedPackages.Cells[1, i+1] := pack.RelatedPackages[i].Relationship; + end; + + EnableDetailsTabControls; + finally + Dec(FSetup); + end; +end; + +function TfrmPackageEditor.SelectedRelatedPackage: TPackageRelatedPackage; +begin + if gridRelatedPackages.Row = 0 then + Exit(nil); + + Result := gridRelatedPackages.Objects[0, gridRelatedPackages.Row] as TPackageRelatedPackage; +end; + +procedure TfrmPackageEditor.cmdAddRelatedPackageClick(Sender: TObject); +var + rp: TPackageRelatedPackage; + frm: TfrmEditRelatedPackage; +begin + frm := TfrmEditRelatedPackage.Create(Application.MainForm); + try + frm.SetTitle(True); + if frm.ShowModal = mrOk then + begin + rp := TPackageRelatedPackage.Create(pack); + rp.ID := frm.PackageID; + if frm.Deprecates + then rp.Relationship := S_RelatedPackage_Deprecates + else rp.Relationship := ''; + pack.RelatedPackages.Add(rp); + RefreshRelatedPackagesList; + gridRelatedPackages.Row := gridRelatedPackages.RowCount - 1; + Modified := True; + EnableControls; + end; + finally + frm.Free; + end; +end; + +procedure TfrmPackageEditor.cmdEditRelatedPackageClick(Sender: TObject); +var + rp: TPackageRelatedPackage; + frm: TfrmEditRelatedPackage; +begin + rp := SelectedRelatedPackage; + Assert(Assigned(rp)); + frm := TfrmEditRelatedPackage.Create(Application.MainForm); + try + frm.SetTitle(False); + frm.PackageID := rp.ID; + frm.Deprecates := rp.Relationship = S_RelatedPackage_Deprecates; + if frm.ShowModal = mrOk then + begin + rp.ID := frm.PackageID; + if frm.Deprecates + then rp.Relationship := S_RelatedPackage_Deprecates + else rp.Relationship := ''; + RefreshRelatedPackagesList; + Modified := True; + EnableControls; + end; + finally + frm.Free; + end; +end; + +procedure TfrmPackageEditor.cmdRemoveRelatedPackageClick(Sender: TObject); +var + rp: TPackageRelatedPackage; +begin + rp := SelectedRelatedPackage; + Assert(Assigned(rp)); + + pack.RelatedPackages.Remove(rp); + RefreshRelatedPackagesList; + EnableDetailsTabControls; + Modified := True; +end; + +procedure TfrmPackageEditor.gridRelatedPackagesDblClick(Sender: TObject); +begin + inherited; + if SelectedRelatedPackage <> nil then + cmdEditRelatedPackage.Click; +end; + {------------------------------------------------------------------------------- - Lexical Models tab -------------------------------------------------------------------------------} diff --git a/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas index 4abdbc4cfce..3bd0ab3463c 100644 --- a/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas +++ b/developer/src/tike/dialogs/examples/Keyman.Developer.UI.UfrmEditLanguageExample.pas @@ -50,6 +50,7 @@ TfrmEditLanguageExample = class(TTikeForm) function GetHelpTopic: string; override; public { Public declarations } + procedure SetTitle(IsAdding: Boolean); property LanguageID: string read GetLanguageID write SetLanguageID; property ExampleKeys: string read GetExampleKeys write SetExampleKeys; property ExampleText: string read GetExampleText write SetExampleText; @@ -136,4 +137,10 @@ procedure TfrmEditLanguageExample.SetLanguageID(const Value: string); EnableControls; end; +procedure TfrmEditLanguageExample.SetTitle(IsAdding: Boolean); +begin + if IsAdding then + Caption := 'Add Language Example'; +end; + end. diff --git a/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.dfm b/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.dfm new file mode 100644 index 00000000000..fc59c98a343 --- /dev/null +++ b/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.dfm @@ -0,0 +1,57 @@ +inherited frmEditRelatedPackage: TfrmEditRelatedPackage + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'Edit Related Package' + ClientHeight = 163 + ClientWidth = 289 + Position = poScreenCenter + ExplicitWidth = 295 + ExplicitHeight = 192 + PixelsPerInch = 96 + TextHeight = 13 + object lblPackageID: TLabel + Left = 8 + Top = 11 + Width = 58 + Height = 13 + Caption = '&Package ID:' + FocusControl = editPackageID + end + object cmdOK: TButton + Left = 124 + Top = 127 + Width = 75 + Height = 25 + Caption = 'OK' + Default = True + ModalResult = 1 + TabOrder = 2 + end + object cmdCancel: TButton + Left = 205 + Top = 127 + Width = 75 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 3 + end + object editPackageID: TEdit + Left = 94 + Top = 8 + Width = 131 + Height = 21 + TabOrder = 0 + OnChange = FieldChange + end + object chkDeprecates: TCheckBox + Left = 94 + Top = 35 + Width = 187 + Height = 17 + Caption = 'Deprecated' + TabOrder = 1 + OnClick = FieldChange + end +end diff --git a/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.pas b/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.pas new file mode 100644 index 00000000000..9b71b27b1c8 --- /dev/null +++ b/developer/src/tike/dialogs/relatedPackages/Keyman.Developer.UI.UfrmEditRelatedPackage.pas @@ -0,0 +1,103 @@ +(* + Name: UfrmEditRelatedPackage + Copyright: Copyright (C) SIL International. + Date: 14 Aug 2023 + Authors: mcdurdin +*) +unit Keyman.Developer.UI.UfrmEditRelatedPackage; + +interface + +uses + System.Classes, + System.SysUtils, + Winapi.Messages, + Winapi.Windows, + Vcl.Controls, + Vcl.Forms, + Vcl.StdCtrls, + + UfrmTike; + +type + TfrmEditRelatedPackage = class(TTikeForm) + cmdOK: TButton; + cmdCancel: TButton; + lblPackageID: TLabel; + editPackageID: TEdit; + chkDeprecates: TCheckBox; + procedure FormCreate(Sender: TObject); + procedure FieldChange(Sender: TObject); + private + procedure EnableControls; + function GetDeprecates: Boolean; + function GetPackageID: string; + procedure SetDeprecates(const Value: Boolean); + procedure SetPackageID(const Value: string); + protected + function GetHelpTopic: string; override; + public + { Public declarations } + procedure SetTitle(IsAdding: Boolean); + property PackageID: string read GetPackageID write SetPackageID; + property Deprecates: Boolean read GetDeprecates write SetDeprecates; + end; + +implementation + +uses + Keyman.Developer.System.HelpTopics; + +{$R *.dfm} + +{ TfrmEditRelatedPackage } + +procedure TfrmEditRelatedPackage.FieldChange(Sender: TObject); +begin + inherited; + EnableControls; +end; + +procedure TfrmEditRelatedPackage.FormCreate(Sender: TObject); +begin + inherited; + EnableControls; +end; + +function TfrmEditRelatedPackage.GetDeprecates: Boolean; +begin + Result := chkDeprecates.Checked; +end; + +function TfrmEditRelatedPackage.GetHelpTopic: string; +begin + Result := SHelpTopic_Context_EditRelatedPackage; +end; + +function TfrmEditRelatedPackage.GetPackageID: string; +begin + Result := Trim(editPackageID.Text); +end; + +procedure TfrmEditRelatedPackage.EnableControls; +begin + cmdOK.Enabled := (PackageID <> ''); +end; + +procedure TfrmEditRelatedPackage.SetDeprecates(const Value: Boolean); +begin + chkDeprecates.Checked := Value; +end; + +procedure TfrmEditRelatedPackage.SetPackageID(const Value: string); +begin + editPackageID.Text := Value.Trim; +end; + +procedure TfrmEditRelatedPackage.SetTitle(IsAdding: Boolean); +begin + if IsAdding then + Caption := 'Add Related Package'; +end; + +end. diff --git a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas index 4f998160c33..3268edc5998 100644 --- a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas +++ b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas @@ -48,6 +48,7 @@ interface SHelpTopic_Context_TestKeyboard = 'context/debug#toc-test-mode'; SHelpTopic_Context_WordlistEditor = 'context/wordlist-editor'; SHelpTopic_Context_EditLanguageExample = 'context/edit-language-example'; + SHelpTopic_Context_EditRelatedPackage = 'context/edit-related-package'; // For all .kmn language reference topics, prefix with this path: SHelpTopic_LanguageReference_Prefix = 'language/reference/'; diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index aca7985c636..f9e7db2068a 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -285,7 +285,8 @@ uses Keyman.System.FontLoadUtil in 'main\Keyman.System.FontLoadUtil.pas', Keyman.Developer.UI.ServerUI in 'http\Keyman.Developer.UI.ServerUI.pas', Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', - Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}; + Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}, + Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}; {$R *.RES} {$R ICONS.RES} diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index 01655c3fab2..a8fa4a7e0bf 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -557,6 +557,10 @@
frmEditLanguageExample
dfm + +
frmEditRelatedPackage
+ dfm +
Cfg_2 From 8a9243eba59576c7d844b585c2969605f478c0a7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 14 Aug 2023 14:31:17 +0700 Subject: [PATCH 008/207] chore(developer): tweak Relationship values --- common/windows/delphi/packages/PackageInfo.pas | 15 +++++++++++++-- .../src/kmc-package/src/compiler/kmp-compiler.ts | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index d7fadc409a8..2b750997e89 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -2611,6 +2611,9 @@ procedure TPackageRelatedPackageList.LoadJSON(ARoot: TJSONObject); rp := TPackageRelatedPackage.Create(Package); rp.ID := GetJsonValueString(ARelatedPackage,SJSON_RelatedPackage_ID); rp.Relationship := GetJsonValueString(ARelatedPackage, SJSON_RelatedPackage_Relationship); + if rp.Relationship <> 'deprecates' then + rp.Relationship := ''; + Add(rp); end; end; @@ -2631,6 +2634,9 @@ procedure TPackageRelatedPackageList.LoadXML(ARoot: IXMLNode); rp := TPackageRelatedPackage.Create(Package); rp.ID := XmlVarToStr(ARelatedPackage.Attributes[SXML_PackageRelatedPackage_ID]); rp.Relationship := XmlVarToStr(ARelatedPackage.Attributes[SXML_PackageRelatedPackage_Relationship]); + if rp.Relationship <> 'deprecates' then + rp.Relationship := ''; + Add(rp); end; end; @@ -2653,7 +2659,10 @@ procedure TPackageRelatedPackageList.SaveJSON(ARoot: TJSONObject); ARelatedPackages.Add(ARelatedPackage); ARelatedPackage.AddPair(SJSON_RelatedPackage_ID, Items[i].ID); - ARelatedPackage.AddPair(SJSON_RelatedPackage_Relationship, Items[i].Relationship); + // Relationship field required for kmp.json + if Items[i].Relationship = '' + then ARelatedPackage.AddPair(SJSON_RelatedPackage_Relationship, 'related') + else ARelatedPackage.AddPair(SJSON_RelatedPackage_Relationship, Items[i].Relationship); end; end; @@ -2668,7 +2677,9 @@ procedure TPackageRelatedPackageList.SaveXML(ARoot: IXMLNode); ARelatedPackage := ANode.AddChild(SXML_PackageRelatedPackage); ARelatedPackage.Attributes[SXML_PackageRelatedPackage_ID] := Items[i].ID; - ARelatedPackage.Attributes[SXML_PackageRelatedPackage_Relationship] := Items[i].Relationship; + // Relationship field optional for .kps + if Items[i].Relationship <> '' then + ARelatedPackage.Attributes[SXML_PackageRelatedPackage_Relationship] := Items[i].Relationship; end; end; diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 3c88064e6d5..b0993572826 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -94,8 +94,10 @@ export class KmpCompiler { // if(kps.relatedPackages) { + // Note: 'relationship' field is required for kmp.json but optional for .kps, only + // two values are supported -- deprecates or related. kmp.relatedPackages = (this.arrayWrap(kps.relatedPackages.relatedPackage) as KpsFile.KpsFileRelatedPackage[]).map(p => - ({id: p.$.ID, relationship: p.$.Relationship || "related"}) + ({id: p.$.ID, relationship: p.$.Relationship == 'deprecates' ? 'deprecates' : 'related'}) ); } From fea10f0eeb51f1de67661147e82d95edc99b1048 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 15 Aug 2023 15:13:24 +0700 Subject: [PATCH 009/207] feat(developer): add web font references to package While a package supports a .ttf or .otf font for the OSK and Display fonts for use within the Keyman apps, on websites it also supports .woff and .woff2. This change makes it possible to specify a set of fonts for a package which will be provided through api.keyman.com to websites. This means that .woff and .woff2 fonts may also be included in the package and may not be used on target devices at this time, it further opens the pathway to deploying .kmp packages to web in the future. The deployment stage of keymanapp/keyboards will require a new phase to copy web fonts from packages to s.keyman.com. --- common/schemas/kps/kps.xsd | 12 + common/web/types/src/package/kmp-json-file.ts | 13 +- common/web/types/src/package/kps-file.ts | 18 ++ .../windows/delphi/general/utilfiletypes.pas | 5 +- .../windows/delphi/packages/PackageInfo.pas | 231 ++++++++++++++++-- .../kmc-package/src/compiler/kmp-compiler.ts | 14 +- .../test/fixtures/kmp_2.0/DejaVuSans.ttf | Bin 0 -> 720012 bytes .../test/fixtures/kmp_2.0/khmer_angkor.kps | 12 + .../test/fixtures/kmp_2.0/kmp.json | 6 + .../src/tike/child/UfrmPackageEditor.dfm | 42 +++- .../src/tike/child/UfrmPackageEditor.pas | 55 +++++ ...n.Developer.UI.UfrmEditPackageWebFonts.dfm | 40 +++ ...n.Developer.UI.UfrmEditPackageWebFonts.pas | 102 ++++++++ .../Keyman.Developer.System.HelpTopics.pas | 1 + developer/src/tike/tike.dpr | 3 +- developer/src/tike/tike.dproj | 18 +- 16 files changed, 527 insertions(+), 45 deletions(-) create mode 100644 developer/src/kmc-package/test/fixtures/kmp_2.0/DejaVuSans.ttf create mode 100644 developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.dfm create mode 100644 developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.pas diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd index b53ce6b2339..1e305eba275 100644 --- a/common/schemas/kps/kps.xsd +++ b/common/schemas/kps/kps.xsd @@ -125,6 +125,8 @@ + + @@ -242,4 +244,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 87aaaf63048..eabb0624e24 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -20,11 +20,6 @@ export interface KmpJsonFileOptions { executeProgram?: string; msiFilename?: string; msiOptions?: string; - /** - * List of all font files, grouped by font family - * Compiler extracts font metadata from .ttf,.otf,.woff,.woff2 - */ - fonts?: {[family:string]: string[]}; } export interface KmpJsonFileInfo { @@ -71,6 +66,14 @@ export interface KmpJsonFileKeyboard { rtl?: boolean; languages?: KmpJsonFileLanguage[]; examples?: KmpJsonFileExample[]; + /** + * array of web font alternatives for OSK. should be same font data as oskFont + */ + webOskFonts?: string[]; + /** + * array of web font alternatives for display. should be same font data as displayFont + */ + webDisplayFonts?: string[]; } export interface KmpJsonFileStartMenu { diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 43f2097b7a9..2a49df3dd7f 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -116,6 +116,24 @@ export interface KpsFileKeyboard { rTL?: string; languages?: KpsFileLanguages; examples?: KpsFileLanguageExamples; + /** + * array of web font alternatives for OSK. should be same font data as oskFont + */ + webOSKFonts?: KpsFileFonts; + /** + * array of web font alternatives for display. should be same font data as displayFont + */ + webDisplayFonts?: KpsFileFonts; +} + +export interface KpsFileFonts { + font: KpsFileFont[] | KpsFileFont; +} + +export interface KpsFileFont { + $: { + Filename: string; + } } export interface KpsFileKeyboards { diff --git a/common/windows/delphi/general/utilfiletypes.pas b/common/windows/delphi/general/utilfiletypes.pas index bf1f09fbf40..56a718bcd06 100644 --- a/common/windows/delphi/general/utilfiletypes.pas +++ b/common/windows/delphi/general/utilfiletypes.pas @@ -62,14 +62,15 @@ TKMFileTypeInfo = record Ext_LexicalModelPackageSource = '.model.kps'; Ext_LexicalModelProject = '.model.kpj'; -const ExtFileTypes: array[0..13] of TKMFileTypeInfo = ( +const ExtFileTypes: array[0..14] of TKMFileTypeInfo = ( (Ext: Ext_KeymanSource; FileType: ftKeymanSource), (Ext: Ext_PackageSource; FileType: ftPackageSource), (Ext: Ext_KeymanFile; FileType: ftKeymanFile), (Ext: Ext_PackageFile; FileType: ftPackageFile), (Ext: '.ttf'; FileType: ftFont), (Ext: '.otf'; FileType: ftFont), - (Ext: '.fon'; FileType: ftFont), + (Ext: '.woff'; FileType: ftFont), + (Ext: '.woff2'; FileType: ftFont), (Ext: '.ttc'; FileType: ftFont), (Ext: Ext_VisualKeyboard; FileType: ftVisualKeyboard), (Ext: Ext_VisualKeyboardSource; FileType: ftVisualKeyboardSource), diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index 2b750997e89..abe520aaefb 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -139,6 +139,7 @@ TPackageObjectList = class(TObjectList) TPackageContentFile = class; TPackageContentFileList = class; + TPackageContentFileReferenceList = class; { Package Options } @@ -313,12 +314,14 @@ TPackageKeyboard = class(TPackageBaseObject) FExamples: TPackageKeyboardExampleList; FVersion: string; FMinKeymanVersion: string; + FWebOSKFonts: TPackageContentFileReferenceList; + FWebDisplayFonts: TPackageContentFileReferenceList; procedure SetDisplayFont(const Value: TPackageContentFile); procedure SetOSKFont(const Value: TPackageContentFile); - procedure DisplayFontRemoved(Sender: TObject; - EventType: TPackageNotifyEventType; var FAllow: Boolean); - procedure OSKFontRemoved(Sender: TObject; + procedure FontRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); + procedure FontListNotify(Sender: TObject; const Item: TPackageContentFile; + Action: TCollectionNotification); public constructor Create(APackage: TPackage); override; destructor Destroy; override; @@ -331,10 +334,23 @@ TPackageKeyboard = class(TPackageBaseObject) property Examples: TPackageKeyboardExampleList read FExamples; property OSKFont: TPackageContentFile read FOSKFont write SetOSKFont; property DisplayFont: TPackageContentFile read FDisplayFont write SetDisplayFont; + property WebOSKFonts: TPackageContentFileReferenceList read FWebOSKFonts; + property WebDisplayFonts: TPackageContentFileReferenceList read FWebDisplayFonts; // The following properties are used only in memory and never streamed in or out property MinKeymanVersion: string read FMinKeymanVersion write FMinKeymanVersion; end; + TPackageContentFileReferenceList = class(TPackageObjectList) + public + constructor Create(APackage: TPackage); + procedure Assign(Source: TPackageContentFileReferenceList); virtual; + procedure LoadXML(ARoot: IXMLNode); virtual; + procedure SaveXML(ARoot: IXMLNode); virtual; + procedure LoadJSON(ARoot: TJSONArray); virtual; + procedure SaveJSON(ARoot: TJSONArray); virtual; + function GetAsString: string; + end; + TPackageKeyboardList = class(TPackageObjectList) public procedure Assign(Source: TPackageKeyboardList); virtual; @@ -519,6 +535,11 @@ implementation SXML_PackageKeyboard_Example_Text = 'Text'; SXML_PackageKeyboard_Example_Note = 'Note'; + SXML_PackageKeyboard_WebOskFonts = 'WebOSKFonts'; + SXML_PackageKeyboard_WebDisplayFonts = 'WebDisplayFonts'; + SXML_PackageKeyboardFont = 'Font'; + SXML_PackageKeyboardFont_Filename = 'Filename'; + SXML_PackageLexicalModels = 'LexicalModels'; SXML_PackageLexicalModel = 'LexicalModel'; SXML_PackageLexicalModel_Name = 'Name'; @@ -602,6 +623,9 @@ implementation SJSON_Keyboard_Example_Text = 'text'; SJSON_Keyboard_Example_Note = 'note'; + SJSON_Keyboard_WebOSKFonts = 'webOskFonts'; + SJSON_Keyboard_WebDisplayFonts = 'webDisplayFonts'; + SJSON_LexicalModels = 'lexicalModels'; SJSON_LexicalModel_Name = 'name'; SJSON_LexicalModel_ID = 'id'; @@ -1949,18 +1973,25 @@ procedure TPackageKeyboard.Assign(Source: TPackageKeyboard); FExample.Note := Source.Examples[i].Note; FExamples.Add(FExample); end; + + FWebOSKFonts.Clear; + FWebOSKFonts.Assign(Source.WebOSKFonts); + + FWebDisplayFonts.Clear; + FWebDisplayFonts.Assign(Source.WebDisplayFonts); end; procedure TPackageKeyboard.SetDisplayFont(const Value: TPackageContentFile); begin - if Assigned(FDisplayFont) then FDisplayFont.RemoveNotifyObject(DisplayFontRemoved); + if Assigned(FDisplayFont) then + FDisplayFont.RemoveNotifyObject(FontRemoved); if not Assigned(Value) then FDisplayFont := nil else begin if Value.Package <> Package then raise EPackageInfo.CreateFmt(SDisplayFontNotOwnedCorrectly, [Value]); FDisplayFont := Value; - FDisplayFont.AddNotifyObject(DisplayFontRemoved); + FDisplayFont.AddNotifyObject(FontRemoved); end; end; @@ -1969,40 +2000,67 @@ constructor TPackageKeyboard.Create(APackage: TPackage); inherited Create(APackage); FLanguages := TPackageKeyboardLanguageList.Create(APackage); FExamples := TPackageKeyboardExampleList.Create(APackage); + FWebOSKFonts := TPackageContentFileReferenceList.Create(APackage); + FWebOSKFonts.OnNotify := FontListNotify; + FWebDisplayFonts := TPackageContentFileReferenceList.Create(APackage); + FWebDisplayFonts.OnNotify := FontListNotify; end; destructor TPackageKeyboard.Destroy; begin + if Assigned(FDisplayFont) then + FDisplayFont.RemoveNotifyObject(FontRemoved); + FDisplayFont := nil; + + if Assigned(FOSKFont) then + FOSKFont.RemoveNotifyObject(FontRemoved); + FOSKFont := nil; + FreeAndNil(FLanguages); FreeAndNil(FExamples); + FreeAndNil(FWebOSKFonts); + FreeAndNil(FWebDisplayFonts); inherited Destroy; end; -procedure TPackageKeyboard.DisplayFontRemoved(Sender: TObject; +procedure TPackageKeyboard.FontListNotify(Sender: TObject; + const Item: TPackageContentFile; Action: TCollectionNotification); +begin + if Action = cnRemoved then + Item.RemoveNotifyObject(FontRemoved) + else if Action = cnAdded then + begin + Assert(Item.Package = Self.Package); + Item.AddNotifyObject(FontRemoved); + end; +end; + +procedure TPackageKeyboard.FontRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); begin - FDisplayFont := nil; + // For all EventTypes + if Sender = FDisplayFont then + FDisplayFont := nil; + if Sender = FOSKFont then + FOSKFont := nil; + FWebDisplayFonts.Remove(Sender as TPackageContentFile); + FWebOSKFonts.Remove(Sender as TPackageContentFile); end; procedure TPackageKeyboard.SetOSKFont(const Value: TPackageContentFile); begin - if Assigned(FOSKFont) then FOSKFont.RemoveNotifyObject(OSKFontRemoved); + if Assigned(FOSKFont) then + FOSKFont.RemoveNotifyObject(FontRemoved); if not Assigned(Value) then FOSKFont := nil else begin if Value.Package <> Package then raise EPackageInfo.CreateFmt(SOSKFontNotOwnedCorrectly, [Value]); FOSKFont := Value; - FOSKFont.AddNotifyObject(OSKFontRemoved); + FOSKFont.AddNotifyObject(FontRemoved); end; end; -procedure TPackageKeyboard.OSKFontRemoved(Sender: TObject; - EventType: TPackageNotifyEventType; var FAllow: Boolean); -begin - FOSKFont := nil; -end; - { TPackageKeyboardList } procedure TPackageKeyboardList.Assign(Source: TPackageKeyboardList); @@ -2067,6 +2125,8 @@ procedure TPackageKeyboardList.LoadIni(AIni: TIniFile); keyboard.Languages.Add(FLanguage); end; + // web fonts not supported in ini + Add(keyboard); end; finally @@ -2078,7 +2138,7 @@ procedure TPackageKeyboardList.LoadJSON(ARoot: TJSONObject); var keyboard: TPackageKeyboard; i: Integer; - ANode: TJSONArray; + ASubNode, ANode: TJSONArray; AKeyboard: TJSONObject; begin Clear; @@ -2100,6 +2160,15 @@ procedure TPackageKeyboardList.LoadJSON(ARoot: TJSONObject); keyboard.DisplayFont := Package.Files.FromFileNameEx(GetJsonValueString(AKeyboard, SJSON_Keyboard_DisplayFont)); keyboard.Languages.LoadJSON(AKeyboard); keyboard.Examples.LoadJSON(AKeyboard); + + ASubNode := AKeyboard.GetValue(SJSON_Keyboard_WebOSKFonts) as TJSONArray; + if Assigned(ASubNode) then + keyboard.WebOSKFonts.LoadJSON(ASubNode); + + ASubNode := AKeyboard.GetValue(SJSON_Keyboard_WebDisplayFonts) as TJSONArray; + if Assigned(ASubNode) then + keyboard.WebDisplayFonts.LoadJSON(ASubNode); + Add(keyboard); end; end; @@ -2108,7 +2177,7 @@ procedure TPackageKeyboardList.LoadXML(ARoot: IXMLNode); var keyboard: TPackageKeyboard; i: Integer; - AKeyboard, ANode: IXMLNode; + AKeyboard, ANode, ASubNode: IXMLNode; begin Clear; @@ -2127,6 +2196,19 @@ procedure TPackageKeyboardList.LoadXML(ARoot: IXMLNode); keyboard.Languages.LoadXML(AKeyboard); keyboard.Examples.LoadXML(AKeyboard); + + ASubNode := AKeyboard.ChildNodes[SXML_PackageKeyboard_WebOSKFonts]; + if Assigned(ASubNode) then + begin + keyboard.WebOSKFonts.LoadXML(ASubNode); + end; + + ASubNode := AKeyboard.ChildNodes[SXML_PackageKeyboard_WebDisplayFonts]; + if Assigned(ASubNode) then + begin + keyboard.WebDisplayFonts.LoadXML(ASubNode); + end; + Add(keyboard); end; end; @@ -2152,6 +2234,8 @@ procedure TPackageKeyboardList.SaveIni(AIni: TIniFile); begin AIni.WriteString(FSectionName, SXML_PackageKeyboard_Language+IntToStr(j), Items[i].Languages[j].ID+','+Items[i].Languages[j].Name); end; + + // web fonts not supported in ini end; end; @@ -2159,7 +2243,7 @@ procedure TPackageKeyboardList.SaveJSON(ARoot: TJSONObject); var i: Integer; AKeyboard: TJSONObject; - AKeyboards: TJSONArray; + AFonts, AKeyboards: TJSONArray; begin if Count = 0 then Exit; @@ -2183,13 +2267,26 @@ procedure TPackageKeyboardList.SaveJSON(ARoot: TJSONObject); Items[i].Languages.SaveJSON(AKeyboard); Items[i].Examples.SaveJSON(AKeyboard); + if Items[i].WebOSKFonts.Count > 0 then + begin + AFonts := TJSONArray.Create; + Items[i].WebOSKFonts.SaveJSON(AFonts); + AKeyboard.AddPair(SJSON_Keyboard_WebOSKFonts, AFonts); + end; + + if Items[i].WebDisplayFonts.Count > 0 then + begin + AFonts := TJSONArray.Create; + Items[i].WebDisplayFonts.SaveJSON(AFonts); + AKeyboard.AddPair(SJSON_Keyboard_WebDisplayFonts, AFonts); + end; end; end; procedure TPackageKeyboardList.SaveXML(ARoot: IXMLNode); var i: Integer; - AKeyboard, ANode: IXMLNode; + AFonts, AKeyboard, ANode: IXMLNode; begin ANode := ARoot.AddChild(SXML_PackageKeyboards); for i := 0 to Count - 1 do @@ -2208,6 +2305,17 @@ procedure TPackageKeyboardList.SaveXML(ARoot: IXMLNode); Items[i].Languages.SaveXML(AKeyboard); Items[i].Examples.SaveXML(AKeyboard); + if Items[i].WebOSKFonts.Count > 0 then + begin + AFonts := AKeyboard.AddChild(SXML_PackageKeyboard_WebOskFonts); + Items[i].WebOSKFonts.SaveXML(AFonts); + end; + + if Items[i].WebDisplayFonts.Count > 0 then + begin + AFonts := AKeyboard.AddChild(SXML_PackageKeyboard_WebDisplayFonts); + Items[i].WebDisplayFonts.SaveXML(AFonts); + end; end; end; @@ -2683,5 +2791,90 @@ procedure TPackageRelatedPackageList.SaveXML(ARoot: IXMLNode); end; end; +{ TPackageContentFileReferenceList } + +procedure TPackageContentFileReferenceList.Assign(Source: TPackageContentFileReferenceList); +var + i: Integer; + f: TPackageContentFile; +begin + Clear; + for i := 0 to Source.Count - 1 do + begin + f := Package.Files.FromFileNameEx(Source[i].FileName); + if Assigned(f) then + Add(f); + end; +end; + +constructor TPackageContentFileReferenceList.Create(APackage: TPackage); +begin + inherited Create(APackage); + OwnsObjects := False; +end; + +function TPackageContentFileReferenceList.GetAsString: string; +var + f: TPackageContentFile; +begin + Result := ''; + for f in Self do + begin + Result := Result + ', ' + ExtractFileName(f.FileName); + end; + System.Delete(Result, 1, 2); +end; + +procedure TPackageContentFileReferenceList.LoadJSON(ARoot: TJSONArray); +var + i: Integer; + f: TPackageContentFile; +begin + for i := 0 to ARoot.Count - 1 do + begin + f := Package.Files.FromFileNameEx(ARoot.Items[i].Value); + if Assigned(f) then + Add(f); + end; +end; + +procedure TPackageContentFileReferenceList.LoadXML(ARoot: IXMLNode); +var + i: Integer; + ANode: IXMLNode; + f: TPackageContentFile; +begin + Clear; + for i := 0 to ARoot.ChildNodes.Count - 1 do + begin + ANode := ARoot.ChildNodes[i]; + f := Package.Files.FromFileNameEx(ANode.Attributes[SXML_PackageKeyboardFont_Filename]); + if Assigned(f) then + Add(f); + end; +end; + +procedure TPackageContentFileReferenceList.SaveJSON(ARoot: TJSONArray); +var + i: Integer; +begin + for i := 0 to Count - 1 do + begin + ARoot.Add(Items[i].FileName); + end; +end; + +procedure TPackageContentFileReferenceList.SaveXML(ARoot: IXMLNode); +var + i: Integer; + ANode: IXMLNode; +begin + for i := 0 to Count - 1 do + begin + ANode := ARoot.AddChild(SXML_PackageKeyboardFont); + ANode.Attributes[SXML_PackageKeyboardFont_Filename] := Items[i].FileName; + end; +end; + end. diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b0993572826..931b17eac9e 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -79,8 +79,6 @@ export class KmpCompiler { } } - // TODO: this.addFontMetadata(); - // // Add basic metadata // @@ -151,7 +149,17 @@ export class KmpCompiler { (this.arrayWrap(keyboard.examples.example) as KpsFile.KpsFileLanguageExample[]).map( e => ({id: e.$.ID, keys: e.$.Keys, text: e.$.Text, note: e.$.Note}) ) as KmpJsonFile.KmpJsonFileExample[] : - undefined + undefined, + webDisplayFonts: keyboard.webDisplayFonts ? + (this.arrayWrap(keyboard.webDisplayFonts.font) as KpsFile.KpsFileFont[]).map( + e => (this.callbacks.path.basename(e.$.Filename)) + ) : + undefined, + webOskFonts: keyboard.webOSKFonts ? + (this.arrayWrap(keyboard.webOSKFonts.font) as KpsFile.KpsFileFont[]).map( + e => (this.callbacks.path.basename(e.$.Filename)) + ) : + undefined, })); } diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/DejaVuSans.ttf b/developer/src/kmc-package/test/fixtures/kmp_2.0/DejaVuSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..27cff476ef317e4666f7ffb77e82b9398a90825a GIT binary patch literal 720012 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�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�@_$#aU@6laZ58j-; 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 literal 0 HcmV?d00001 diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps index 4f747b58893..15867cd2fee 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/khmer_angkor.kps @@ -116,6 +116,12 @@ Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically c 0 .ttf + + DejaVuSans.ttf + Font DejaVuSans + 0 + .ttf + splash.gif File splash.gif @@ -141,6 +147,12 @@ Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically c + + + + + + diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json index df70b4c97a4..bcfc7e8d354 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json @@ -86,6 +86,10 @@ "name": "khmer_busra_kbd.ttf", "description": "Font KhmerBusraKbd" }, + { + "description": "Font DejaVuSans", + "name": "DejaVuSans.ttf" + }, { "name": "splash.gif", "description": "File splash.gif" @@ -106,6 +110,8 @@ "version": "1.3", "oskFont": "khmer_busra_kbd.ttf", "displayFont": "Mondulkiri-R.ttf", + "webOskFonts": ["khmer_busra_kbd.ttf"], + "webDisplayFonts": ["DejaVuSans.ttf"], "languages": [ { "name": "Central Khmer (Khmer, Cambodia)", diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index d23089ab1c1..480fb62f4a6 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -236,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 546 Height = 32 AutoSize = False - Caption = + Caption = 'A typical package will need keyboards, fonts, and documentation.' + ' You shouldn'#39't typically add source files. Also, don'#39't add any s' + 'tandard Keyman files (such as keyman.exe) here.' @@ -454,9 +454,23 @@ inherited frmPackageEditor: TfrmPackageEditor Caption = 'Examples' FocusControl = gridKeyboardExamples end + object lblWebOSKFonts: TLabel + Left = 600 + Top = 76 + Width = 87 + Height = 13 + Caption = 'lblWebOSKFonts' + end + object lblWebDisplayFonts: TLabel + Left = 600 + Top = 103 + Width = 103 + Height = 13 + Caption = 'lblWebDisplayFonts' + end object lbKeyboards: TListBox Left = 15 - Top = 72 + Top = 68 Width = 229 Height = 408 Anchors = [akLeft, akTop, akBottom] @@ -500,7 +514,7 @@ inherited frmPackageEditor: TfrmPackageEditor object cbKeyboardOSKFont: TComboBox Left = 339 Top = 72 - Width = 230 + Width = 174 Height = 21 Style = csDropDownList TabOrder = 5 @@ -509,7 +523,7 @@ inherited frmPackageEditor: TfrmPackageEditor object cbKeyboardDisplayFont: TComboBox Left = 339 Top = 99 - Width = 230 + Width = 174 Height = 21 Style = csDropDownList TabOrder = 6 @@ -621,6 +635,24 @@ inherited frmPackageEditor: TfrmPackageEditor TabOrder = 14 OnClick = cmdKeyboardRemoveExampleClick end + object cmdKeyboardWebOSKFonts: TButton + Left = 519 + Top = 70 + Width = 75 + Height = 25 + Caption = 'Web fonts...' + TabOrder = 15 + OnClick = cmdKeyboardWebOSKFontsClick + end + object cmdKeyboardWebDisplayFonts: TButton + Left = 519 + Top = 97 + Width = 75 + Height = 25 + Caption = 'Web fonts...' + TabOrder = 16 + OnClick = cmdKeyboardWebDisplayFontsClick + end end end object pageLexicalModels: TTabSheet @@ -1275,7 +1307,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 48 Width = 551 Height = 13 - Caption = + Caption = 'Compiling the package takes all the files you have selected and ' + 'compresses them into a single package file.' end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index 7b7ee3d949c..f239494b623 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -220,6 +220,10 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 cmdAddRelatedPackage: TButton; cmdEditRelatedPackage: TButton; cmdRemoveRelatedPackage: TButton; + cmdKeyboardWebOSKFonts: TButton; + cmdKeyboardWebDisplayFonts: TButton; + lblWebOSKFonts: TLabel; + lblWebDisplayFonts: TLabel; procedure cmdCloseClick(Sender: TObject); procedure cmdAddFileClick(Sender: TObject); procedure cmdRemoveFileClick(Sender: TObject); @@ -295,6 +299,8 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure cmdAddRelatedPackageClick(Sender: TObject); procedure cmdEditRelatedPackageClick(Sender: TObject); procedure cmdRemoveRelatedPackageClick(Sender: TObject); + procedure cmdKeyboardWebOSKFontsClick(Sender: TObject); + procedure cmdKeyboardWebDisplayFontsClick(Sender: TObject); private pack: TKPSFile; FSetup: Integer; @@ -348,6 +354,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure ResizeGridColumns; procedure RefreshRelatedPackagesList; function SelectedRelatedPackage: TPackageRelatedPackage; + procedure ShowEditWebFontsForm(Fonts: TPackageContentFileReferenceList); protected function GetHelpTopic: string; override; @@ -406,6 +413,7 @@ implementation UfrmSendURLsToEmail, utilexecute, Keyman.Developer.UI.UfrmEditLanguageExample, + Keyman.Developer.UI.UfrmEditPackageWebFonts, Keyman.Developer.UI.UfrmEditRelatedPackage, Keyman.Developer.UI.UfrmSelectBCP47Language, xmldoc; @@ -1618,6 +1626,8 @@ procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); cbKeyboardDisplayFont.ItemIndex := -1; gridKeyboardLanguages.RowCount := 1; gridKeyboardExamples.RowCount := 1; + lblWebOSKFonts.Caption := ''; + lblWebDisplayFonts.Caption := ''; EnableKeyboardTabControls; Exit; end; @@ -1643,6 +1653,9 @@ procedure TfrmPackageEditor.lbKeyboardsClick(Sender: TObject); FillFileList(cbKeyboardOSKFont, k.OSKFont, ftFont); FillFileList(cbKeyboardDisplayFont, k.DisplayFont, ftFont); + lblWebOSKFonts.Caption := k.WebOSKFonts.GetAsString; + lblWebDisplayFonts.Caption := k.WebDisplayFonts.GetAsString; + // Languages RefreshKeyboardLanguageList(k); @@ -1766,6 +1779,12 @@ procedure TfrmPackageEditor.EnableKeyboardTabControls; cbKeyboardOSKFont.Enabled := e; lblKeyboardDisplayFont.Enabled := e; cbKeyboardDisplayFont.Enabled := e; + + cmdKeyboardWebOSKFonts.Enabled := e; + cmdKeyboardWebDisplayFonts.Enabled := e; + lblWebOSKFonts.Enabled := e; + lblWebDisplayFonts.Enabled := e; + lblKeyboardLanguages.Enabled := e; cmdKeyboardAddLanguage.Enabled := e; lblKeyboardExamples.Enabled := e; @@ -1883,6 +1902,42 @@ procedure TfrmPackageEditor.cmdKeyboardRemoveLanguageClick(Sender: TObject); Modified := True; end; +procedure TfrmPackageEditor.cmdKeyboardWebDisplayFontsClick(Sender: TObject); +var + k: TPackageKeyboard; +begin + k := SelectedKeyboard; + Assert(Assigned(k)); + ShowEditWebFontsForm(k.WebDisplayFonts); +end; + +procedure TfrmPackageEditor.cmdKeyboardWebOSKFontsClick(Sender: TObject); +var + k: TPackageKeyboard; +begin + k := SelectedKeyboard; + Assert(Assigned(k)); + ShowEditWebFontsForm(k.WebOSKFonts); +end; + +procedure TfrmPackageEditor.ShowEditWebFontsForm(Fonts: TPackageContentFileReferenceList); +var + frm: TfrmEditPackageWebFonts; +begin + frm := TfrmEditPackageWebFonts.Create(Application.MainForm); + try + frm.Files := pack.Files; + frm.SelectedFonts := Fonts; + if frm.ShowModal = mrOk then + begin + lbKeyboardsClick(lbKeyboards); + Modified := True; + end; + finally + frm.Free; + end; +end; + procedure TfrmPackageEditor.RefreshTargetPanels; var i: Integer; diff --git a/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.dfm b/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.dfm new file mode 100644 index 00000000000..9599e0c41ab --- /dev/null +++ b/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.dfm @@ -0,0 +1,40 @@ +inherited frmEditPackageWebFonts: TfrmEditPackageWebFonts + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'Select Web Fonts' + ClientHeight = 303 + ClientWidth = 289 + Position = poScreenCenter + ExplicitWidth = 295 + ExplicitHeight = 332 + PixelsPerInch = 96 + TextHeight = 13 + object cmdOK: TButton + Left = 125 + Top = 270 + Width = 75 + Height = 25 + Caption = 'OK' + Default = True + TabOrder = 0 + OnClick = cmdOKClick + end + object cmdCancel: TButton + Left = 206 + Top = 270 + Width = 75 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 1 + end + object clbFonts: TCheckListBox + Left = 8 + Top = 8 + Width = 273 + Height = 256 + ItemHeight = 13 + TabOrder = 2 + end +end diff --git a/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.pas b/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.pas new file mode 100644 index 00000000000..f4a01bb3611 --- /dev/null +++ b/developer/src/tike/dialogs/packageWebFonts/Keyman.Developer.UI.UfrmEditPackageWebFonts.pas @@ -0,0 +1,102 @@ +(* + Name: UfrmEditPackageWebFonts + Copyright: Copyright (C) SIL International. + Date: 15 Aug 2023 + Authors: mcdurdin +*) +unit Keyman.Developer.UI.UfrmEditPackageWebFonts; + +interface + +uses + System.Classes, + System.SysUtils, + System.Variants, + Winapi.Messages, + Winapi.Windows, + Vcl.CheckLst, + Vcl.Controls, + Vcl.Dialogs, + Vcl.Forms, + Vcl.Graphics, + Vcl.StdCtrls, + + packageinfo, + UfrmTike; + +type + TfrmEditPackageWebFonts = class(TTikeForm) + cmdOK: TButton; + cmdCancel: TButton; + clbFonts: TCheckListBox; + procedure cmdOKClick(Sender: TObject); + private + FSelectedFonts: TPackageContentFileReferenceList; + FFiles: TPackageContentFileList; + procedure SetFiles(const Value: TPackageContentFileList); + procedure SetSelectedFonts(const Value: TPackageContentFileReferenceList); + protected + function GetHelpTopic: string; override; + public + { Public declarations } + property Files: TPackageContentFileList read FFiles write SetFiles; + property SelectedFonts: TPackageContentFileReferenceList read FSelectedFonts write SetSelectedFonts; + end; + +implementation + +uses + System.Generics.Collections, + + Keyman.Developer.System.HelpTopics, + utilfiletypes; + +{$R *.dfm} + +{ TfrmEditPackageWebFonts } + +procedure TfrmEditPackageWebFonts.cmdOKClick(Sender: TObject); +var + i: Integer; +begin + FSelectedFonts.Clear; + for i := 0 to clbFonts.Items.Count - 1 do + if clbFonts.Checked[i] then + FSelectedFonts.Add(clbFonts.Items.Objects[i] as TPackageContentFile); + ModalResult := mrOk; +end; + +function TfrmEditPackageWebFonts.GetHelpTopic: string; +begin + Result := SHelpTopic_Context_EditPackageWebFonts; +end; + +procedure TfrmEditPackageWebFonts.SetFiles( + const Value: TPackageContentFileList); +var + f: TPackageContentFile; +begin + FFiles := Value; + for f in FFiles do + begin + if GetFileTypeFromFileName(f.FileName) = ftFont then + clbFonts.AddItem(ExtractFileName(f.FileName), f); + end; +end; + +procedure TfrmEditPackageWebFonts.SetSelectedFonts( + const Value: TPackageContentFileReferenceList); +var + n: Integer; + f: TPackageContentFile; +begin + FSelectedFonts := Value; + for f in FSelectedFonts do + begin + n := clbFonts.Items.IndexOfObject(f); + if n >= 0 then + clbFonts.Checked[n] := True; + end; +end; + +end. diff --git a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas index 3268edc5998..337ed507f36 100644 --- a/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas +++ b/developer/src/tike/help/Keyman.Developer.System.HelpTopics.pas @@ -49,6 +49,7 @@ interface SHelpTopic_Context_WordlistEditor = 'context/wordlist-editor'; SHelpTopic_Context_EditLanguageExample = 'context/edit-language-example'; SHelpTopic_Context_EditRelatedPackage = 'context/edit-related-package'; + SHelpTopic_Context_EditPackageWebFonts = 'context/edit-package-web-fonts'; // For all .kmn language reference topics, prefix with this path: SHelpTopic_LanguageReference_Prefix = 'language/reference/'; diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index f9e7db2068a..df770511b9c 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -286,7 +286,8 @@ uses Keyman.Developer.UI.ServerUI in 'http\Keyman.Developer.UI.ServerUI.pas', Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}, - Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}; + Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}, + Keyman.Developer.UI.UfrmEditPackageWebFonts in 'dialogs\packageWebFonts\Keyman.Developer.UI.UfrmEditPackageWebFonts.pas' {frmEditPackageWebFonts}; {$R *.RES} {$R ICONS.RES} diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index a8fa4a7e0bf..88564a6ce45 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -561,6 +561,10 @@

frmEditRelatedPackage dfm + +
frmEditPackageWebFonts
+ dfm +
Cfg_2 @@ -622,21 +626,15 @@ False - - - .\ - true - - tike.exe true - + - tike.rsm + tike.exe true @@ -646,9 +644,9 @@ true - + - tike.exe + .\ true From 194fd1a8bfa04425720dff3a8513929e147dbdbd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 15 Aug 2023 15:34:38 +0700 Subject: [PATCH 010/207] chore: update unit test - will be removed later --- .../test-invalid-language-code.keyboard_info | 13 ++++++++++++- .../test-non-canonical-language-code.keyboard_info | 13 ++++++++++++- developer/src/test/auto/keyboard-info/test.bat | 5 ++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info b/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info index 79de65e9901..97459477c8a 100644 --- a/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info +++ b/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info @@ -1,6 +1,17 @@ { + "id": "test", + "name": "test", "license": "mit", "languages": [ "foooooooo=amh" - ] + ], + "platformSupport": { + "windows": "full", + "macos": "full", + "desktopWeb": "full", + "mobileWeb": "full", + "ios": "full", + "android": "full" + }, + "lastModifiedDate": "2017-09-06" } \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info b/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info index c245d3f524f..eb08779d8fb 100644 --- a/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info +++ b/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info @@ -1,6 +1,17 @@ { + "id": "test", + "name": "test", "license": "mit", "languages": [ "amh" - ] + ], + "platformSupport": { + "windows": "full", + "macos": "full", + "desktopWeb": "full", + "mobileWeb": "full", + "ios": "full", + "android": "full" + }, + "lastModifiedDate": "2017-09-06" } \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-info/test.bat b/developer/src/test/auto/keyboard-info/test.bat index d7d896d38b5..a08fd723c53 100644 --- a/developer/src/test/auto/keyboard-info/test.bat +++ b/developer/src/test/auto/keyboard-info/test.bat @@ -3,6 +3,9 @@ rem We use test.bat so we can test errorlevels setlocal set ESC= +rem This is temporary until source keyboard_info files go away +copy "%KEYMAN_ROOT%\common\schemas\keyboard_info\keyboard_info.schema.json" "%KEYMAN_ROOT%\common\schemas\keyboard_info\keyboard_info.source.json" + if "%1"=="-h" goto usage if "%1"=="--help" goto usage if "%1"=="-?" goto usage @@ -34,7 +37,7 @@ goto :eof :should-pass echo %BLUE%TEST: %1 %WHITE% -"%compiler%" -s -vs -schema-path "%KEYMAN_ROOT%\common\schemas\keyboard_info" "%2" +"%compiler%" -vs -schema-path "%KEYMAN_ROOT%\common\schemas\keyboard_info" "%2" if %ERRORLEVEL% EQU 0 ( echo %GREEN%TEST PASSED%WHITE% exit /b 0 From 1cce7ed8e8c170a3a2ab197a702ef3c5b5100502 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 16 Aug 2023 05:40:33 +0700 Subject: [PATCH 011/207] chore(developer): remove fonts sidetrack --- common/web/types/src/package/kmp-json-file.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 87aaaf63048..03b097ab7cb 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -20,11 +20,6 @@ export interface KmpJsonFileOptions { executeProgram?: string; msiFilename?: string; msiOptions?: string; - /** - * List of all font files, grouped by font family - * Compiler extracts font metadata from .ttf,.otf,.woff,.woff2 - */ - fonts?: {[family:string]: string[]}; } export interface KmpJsonFileInfo { From 158bc28e8505b281c413c9c87630457658a9c9dd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 16 Aug 2023 09:13:38 +0700 Subject: [PATCH 012/207] feat(developer): add LicenseFile as property of package Adds license file to .kps, kmp.json, kmc-package, package editor, and new project templates. The intent is for LICENSE.md to be the file used, and for any keyboards on keymanapp/keyboards, it should always be a standard MIT license. --- common/schemas/kps/kps.xsd | 1 + common/web/types/src/package/kmp-json-file.ts | 1 + common/web/types/src/package/kps-file.ts | 1 + .../windows/delphi/packages/PackageInfo.pas | 38 +++++++ .../kmc-package/src/compiler/kmp-compiler.ts | 3 + ...veloper.System.KeyboardProjectTemplate.pas | 6 + ....Developer.System.ModelProjectTemplate.pas | 6 + .../src/tike/child/UfrmPackageEditor.dfm | 105 +++++++++++------- .../src/tike/child/UfrmPackageEditor.pas | 23 ++++ 9 files changed, 141 insertions(+), 43 deletions(-) diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd index 1e305eba275..013ffa34f93 100644 --- a/common/schemas/kps/kps.xsd +++ b/common/schemas/kps/kps.xsd @@ -31,6 +31,7 @@ + diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index eabb0624e24..1450e2418cc 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -17,6 +17,7 @@ export interface KmpJsonFileSystem { export interface KmpJsonFileOptions { readmeFile?: string; graphicFile?: string; + licenseFile?: string; executeProgram?: string; msiFilename?: string; msiOptions?: string; diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 2a49df3dd7f..036a9ca167d 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -41,6 +41,7 @@ export interface KpsFileOptions { followKeyboardVersion?: string; readMeFile?: string; graphicFile?: string; + licenseFile?: string; executeProgram?: string; msiFileName?: string; msiOptions?: string; diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index abe520aaefb..a5d034d9c45 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -149,6 +149,7 @@ TPackageOptions = class(TPackageBaseObject) FExecuteProgram: WideString; FReadmeFile: TPackageContentFile; FGraphicFile: TPackageContentFile; + FLicenseFile: TPackageContentFile; FLoadLegacy: Boolean; procedure SetReadmeFile(const Value: TPackageContentFile); procedure SetExecuteProgram(Value: WideString); @@ -158,6 +159,9 @@ TPackageOptions = class(TPackageBaseObject) EventType: TPackageNotifyEventType; var FAllow: Boolean); procedure ReadmeRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); + procedure SetLicenseFile(const Value: TPackageContentFile); + procedure LicenseRemoved(Sender: TObject; + EventType: TPackageNotifyEventType; var FAllow: Boolean); public constructor Create(APackage: TPackage); override; destructor Destroy; override; @@ -173,6 +177,7 @@ TPackageOptions = class(TPackageBaseObject) property ExecuteProgram: WideString read FExecuteProgram write SetExecuteProgram; property ReadmeFile: TPackageContentFile read FReadmeFile write SetReadmeFile; property GraphicFile: TPackageContentFile read FGraphicFile write SetGraphicFile; + property LicenseFile: TPackageContentFile read FLicenseFile write SetLicenseFile; end; { Package Information } @@ -561,6 +566,7 @@ implementation SJSON_Options_ExecuteProgram = 'executeProgram'; SJSON_Options_ReadMeFile = 'readmeFile'; SJSON_Options_GraphicFile = 'graphicFile'; + SJSON_Options_LicenseFile = 'licenseFile'; SJSON_Registry = 'registry'; SJSON_Registry_Root = 'root'; @@ -655,6 +661,9 @@ procedure TPackageOptions.Assign(Source: TPackageOptions); if Assigned(Source.GraphicFile) then GraphicFile := Package.Files.FromFileName(Source.GraphicFile.FileName) else GraphicFile := nil; + if Assigned(Source.LicenseFile) + then LicenseFile := Package.Files.FromFileName(Source.LicenseFile.FileName) + else LicenseFile := nil; end; constructor TPackageOptions.Create(APackage: TPackage); @@ -668,6 +677,7 @@ destructor TPackageOptions.Destroy; begin ReadmeFile := nil; GraphicFile := nil; + LicenseFile := nil; inherited Destroy; end; @@ -681,14 +691,21 @@ procedure TPackageOptions.GraphicRemoved(Sender: TObject; EventType: TPackageNot FGraphicFile := nil; end; +procedure TPackageOptions.LicenseRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); +begin + FLicenseFile := nil; +end; + procedure TPackageOptions.LoadXML(ARoot: IXMLNode); begin FileVersion := XmlVarToStr(ARoot.ChildNodes['System'].ChildNodes['FileVersion'].NodeValue); ExecuteProgram := XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ExecuteProgram'].NodeValue); ReadmeFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ReadMeFile'].NodeValue)); GraphicFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue)); + LicenseFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue)); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); + if Assigned(LicenseFile) then LicenseFile.AddNotifyObject(LicenseRemoved); end; procedure TPackageOptions.SaveXML(ARoot: IXMLNode); @@ -699,6 +716,8 @@ procedure TPackageOptions.SaveXML(ARoot: IXMLNode); ARoot.ChildNodes['Options'].ChildNodes['ReadMeFile'].NodeValue := ReadmeFile.RelativeFileName; if Assigned(GraphicFile) then ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue := GraphicFile.RelativeFileName; + if Assigned(LicenseFile) then + ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue := LicenseFile.RelativeFileName; end; procedure TPackageOptions.LoadIni(AIni: TIniFile); @@ -709,6 +728,7 @@ procedure TPackageOptions.LoadIni(AIni: TIniFile); GraphicFile := Package.Files.FromFileName(AIni.ReadString('Package', 'GraphicFile', '')); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); + // LicenseFile not supported in ini end; procedure TPackageOptions.LoadJSON(ARoot: TJSONObject); @@ -722,8 +742,10 @@ procedure TPackageOptions.LoadJSON(ARoot: TJSONObject); ExecuteProgram := GetJsonValueString(FOptions, SJSON_Options_ExecuteProgram); ReadmeFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_ReadMeFile)); GraphicFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_GraphicFile)); + LicenseFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_LicenseFile)); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); + if Assigned(LicenseFile) then LicenseFile.AddNotifyObject(LicenseRemoved); end; procedure TPackageOptions.SaveIni(AIni: TIniFile); @@ -734,6 +756,7 @@ procedure TPackageOptions.SaveIni(AIni: TIniFile); AIni.WriteString('Package', 'ReadMeFile', ReadmeFile.RelativeFileName); if Assigned(GraphicFile) then AIni.WriteString('Package', 'GraphicFile', GraphicFile.RelativeFileName); + // licenseFile not supported in ini end; procedure TPackageOptions.SaveJSON(ARoot: TJSONObject); @@ -753,6 +776,8 @@ procedure TPackageOptions.SaveJSON(ARoot: TJSONObject); FOptions.AddPair(SJSON_Options_ReadMeFile, ReadmeFile.RelativeFileName); if Assigned(GraphicFile) then FOptions.AddPair(SJSON_Options_GraphicFile, GraphicFile.RelativeFileName); + if Assigned(LicenseFile) then + FOptions.AddPair(SJSON_Options_LicenseFile, LicenseFile.RelativeFileName); end; procedure TPackageOptions.SetExecuteProgram(Value: WideString); @@ -780,6 +805,19 @@ procedure TPackageOptions.SetGraphicFile(const Value: TPackageContentFile); end; end; +procedure TPackageOptions.SetLicenseFile(const Value: TPackageContentFile); +begin + if Assigned(FLicenseFile) then FLicenseFile.RemoveNotifyObject(LicenseRemoved); + if not Assigned(Value) then + FLicenseFile := nil + else + begin + if Value.Package <> Package then raise EPackageInfo.CreateFmt(SGraphicNotOwnedCorrectly, [Value]); + FLicenseFile := Value; + FLicenseFile.AddNotifyObject(LicenseRemoved); + end; +end; + procedure TPackageOptions.SetReadmeFile(const Value: TPackageContentFile); begin if Assigned(FReadmeFile) then FReadmeFile.RemoveNotifyObject(ReadmeRemoved); diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 931b17eac9e..b005cb80ac2 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -77,6 +77,9 @@ export class KmpCompiler { if(kps.options.readMeFile) { kmp.options.readmeFile = /[/\\]?([^/\\]*)$/.exec(kps.options.readMeFile)[1]; } + if(kps.options.licenseFile) { + kmp.options.licenseFile = /[/\\]?([^/\\]*)$/.exec(kps.options.licenseFile)[1]; + } } // diff --git a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas index 0d2fb9f05fd..3982366e223 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas @@ -285,6 +285,12 @@ procedure TKeyboardProjectTemplate.WriteKPS; kps.Files.Add(f); kps.Options.ReadmeFile := f; + // Add license + f := TPackageContentFile.Create(kps); + f.FileName := BasePath + ID + '\' + SFile_LicenseMD; + kps.Files.Add(f); + kps.Options.LicenseFile := f; + // Add metadata about the keyboard pk := TPackageKeyboard.Create(kps); pk.Name := Name; diff --git a/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas index ac7349fc469..be0d0c98254 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas @@ -179,6 +179,12 @@ procedure TModelProjectTemplate.WriteKPS; kps.Files.Add(f); kps.Options.ReadmeFile := f; + // Add license + f := TPackageContentFile.Create(kps); + f.FileName := BasePath + ID + '\' + SFile_LicenseMD; + kps.Files.Add(f); + kps.Options.LicenseFile := f; + // Add metadata about the lexical model plm := TPackageLexicalModel.Create(kps); plm.Name := Name; diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index 480fb62f4a6..4c6967d4618 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -236,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 546 Height = 32 AutoSize = False - Caption = + Caption = 'A typical package will need keyboards, fonts, and documentation.' + ' You shouldn'#39't typically add source files. Also, don'#39't add any s' + 'tandard Keyman files (such as keyman.exe) here.' @@ -823,7 +823,7 @@ inherited frmPackageEditor: TfrmPackageEditor 622) object lblKMPImageFile: TLabel Left = 15 - Top = 327 + Top = 345 Width = 53 Height = 13 Caption = 'Image file:' @@ -831,7 +831,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblKMPImageSize: TLabel Left = 114 - Top = 348 + Top = 366 Width = 92 Height = 13 Caption = 'Image size: (w x h)' @@ -853,35 +853,35 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblStep2c: TLabel Left = 15 - Top = 155 + Top = 173 Width = 41 Height = 13 Caption = 'Version:' end object lblStep2d: TLabel Left = 15 - Top = 203 + Top = 221 Width = 54 Height = 13 Caption = 'Copyright:' end object lblStep2e: TLabel Left = 15 - Top = 235 + Top = 253 Width = 39 Height = 13 Caption = 'Author:' end object lblStep2f: TLabel Left = 15 - Top = 259 + Top = 277 Width = 77 Height = 13 Caption = 'E-mail address:' end object lblStep2g: TLabel Left = 15 - Top = 283 + Top = 301 Width = 49 Height = 13 Caption = 'Web Site:' @@ -927,7 +927,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblVersionHint: TLabel Left = 622 - Top = 157 + Top = 175 Width = 46 Height = 13 Anchors = [akTop, akRight] @@ -935,7 +935,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblDescription: TLabel Left = 15 - Top = 379 + Top = 397 Width = 62 Height = 13 Caption = 'Description:' @@ -943,7 +943,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblDescriptionMarkdown: TLabel Left = 114 - Top = 479 + Top = 497 Width = 215 Height = 13 Caption = 'Markdown accepted, no embedded HTML' @@ -951,20 +951,28 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblRelatedPackages: TLabel Left = 16 - Top = 515 + Top = 533 Width = 93 Height = 13 Caption = 'Related packages:' FocusControl = gridRelatedPackages end + object lblLicenseFile: TLabel + Left = 15 + Top = 147 + Width = 59 + Height = 13 + Caption = '&License file:' + FocusControl = cbLicense + end object cbReadMe: TComboBox Left = 114 - Top = 116 + Top = 115 Width = 499 Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 0 + TabOrder = 1 OnClick = cbReadMeClick end object editInfoName: TEdit @@ -973,73 +981,73 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 1 + TabOrder = 0 OnChange = editInfoNameChange end object editInfoVersion: TEdit Left = 114 - Top = 152 + Top = 170 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 2 + TabOrder = 3 OnChange = editInfoVersionChange end object editInfoCopyright: TEdit Left = 114 - Top = 200 + Top = 218 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 4 + TabOrder = 5 Text = #169 OnChange = editInfoCopyrightChange end object editInfoAuthor: TEdit Left = 114 - Top = 232 + Top = 250 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 5 + TabOrder = 7 OnChange = editInfoAuthorChange end object editInfoEmail: TEdit Left = 114 - Top = 256 + Top = 274 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 6 + TabOrder = 8 OnChange = editInfoEmailChange end object editInfoWebSite: TEdit Left = 114 - Top = 280 + Top = 298 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 7 + TabOrder = 9 OnChange = editInfoWebSiteChange end object cmdInsertCopyright: TButton Left = 620 - Top = 200 + Top = 218 Width = 49 Height = 21 Anchors = [akTop, akRight] Caption = '&Insert '#169 - TabOrder = 8 + TabOrder = 6 OnClick = cmdInsertCopyrightClick end object cbKMPImageFile: TComboBox Left = 114 - Top = 324 + Top = 342 Width = 499 Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 9 + TabOrder = 10 OnClick = cbKMPImageFileClick end object panKMPImageSample: TPanel @@ -1049,7 +1057,7 @@ inherited frmPackageEditor: TfrmPackageEditor Height = 251 Anchors = [akTop, akRight] BevelOuter = bvLowered - TabOrder = 10 + TabOrder = 11 object imgKMPSample: TImage Left = 1 Top = 0 @@ -1059,24 +1067,24 @@ inherited frmPackageEditor: TfrmPackageEditor end object chkFollowKeyboardVersion: TCheckBox Left = 114 - Top = 178 + Top = 196 Width = 237 Height = 17 Caption = 'Package version follows keyboard version' - TabOrder = 3 + TabOrder = 4 OnClick = chkFollowKeyboardVersionClick end object memoInfoDescription: TMemo Left = 114 - Top = 376 + Top = 394 Width = 499 Height = 97 - TabOrder = 11 + TabOrder = 12 OnChange = memoInfoDescriptionChange end object gridRelatedPackages: TStringGrid Left = 114 - Top = 512 + Top = 530 Width = 415 Height = 89 ColCount = 2 @@ -1084,7 +1092,7 @@ inherited frmPackageEditor: TfrmPackageEditor FixedCols = 0 RowCount = 9 Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] - TabOrder = 12 + TabOrder = 13 OnDblClick = gridRelatedPackagesDblClick ColWidths = ( 78 @@ -1092,31 +1100,42 @@ inherited frmPackageEditor: TfrmPackageEditor end object cmdAddRelatedPackage: TButton Left = 541 - Top = 512 + Top = 530 Width = 73 Height = 25 Caption = '&Add...' - TabOrder = 13 + TabOrder = 14 OnClick = cmdAddRelatedPackageClick end object cmdEditRelatedPackage: TButton Left = 540 - Top = 544 + Top = 562 Width = 73 Height = 25 Caption = 'Ed&it...' - TabOrder = 14 + TabOrder = 15 OnClick = cmdEditRelatedPackageClick end object cmdRemoveRelatedPackage: TButton Left = 541 - Top = 576 + Top = 594 Width = 72 Height = 25 Caption = '&Remove' - TabOrder = 15 + TabOrder = 16 OnClick = cmdRemoveRelatedPackageClick end + object cbLicense: TComboBox + Left = 114 + Top = 143 + Width = 499 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 2 + OnClick = cbLicenseClick + ExplicitWidth = 482 + end end end object pageShortcuts: TTabSheet @@ -1307,7 +1326,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 48 Width = 551 Height = 13 - Caption = + Caption = 'Compiling the package takes all the files you have selected and ' + 'compresses them into a single package file.' end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index f239494b623..d11cee99abe 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -224,6 +224,8 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 cmdKeyboardWebDisplayFonts: TButton; lblWebOSKFonts: TLabel; lblWebDisplayFonts: TLabel; + lblLicenseFile: TLabel; + cbLicense: TComboBox; procedure cmdCloseClick(Sender: TObject); procedure cmdAddFileClick(Sender: TObject); procedure cmdRemoveFileClick(Sender: TObject); @@ -301,6 +303,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure cmdRemoveRelatedPackageClick(Sender: TObject); procedure cmdKeyboardWebOSKFontsClick(Sender: TObject); procedure cmdKeyboardWebDisplayFontsClick(Sender: TObject); + procedure cbLicenseClick(Sender: TObject); private pack: TKPSFile; FSetup: Integer; @@ -355,6 +358,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure RefreshRelatedPackagesList; function SelectedRelatedPackage: TPackageRelatedPackage; procedure ShowEditWebFontsForm(Fonts: TPackageContentFileReferenceList); + procedure UpdateLicense; protected function GetHelpTopic: string; override; @@ -457,6 +461,7 @@ procedure TfrmPackageEditor.FormCreate(Sender: TObject); UpdateStartMenuPrograms; UpdateReadme; + UpdateLicense; UpdateImageFiles; UpdateImagePreviews; RefreshKeyboardList; @@ -787,6 +792,7 @@ procedure TfrmPackageEditor.AddFile(FileName: WideString); lbFiles.ItemIndex := lbFiles.Items.AddObject(ExtractFileName(FileName), f); lbFilesClick(lbFiles); UpdateReadme; + UpdateLicense; UpdateImageFiles; UpdateStartMenuPrograms; @@ -832,6 +838,7 @@ procedure TfrmPackageEditor.cmdRemoveFileClick(Sender: TObject); if lbFiles.Items.Count > 0 then lbFiles.ItemIndex := 0; lbFilesClick(lbFiles); UpdateReadme; + UpdateLicense; UpdateImageFiles; UpdateStartMenuPrograms; @@ -967,6 +974,16 @@ procedure TfrmPackageEditor.cbReadMeClick(Sender: TObject); Modified := True; end; +procedure TfrmPackageEditor.cbLicenseClick(Sender: TObject); +begin + if FSetup > 0 then Exit; + if cbLicense.ItemIndex <= 0 + then pack.Options.LicenseFile := nil + else pack.Options.LicenseFile := cbLicense.Items.Objects[cbLicense.ItemIndex] as TPackageContentFile; + Modified := True; +end; + + procedure TfrmPackageEditor.editCmdLineChange(Sender: TObject); begin if FSetup > 0 then Exit; @@ -1280,6 +1297,11 @@ procedure TfrmPackageEditor.UpdateReadme; FillFileList(cbReadme, pack.Options.ReadmeFile); end; +procedure TfrmPackageEditor.UpdateLicense; +begin + FillFileList(cbLicense, pack.Options.LicenseFile); +end; + procedure TfrmPackageEditor.FillFileList(combo: TComboBox; obj: TObject; FileType: TKMFileType); var i: Integer; @@ -1310,6 +1332,7 @@ procedure TfrmPackageEditor.UpdateData; UpdateOutPath; UpdateReadme; + UpdateLicense; UpdateImageFiles; UpdateStartMenuPrograms; From 395b76df05321fb91a38a47c4fa6ec6705ae2685 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 16 Aug 2023 09:26:39 +0700 Subject: [PATCH 013/207] chore(developer): cleanup keyboard_info schema * Removes font size * Restricts font source to array * Removes example * Add examples * Loosen structure of KeyboardExample --- .../keyboard_info/keyboard_info.schema.json | 53 ++++--------------- common/web/types/build.sh | 1 + common/web/types/src/schemas.ts | 2 + developer/src/kmc-keyboard-info/src/index.ts | 8 +-- .../src/keyboard-info-file.ts | 5 +- 5 files changed, 18 insertions(+), 51 deletions(-) diff --git a/common/schemas/keyboard_info/keyboard_info.schema.json b/common/schemas/keyboard_info/keyboard_info.schema.json index 4ad6f67a292..b56b8dfe158 100644 --- a/common/schemas/keyboard_info/keyboard_info.schema.json +++ b/common/schemas/keyboard_info/keyboard_info.schema.json @@ -52,7 +52,7 @@ "properties": { "font": { "$ref": "#/definitions/KeyboardFontInfo" }, "oskFont": { "$ref": "#/definitions/KeyboardFontInfo" }, - "example": { "$ref": "#/definitions/KeyboardExampleInfo" }, + "examples": { "type": "array", "items": { "$ref": "#/definitions/KeyboardExampleInfo" } }, "displayName": { "type": "string" }, "languageName": { "type": "string" }, "scriptName": { "type": "string" }, @@ -66,11 +66,7 @@ "type": "object", "properties": { "family": { "type": "string" }, - "source": { "anyOf": [ - { "type": "string" }, - { "type": "array", "items": { "type": "string" } } - ] }, - "size": { "type": "string" } + "source": { "type": "array", "items": { "type": "string" } } }, "required": ["family", "source"], "additionalProperties": false @@ -79,55 +75,24 @@ "KeyboardExampleInfo": { "type": "object", "properties": { - "keys": { "anyOf": [ - { "type": "string" }, - { "type": "array", "items": { - "anyOf": [ - { "type": "string" }, - { "$ref": "#/definitions/KeyboardExampleKeyInfo" } - ] } - } - ] }, + "keys": { + "type": "array", + "items": { "$ref": "#/definitions/KeyboardExampleKeyInfo" } + }, "text": { "type": "string" }, "note": { "type": "string" } }, - "required": [], + "required": ["keys", "text"], "additionalProperties": false }, "KeyboardExampleKeyInfo": { "type": "object", "properties": { - "key": { "type": "string", "enum": [ - "K_SPACE", - "K_A", "K_B", "K_C", "K_D", "K_E", "K_F", "K_G", "K_H", "K_I", "K_J", "K_K", "K_L", "K_M", - "K_N", "K_O", "K_P", "K_Q", "K_R", "K_S", "K_T", "K_U", "K_V", "K_W", "K_X", "K_Y", "K_Z", - "K_1", "K_2", "K_3", "K_4", "K_5", "K_6", "K_7", "K_8", "K_9", "K_0", - "K_BKQUOTE", "K_HYPHEN", "K_EQUAL", "K_LBRKT", "K_RBRKT", "K_BKSLASH", "K_COLON", - "K_QUOTE", "K_COMMA", "K_PERIOD", "K_SLASH", - "K_oE2", "K_BKSP", "K_TAB", "K_ENTER", "K_ESC", - "K_LEFT", "K_UP", "K_RIGHT", "K_DOWN", "K_PGUP", "K_PGDN", "K_HOME", "K_END", "K_INS", "K_DEL", - "K_F1", "K_F2", "K_F3", "K_F4", "K_F5", "K_F6", "K_F7", "K_F8", "K_F9", "K_F10", "K_F11", "K_F12", - "K_KP5", "K_NP0", "K_NP1", "K_NP2", "K_NP3", "K_NP4", "K_NP5", "K_NP6", "K_NP7", "K_NP8", "K_NP9", - "K_NPSTAR", "K_NPPLUS", "K_NPMINUS", "K_NPDOT", "K_NPSLASH", - "K_SEL", "K_PRINT", "K_EXEC", "K_HELP", "K_SEPARATOR", - "K_F13", "K_F14", "K_F15", "K_F16", "K_F17", "K_F18", "K_F19", "K_F20", "K_F21", "K_F22", "K_F23", "K_F24", - "K_KANJI?15", "K_KANJI?16", "K_KANJI?17", "K_KANJI?18", "K_KANJI?19", "K_KANJI?1C", "K_KANJI?1D", "K_KANJI?1E", "K_KANJI?1F", - "K_oE0", "K_oE1", "K_oE3", "K_oE4", "K_oE6", "K_oE9", "K_oEA", "K_oEB", "K_oEC", "K_oED", "K_oEE", "K_oEF", - "K_oF0", "K_oF1", "K_oF2", "K_oF3", "K_oF4", "K_oF5", "K_?00", "K_?05", "K_NPENTER", - "K_?06", "K_?07", "K_?0A", "K_?0B", "K_?0E", "K_?0F", "K_?1A", "K_?3A", "K_?3B", "K_?3C", "K_?3D", "K_?3E", - "K_?3F", "K_?40", "K_?5B", "K_?5C", "K_?5D", "K_?5E", "K_?5F", "K_?88", "K_?89", "K_?8A", "K_?8B", "K_?8C", - "K_?8D", "K_?8E", "K_?8F", "K_?92", "K_?94", "K_?95", "K_?96", "K_?97", "K_?98", "K_?99", "K_?9A", "K_?9B", - "K_?9C", "K_?9D", "K_?9E", "K_?9F", "K_?A0", "K_?A1", "K_?A2", "K_?A3", "K_?A4", "K_?A5", "K_?A6", "K_?A7", - "K_?A8", "K_?A9", "K_?AA", "K_?AB", "K_?AC", "K_?AD", "K_?AE", "K_?AF", "K_?B0", "K_?B1", "K_?B2", "K_?B3", - "K_?B4", "K_?B5", "K_?B6", "K_?B7", "K_?B8", "K_?B9", "K_?C1", "K_?C2", "K_?C3", "K_?C4", "K_?C5", "K_?C6", - "K_?C7", "K_?C8", "K_?C9", "K_?CA", "K_?CB", "K_?CC", "K_?CD", "K_?CE", "K_?CF", "K_?D0", "K_?D1", "K_?D2", - "K_?D3", "K_?D4", "K_?D5", "K_?D6", "K_?D7", "K_?D8", "K_?D9", "K_?DA", "K_oDF", "K_?E5", "K_?E7", "K_?E8", - "K_?F6", "K_?F7", "K_?F8", "K_?F9", "K_?FA", "K_?FB", "K_?FC", "K_?FD", "K_?FE", "K_?FF" - ] }, + "key": { "type": "string" }, "modifiers": { "type": "array", - "items": { "type": "string", "enum": ["shift", "s", "ctrl", "c", "alt", "a", "left-ctrl", "lc", "right-ctrl", "rc", "left-alt", "la", "right-alt", "ra"] } + "items": { "type": "string" } } }, "required": ["key"], diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 74b200e6d65..7b1a2ad4564 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -37,6 +37,7 @@ function compile_schemas() { "$KEYMAN_ROOT/common/schemas/displaymap/displaymap.schema.json" "$KEYMAN_ROOT/common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json" "$KEYMAN_ROOT/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json" + "$KEYMAN_ROOT/common/schemas/keyboard_info/keyboard_info.schema.json" ) rm -rf "$THIS_SCRIPT_PATH/src/schemas" diff --git a/common/web/types/src/schemas.ts b/common/web/types/src/schemas.ts index 451afc416bc..5bb1f7a23fe 100644 --- a/common/web/types/src/schemas.ts +++ b/common/web/types/src/schemas.ts @@ -7,6 +7,7 @@ import ldmlKeyboardTest from './schemas/ldml-keyboardtest.schema.js'; import displayMap from './schemas/displaymap.schema.js'; import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.js'; import touchLayout from './schemas/keyman-touch-layout.spec.js'; +import keyboard_info from './schemas/keyboard_info.schema.js'; const Schemas = { kpj, @@ -17,6 +18,7 @@ const Schemas = { displayMap, touchLayoutClean, touchLayout, + keyboard_info, }; export default Schemas; \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index ed3e7a1734d..29c0c1f4056 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -104,10 +104,6 @@ export class KeyboardInfoCompiler { return null; } keyboard_info = { - // Only two fields that won't be automatically filled if file is not - // present - encodings: ['unicode'], - license: 'mit' }; } @@ -131,6 +127,10 @@ export class KeyboardInfoCompiler { // https://help.keyman.com/developer/cloud/keyboard_info/2.0 // + // TODO: read LicenseFilename from .kps and verify (OR ADD TO KPS?) + // TODO: use mit license validation from feat/developer/compile-without-source-keyboard-info + keyboard_info.license = 'mit'; + keyboard_info.isRTL = keyboard_info.isRTL ?? !!jsFile?.match(/this\.KRTL=1/); if(!keyboard_info.isRTL) { delete keyboard_info.isRTL; diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts index cd15bff6f50..87620c1fb54 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts @@ -17,7 +17,7 @@ export interface KeyboardInfoFile { jsFilename?: string; jsFileSize?: number; isRTL?: boolean; - encodings: KeyboardInfoFileEncodings[]; + encodings?: KeyboardInfoFileEncodings[]; packageIncludes?: KeyboardInfoFileIncludes[]; version?: string; minKeymanVersion?: string; @@ -45,8 +45,7 @@ export interface KeyboardInfoFileLanguage { export interface KeyboardInfoFileLanguageFont { family?: string; - source?: string | string[]; - size?: string; + source?: string[]; } export interface KeyboardInfoFileExample { From a7f8123e565b3694db9bbaee289684108d3644c9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 7 Aug 2023 10:22:00 +0700 Subject: [PATCH 014/207] feat(developer): support building .keyboard_info without source version Relates to #9351. Adds support for building a .keyboard_info file without having source .keyboard_info file: * Constructs a default source .keyboard_info in memory * Hints if license is missing * If LICENSE.md is present, verifies it matches the MIT license text * Adds project option to turn on or off metadata generation. This will default to False for version 1.0 projects, and to True for version 2.0 projects. This means that the keyboard repository will need a PR to enable metadata generation for existing projects in the repository, but this is important to avoid breaking builds for existing projects that are not in the repository. * Turns on additional c8 coverage for kmc projects --- common/schemas/kpj/README.md | 3 + common/schemas/kpj/kpj.schema.json | 4 + .../types/src/kpj/keyman-developer-project.ts | 7 ++ common/web/types/src/kpj/kpj-file-reader.ts | 2 + common/web/types/src/kpj/kpj-file.ts | 1 + common/web/types/src/util/file-types.ts | 10 +++ .../types/test/kpj/test-kpj-file-reader.ts | 2 + developer/src/kmc-analyze/build.sh | 2 +- developer/src/kmc-keyboard-info/build.sh | 2 +- developer/src/kmc-keyboard-info/src/index.ts | 88 ++++++++++++++----- .../src/kmc-keyboard-info/src/messages.ts | 21 ++++- .../src/validate-mit-license.ts | 69 +++++++++++++++ .../test/fixtures/khmer_angkor/LICENSE.md | 21 +++++ .../khmer_angkor_(no_source).keyboard_info | 40 +++++++++ .../license/LICENSE-changed-clause.md | 21 +++++ .../license/LICENSE-missing-copyright.md | 21 +++++ .../fixtures/license/LICENSE-missing-title.md | 21 +++++ .../test/fixtures/license/LICENSE-too-long.md | 24 +++++ .../fixtures/license/LICENSE-too-short.md | 18 ++++ .../test/fixtures/license/LICENSE.md | 21 +++++ .../test/test-keyboard-info-compiler.ts | 59 +++++++++++-- .../kmc-keyboard-info/test/test-license.ts | 23 +++++ developer/src/kmc-kmn/build.sh | 2 +- developer/src/kmc-model-info/build.sh | 3 +- developer/src/kmc/build.sh | 5 +- .../buildClasses/BuildKeyboardInfo.ts | 24 ++--- .../src/commands/buildClasses/BuildProject.ts | 7 +- developer/src/kmc/test/test-build.ts | 10 ++- developer/src/kmc/test/test-project-build.ts | 12 ++- 29 files changed, 482 insertions(+), 61 deletions(-) create mode 100644 developer/src/kmc-keyboard-info/src/validate-mit-license.ts create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/LICENSE.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md create mode 100644 developer/src/kmc-keyboard-info/test/test-license.ts diff --git a/common/schemas/kpj/README.md b/common/schemas/kpj/README.md index 0f8e55de60d..bfca5c5bdc0 100644 --- a/common/schemas/kpj/README.md +++ b/common/schemas/kpj/README.md @@ -6,6 +6,9 @@ always '1.0'. It will be required for version 2.0 and later of the format. **Note:** An additional schema file, kpj-9.0.schema.json, for supporting legacy versions of .kpj, from Keyman Developer 9.0 and earlier, is now available. +## 2023-08-07 2.0.1 +* Add Options/SkipMetadataFiles, defaults to True for 1.0 projects. + ## 2023-02-27 2.0 * Version 2.0 makes 'Files' optional (internally, Files/File will be ignored, deleted on load and populated from folder structure). Adds Options/SourcePath, diff --git a/common/schemas/kpj/kpj.schema.json b/common/schemas/kpj/kpj.schema.json index 77721345f28..8a59fa40c54 100644 --- a/common/schemas/kpj/kpj.schema.json +++ b/common/schemas/kpj/kpj.schema.json @@ -45,6 +45,10 @@ "type": "string", "pattern": "^(True|False)$" }, + "SkipMetadataFiles": { + "type": "string", + "pattern": "^(True|False)$" + }, "ProjectType": { "type": "string", "pattern": "^(keyboard|lexicalmodel)$" diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/common/web/types/src/kpj/keyman-developer-project.ts index ce9f78cf591..1eee3a18cce 100644 --- a/common/web/types/src/kpj/keyman-developer-project.ts +++ b/common/web/types/src/kpj/keyman-developer-project.ts @@ -108,6 +108,11 @@ export class KeymanDeveloperProjectOptions { compilerWarningsAsErrors: boolean = false; warnDeprecatedCode: boolean = true; checkFilenameConventions: boolean = false; // missing option defaults to False + /** + * Skip building .keyboard_info and .model_info files, for example in + * unit tests or for legacy keyboards + */ + skipMetadataFiles: boolean; projectType: KeymanDeveloperProjectType = KeymanDeveloperProjectType.Keyboard; readonly version: KeymanDeveloperProjectVersion; constructor(version: KeymanDeveloperProjectVersion) { @@ -116,10 +121,12 @@ export class KeymanDeveloperProjectOptions { case "1.0": this.buildPath = ''; this.sourcePath = ''; + this.skipMetadataFiles = true; break; case "2.0": this.buildPath = '$PROJECTPATH/build'; this.sourcePath = '$PROJECTPATH/source'; + this.skipMetadataFiles = false; break; default: throw new Error('Invalid version'); diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 2c8589b5c14..5693d3a0b69 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -62,8 +62,10 @@ export class KPJFileReader { if(result.options.version == '2.0') { result.options.buildPath = (project.Options?.BuildPath || result.options.buildPath).replace(/\\/g, '/'); result.options.sourcePath = (project.Options?.SourcePath || result.options.sourcePath).replace(/\\/g, '/'); + result.options.skipMetadataFiles = this.boolFromString(project.Options?.SkipMetadataFiles, false); } else { result.options.buildPath = (project.Options?.BuildPath || '').replace(/\\/g, '/'); + result.options.skipMetadataFiles = this.boolFromString(project.Options?.SkipMetadataFiles, true); } result.options.checkFilenameConventions = this.boolFromString(project.Options?.CheckFilenameConventions, false); result.options.compilerWarningsAsErrors = this.boolFromString(project.Options?.CompilerWarningsAsErrors, false); diff --git a/common/web/types/src/kpj/kpj-file.ts b/common/web/types/src/kpj/kpj-file.ts index 317f24d013c..1c7da704378 100644 --- a/common/web/types/src/kpj/kpj-file.ts +++ b/common/web/types/src/kpj/kpj-file.ts @@ -20,6 +20,7 @@ export interface KPJFileOptions { SourcePath?: string; // default '' in 1.0, '$PROJECTPATH/source' in 2.0 CompilerWarningsAsErrors?: string; // default False WarnDeprecatedCode?: string; // default True + SkipMetadataFiles?: string; // default True for 1.0, False for 2.0 CheckFilenameConventions?: string; // default False ProjectType?: 'keyboard' | 'lexicalmodel'; // default 'keyboard' Version?: '1.0' | '2.0'; // default 1.0 diff --git a/common/web/types/src/util/file-types.ts b/common/web/types/src/util/file-types.ts index ebad759638a..52aa7495457 100644 --- a/common/web/types/src/util/file-types.ts +++ b/common/web/types/src/util/file-types.ts @@ -146,6 +146,16 @@ export function filenameIs(filename: string, fileType: Source | Binary) { return filename.toLowerCase().endsWith(fileType); } +/** + * Returns true if the file is either a .keyboard_info file or a .model_info + * file + * @param filename + * @returns + */ +export function filenameIsMetadata(filename: string) { + return filenameIs(filename, Source.KeyboardInfo) || filenameIs(filename, Source.ModelInfo); +} + /** * Replaces a filename extension with the new extension. Returns `null` if the * filename does not end with oldExtension. diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index 2c3a5c383b9..0523bac078b 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -23,6 +23,7 @@ describe('kpj-file-reader', function () { assert.equal(kpj.KeymanDeveloperProject.Options.CompilerWarningsAsErrors, 'True'); assert.equal(kpj.KeymanDeveloperProject.Options.ProjectType, 'keyboard'); assert.equal(kpj.KeymanDeveloperProject.Options.WarnDeprecatedCode, 'True'); + assert.equal(kpj.KeymanDeveloperProject.Options.SkipMetadataFiles, 'True'); // because this is a 1.0 version file assert.isUndefined(kpj.KeymanDeveloperProject.Options.Version); assert.lengthOf(kpj.KeymanDeveloperProject.Files.File, 21); @@ -73,6 +74,7 @@ describe('kpj-file-reader', function () { assert.isTrue(project.options.compilerWarningsAsErrors); assert.equal(project.options.projectType, KeymanDeveloperProjectType.Keyboard); assert.isTrue(project.options.warnDeprecatedCode); + assert.isTrue(project.options.skipMetadataFiles); assert.equal(project.options.version, '1.0'); assert.lengthOf(project.files, 3); diff --git a/developer/src/kmc-analyze/build.sh b/developer/src/kmc-analyze/build.sh index 8da7157d5dc..4d48b304313 100755 --- a/developer/src/kmc-analyze/build.sh +++ b/developer/src/kmc-analyze/build.sh @@ -29,7 +29,7 @@ function do_test() { # TODO: enable tests # cd test && tsc --build && cd .. && mocha # TODO: enable c8 (disabled because no coverage at present) - # c8 --reporter=lcov --reporter=text mocha + # c8 --reporter=lcov --reporter=text --exclude-after-remap mocha } builder_run_action clean rm -rf ./build/ diff --git a/developer/src/kmc-keyboard-info/build.sh b/developer/src/kmc-keyboard-info/build.sh index 345d0e00a95..094221c2e43 100755 --- a/developer/src/kmc-keyboard-info/build.sh +++ b/developer/src/kmc-keyboard-info/build.sh @@ -43,7 +43,7 @@ builder_run_action build tsc --build if builder_start_action test; then eslint . tsc --build test - c8 --reporter=lcov --reporter=text mocha + c8 --reporter=lcov --reporter=text --exclude-after-remap mocha builder_finish_action success test fi diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index 29c0c1f4056..79b00e90add 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -8,6 +8,7 @@ import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFileLanguage, K import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets } from "@keymanapp/common-types"; import { KeyboardInfoCompilerMessages } from "./messages.js"; import langtags from "./imports/langtags.js"; +import { validateMITLicense } from "./validate-mit-license.js"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); const scriptNames = new Intl.DisplayNames(['en'], { type: "script" }); @@ -49,15 +50,17 @@ export interface KeyboardInfoSources { helpLink?: string; /** The compiled keyboard filename and relative path (.js only) */ - keyboardFileNameJs?: string; + keyboardFilenameJs?: string; /** The compiled package filename and relative path (.kmp) */ - kmpFileName: string; + kmpFilename: string; /** The source package filename and relative path (.kps) */ - kpsFileName: string; + kpsFilename: string; + + /** The license filename */ + licenseFilename?: string; }; -/* c8 ignore stop */ export class KeyboardInfoCompiler { constructor(private callbacks: CompilerCallbacks) { @@ -96,21 +99,29 @@ export class KeyboardInfoCompiler { return null; } } else { - // TODO: this code pathway is still under development and won't yet be - // called by kmc. Building metadata without a source .keyboard_info file - // is on the roadmap but more data is needed in the .kps to complete this. - if(!sources.kmpFileName) { + // TODO: #9351 building metadata without a source .keyboard_info file is + // on the roadmap but more data is needed in the .kps to complete this + // fully. + if(!sources.kmpFilename) { // We can't build any metadata without a .kmp file + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_CannotBuildWithoutKmpFile()); return null; } + + if(!sources.licenseFilename) { + // For keymanapp/keyboards, the license is always in LICENSE.md, but if license + // is not found, we don't want to cancel the build + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_NoLicenseFound()); + } + keyboard_info = { }; } let jsFile: string = null; - if(sources.keyboardFileNameJs) { - jsFile = this.loadJsFile(sources.keyboardFileNameJs); + if(sources.keyboardFilenameJs) { + jsFile = this.loadJsFile(sources.keyboardFilenameJs); if(!jsFile) { return null; } @@ -119,7 +130,7 @@ export class KeyboardInfoCompiler { const kmxFiles: { filename: string, data: KMX.KEYBOARD - }[] = this.loadKmxFiles(sources.kpsFileName, sources.kmpJsonData); + }[] = this.loadKmxFiles(sources.kpsFilename, sources.kmpJsonData); // // Build .keyboard_info file @@ -140,6 +151,17 @@ export class KeyboardInfoCompiler { this.setField(keyboard_info, 'name', sources.kmpJsonData.info.name.description); + if(!sources.licenseFilename) { + if(!keyboard_info.license) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_NoLicenseFound()); + } + } else { + if(!this.isLicenseMIT(sources.licenseFilename)) { + // errors will have been reported by isLicenseMIT + return null; + } + } + const author = sources.kmpJsonData.info.author; if(author && (author.description || author.url)) { this.setField(keyboard_info, 'authorName', author?.description); @@ -166,13 +188,13 @@ export class KeyboardInfoCompiler { this.setField(keyboard_info, 'lastModifiedDate', (new Date).toISOString(), false); - if(sources.kmpFileName) { - this.setField(keyboard_info, 'packageFilename', this.callbacks.path.basename(sources.kmpFileName)); + if(sources.kmpFilename) { + this.setField(keyboard_info, 'packageFilename', this.callbacks.path.basename(sources.kmpFilename)); // Always overwrite with actual file size - keyboard_info.packageFileSize = this.callbacks.fileSize(sources.kmpFileName); + keyboard_info.packageFileSize = this.callbacks.fileSize(sources.kmpFilename); if(keyboard_info.packageFileSize === undefined) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.kmpFileName})); + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.kmpFilename})); return null; } } else { @@ -181,12 +203,12 @@ export class KeyboardInfoCompiler { delete keyboard_info.packageFileSize; } - if(sources.keyboardFileNameJs) { - this.setField(keyboard_info, 'jsFilename', this.callbacks.path.basename(sources.keyboardFileNameJs)); + if(sources.keyboardFilenameJs) { + this.setField(keyboard_info, 'jsFilename', this.callbacks.path.basename(sources.keyboardFilenameJs)); // Always overwrite with actual file size - keyboard_info.jsFileSize = this.callbacks.fileSize(sources.keyboardFileNameJs); + keyboard_info.jsFileSize = this.callbacks.fileSize(sources.keyboardFilenameJs); if(keyboard_info.jsFileSize === undefined) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.keyboardFileNameJs})); + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.keyboardFilenameJs})); return null; } } else { @@ -211,7 +233,7 @@ export class KeyboardInfoCompiler { keyboard_info.packageIncludes = [...includes]; // Always overwrite source data - if(sources.kmpFileName) { + if(sources.kmpFilename) { this.setField(keyboard_info, 'version', sources.kmpJsonData.info.version.description); } else { const m = jsFile?.match(/this\.KBVER\s*=\s*(['"])([^'"]+)(\1)/); @@ -339,6 +361,32 @@ export class KeyboardInfoCompiler { ( keyboard_info[field]) = keyboard_info[field] || expected; } + private isLicenseMIT(filename: string) { + const data = this.callbacks.loadFile(filename); + if(!data) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_LicenseFileDoesNotExist({filename})); + return false; + } + + let license = null; + try { + license = new TextDecoder().decode(data); + } catch(e) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_LicenseFileIsDamaged({filename})); + return false; + } + if(!license) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_LicenseFileIsDamaged({filename})); + return false; + } + const message = validateMITLicense(license); + if(message != null) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_LicenseIsNotValid({filename, message})); + return false; + } + return true; + } + private loadKmxFiles(kpsFilename: string, kmpJsonData: KmpJsonFile.KmpJsonFile) { const reader = new KmxFileReader(); return kmpJsonData.files diff --git a/developer/src/kmc-keyboard-info/src/messages.ts b/developer/src/kmc-keyboard-info/src/messages.ts index e72fd2f0e92..5a5cfcd372f 100644 --- a/developer/src/kmc-keyboard-info/src/messages.ts +++ b/developer/src/kmc-keyboard-info/src/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.KeyboardInfoCompiler; // const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -26,5 +26,24 @@ export class KeyboardInfoCompilerMessages { `Invalid author email: ${o.email}`); static ERROR_InvalidAuthorEmail = SevError | 0x0005; + static Error_LicenseFileDoesNotExist = (o:{filename:string}) => m(this.ERROR_LicenseFileIsMissing, + `License file ${o.filename} does not exist.`); + static ERROR_LicenseFileIsMissing = SevError | 0x0006; + + static Error_LicenseFileIsDamaged = (o:{filename:string}) => m(this.ERROR_LicenseFileIsDamaged, + `License file ${o.filename} could not be loaded or decoded.`); + static ERROR_LicenseFileIsDamaged = SevError | 0x0007; + + static Error_LicenseIsNotValid = (o:{filename:string,message:string}) => m(this.ERROR_LicenseIsNotValid, + `An error was encountered parsing license file ${o.filename}: ${o.message}.`); + static ERROR_LicenseIsNotValid = SevError | 0x0008; + + static Error_CannotBuildWithoutKmpFile = () => m(this.ERROR_CannotBuildWithoutKmpFile, + `Compiling the .keyboard_info file requires a .kmp file for metadata.`); + static ERROR_CannotBuildWithoutKmpFile = SevError | 0x0009; + + static Hint_NoLicenseFound = () => m(this.HINT_NoLicenseFound, + `No license for the keyboard was found, marking as "Other" license. MIT license is required for publication to Keyman keyboards repository.`); + static HINT_NoLicenseFound = SevHint | 0x000A; } diff --git a/developer/src/kmc-keyboard-info/src/validate-mit-license.ts b/developer/src/kmc-keyboard-info/src/validate-mit-license.ts new file mode 100644 index 00000000000..756abbeb567 --- /dev/null +++ b/developer/src/kmc-keyboard-info/src/validate-mit-license.ts @@ -0,0 +1,69 @@ +/** + * Returns an error string for license issues, or null if no issues found + * @param license + * @returns + */ +export function validateMITLicense(license: string) { + // NOTE: this function returns error messages which will not be easily + // localizable. Given this is only for use with the keyboards and + // lexical-models repositories, suggest we don't worry about this + + // split input text into paragraphs, trimming whitespace + const text = license.split('\n').map(line => line.trim()).join('\n').split(/\n\s*\n/); + + // MIT license, cleanup whitespace + const clauses = [ + `The MIT License (MIT)`, + + `Copyright ...`, // Copyright is tested separately below + + `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.` + ] + .map(clause => clause.split('\n').map(line => line.trim()).join(' ')); + + if(text.length < clauses.length) { + // not enough clauses in the file + return 'License is missing clauses from MIT license'; + } + + if(text.length > clauses.length) { + // too many clauses in the file + return 'License contains extra text'; + } + + for(let i = 0; i < clauses.length; i++) { + if(i == 1) { + // Clause 1 is the only one that can differ, and just with + // a copyright holder + if(!text[i].match(/^Copyright/)) { + // No Copyright clause + return `Clause 2 does not start with 'Copyright'`; + } + } else { + const t0 = text[i].replaceAll(/\s+/g, ' ').trim(); + const c0 = clauses[i].replaceAll(/\s+/g, ' ').trim(); + if(t0 != c0) { + // Clause does not match + return `Clause ${i+1} differs from MIT license`; + } + } + } + + return null; +} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/LICENSE.md b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/LICENSE.md new file mode 100644 index 00000000000..0a8c9054319 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 SIL International + +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. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info new file mode 100644 index 00000000000..c59a7c2c91c --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info @@ -0,0 +1,40 @@ +{ + "license": "mit", + "languages": { + "km": { + "displayName": "Khmer", + "languageName": "Khmer" + } + }, + "id": "khmer_angkor", + "name": "Khmer Angkor", + "authorName": "Makara Sok", + "authorEmail": "makara_sok@sil.org", + "lastModifiedDate": "2022-09-20T19:44:08Z", + "sourcePath": "release/k/khmer_angkor", + "version": "1.3", + "packageFilename": "khmer_angkor.kmp", + "jsFilename": "khmer_angkor.js", + "encodings": [ + "unicode" + ], + "jsFileSize": 159640, + "packageFileSize": 4291221, + "packageIncludes": [ + "visualKeyboard", + "welcome", + "fonts", + "documentation" + ], + "minKeymanVersion": "10.0", + "helpLink": "https://help.keyman.com/keyboard/khmer_angkor", + "platformSupport": { + "windows": "full", + "macos": "full", + "linux": "full", + "android": "full", + "ios": "full", + "desktopWeb": "full", + "mobileWeb": "full" + } +} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md new file mode 100644 index 00000000000..fc4e6d75a36 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 Me + +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, +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. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md new file mode 100644 index 00000000000..9f044869ef0 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +(c) 2015-2022 Me + +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. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md new file mode 100644 index 00000000000..2e535c2b5a1 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md @@ -0,0 +1,21 @@ +The Almost MIT License (AMIT) + +Copyright (c) 2015-2022 Me + +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. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md new file mode 100644 index 00000000000..ad11d55dd09 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 Me + +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. + +You must build a golden statue of me and place it in a prominent place for +each and every copy of the Software that you make. \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md new file mode 100644 index 00000000000..9debae50748 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 Me + +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 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. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md new file mode 100644 index 00000000000..0a8c9054319 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 SIL International + +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. diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 6ab3f1e2c4f..0630706d267 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -15,24 +15,29 @@ beforeEach(function() { describe('keyboard-info-compiler', function () { it('compile a .keyboard_info file correctly', function() { const path = makePathToFixture('khmer_angkor', 'khmer_angkor.keyboard_info'); - const jsFileName = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); - const kpsFileName = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); - const kmpFileName = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); + const licenseFilename = makePathToFixture('khmer_angkor', 'LICENSE.md'); + const jsFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); + const kpsFilename = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); + const kmpFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.keyboard_info'); const kmpCompiler = new KmpCompiler(callbacks); - const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFileName); + const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); const compiler = new KeyboardInfoCompiler(callbacks); const data = compiler.writeMergedKeyboardInfoFile(path, { - kmpFileName, + kmpFilename, kmpJsonData, keyboard_id: 'khmer_angkor', sourcePath: 'release/k/khmer_angkor', - kpsFileName, + kpsFilename, helpLink: 'https://help.keyman.com/keyboard/khmer_angkor', - keyboardFileNameJs: jsFileName, + keyboardFilenameJs: jsFilename, + licenseFilename }); + if(data == null) { + callbacks.printMessages(); + } assert.isNotNull(data); const actual = JSON.parse(new TextDecoder().decode(data)); @@ -46,6 +51,44 @@ describe('keyboard-info-compiler', function () { }); it('compile a .keyboard_info file correctly when no source .keyboard_info exists', function() { - this.skip(); // TODO: support keyboard_info when no source file exists (determine license from LICENSE.md) + // Note that this file should not exist: + const path = makePathToFixture('khmer_angkor', 'khmer_angkor_(missing).keyboard_info'); + + const licenseFilename = makePathToFixture('khmer_angkor', 'LICENSE.md'); + const jsFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); + const kpsFilename = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); + const kmpFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); + + // This file mirrors the real .keyboard_info, but strips fields that cannot + // currently be constructed from the package metadata + const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor_(no_source).keyboard_info'); + + const kmpCompiler = new KmpCompiler(callbacks); + const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); + + const compiler = new KeyboardInfoCompiler(callbacks); + const data = compiler.writeMergedKeyboardInfoFile(path, { + kmpFilename, + kmpJsonData, + keyboard_id: 'khmer_angkor', + sourcePath: 'release/k/khmer_angkor', + kpsFilename, + helpLink: 'https://help.keyman.com/keyboard/khmer_angkor', + keyboardFilenameJs: jsFilename, + licenseFilename, + }); + if(data == null) { + callbacks.printMessages(); + } + assert.isNotNull(data); + + const actual = JSON.parse(new TextDecoder().decode(data)); + const expected = JSON.parse(fs.readFileSync(buildKeyboardInfoFilename, 'utf-8')); + + // `lastModifiedDate` is dependent on time of run (not worth mocking) + delete actual['lastModifiedDate']; + delete expected['lastModifiedDate']; + + assert.deepEqual(actual, expected); }); }); diff --git a/developer/src/kmc-keyboard-info/test/test-license.ts b/developer/src/kmc-keyboard-info/test/test-license.ts new file mode 100644 index 00000000000..653f89ba69c --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/test-license.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs'; +import { assert } from 'chai'; +import 'mocha'; +import { makePathToFixture } from './helpers/index.js'; +import { validateMITLicense } from '../src/validate-mit-license.js'; + +function verifyLicenseFile(filename: string) { + return validateMITLicense(fs.readFileSync(makePathToFixture('license', filename), 'utf-8')); +} + +describe('validate-mit-license', function () { + it('should accept a valid license', function() { + assert.isNull(verifyLicenseFile('LICENSE.md')); + + }); + it('should catch invalid licenses', function() { + assert.equal(verifyLicenseFile('LICENSE-changed-clause.md'), 'Clause 3 differs from MIT license'); + assert.equal(verifyLicenseFile('LICENSE-missing-copyright.md'), "Clause 2 does not start with 'Copyright'"); + assert.equal(verifyLicenseFile('LICENSE-missing-title.md'), 'Clause 1 differs from MIT license'); + assert.equal(verifyLicenseFile('LICENSE-too-long.md'), 'License contains extra text'); + assert.equal(verifyLicenseFile('LICENSE-too-short.md'), 'License is missing clauses from MIT license'); + }); +}); diff --git a/developer/src/kmc-kmn/build.sh b/developer/src/kmc-kmn/build.sh index a69dec76466..7766610ac7a 100755 --- a/developer/src/kmc-kmn/build.sh +++ b/developer/src/kmc-kmn/build.sh @@ -70,7 +70,7 @@ if builder_start_action test; then copy_deps tsc --build test/ npm run lint - c8 --reporter=lcov --reporter=text mocha "${builder_extra_params[@]}" + c8 --reporter=lcov --reporter=text --exclude-after-remap mocha "${builder_extra_params[@]}" builder_finish_action success test fi diff --git a/developer/src/kmc-model-info/build.sh b/developer/src/kmc-model-info/build.sh index 994c6590196..68dfc78fc29 100755 --- a/developer/src/kmc-model-info/build.sh +++ b/developer/src/kmc-model-info/build.sh @@ -52,7 +52,8 @@ fi if builder_start_action test; then eslint . tsc --build test - c8 --reporter=lcov --reporter=text mocha + c8 --reporter=lcov --reporter=text --exclude-after-remap --lines 80 mocha + # TODO: remove --lines 80 and improve coverage builder_finish_action success test fi diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 0c85d8764ef..2f8045f455e 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -74,9 +74,8 @@ fi if builder_start_action test; then eslint . tsc --build test/ - mocha - # TODO: enable c8 (disabled because no coverage at present) - # && c8 --reporter=lcov --reporter=text mocha + # TODO: increase coverage to 90% + c8 --reporter=lcov --reporter=text --exclude-after-remap --lines=30 mocha builder_finish_action success test fi diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 42bc4e4a950..5e76f6047ce 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -8,6 +8,7 @@ import { KmpCompiler } from '@keymanapp/kmc-package'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; const HelpRoot = 'https://help.keyman.com/keyboard/'; +const LICENSE_FILENAME = 'LICENSE.md'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } @@ -21,6 +22,12 @@ export class BuildKeyboardInfo extends BuildActivity { // version 2.0 projects (where the .kpj file is optional). infile = KeymanFileTypes.replaceExtension(infile, KeymanFileTypes.Source.KeyboardInfo, KeymanFileTypes.Source.Project); } + + if(!callbacks.fs.existsSync(infile)) { + // We cannot build a .keyboard_info if we don't have a repository-style project + return false; + } + const project = loadProject(infile, callbacks); if(!project) { return false; @@ -32,13 +39,7 @@ export class BuildKeyboardInfo extends BuildActivity { return false; } - if(!fs.existsSync(project.resolveInputFilePath(metadata))) { - // For now, if the metadata file does not exist, we won't attempt to build - // it. One day in the future, when source metadata files become optional, - // we'll need to skip this - return true; - } - + const license = project.files.find(file => file.filename == LICENSE_FILENAME); const keyboard = findProjectFile(callbacks, project, KeymanFileTypes.Source.KeymanKeyboard); const kps = findProjectFile(callbacks, project, KeymanFileTypes.Source.Package); @@ -58,12 +59,13 @@ export class BuildKeyboardInfo extends BuildActivity { const compiler = new KeyboardInfoCompiler(callbacks); const data = compiler.writeMergedKeyboardInfoFile(project.resolveInputFilePath(metadata), { keyboard_id, - kmpFileName: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), + kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kmpJsonData, - kpsFileName: project.resolveInputFilePath(kps), + kpsFilename: project.resolveInputFilePath(kps), helpLink: HelpRoot + keyboard_id, - keyboardFileNameJs: fs.existsSync(keyboardFileNameJs) ? keyboardFileNameJs : undefined, - sourcePath: calculateSourcePath(infile) + keyboardFilenameJs: fs.existsSync(keyboardFileNameJs) ? keyboardFileNameJs : undefined, + sourcePath: calculateSourcePath(infile), + licenseFilename: license ? project.resolveInputFilePath(license) : undefined }); if(data == null) { diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 460a5af88f7..fdced30e021 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -47,14 +47,15 @@ class ProjectBuilder { continue; } + if(KeymanFileTypes.filenameIsMetadata(builder.sourceExtension) && this.project.options.skipMetadataFiles) { + continue; + } + if(!await this.buildProjectTargets(builder)) { return false; } } - // TODO: generate .keyboard_info from .kps + etc (and support merge of - // $PROJECTPATH/.keyboard_info for version 1.0 projects) - return true; } diff --git a/developer/src/kmc/test/test-build.ts b/developer/src/kmc/test/test-build.ts index 65203efe88d..26ef09e746b 100644 --- a/developer/src/kmc/test/test-build.ts +++ b/developer/src/kmc/test/test-build.ts @@ -34,11 +34,13 @@ describe('compilerWarningsAsErrors', function () { const builder = new BuildProject(); const path = makePathToFixture('compiler-warnings-as-errors', `compiler_warnings_as_errors_${truth.kpj === true ? 'true' : (truth.kpj === false ? 'false' : 'undefined')}.kpj`); - const result = await builder.build(path, callbacks, {compilerWarningsAsErrors: truth.cli}); + const result = await builder.build(path, callbacks, { + compilerWarningsAsErrors: truth.cli, + }); + if(truth.result != result) { + callbacks.printMessages(); + } if(truth.result) { - if(callbacks.messages.length != 0) { - callbacks.printMessages(); - } assert.isTrue(result); } else { assert.isFalse(result); diff --git a/developer/src/kmc/test/test-project-build.ts b/developer/src/kmc/test/test-project-build.ts index 088de1e40f2..88563168805 100644 --- a/developer/src/kmc/test/test-project-build.ts +++ b/developer/src/kmc/test/test-project-build.ts @@ -16,20 +16,18 @@ describe('BuildProject', function () { compilerWarningsAsErrors: true, saveDebug: false, warnDeprecatedCode: true, - logLevel: 'info' + logLevel: 'info', }); - if(callbacks.messages.length != 6) { - callbacks.printMessages(); - } - assert.equal(callbacks.messages.length, 6); const messages = [ InfrastructureMessages.INFO_BuildingFile, // kmn InfrastructureMessages.INFO_FileBuiltSuccessfully, InfrastructureMessages.INFO_BuildingFile, // kps InfrastructureMessages.INFO_FileBuiltSuccessfully, - InfrastructureMessages.INFO_BuildingFile, // keyboard_info - InfrastructureMessages.INFO_FileBuiltSuccessfully, ]; + if(callbacks.messages.length != messages.length) { + callbacks.printMessages(); + } + assert.equal(callbacks.messages.length, messages.length); for(let i = 0; i < messages.length; i++) { assert.equal(callbacks.messages[i].code, messages[i]); } From 47e05a7b053200926f4cb20b9dfe807c7a7d0858 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 16 Aug 2023 16:01:14 +0700 Subject: [PATCH 015/207] feat(developer): build .keyboard_info without source .keyboard_info Also fixes a file reference issue in kmpJson.options, where paths were stripped too early in the process. Removes a now-irrelevant unit test for keyboard_info. TODO: markdown to html during the keyboard_info build. --- developer/src/kmc-keyboard-info/package.json | 3 +- developer/src/kmc-keyboard-info/src/index.ts | 237 +++++++++--------- .../src/keyboard-info-file.ts | 41 +-- .../src/kmc-keyboard-info/src/messages.ts | 8 +- .../build/khmer_angkor.keyboard_info | 12 +- .../khmer_angkor_(no_source).keyboard_info | 40 --- .../khmer_angkor/khmer_angkor.keyboard_info | 30 --- .../khmer_angkor/source/khmer_angkor.kps | 14 ++ .../test/test-keyboard-info-compiler.ts | 47 +--- developer/src/kmc-keyboard-info/tsconfig.json | 2 + .../kmc-package/src/compiler/kmp-compiler.ts | 33 ++- 11 files changed, 166 insertions(+), 301 deletions(-) delete mode 100644 developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info delete mode 100644 developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/khmer_angkor.keyboard_info diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 3a2bc226b1f..4f96e31c7a1 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -24,7 +24,8 @@ "url": "https://github.com/keymanapp/keyman/issues" }, "dependencies": { - "@keymanapp/common-types": "*" + "@keymanapp/common-types": "*", + "@keymanapp/kmc-package": "*" }, "devDependencies": { "@types/chai": "^4.1.7", diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index 79b00e90add..f221754b08e 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -4,7 +4,7 @@ */ import { minKeymanVersion } from "./min-keyman-version.js"; -import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFileLanguage, KeyboardInfoFilePlatform } from "./keyboard-info-file.js"; +import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFilePlatform } from "./keyboard-info-file.js"; import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets } from "@keymanapp/common-types"; import { KeyboardInfoCompilerMessages } from "./messages.js"; import langtags from "./imports/langtags.js"; @@ -57,9 +57,6 @@ export interface KeyboardInfoSources { /** The source package filename and relative path (.kps) */ kpsFilename: string; - - /** The license filename */ - licenseFilename?: string; }; export class KeyboardInfoCompiler { @@ -75,49 +72,22 @@ export class KeyboardInfoCompiler { * For full documentation, see: * https://help.keyman.com/developer/cloud/keyboard_info/ * - * @param sourceKeyboardInfoFileName Path for the source .keyboard_info file * @param sources Details on files from which to extract metadata */ public writeMergedKeyboardInfoFile( - sourceKeyboardInfoFileName: string, sources: KeyboardInfoSources ): Uint8Array { - let keyboard_info: KeyboardInfoFile = null; - - if(this.callbacks.fs.existsSync(sourceKeyboardInfoFileName)) { - const dataInput = this.callbacks.loadFile(sourceKeyboardInfoFileName); - if(!dataInput) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename: sourceKeyboardInfoFileName})); - return null; - } - - try { - const jsonInput = new TextDecoder('utf-8', {fatal: true}).decode(dataInput); - keyboard_info = JSON.parse(jsonInput); - } catch(e) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileIsNotValid({filename: sourceKeyboardInfoFileName, e})); - return null; - } - } else { - // TODO: #9351 building metadata without a source .keyboard_info file is - // on the roadmap but more data is needed in the .kps to complete this - // fully. - if(!sources.kmpFilename) { - // We can't build any metadata without a .kmp file - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_CannotBuildWithoutKmpFile()); - return null; - } - if(!sources.licenseFilename) { - // For keymanapp/keyboards, the license is always in LICENSE.md, but if license - // is not found, we don't want to cancel the build - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_NoLicenseFound()); - } + // TODO: work from .kpj and nothing else as input - keyboard_info = { - }; + if(!sources.kmpFilename) { + // We can't build any metadata without a .kmp file + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_CannotBuildWithoutKmpFile()); + return null; } + const keyboard_info: KeyboardInfoFile = {}; + let jsFile: string = null; if(sources.keyboardFilenameJs) { @@ -138,35 +108,35 @@ export class KeyboardInfoCompiler { // https://help.keyman.com/developer/cloud/keyboard_info/2.0 // - // TODO: read LicenseFilename from .kps and verify (OR ADD TO KPS?) - // TODO: use mit license validation from feat/developer/compile-without-source-keyboard-info - keyboard_info.license = 'mit'; + keyboard_info.id = this.callbacks.path.basename(sources.kmpFilename, '.kmp'); + keyboard_info.name = sources.kmpJsonData.info.name.description; + + // License - keyboard_info.isRTL = keyboard_info.isRTL ?? !!jsFile?.match(/this\.KRTL=1/); - if(!keyboard_info.isRTL) { - delete keyboard_info.isRTL; + if(!sources.kmpJsonData.options?.licenseFile) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_NoLicenseFound()); + return null; } - this.setField(keyboard_info, 'id', sources.keyboard_id); + if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, sources.kmpJsonData.options.licenseFile))) { + return null; + } - this.setField(keyboard_info, 'name', sources.kmpJsonData.info.name.description); + keyboard_info.license = 'mit'; - if(!sources.licenseFilename) { - if(!keyboard_info.license) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_NoLicenseFound()); - } - } else { - if(!this.isLicenseMIT(sources.licenseFilename)) { - // errors will have been reported by isLicenseMIT - return null; - } + // isRTL + + if(jsFile?.match(/this\.KRTL=1/)) { + keyboard_info.isRTL = true; } + // author + const author = sources.kmpJsonData.info.author; - if(author && (author.description || author.url)) { - this.setField(keyboard_info, 'authorName', author?.description); + if(author?.description || author?.url) { + keyboard_info.authorName = author.description; - if (author?.url) { + if (author.url) { // we strip the mailto: from the .kps file for the .keyboard_info const match = author.url.match(/^(mailto\:)?(.+)$/); /* c8 ignore next 3 */ @@ -175,46 +145,42 @@ export class KeyboardInfoCompiler { return null; } - const email = match[2]; - this.setField(keyboard_info, 'authorEmail', email, false); + keyboard_info.authorEmail = match[2]; } } + // description + + if(sources.kmpJsonData.info.description?.description) { + keyboard_info.description = markDownToHTML(sources.kmpJsonData.info.description?.description); + } + // extract the language identifiers from the language metadata arrays for // each of the keyboards in the kmp.json file, and merge into a single array // of identifiers in the .keyboard_info file. this.fillLanguages(keyboard_info, sources.kmpJsonData); - this.setField(keyboard_info, 'lastModifiedDate', (new Date).toISOString(), false); + // TODO: use: TZ=UTC0 git log -1 --no-merges --date=format:%Y-%m-%dT%H:%M:%SZ --format=%ad + keyboard_info.lastModifiedDate = (new Date).toISOString(); - if(sources.kmpFilename) { - this.setField(keyboard_info, 'packageFilename', this.callbacks.path.basename(sources.kmpFilename)); + keyboard_info.packageFilename = this.callbacks.path.basename(sources.kmpFilename); - // Always overwrite with actual file size - keyboard_info.packageFileSize = this.callbacks.fileSize(sources.kmpFilename); - if(keyboard_info.packageFileSize === undefined) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.kmpFilename})); - return null; - } - } else { - // TODO: warn if set in .keyboard_info? - delete keyboard_info.packageFilename; - delete keyboard_info.packageFileSize; + // Always overwrite with actual file size + keyboard_info.packageFileSize = this.callbacks.fileSize(sources.kmpFilename); + if(keyboard_info.packageFileSize === undefined) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.kmpFilename})); + return null; } if(sources.keyboardFilenameJs) { - this.setField(keyboard_info, 'jsFilename', this.callbacks.path.basename(sources.keyboardFilenameJs)); + keyboard_info.jsFilename = this.callbacks.path.basename(sources.keyboardFilenameJs); // Always overwrite with actual file size keyboard_info.jsFileSize = this.callbacks.fileSize(sources.keyboardFilenameJs); if(keyboard_info.jsFileSize === undefined) { this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.keyboardFilenameJs})); return null; } - } else { - // TODO: warn if set in .keyboard_info? - delete keyboard_info.jsFilename; - delete keyboard_info.jsFileSize; } const includes = new Set(); @@ -232,20 +198,8 @@ export class KeyboardInfoCompiler { } keyboard_info.packageIncludes = [...includes]; - // Always overwrite source data - if(sources.kmpFilename) { - this.setField(keyboard_info, 'version', sources.kmpJsonData.info.version.description); - } else { - const m = jsFile?.match(/this\.KBVER\s*=\s*(['"])([^'"]+)(\1)/); - if(m) { - this.setField(keyboard_info, 'version', m[2]); - } else { - keyboard_info.version = '1.0'; - } - } + keyboard_info.version = sources.kmpJsonData.info.version.description; - // The minimum Keyman version detected in the package file may be manually - // set higher by the developer let minVersion = minKeymanVersion; const m = jsFile?.match(/this.KMINVER\s*=\s*(['"])(.*?)\1/); if(m) { @@ -306,17 +260,21 @@ export class KeyboardInfoCompiler { keyboard_info.platformSupport[platform] = 'full'; } - // minKeymanVersion - if(parseFloat(keyboard_info.minKeymanVersion ?? minKeymanVersion) < parseFloat(minVersion)) { - this.setField(keyboard_info, 'minKeymanVersion', minVersion, false); - } + keyboard_info.minKeymanVersion = minVersion; + keyboard_info.sourcePath = sources.sourcePath; - if(sources.sourcePath) { - this.setField(keyboard_info, 'sourcePath', sources.sourcePath); + if(sources.helpLink) { + keyboard_info.helpLink = sources.helpLink; } - if(sources.helpLink) { - this.setField(keyboard_info, 'helpLink', sources.helpLink); + // Related packages + if(sources.kmpJsonData.relatedPackages?.length) { + keyboard_info.related = {}; + for(const p of sources.kmpJsonData.relatedPackages) { + keyboard_info.related[p.id] = { + deprecates: p.relationship == 'deprecates' + }; + } } const jsonOutput = JSON.stringify(keyboard_info, null, 2); @@ -345,22 +303,6 @@ export class KeyboardInfoCompiler { return ((version & 0xFF00) >> 8).toString() + '.' + (version & 0xFF).toString(); } - private setField(keyboard_info: KeyboardInfoFile, field: keyof KeyboardInfoFile, expected: unknown, warn: boolean = true) { - /* c8 ignore next 4 */ - if (keyboard_info[field] && keyboard_info[field] !== expected && expected !== undefined) { - if (warn ?? true) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Warn_MetadataFieldInconsistent({ - field, value: keyboard_info[field], expected - })); - } - - } - // TypeScript gets upset with this assignment, because it cannot deduce - // the exact type of keyboard_info[field] -- there are many possibilities! - // So we assert that it's unknown so that TypeScript can chill. - ( keyboard_info[field]) = keyboard_info[field] || expected; - } - private isLicenseMIT(filename: string) { const data = this.callbacks.loadFile(filename); if(!data) { @@ -409,20 +351,62 @@ export class KeyboardInfoCompiler { } private fillLanguages(keyboard_info: KeyboardInfoFile, kmpJsonData: KmpJsonFile.KmpJsonFile) { - keyboard_info.languages = keyboard_info.languages || - kmpJsonData.keyboards.reduce((a, e) => [].concat(a, e.languages.map((f) => f.id)), []); + // Collapse language data from multiple keyboards + const languages = + kmpJsonData.keyboards.reduce((a, e) => [].concat(a, (e.languages ?? []).map((f) => f.id)), []); + const examples: KmpJsonFile.KmpJsonFileExample[] = + kmpJsonData.keyboards.reduce((a, e) => [].concat(a, e.examples ?? []), []); // Transform array into object - if(Array.isArray(keyboard_info.languages)) { - const languages: {[index:string]: KeyboardInfoFileLanguage} = {}; - for(const language of keyboard_info.languages) { - languages[language] = {}; - } - keyboard_info.languages = languages; + keyboard_info.languages = {}; + for(const language of languages) { + keyboard_info.languages[language] = {}; } + const fontSource = kmpJsonData.keyboards.map(e => e.displayFont).concat(...kmpJsonData.keyboards.map(e => e.webDisplayFonts ?? [])); + const oskFontSource = kmpJsonData.keyboards.map(e => e.oskFont).concat(...kmpJsonData.keyboards.map(e => e.webOskFonts ?? [])); + for(const bcp47 of Object.keys(keyboard_info.languages)) { const language = keyboard_info.languages[bcp47]; + + // + // Add examples + // + language.examples = []; + for(const example of examples) { + if(example.id == bcp47) { + language.examples.push({ + // we don't copy over example.id + keys:example.keys, + note:example.note, + text:example.text + }); + } + } + + // + // Add fonts -- which are duplicated for each language; we'll mark this as a future + // optimization, but it's another keyboard_info breaking change so don't want to + // do it right now. + // + + if(fontSource.length) { + language.font = { + family: keyboard_info.id + ' Keyman Display Font', + source: fontSource + }; + } + + if(oskFontSource.length) { + language.oskFont = { + family: keyboard_info.id + ' Keyman OSK Font', + source: oskFontSource + }; + } + + // + // Add locale description + // const locale = new Intl.Locale(bcp47); // DisplayNames.prototype.of will throw a RangeError if it doesn't understand // the format of the bcp47 tag. This happens with Node 18.14.1, for example, with: @@ -455,4 +439,9 @@ export class KeyboardInfoCompiler { } } -} \ No newline at end of file +} + +function markDownToHTML(markdown: string): string { + // TODO + return markdown; +} diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts index 87620c1fb54..e8a9b2bd09a 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts @@ -36,7 +36,7 @@ export interface KeyboardInfoFileRelated { export interface KeyboardInfoFileLanguage { font?: KeyboardInfoFileLanguageFont; oskFont?: KeyboardInfoFileLanguageFont; - example?: KeyboardInfoFileExample; + examples?: KeyboardInfoFileExample[]; displayName?: string; languageName?: string; scriptName?: string; @@ -49,44 +49,7 @@ export interface KeyboardInfoFileLanguageFont { } export interface KeyboardInfoFileExample { - keys?: string | KeyboardInfoFileExampleKey[]; + keys?: string; text?: string; note?: string; } - -export type KeyboardInfoFileExampleKeyId = - "K_SPACE" | - "K_A" | "K_B" | "K_C" | "K_D" | "K_E" | "K_F" | "K_G" | "K_H" | "K_I" | "K_J" | "K_K" | "K_L" | "K_M" | - "K_N" | "K_O" | "K_P" | "K_Q" | "K_R" | "K_S" | "K_T" | "K_U" | "K_V" | "K_W" | "K_X" | "K_Y" | "K_Z" | - "K_1" | "K_2" | "K_3" | "K_4" | "K_5" | "K_6" | "K_7" | "K_8" | "K_9" | "K_0" | - "K_BKQUOTE" | "K_HYPHEN" | "K_EQUAL" | "K_LBRKT" | "K_RBRKT" | "K_BKSLASH" | "K_COLON" | - "K_QUOTE" | "K_COMMA" | "K_PERIOD" | "K_SLASH" | - "K_oE2" | "K_BKSP" | "K_TAB" | "K_ENTER" | "K_ESC" | - "K_LEFT" | "K_UP" | "K_RIGHT" | "K_DOWN" | "K_PGUP" | "K_PGDN" | "K_HOME" | "K_END" | "K_INS" | "K_DEL" | - "K_F1" | "K_F2" | "K_F3" | "K_F4" | "K_F5" | "K_F6" | "K_F7" | "K_F8" | "K_F9" | "K_F10" | "K_F11" | "K_F12" | - "K_KP5" | "K_NP0" | "K_NP1" | "K_NP2" | "K_NP3" | "K_NP4" | "K_NP5" | "K_NP6" | "K_NP7" | "K_NP8" | "K_NP9" | - "K_NPSTAR" | "K_NPPLUS" | "K_NPMINUS" | "K_NPDOT" | "K_NPSLASH" | - "K_SEL" | "K_PRINT" | "K_EXEC" | "K_HELP" | "K_SEPARATOR" | - "K_F13" | "K_F14" | "K_F15" | "K_F16" | "K_F17" | "K_F18" | "K_F19" | "K_F20" | "K_F21" | "K_F22" | "K_F23" | "K_F24" | - "K_KANJI?15" | "K_KANJI?16" | "K_KANJI?17" | "K_KANJI?18" | "K_KANJI?19" | "K_KANJI?1C" | "K_KANJI?1D" | "K_KANJI?1E" | "K_KANJI?1F" | - "K_oE0" | "K_oE1" | "K_oE3" | "K_oE4" | "K_oE6" | "K_oE9" | "K_oEA" | "K_oEB" | "K_oEC" | "K_oED" | "K_oEE" | "K_oEF" | - "K_oF0" | "K_oF1" | "K_oF2" | "K_oF3" | "K_oF4" | "K_oF5" | "K_?00" | "K_?05" | "K_NPENTER" | - "K_?06" | "K_?07" | "K_?0A" | "K_?0B" | "K_?0E" | "K_?0F" | "K_?1A" | "K_?3A" | "K_?3B" | "K_?3C" | "K_?3D" | "K_?3E" | - "K_?3F" | "K_?40" | "K_?5B" | "K_?5C" | "K_?5D" | "K_?5E" | "K_?5F" | "K_?88" | "K_?89" | "K_?8A" | "K_?8B" | "K_?8C" | - "K_?8D" | "K_?8E" | "K_?8F" | "K_?92" | "K_?94" | "K_?95" | "K_?96" | "K_?97" | "K_?98" | "K_?99" | "K_?9A" | "K_?9B" | - "K_?9C" | "K_?9D" | "K_?9E" | "K_?9F" | "K_?A0" | "K_?A1" | "K_?A2" | "K_?A3" | "K_?A4" | "K_?A5" | "K_?A6" | "K_?A7" | - "K_?A8" | "K_?A9" | "K_?AA" | "K_?AB" | "K_?AC" | "K_?AD" | "K_?AE" | "K_?AF" | "K_?B0" | "K_?B1" | "K_?B2" | "K_?B3" | - "K_?B4" | "K_?B5" | "K_?B6" | "K_?B7" | "K_?B8" | "K_?B9" | "K_?C1" | "K_?C2" | "K_?C3" | "K_?C4" | "K_?C5" | "K_?C6" | - "K_?C7" | "K_?C8" | "K_?C9" | "K_?CA" | "K_?CB" | "K_?CC" | "K_?CD" | "K_?CE" | "K_?CF" | "K_?D0" | "K_?D1" | "K_?D2" | - "K_?D3" | "K_?D4" | "K_?D5" | "K_?D6" | "K_?D7" | "K_?D8" | "K_?D9" | "K_?DA" | "K_oDF" | "K_?E5" | "K_?E7" | "K_?E8" | - "K_?F6" | "K_?F7" | "K_?F8" | "K_?F9" | "K_?FA" | "K_?FB" | "K_?FC" | "K_?FD" | "K_?FE" | "K_?FF"; - -export type KeyboardInfoFileExampleKeyModifier = - "shift" | "s" | "ctrl" | "c" | "alt" | "a" | "left-ctrl" | "lc" | - "right-ctrl" | "rc" | "left-alt" | "la" | "right-alt" | "ra"; - -export interface KeyboardInfoFileExampleKey { - key?: KeyboardInfoFileExampleKeyId; - modifiers?: KeyboardInfoFileExampleKeyModifier[]; -} - diff --git a/developer/src/kmc-keyboard-info/src/messages.ts b/developer/src/kmc-keyboard-info/src/messages.ts index 5a5cfcd372f..72a03278ac6 100644 --- a/developer/src/kmc-keyboard-info/src/messages.ts +++ b/developer/src/kmc-keyboard-info/src/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.KeyboardInfoCompiler; // const SevInfo = CompilerErrorSeverity.Info | Namespace; -const SevHint = CompilerErrorSeverity.Hint | Namespace; +// const SevHint = CompilerErrorSeverity.Hint | Namespace; const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -42,8 +42,8 @@ export class KeyboardInfoCompilerMessages { `Compiling the .keyboard_info file requires a .kmp file for metadata.`); static ERROR_CannotBuildWithoutKmpFile = SevError | 0x0009; - static Hint_NoLicenseFound = () => m(this.HINT_NoLicenseFound, - `No license for the keyboard was found, marking as "Other" license. MIT license is required for publication to Keyman keyboards repository.`); - static HINT_NoLicenseFound = SevHint | 0x000A; + static Error_NoLicenseFound = () => m(this.ERROR_NoLicenseFound, + `No license for the keyboard was found. MIT license is required for publication to Keyman keyboards repository.`); + static ERROR_NoLicenseFound = SevError | 0x000A; } diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info index 20b86397d4b..0b854bda12d 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info @@ -3,27 +3,27 @@ "languages": { "km": { "oskFont": { - "family": "Khmer Busra Kbd", + "family": "khmer_angkor Keyman OSK Font", "source": [ "khmer_busra_kbd.ttf" ] }, "font": { - "family": "Khmer Mondulkiri", + "family": "khmer_angkor Keyman Display Font", "source": [ "Mondulkiri-R.ttf" ] }, - "example": { - "keys": "xjmEr", + "examples": [{ + "keys": "x j m E r", "text": "\u1781\u17D2\u1798\u17C2\u179A", "note": "Name of language" - }, + }], "displayName": "Khmer", "languageName": "Khmer" } }, - "description": "

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.

", + "description": "Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.", "related": { "khmer10": { "deprecates": true diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info deleted file mode 100644 index c59a7c2c91c..00000000000 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor_(no_source).keyboard_info +++ /dev/null @@ -1,40 +0,0 @@ -{ - "license": "mit", - "languages": { - "km": { - "displayName": "Khmer", - "languageName": "Khmer" - } - }, - "id": "khmer_angkor", - "name": "Khmer Angkor", - "authorName": "Makara Sok", - "authorEmail": "makara_sok@sil.org", - "lastModifiedDate": "2022-09-20T19:44:08Z", - "sourcePath": "release/k/khmer_angkor", - "version": "1.3", - "packageFilename": "khmer_angkor.kmp", - "jsFilename": "khmer_angkor.js", - "encodings": [ - "unicode" - ], - "jsFileSize": 159640, - "packageFileSize": 4291221, - "packageIncludes": [ - "visualKeyboard", - "welcome", - "fonts", - "documentation" - ], - "minKeymanVersion": "10.0", - "helpLink": "https://help.keyman.com/keyboard/khmer_angkor", - "platformSupport": { - "windows": "full", - "macos": "full", - "linux": "full", - "android": "full", - "ios": "full", - "desktopWeb": "full", - "mobileWeb": "full" - } -} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/khmer_angkor.keyboard_info deleted file mode 100644 index 18a02278670..00000000000 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/khmer_angkor.keyboard_info +++ /dev/null @@ -1,30 +0,0 @@ -{ - "license": "mit", - "languages": { - "km": { - "oskFont": { - "family": "Khmer Busra Kbd", - "source": [ - "khmer_busra_kbd.ttf" - ] - }, - "font": { - "family": "Khmer Mondulkiri", - "source": [ - "Mondulkiri-R.ttf" - ] - }, - "example": { - "keys": "xjmEr", - "text": "ខ្មែរ", - "note": "Name of language" - } - } - }, - "description": "

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.

", - "related": { - "khmer10": { - "deprecates": true - } - } -} \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/source/khmer_angkor.kps b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/source/khmer_angkor.kps index 5e27ab92a70..0e646475d59 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/source/khmer_angkor.kps +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/source/khmer_angkor.kps @@ -10,6 +10,7 @@ splash.gif + ..\LICENSE.md @@ -22,8 +23,18 @@ Makara Sok https://keyman.com/keyboards/khmer_angkor + Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors. + + + + + ..\LICENSE.md + File LICENSE.md + 0 + .md + ..\build\khmer_angkor.js File khmer_angkor.js @@ -143,6 +154,9 @@ Central Khmer (Khmer, Cambodia) + + +

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

diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 0630706d267..e1f09f132f0 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -14,8 +14,6 @@ beforeEach(function() { describe('keyboard-info-compiler', function () { it('compile a .keyboard_info file correctly', function() { - const path = makePathToFixture('khmer_angkor', 'khmer_angkor.keyboard_info'); - const licenseFilename = makePathToFixture('khmer_angkor', 'LICENSE.md'); const jsFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); const kpsFilename = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); const kmpFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); @@ -25,7 +23,7 @@ describe('keyboard-info-compiler', function () { const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile(path, { + const data = compiler.writeMergedKeyboardInfoFile({ kmpFilename, kmpJsonData, keyboard_id: 'khmer_angkor', @@ -33,49 +31,6 @@ describe('keyboard-info-compiler', function () { kpsFilename, helpLink: 'https://help.keyman.com/keyboard/khmer_angkor', keyboardFilenameJs: jsFilename, - licenseFilename - }); - if(data == null) { - callbacks.printMessages(); - } - assert.isNotNull(data); - - const actual = JSON.parse(new TextDecoder().decode(data)); - const expected = JSON.parse(fs.readFileSync(buildKeyboardInfoFilename, 'utf-8')); - - // `lastModifiedDate` is dependent on time of run (not worth mocking) - delete actual['lastModifiedDate']; - delete expected['lastModifiedDate']; - - assert.deepEqual(actual, expected); - }); - - it('compile a .keyboard_info file correctly when no source .keyboard_info exists', function() { - // Note that this file should not exist: - const path = makePathToFixture('khmer_angkor', 'khmer_angkor_(missing).keyboard_info'); - - const licenseFilename = makePathToFixture('khmer_angkor', 'LICENSE.md'); - const jsFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); - const kpsFilename = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); - const kmpFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); - - // This file mirrors the real .keyboard_info, but strips fields that cannot - // currently be constructed from the package metadata - const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor_(no_source).keyboard_info'); - - const kmpCompiler = new KmpCompiler(callbacks); - const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); - - const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile(path, { - kmpFilename, - kmpJsonData, - keyboard_id: 'khmer_angkor', - sourcePath: 'release/k/khmer_angkor', - kpsFilename, - helpLink: 'https://help.keyman.com/keyboard/khmer_angkor', - keyboardFilenameJs: jsFilename, - licenseFilename, }); if(data == null) { callbacks.printMessages(); diff --git a/developer/src/kmc-keyboard-info/tsconfig.json b/developer/src/kmc-keyboard-info/tsconfig.json index 85630292e88..c10b82f8637 100644 --- a/developer/src/kmc-keyboard-info/tsconfig.json +++ b/developer/src/kmc-keyboard-info/tsconfig.json @@ -12,6 +12,7 @@ "noImplicitAny": false, "paths": { "@keymanapp/common-types": ["../../../common/web/types/src/main"], + "@keymanapp/kmc-package": ["../kmc-package/source"], } }, "include": [ @@ -20,5 +21,6 @@ ], "references": [ { "path": "../../../common/web/types" }, + { "path": "../kmc-package/" }, ] } diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b005cb80ac2..9ffde6e69a0 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -69,17 +69,11 @@ export class KmpCompiler { if(kps.options) { kmp.options.executeProgram = kps.options?.executeProgram || undefined; - if(kps.options.graphicFile) { - kmp.options.graphicFile = /[/\\]?([^/\\]*)$/.exec(kps.options.graphicFile)[1]; - } - kmp.options.msiFilename = kps.options.msiFileName; - kmp.options.msiOptions = kps.options.msiOptions; - if(kps.options.readMeFile) { - kmp.options.readmeFile = /[/\\]?([^/\\]*)$/.exec(kps.options.readMeFile)[1]; - } - if(kps.options.licenseFile) { - kmp.options.licenseFile = /[/\\]?([^/\\]*)$/.exec(kps.options.licenseFile)[1]; - } + kmp.options.graphicFile = kps.options.graphicFile || undefined; + kmp.options.msiFilename = kps.options.msiFileName || undefined; + kmp.options.msiOptions = kps.options.msiOptions || undefined; + kmp.options.readmeFile = kps.options.readMeFile || undefined; + kmp.options.licenseFile = kps.options.licenseFile || undefined; } // @@ -362,6 +356,23 @@ export class KmpCompiler { return null; } + // Remove path data from file references in options + + if(data.options.graphicFile) { + data.options.graphicFile = this.callbacks.path.basename(data.options.graphicFile); + } + if(data.options.readmeFile) { + data.options.readmeFile = this.callbacks.path.basename(data.options.readmeFile); + } + if(data.options.licenseFile) { + data.options.licenseFile = this.callbacks.path.basename(data.options.licenseFile); + } + if(data.options.msiFilename) { + data.options.msiFilename = this.callbacks.path.basename(data.options.msiFilename); + } + + // Write kmp.json and kmp.inf + zip.file(KMP_JSON_FILENAME, JSON.stringify(data, null, 2)); if(hasKmpInf) { zip.file(KMP_INF_FILENAME, this.buildKmpInf(data)); From 72efe0cd5cc935f0d23ce89b8639fb264fa08c31 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 10:17:52 +0700 Subject: [PATCH 016/207] feat(developer): markdown conversion for description in package editor --- developer/src/kmc-keyboard-info/package.json | 2 +- developer/src/kmc-keyboard-info/src/index.ts | 7 +-- .../build/khmer_angkor.keyboard_info | 2 +- developer/src/kmc-package/package.json | 3 +- .../kmc-package/src/compiler/kmp-compiler.ts | 36 +++++++------ .../src/kmc-package/src/compiler/markdown.ts | 50 +++++++++++++++++++ .../test/fixtures/kmp_2.0/kmp.json | 2 +- .../src/kmc-package/test/test-markdown.ts | 20 ++++++++ package-lock.json | 36 +++++++++---- 9 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 developer/src/kmc-package/src/compiler/markdown.ts create mode 100644 developer/src/kmc-package/test/test-markdown.ts diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 4f96e31c7a1..ad3572ec1e8 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -28,7 +28,7 @@ "@keymanapp/kmc-package": "*" }, "devDependencies": { - "@types/chai": "^4.1.7", + "@types/chai": "^4.3.5", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "c8": "^7.12.0", diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index f221754b08e..8da0ef34dac 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -152,7 +152,7 @@ export class KeyboardInfoCompiler { // description if(sources.kmpJsonData.info.description?.description) { - keyboard_info.description = markDownToHTML(sources.kmpJsonData.info.description?.description); + keyboard_info.description = sources.kmpJsonData.info.description?.description.trim(); } // extract the language identifiers from the language metadata arrays for @@ -438,10 +438,5 @@ export class KeyboardInfoCompiler { ); } } - } -function markDownToHTML(markdown: string): string { - // TODO - return markdown; -} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info index 0b854bda12d..7d85bbfb6cf 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info @@ -23,7 +23,7 @@ "languageName": "Khmer" } }, - "description": "Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.", + "description": "

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.

", "related": { "khmer10": { "deprecates": true diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index 5ae24b58b14..8a9882acb1d 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "jszip": "^3.7.0" + "jszip": "^3.7.0", + "marked": "^7.0.0" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 9ffde6e69a0..c384ded31a0 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -11,6 +11,7 @@ import { transcodeToCP1252 } from './cp1252.js'; import { MIN_LM_FILEVERSION_KMP_JSON, PackageVersionValidator } from './package-version-validator.js'; import { PackageKeyboardTargetValidator } from './package-keyboard-target-validator.js'; import { PackageMetadataUpdater } from './package-metadata-updater.js'; +import { markdownToHTML } from './markdown.js'; const KMP_JSON_FILENAME = 'kmp.json'; const KMP_INF_FILENAME = 'kmp.inf'; @@ -246,26 +247,33 @@ export class KmpCompiler { // Helper functions - private kpsInfoToKmpInfo(info: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo { - let ni: KmpJsonFile.KmpJsonFileInfo = {}; + private kpsInfoToKmpInfo(kpsInfo: KpsFile.KpsFileInfo): KmpJsonFile.KmpJsonFileInfo { + let kmpInfo: KmpJsonFile.KmpJsonFileInfo = {}; - const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo)][] = [ - ['author','author'], - ['copyright','copyright'], - ['name','name'], - ['version','version'], - ['webSite','website'], - ['description','description'], + const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo), boolean][] = [ + ['author','author',false], + ['copyright','copyright',false], + ['name','name',false], + ['version','version',false], + ['webSite','website',false], + ['description','description',true], ]; - for (let [src,dst] of keys) { - if (info[src]) { - ni[dst] = {description: (info[src]._ ?? (typeof info[src] == 'string' ? info[src].toString() : '').trim())}; - if(info[src].$ && info[src].$.URL) ni[dst].url = info[src].$.URL.trim(); + for (let [src,dst,isMarkdown] of keys) { + if (kpsInfo[src]) { + kmpInfo[dst] = { + description: (kpsInfo[src]._ ?? (typeof kpsInfo[src] == 'string' ? kpsInfo[src].toString() : '')).trim() + }; + if(isMarkdown) { + kmpInfo[dst].description = markdownToHTML(kmpInfo[dst].description, false).trim(); + } + if(kpsInfo[src].$?.URL) { + kmpInfo[dst].url = kpsInfo[src].$.URL.trim(); + } } } - return ni; + return kmpInfo; }; private arrayWrap(a: unknown) { diff --git a/developer/src/kmc-package/src/compiler/markdown.ts b/developer/src/kmc-package/src/compiler/markdown.ts new file mode 100644 index 00000000000..75ed984fa6c --- /dev/null +++ b/developer/src/kmc-package/src/compiler/markdown.ts @@ -0,0 +1,50 @@ +/** + * Markdown transform for our `description` field. Tweaked to disable all inline + * HTML, because we want descriptions to be short and sweet, and don't need any + * of the more complex formatting that inline HTML affords. + */ + +// +// Note: using marked 7.0.0. +// https://github.com/markedjs/marked/issues/2926 +// +// Version 7.0.1 introduced a TypeScript 5.0+ feature `export type *` which causes: +// +// ../../../node_modules/marked/lib/marked.d.ts:722:5 - error TS1383: Only named exports may use 'export type'. +// 722 export type * from "MarkedOptions"; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// https://github.com/markedjs/marked/compare/v7.0.0...v7.0.1#diff-32d87a2bc59f429470ccf7afc8ae8818914d4d45c3f7c5b1767c6a0a240b55c9R449-R451 +// +// When we move to TS 5.0, we can upgrade marked. +// +import { Marked } from 'marked'; + +/* + Markdown rendering: we don't want to use the global object, because this + pollutes the settings for all modules. So we construct our own instance, + so that we can strip all inline HTML; we don't need any +*/ + +const renderer = { + html(_html:string, _block:boolean) { + // we don't allow inline HTML + return ''; + } +} +const markedStripHtml = new Marked({renderer}); +const marked = new Marked(); + +/** + * + * @param markdown + * @param allowHTML + * @returns + */ +export function markdownToHTML(markdown: string, allowHTML: boolean): string { + // : .parse can return a Promise if async=true. We don't pass ths + // option, and this sync usage isn't separated out in the types for + // Marked.prototype.parse, so avoids tsc complaints here. + const html = (allowHTML ? marked : markedStripHtml).parse(markdown.trim()); + return html; +} diff --git a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json index bcfc7e8d354..8cc2f03cd69 100644 --- a/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json +++ b/developer/src/kmc-package/test/fixtures/kmp_2.0/kmp.json @@ -26,7 +26,7 @@ "url": "https://keyman.com/keyboards/khmer_angkor" }, "description": { - "description": "# Khmer Angkor\r\n\r\nKhmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:\r\n\r\n* Using wrong vowel combination\r\n* Typing clusters out of order\r\n* Typing wrong mark for consonant shifters\r\n* And more!" + "description": "

Khmer Angkor

\n

Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors, including:

\n
    \n
  • Using wrong vowel combination
  • \n
  • Typing clusters out of order
  • \n
  • Typing wrong mark for consonant shifters
  • \n
  • And more!
  • \n
" } }, "files": [ diff --git a/developer/src/kmc-package/test/test-markdown.ts b/developer/src/kmc-package/test/test-markdown.ts new file mode 100644 index 00000000000..348d6426a7a --- /dev/null +++ b/developer/src/kmc-package/test/test-markdown.ts @@ -0,0 +1,20 @@ +import { assert } from 'chai'; +import 'mocha'; +import { markdownToHTML } from '../src/compiler/markdown.js'; + +describe('markdownToHTML', function () { + it('should convert markdown into HTML', function() { + const html = markdownToHTML('# heading\n\n**bold** and _beautiful_', true); + assert.equal(html, `

heading

\n

bold and beautiful

\n`); + }); + + it('should strip inline html if asked to do so', function() { + const html = markdownToHTML(`# heading\n\n\n\n**bold** and _beautiful_`, false); + assert.equal(html, `

heading

\n

bold and beautiful

\n`); + }); + + it('should keep inline html if asked to do so', function() { + const html = markdownToHTML(`# heading\n\n\n\n**bold** and _beautiful_`, true); + assert.equal(html, `

heading

\n\n\n

bold and beautiful

\n`); + }); +}); diff --git a/package-lock.json b/package-lock.json index 405d2154cfb..7caa96d15ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -997,10 +997,11 @@ "name": "@keymanapp/kmc-keyboard-info", "license": "MIT", "dependencies": { - "@keymanapp/common-types": "*" + "@keymanapp/common-types": "*", + "@keymanapp/kmc-package": "*" }, "devDependencies": { - "@types/chai": "^4.1.7", + "@types/chai": "^4.3.5", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "c8": "^7.12.0", @@ -2138,7 +2139,8 @@ "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", - "jszip": "^3.7.0" + "jszip": "^3.7.0", + "marked": "^7.0.0" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -7392,8 +7394,9 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.0", - "license": "MIT", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dependencies": { "agent-base": "6", "debug": "4" @@ -8512,6 +8515,17 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/marked": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.0.tgz", + "integrity": "sha512-7Gv1Ry8tqR352ElQOQfxdGpIh8kNZh/yYjNCxAQCN1DDbY4bCTG3qDCSkZWlRElSseeEILDxkY/G9w7cgziBNw==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/mdurl": { "version": "1.0.1", "dev": true, @@ -9756,8 +9770,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "license": "MIT", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "engines": { "node": ">=6" } @@ -11290,14 +11305,15 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.5.0", - "license": "MIT", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { From 0e382ba642f8b27269f58c7962b566f14aae3d32 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 10:26:01 +0700 Subject: [PATCH 017/207] chore(developer): fixup call to KeyboardInfoCompiler --- .../kmc/src/commands/buildClasses/BuildKeyboardInfo.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 5e76f6047ce..52dad474a6b 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -8,7 +8,6 @@ import { KmpCompiler } from '@keymanapp/kmc-package'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; const HelpRoot = 'https://help.keyman.com/keyboard/'; -const LICENSE_FILENAME = 'LICENSE.md'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } @@ -39,8 +38,6 @@ export class BuildKeyboardInfo extends BuildActivity { return false; } - const license = project.files.find(file => file.filename == LICENSE_FILENAME); - const keyboard = findProjectFile(callbacks, project, KeymanFileTypes.Source.KeymanKeyboard); const kps = findProjectFile(callbacks, project, KeymanFileTypes.Source.Package); if(!keyboard || !kps) { @@ -57,15 +54,14 @@ export class BuildKeyboardInfo extends BuildActivity { const keyboardFileNameJs = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); const keyboard_id = callbacks.path.basename(metadata.filename, KeymanFileTypes.Source.KeyboardInfo); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile(project.resolveInputFilePath(metadata), { + const data = compiler.writeMergedKeyboardInfoFile({ keyboard_id, kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kmpJsonData, kpsFilename: project.resolveInputFilePath(kps), helpLink: HelpRoot + keyboard_id, keyboardFilenameJs: fs.existsSync(keyboardFileNameJs) ? keyboardFileNameJs : undefined, - sourcePath: calculateSourcePath(infile), - licenseFilename: license ? project.resolveInputFilePath(license) : undefined + sourcePath: calculateSourcePath(infile) }); if(data == null) { From 0d6894ad891949a804d51ed3830ce7c56f8746d8 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 10:26:01 +0700 Subject: [PATCH 018/207] chore(developer): fixup call to KeyboardInfoCompiler --- .../kmc/src/commands/buildClasses/BuildKeyboardInfo.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 5e76f6047ce..52dad474a6b 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -8,7 +8,6 @@ import { KmpCompiler } from '@keymanapp/kmc-package'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; const HelpRoot = 'https://help.keyman.com/keyboard/'; -const LICENSE_FILENAME = 'LICENSE.md'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } @@ -39,8 +38,6 @@ export class BuildKeyboardInfo extends BuildActivity { return false; } - const license = project.files.find(file => file.filename == LICENSE_FILENAME); - const keyboard = findProjectFile(callbacks, project, KeymanFileTypes.Source.KeymanKeyboard); const kps = findProjectFile(callbacks, project, KeymanFileTypes.Source.Package); if(!keyboard || !kps) { @@ -57,15 +54,14 @@ export class BuildKeyboardInfo extends BuildActivity { const keyboardFileNameJs = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); const keyboard_id = callbacks.path.basename(metadata.filename, KeymanFileTypes.Source.KeyboardInfo); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile(project.resolveInputFilePath(metadata), { + const data = compiler.writeMergedKeyboardInfoFile({ keyboard_id, kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kmpJsonData, kpsFilename: project.resolveInputFilePath(kps), helpLink: HelpRoot + keyboard_id, keyboardFilenameJs: fs.existsSync(keyboardFileNameJs) ? keyboardFileNameJs : undefined, - sourcePath: calculateSourcePath(infile), - licenseFilename: license ? project.resolveInputFilePath(license) : undefined + sourcePath: calculateSourcePath(infile) }); if(data == null) { From b87667ac76642991c9f9bd1d5e2d68ff8e79d43d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 11:12:15 +0700 Subject: [PATCH 019/207] chore(developer): cleanup kmc-keyboard-info interface Moves responsibility for loading .kps into kmc-keyboard-info, away from the caller, and removes other fields with kmc-keyboard-info can calculate by itself. Have not moved project parsing into kmc-keyboard-info, because that's a bigger job, as currently that is mostly happening within kmc itself. A project for a future version I think. --- developer/src/kmc-keyboard-info/src/index.ts | 72 +++++++++---------- .../test/test-keyboard-info-compiler.ts | 9 +-- .../buildClasses/BuildKeyboardInfo.ts | 18 +---- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index 8da0ef34dac..c38f9b1af55 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -9,11 +9,14 @@ import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, Ke import { KeyboardInfoCompilerMessages } from "./messages.js"; import langtags from "./imports/langtags.js"; import { validateMITLicense } from "./validate-mit-license.js"; +import { KmpCompiler } from "@keymanapp/kmc-package"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); const scriptNames = new Intl.DisplayNames(['en'], { type: "script" }); const langtagsByTag = {}; +const HelpRoot = 'https://help.keyman.com/keyboard/'; + /** * Build a dictionary of language tags from langtags.json */ @@ -37,20 +40,11 @@ function init(): void { } export interface KeyboardInfoSources { - /** The identifier for the keyboard */ - keyboard_id: string; - - /** The data from the .kps file, transformed to kmp.json */ - kmpJsonData: KmpJsonFile.KmpJsonFile; - - /** The path in the keymanapp/keyboards repo where this keyboard may be found (optional) */ - sourcePath?: string; - - /** The full URL to the keyboard help, starting with https://help.keyman.com/keyboard/ (optional) */ - helpLink?: string; + /** The path in the keymanapp/keyboards repo where this keyboard may be found */ + sourcePath: string; /** The compiled keyboard filename and relative path (.js only) */ - keyboardFilenameJs?: string; + jsFilename?: string; /** The compiled package filename and relative path (.kmp) */ kmpFilename: string; @@ -78,7 +72,16 @@ export class KeyboardInfoCompiler { sources: KeyboardInfoSources ): Uint8Array { - // TODO: work from .kpj and nothing else as input + // TODO(lowpri): work from .kpj and nothing else as input. Blocked because + // .kpj work is largely in kmc at present, so that would need to move to + // a separate module. + + const kmpCompiler = new KmpCompiler(this.callbacks); + const kmpJsonData = kmpCompiler.transformKpsToKmpObject(sources.kpsFilename); + if(!kmpJsonData) { + // Errors will have been emitted by KmpCompiler + return null; + } if(!sources.kmpFilename) { // We can't build any metadata without a .kmp file @@ -90,8 +93,8 @@ export class KeyboardInfoCompiler { let jsFile: string = null; - if(sources.keyboardFilenameJs) { - jsFile = this.loadJsFile(sources.keyboardFilenameJs); + if(sources.jsFilename) { + jsFile = this.loadJsFile(sources.jsFilename); if(!jsFile) { return null; } @@ -100,7 +103,7 @@ export class KeyboardInfoCompiler { const kmxFiles: { filename: string, data: KMX.KEYBOARD - }[] = this.loadKmxFiles(sources.kpsFilename, sources.kmpJsonData); + }[] = this.loadKmxFiles(sources.kpsFilename, kmpJsonData); // // Build .keyboard_info file @@ -109,16 +112,16 @@ export class KeyboardInfoCompiler { // keyboard_info.id = this.callbacks.path.basename(sources.kmpFilename, '.kmp'); - keyboard_info.name = sources.kmpJsonData.info.name.description; + keyboard_info.name = kmpJsonData.info.name.description; // License - if(!sources.kmpJsonData.options?.licenseFile) { + if(!kmpJsonData.options?.licenseFile) { this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_NoLicenseFound()); return null; } - if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, sources.kmpJsonData.options.licenseFile))) { + if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, kmpJsonData.options.licenseFile))) { return null; } @@ -132,7 +135,7 @@ export class KeyboardInfoCompiler { // author - const author = sources.kmpJsonData.info.author; + const author = kmpJsonData.info.author; if(author?.description || author?.url) { keyboard_info.authorName = author.description; @@ -151,15 +154,15 @@ export class KeyboardInfoCompiler { // description - if(sources.kmpJsonData.info.description?.description) { - keyboard_info.description = sources.kmpJsonData.info.description?.description.trim(); + if(kmpJsonData.info.description?.description) { + keyboard_info.description = kmpJsonData.info.description?.description.trim(); } // extract the language identifiers from the language metadata arrays for // each of the keyboards in the kmp.json file, and merge into a single array // of identifiers in the .keyboard_info file. - this.fillLanguages(keyboard_info, sources.kmpJsonData); + this.fillLanguages(keyboard_info, kmpJsonData); // TODO: use: TZ=UTC0 git log -1 --no-merges --date=format:%Y-%m-%dT%H:%M:%SZ --format=%ad keyboard_info.lastModifiedDate = (new Date).toISOString(); @@ -173,19 +176,19 @@ export class KeyboardInfoCompiler { return null; } - if(sources.keyboardFilenameJs) { - keyboard_info.jsFilename = this.callbacks.path.basename(sources.keyboardFilenameJs); + if(sources.jsFilename) { + keyboard_info.jsFilename = this.callbacks.path.basename(sources.jsFilename); // Always overwrite with actual file size - keyboard_info.jsFileSize = this.callbacks.fileSize(sources.keyboardFilenameJs); + keyboard_info.jsFileSize = this.callbacks.fileSize(sources.jsFilename); if(keyboard_info.jsFileSize === undefined) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.keyboardFilenameJs})); + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.jsFilename})); return null; } } const includes = new Set(); keyboard_info.packageIncludes = []; - for(const file of sources.kmpJsonData.files) { + for(const file of kmpJsonData.files) { if(file.name.match(/\.(otf|ttf|ttc)$/)) { includes.add('fonts'); } else if(file.name.match(/welcome\.htm$/)) { @@ -198,7 +201,7 @@ export class KeyboardInfoCompiler { } keyboard_info.packageIncludes = [...includes]; - keyboard_info.version = sources.kmpJsonData.info.version.description; + keyboard_info.version = kmpJsonData.info.version.description; let minVersion = minKeymanVersion; const m = jsFile?.match(/this.KMINVER\s*=\s*(['"])(.*?)\1/); @@ -235,7 +238,7 @@ export class KeyboardInfoCompiler { // and if the .js is in the package, that it is mobile native as well, // because the targets metadata is not available in the .js. platforms.add('mobileWeb').add('desktopWeb'); - if(sources.kmpJsonData.files.find(file => file.name.match(/\.js$/))) { + if(kmpJsonData.files.find(file => file.name.match(/\.js$/))) { platforms.add('android').add('ios'); } } @@ -262,15 +265,12 @@ export class KeyboardInfoCompiler { keyboard_info.minKeymanVersion = minVersion; keyboard_info.sourcePath = sources.sourcePath; - - if(sources.helpLink) { - keyboard_info.helpLink = sources.helpLink; - } + keyboard_info.helpLink = HelpRoot + keyboard_info.id; // Related packages - if(sources.kmpJsonData.relatedPackages?.length) { + if(kmpJsonData.relatedPackages?.length) { keyboard_info.related = {}; - for(const p of sources.kmpJsonData.relatedPackages) { + for(const p of kmpJsonData.relatedPackages) { keyboard_info.related[p.id] = { deprecates: p.relationship == 'deprecates' }; diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index e1f09f132f0..27c2a383bea 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -4,7 +4,6 @@ import 'mocha'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; import { KeyboardInfoCompiler } from '../src/index.js'; -import { KmpCompiler } from '@keymanapp/kmc-package'; const callbacks = new TestCompilerCallbacks(); @@ -19,18 +18,12 @@ describe('keyboard-info-compiler', function () { const kmpFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.keyboard_info'); - const kmpCompiler = new KmpCompiler(callbacks); - const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); - const compiler = new KeyboardInfoCompiler(callbacks); const data = compiler.writeMergedKeyboardInfoFile({ kmpFilename, - kmpJsonData, - keyboard_id: 'khmer_angkor', sourcePath: 'release/k/khmer_angkor', kpsFilename, - helpLink: 'https://help.keyman.com/keyboard/khmer_angkor', - keyboardFilenameJs: jsFilename, + jsFilename: jsFilename, }); if(data == null) { callbacks.printMessages(); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 52dad474a6b..8af41d3012a 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -4,11 +4,8 @@ import { CompilerCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanFileT import { KeyboardInfoCompiler } from '@keymanapp/kmc-keyboard-info'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; -import { KmpCompiler } from '@keymanapp/kmc-package'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; -const HelpRoot = 'https://help.keyman.com/keyboard/'; - export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.KeyboardInfo; } @@ -44,23 +41,14 @@ export class BuildKeyboardInfo extends BuildActivity { return false; } - let kmpCompiler = new KmpCompiler(callbacks); - let kmpJsonData = kmpCompiler.transformKpsToKmpObject(project.resolveInputFilePath(kps)); - if(!kmpJsonData) { - // Errors will have been emitted by KmpCompiler - return false; - } - const keyboardFileNameJs = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); - const keyboard_id = callbacks.path.basename(metadata.filename, KeymanFileTypes.Source.KeyboardInfo); + const jsFilename = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); + const compiler = new KeyboardInfoCompiler(callbacks); const data = compiler.writeMergedKeyboardInfoFile({ - keyboard_id, kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), - kmpJsonData, kpsFilename: project.resolveInputFilePath(kps), - helpLink: HelpRoot + keyboard_id, - keyboardFilenameJs: fs.existsSync(keyboardFileNameJs) ? keyboardFileNameJs : undefined, + jsFilename: fs.existsSync(jsFilename) ? jsFilename : undefined, sourcePath: calculateSourcePath(infile) }); From e751a250a4060dda6c4b8aa78559125dfd0b6983 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 11:18:30 +0700 Subject: [PATCH 020/207] chore(common): fixup test for SkipMetadataFiles --- common/web/types/test/kpj/test-kpj-file-reader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index 0523bac078b..4734907877b 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -23,7 +23,7 @@ describe('kpj-file-reader', function () { assert.equal(kpj.KeymanDeveloperProject.Options.CompilerWarningsAsErrors, 'True'); assert.equal(kpj.KeymanDeveloperProject.Options.ProjectType, 'keyboard'); assert.equal(kpj.KeymanDeveloperProject.Options.WarnDeprecatedCode, 'True'); - assert.equal(kpj.KeymanDeveloperProject.Options.SkipMetadataFiles, 'True'); // because this is a 1.0 version file + assert.isUndefined(kpj.KeymanDeveloperProject.Options.SkipMetadataFiles); // because this is a 1.0 version file assert.isUndefined(kpj.KeymanDeveloperProject.Options.Version); assert.lengthOf(kpj.KeymanDeveloperProject.Files.File, 21); From 3af9f6d610e74d6ef5a4b2e6781449f32a2ea4bf Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 11:22:28 +0700 Subject: [PATCH 021/207] chore(developer): fixup license vs graphic file error --- common/windows/delphi/packages/PackageInfo.pas | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index a5d034d9c45..a5629e1bdf6 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -514,6 +514,7 @@ implementation SPackageInfoTooNew = 'The package file is version %s. This version can only read version '+SKeymanVersion+' and older files.'; SReadmeNotOwnedCorrectly = 'The readme file ''%s'' referred to is not part of the package.'; SGraphicNotOwnedCorrectly = 'The graphic file ''%s'' referred to is not part of the package.'; + SLicenseNotOwnedCorrectly = 'The license file ''%s'' referred to is not part of the package.'; SFileNotOwnedCorrectly = 'The file ''%s'' referred to is not part of the package.'; SDisplayFontNotOwnedCorrectly = 'The display font file ''%s'' referred to is not part of the package.'; SOSKFontNotOwnedCorrectly = 'The OSK font file ''%s'' referred to is not part of the package.'; @@ -812,7 +813,7 @@ procedure TPackageOptions.SetLicenseFile(const Value: TPackageContentFile); FLicenseFile := nil else begin - if Value.Package <> Package then raise EPackageInfo.CreateFmt(SGraphicNotOwnedCorrectly, [Value]); + if Value.Package <> Package then raise EPackageInfo.CreateFmt(SLicenseNotOwnedCorrectly, [Value]); FLicenseFile := Value; FLicenseFile.AddNotifyObject(LicenseRemoved); end; From 6624b8b344740bf4d65e8d33227d8ce437daba5b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 13:25:29 +0700 Subject: [PATCH 022/207] feat(developer): use git's last commit date for keyboard_info --- developer/src/kmc-keyboard-info/src/index.ts | 7 ++- .../buildClasses/BuildKeyboardInfo.ts | 5 ++- .../src/kmc/src/util/getLastGitCommitDate.ts | 45 +++++++++++++++++++ .../src/kmc/test/test-getLastGitCommitDate.ts | 17 +++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 developer/src/kmc/src/util/getLastGitCommitDate.ts create mode 100644 developer/src/kmc/test/test-getLastGitCommitDate.ts diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index c38f9b1af55..d0f57094b83 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -51,6 +51,9 @@ export interface KeyboardInfoSources { /** The source package filename and relative path (.kps) */ kpsFilename: string; + + /** Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' */ + lastCommitDate: string; }; export class KeyboardInfoCompiler { @@ -164,8 +167,8 @@ export class KeyboardInfoCompiler { this.fillLanguages(keyboard_info, kmpJsonData); - // TODO: use: TZ=UTC0 git log -1 --no-merges --date=format:%Y-%m-%dT%H:%M:%SZ --format=%ad - keyboard_info.lastModifiedDate = (new Date).toISOString(); + // If a last commit date is not given, then just use the current time + keyboard_info.lastModifiedDate = sources.lastCommitDate ?? (new Date).toISOString(); keyboard_info.packageFilename = this.callbacks.path.basename(sources.kmpFilename); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 8af41d3012a..e392f1c62c4 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -5,6 +5,7 @@ import { KeyboardInfoCompiler } from '@keymanapp/kmc-keyboard-info'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; +import { getLastGitCommitDate } from '../../util/getLastGitCommitDate.js'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } @@ -43,13 +44,15 @@ export class BuildKeyboardInfo extends BuildActivity { const jsFilename = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); + const lastCommitDate = getLastGitCommitDate(callbacks.path.dirname(project.resolveInputFilePath(metadata))); const compiler = new KeyboardInfoCompiler(callbacks); const data = compiler.writeMergedKeyboardInfoFile({ kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kpsFilename: project.resolveInputFilePath(kps), jsFilename: fs.existsSync(jsFilename) ? jsFilename : undefined, - sourcePath: calculateSourcePath(infile) + sourcePath: calculateSourcePath(infile), + lastCommitDate }); if(data == null) { diff --git a/developer/src/kmc/src/util/getLastGitCommitDate.ts b/developer/src/kmc/src/util/getLastGitCommitDate.ts new file mode 100644 index 00000000000..7cc730227ee --- /dev/null +++ b/developer/src/kmc/src/util/getLastGitCommitDate.ts @@ -0,0 +1,45 @@ +import { execFileSync } from 'child_process'; + +/** + * Returns the date and time of the last commit from git for the passed in path + * @param path Path for which to retrieve the last commit message + * @returns string, in RFC3339, 'YYYY-MM-DDThh:nn:ssZ' + */ +export function getLastGitCommitDate(path: string): string { + // TZ=UTC0 git log -1 --no-merges --date=format:%Y-%m-%dT%H:%M:%SZ --format=%ad + let result = null; + + try { + result = execFileSync('git', [ + 'log', // git log + '-1', // one commit only + '--no-merges', // we're only interested in 'real' commits + '--date=format:%Y-%m-%dT%H:%M:%SZ', // format the date in our expected RFC3339 format + '--format=%ad' // emit only the commit date + ], { + env: { ...process.env, TZ: 'TZ0' }, // use UTC timezone, not local + encoding: 'utf-8', // force a string result rather than Buffer + windowsHide: true, // on windows, we may need this to suppress a console window popup + cwd: path, // path to run git from + stdio: ['pipe', 'pipe', 'pipe'] // all output via pipe, so we don't get git errors on console + }); + } catch (e) { + // If git is not available, or the file is not in-repo, then it is probably + // fine to just silently return null, as the only machines where this is + // critical are the CI machines where we build and deploy .keyboard_info + // files, and where git will always be available. It would be possible to + // have this raise an error in CI environments, but the chance of error + // seems low. + return null; + } + + result = result.trim(); + + // We'll only return the result if it walks like a date, swims like a date, + // and quacks like a date. + if (!result.match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/)) { + return null; + } + + return result; +} diff --git a/developer/src/kmc/test/test-getLastGitCommitDate.ts b/developer/src/kmc/test/test-getLastGitCommitDate.ts new file mode 100644 index 00000000000..69c2e92f5dc --- /dev/null +++ b/developer/src/kmc/test/test-getLastGitCommitDate.ts @@ -0,0 +1,17 @@ +import { assert } from 'chai'; +import 'mocha'; +import { makePathToFixture } from './helpers/index.js'; +import { getLastGitCommitDate } from '../src/util/getLastGitCommitDate.js'; + +describe('getLastGitCommitDate', function () { + it('should return a valid date for a folder in this repo', async function() { + const path = makePathToFixture('.'); + const date = getLastGitCommitDate(path); + assert.match(date, /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/); + }); + + it('should return null for a folder outside the repo', async function() { + const date = getLastGitCommitDate('/'); + assert.isNull(date); + }); +}); From e9e7d5b85f5f1ba242a50742b4ed2b16c6871d42 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 17 Aug 2023 13:32:40 +0700 Subject: [PATCH 023/207] chore(developer): optional lastCommitDate --- developer/src/kmc-keyboard-info/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index d0f57094b83..ef86efed05d 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -53,7 +53,7 @@ export interface KeyboardInfoSources { kpsFilename: string; /** Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' */ - lastCommitDate: string; + lastCommitDate?: string; }; export class KeyboardInfoCompiler { From c42ab6981910f93ab957dd12757c52627058958c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 18 Aug 2023 08:09:17 +0700 Subject: [PATCH 024/207] chore(developer): seed epic branch --- developer/src/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/developer/src/README.md b/developer/src/README.md index 95b33c36f44..fd70a5bfd75 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -9,6 +9,8 @@ Windows. Shared units are intended to live in common, however the following projects will need to be updated in the future. * buildpkg in Windows depends on various compiler units in multiple folders. + -- should be removed as part of 17.0. + * kmbrowserhost in Windows is included as part of Keyman Developer. # Folders From d488f22fd9590c55af7fd1c97ac6b8b3f792f5fb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 21 Aug 2023 10:42:06 +0700 Subject: [PATCH 025/207] feat(web): browser-KMW support for default subkeys --- .../src/keyboards/activeLayout.ts | 6 + .../src/keyboards/defaultLayouts.ts | 1 + .../src/input/gestures/browser/subkeyPopup.ts | 13 +- .../engine/osk/src/keyboard-layout/oskKey.ts | 1 + .../web/default-subkey/default_subkey.js | 1 + .../test/manual/web/default-subkey/index.html | 99 +++ .../web/default-subkey/kbd_source/HISTORY.md | 6 + .../web/default-subkey/kbd_source/LICENSE.md | 21 + .../web/default-subkey/kbd_source/README.md | 30 + .../kbd_source/default_subkey.keyboard_info | 7 + .../kbd_source/default_subkey.kpj | 110 +++ .../kbd_source/source/default_subkey.ico | Bin 0 -> 1150 bytes .../source/default_subkey.keyman-touch-layout | 717 ++++++++++++++++++ .../kbd_source/source/default_subkey.kmn | 16 + .../kbd_source/source/default_subkey.kps | 65 ++ .../kbd_source/source/default_subkey.kvks | 8 + .../kbd_source/source/readme.htm | 24 + .../kbd_source/source/welcome.htm | 26 + web/src/test/manual/web/index.html | 1 + 19 files changed, 1145 insertions(+), 7 deletions(-) create mode 100644 web/src/test/manual/web/default-subkey/default_subkey.js create mode 100644 web/src/test/manual/web/default-subkey/index.html create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/HISTORY.md create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/LICENSE.md create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/README.md create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/default_subkey.keyboard_info create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/default_subkey.kpj create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.ico create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.keyman-touch-layout create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kmn create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kvks create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/readme.htm create mode 100644 web/src/test/manual/web/default-subkey/kbd_source/source/welcome.htm diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index e6d2ab9f157..9050d393a19 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -48,6 +48,12 @@ export class ActiveKey implements LayoutKey { _baseKeyEvent: KeyEvent; isMnemonic: boolean = false; + /** + * Only available on subkeys, but we don't distinguish between base keys and subkeys + * at this level yet in KMW. + */ + default?: boolean; + proportionalPad: number; proportionalX: number; proportionalWidth: number; diff --git a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts index 8d187ac8544..7e0153d0a52 100644 --- a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts +++ b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts @@ -22,6 +22,7 @@ export type LayoutKey = { "nextlayer"?: string, "pad"?: string | number, "sk"?: LayoutKey[] + "default"?: boolean } export type LayoutRow = { diff --git a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts index e36b24b0e82..9d7f0030002 100644 --- a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts +++ b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts @@ -219,25 +219,24 @@ export default class SubkeyPopup implements RealizedGesture { let skElement = popupBase.childNodes[i].firstChild; // Preference order: - // #1: if a default subkey has been specified, select it. (pending, for 15.0+) + // #1: if a default subkey has been specified, select it. // #2: if no default subkey is specified, default to a subkey with the same // key ID and layer / modifier spec. - //if(skSpec.isDefault) { TODO for 15.0 - // bk = skElement; - // break; - //} else - if(!baseKey.key || !baseKey.key.spec) { + if(skSpec.default) { + bk = skElement; + break; + } else if(!baseKey.key || !baseKey.key.spec) { continue; } if(skSpec.elementID == baseKey.key.spec.elementID) { bk = skElement; - break; // Best possible match has been found. (Disable 'break' once above block is implemented.) } } if(bk) { vkbd.keyPending = bk; + this.currentSelection = bk; // Subkeys never get key previews, so we can directly highlight the subkey. bk.key.highlight(true); } diff --git a/web/src/engine/osk/src/keyboard-layout/oskKey.ts b/web/src/engine/osk/src/keyboard-layout/oskKey.ts index b99d2076253..77d8927b60a 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskKey.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskKey.ts @@ -25,6 +25,7 @@ export class OSKKeySpec implements LayoutKey { nextlayer?: string; pad?: number; sk?: OSKKeySpec[]; + default?: boolean; constructor(id: string, text?: string, width?: number, sp?: ButtonClass, nextlayer?: string, pad?: number) { this.id = id; diff --git a/web/src/test/manual/web/default-subkey/default_subkey.js b/web/src/test/manual/web/default-subkey/default_subkey.js new file mode 100644 index 00000000000..1d656018754 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/default_subkey.js @@ -0,0 +1 @@ +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_default_subkey());}function Keyboard_default_subkey(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_default_subkey";this.KN="default-subkey";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0000;this.KVKD="T_d_default";this.KVKL={"tablet":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_Q","text":"q"},{"id":"K_W","text":"w"},{"id":"K_E","text":"e"},{"id":"K_R","text":"r"},{"id":"K_T","text":"t"},{"id":"K_Y","text":"y"},{"id":"K_U","text":"u"},{"id":"K_I","text":"i"},{"id":"K_O","text":"o"},{"id":"K_P","text":"p"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"a","sk":[{"id":"U_00E1","text":"\u00E1"},{"id":"U_00E0","text":"\u00E0"},{"id":"K_A","text":"a"},{"id":"U_00E4","text":"\u00E4"},{"id":"U_00E5","text":"\u00E5"},{"id":"U_00E2","text":"\u00E2"},{"id":"U_00E3","text":"\u00E3"}]},{"id":"K_S","text":"s"},{"id":"K_D","text":"d","sk":[{"id":"U_010F","text":"\u010F"},{"id":"U_0111","text":"\u0111"},{"id":"U_0257","text":"\u0257"},{"id":"T_d_default","text":"default","default":true},{"id":"U_0221","text":"\u0221"}]},{"id":"K_F","text":"f"},{"id":"K_G","text":"g"},{"id":"K_H","text":"h"},{"id":"K_J","text":"j"},{"id":"K_K","text":"k"},{"id":"K_L","text":"l"},{"width":"10","id":"T_new_88","sp":"10"}]},{"id":"3","key":[{"nextlayer":"shift","width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_Z","text":"z"},{"id":"K_X","text":"x"},{"id":"K_C","text":"c"},{"id":"K_V","text":"v"},{"id":"K_B","text":"b"},{"id":"K_N","text":"n"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"Q"},{"id":"K_W","text":"W"},{"id":"K_E","text":"E"},{"id":"K_R","text":"R"},{"id":"K_T","text":"T"},{"id":"K_Y","text":"Y"},{"id":"K_U","text":"U"},{"id":"K_I","text":"I"},{"id":"K_O","text":"O"},{"id":"K_P","text":"P"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"A"},{"id":"K_S","text":"S"},{"id":"K_D","text":"D"},{"id":"K_F","text":"F"},{"id":"K_G","text":"G"},{"id":"K_H","text":"H"},{"id":"K_J","text":"J"},{"id":"K_K","text":"K"},{"id":"K_L","text":"L"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"nextlayer":"default","width":"110","id":"K_SHIFT","sp":"2","text":"*Shift*"},{"id":"K_Z","text":"Z"},{"id":"K_X","text":"X"},{"id":"K_C","text":"C"},{"id":"K_V","text":"V"},{"id":"K_B","text":"B"},{"id":"K_N","text":"N"},{"id":"K_M","text":"M"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"70","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_LBRKT","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"layer":"default","id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"layer":"default","id":"K_SLASH","text":"\/"},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"140","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="17.0.162.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"default");}}return r;};} \ No newline at end of file diff --git a/web/src/test/manual/web/default-subkey/index.html b/web/src/test/manual/web/default-subkey/index.html new file mode 100644 index 00000000000..728355c43b4 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/index.html @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + KeymanWeb Testing Page - Predictive Text: robust testing + + + + + + + + + + + + + + + + +

KeymanWeb Sample Page - default subkey bootstrap-testing

+

Note: the intended tests require use of a touch-form-factor device or emulation thereof. +

+

+ The primary test is held on the default layer's 'd' key - the key labeled 'default' should + be autoselected. +

+

+ A secondary test is held on the default layer's 'a' key - the key labeled 'a', matching the + base key, should be autoselected. +

+
+ + + + + diff --git a/web/src/test/manual/web/default-subkey/kbd_source/HISTORY.md b/web/src/test/manual/web/default-subkey/kbd_source/HISTORY.md new file mode 100644 index 00000000000..cbd807699c4 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/HISTORY.md @@ -0,0 +1,6 @@ +default-subkey Change History +==================== + +1.0 (2023-08-21) +---------------- +* Created by SIL International diff --git a/web/src/test/manual/web/default-subkey/kbd_source/LICENSE.md b/web/src/test/manual/web/default-subkey/kbd_source/LICENSE.md new file mode 100644 index 00000000000..b582b3f9086 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +© 2023 SIL International + +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. diff --git a/web/src/test/manual/web/default-subkey/kbd_source/README.md b/web/src/test/manual/web/default-subkey/kbd_source/README.md new file mode 100644 index 00000000000..9fb82bd00ba --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/README.md @@ -0,0 +1,30 @@ +default-subkey keyboard +============== + +Version 1.0 + +Description +----------- +default-subkey generated from template + +Links +----- + +Copyright +--------- +See [LICENSE.md](LICENSE.md) + +Supported Platforms +------------------- + * Windows + * macOS + * Linux + * Web + * iPhone + * iPad + * Android phone + * Android tablet + * Mobile devices + * Desktop devices + * Tablet devices + diff --git a/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.keyboard_info b/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.keyboard_info new file mode 100644 index 00000000000..1677ce4c2f4 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.keyboard_info @@ -0,0 +1,7 @@ +{ + "license": "mit", + "languages": [ + + ], + "description": "default-subkey generated from template" +} diff --git a/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.kpj b/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.kpj new file mode 100644 index 00000000000..4de714dbd3b --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/default_subkey.kpj @@ -0,0 +1,110 @@ + + + + $PROJECTPATH\build + True + True + True + keyboard + + + + id_8305ac63cf6c1388736030b6b94f996d + default_subkey.kmn + source\default_subkey.kmn + 1.0 + .kmn +
+ default-subkey + © SIL International +
+
+ + id_f586f51f683bde64c75f566e643e59bd + default_subkey.kps + source\default_subkey.kps + + .kps +
+ default-subkey + © SIL International +
+
+ + id_ede98e4633e239f933cbfd1f4e1b766c + HISTORY.md + HISTORY.md + + .md + + + id_53e892b8b41cc4caece1cfd5ef21d6e7 + LICENSE.md + LICENSE.md + + .md + + + id_0730bb7c2e8f9ea2438b52e419dd86c9 + README.md + README.md + + .md + + + id_9cc75be74cee7f9a4937893e80b91b5f + default_subkey.keyboard_info + default_subkey.keyboard_info + + .keyboard_info + + + id_d8483af80632f8b611983c11137efccd + default_subkey.ico + source\default_subkey.ico + + .ico + id_8305ac63cf6c1388736030b6b94f996d + + + id_5086f2e8ef1d731c015a78f418aba1e8 + default_subkey.kmx + source\..\build\default_subkey.kmx + + .kmx + id_f586f51f683bde64c75f566e643e59bd + + + id_c8c9acd9c28010e45856034bff6c9d90 + default_subkey.js + source\..\build\default_subkey.js + + .js + id_f586f51f683bde64c75f566e643e59bd + + + id_7b38ddd4f3836ebf9365696475ade08a + default_subkey.kvk + source\..\build\default_subkey.kvk + + .kvk + id_f586f51f683bde64c75f566e643e59bd + + + id_356e5d149c1e539356d72698c1e401a6 + welcome.htm + source\welcome.htm + + .htm + id_f586f51f683bde64c75f566e643e59bd + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_f586f51f683bde64c75f566e643e59bd + +
+
diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.ico b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.ico new file mode 100644 index 0000000000000000000000000000000000000000..e3628ccc9b2823958484107ff80fed06c5a02228 GIT binary patch literal 1150 zcmZQzU<5(|0R;vS$Y5b$5ChU0Kr8^n3P8*VCV>o~96C5~=orH&9{4bbXx+$=mmkd# zkzd3xqcs{FCor)*pxBz$B8EJmngi=Pu*Mg%T;438e`aB-M^=L#=g4x+>yCl#L$QBW v2Ln1E*$ia;", + "layer": "shift" + }, + { + "id": "K_RBRKT", + "text": "}", + "layer": "shift" + } + ] + }, + { + "id": "K_EQUAL", + "text": "+", + "layer": "shift" + }, + { + "id": "K_HYPHEN", + "text": "-", + "layer": "default" + }, + { + "id": "K_8", + "text": "*", + "layer": "shift" + }, + { + "id": "K_SLASH", + "text": "/", + "layer": "default" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": 90, + "sp": 1 + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LOWER", + "text": "*abc*", + "width": 140, + "sp": 1, + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": 120, + "sp": 1 + }, + { + "id": "K_SPACE", + "text": "", + "width": 630, + "sp": 0 + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": 140, + "sp": 1 + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kmn b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kmn new file mode 100644 index 00000000000..f1ac06ea5e9 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kmn @@ -0,0 +1,16 @@ +c default_subkey generated from template at 2023-08-21 10:14:50 +c with name "default-subkey" +store(&VERSION) '10.0' +store(&NAME) 'default-subkey' +store(©RIGHT) '© SIL International' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'any' +store(&BITMAP) 'default_subkey.ico' +store(&VISUALKEYBOARD) 'default_subkey.kvks' +store(&LAYOUTFILE) 'default_subkey.keyman-touch-layout' + +begin Unicode > use(main) + +group(main) using keys + ++ [ T_d_default ] > "default" diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps new file mode 100644 index 00000000000..291e349d760 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps @@ -0,0 +1,65 @@ + + + + 17.0.162.0 + 7.0 + + + + readme.htm + + + + + + + + + + default-subkey + © SIL International + SIL International + + + + + ..\build\default_subkey.kmx + + 0 + .kmx + + + ..\build\default_subkey.js + + 0 + .js + + + ..\build\default_subkey.kvk + + 0 + .kvk + + + welcome.htm + + 0 + .htm + + + readme.htm + + 0 + .htm + + + + + default-subkey + default_subkey + 1.0 + + + + + diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kvks b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kvks new file mode 100644 index 00000000000..830237b17e9 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kvks @@ -0,0 +1,8 @@ + + +
+ 10.0 + default_subkey + +
+
diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/readme.htm b/web/src/test/manual/web/default-subkey/kbd_source/source/readme.htm new file mode 100644 index 00000000000..8bce21ff5d7 --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/readme.htm @@ -0,0 +1,24 @@ + + + + + + default-subkey + + + + +

default-subkey

+ +

+ default-subkey 1.0 generated from template. +

+ +

© SIL International

+ + + diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/welcome.htm b/web/src/test/manual/web/default-subkey/kbd_source/source/welcome.htm new file mode 100644 index 00000000000..cdf7e1e234b --- /dev/null +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/welcome.htm @@ -0,0 +1,26 @@ + + + + + + Start Using default-subkey + + + + +

Start Using default-subkey

+ +

+ default-subkey 1.0 generated from template. +

+ +

Keyboard Layout

+ + + + + \ No newline at end of file diff --git a/web/src/test/manual/web/index.html b/web/src/test/manual/web/index.html index 9931e3998ba..fce84d11a68 100644 --- a/web/src/test/manual/web/index.html +++ b/web/src/test/manual/web/index.html @@ -64,6 +64,7 @@

Test Caps Lock Layer (#3620)

Test Start of Sentence (#3621)

Test start of sentence keyboard rules (#5963)

Tests predictive text & other handling of rule matching when the final rule group does not match (#6005)

+

Tests handling of new default-subkey feature (#9430)

Other

Keystroke processing regression test engine.

Return to main index. From dc033b98bf5430f1d9963088aad6c9194443bfed Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 21 Aug 2023 10:57:51 +0700 Subject: [PATCH 026/207] docs(android): markers for sites to add default subkey handling code --- .../KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index bc0aed1d049..cf299230d02 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -397,6 +397,7 @@ public boolean onTouchEvent(MotionEvent event) { if (action == MotionEvent.ACTION_UP) { // Cleanup popups. #6636 dismissKeyPreview(0); + // TODO: default subkey handling dismissSubKeysWindow(); } @@ -970,6 +971,7 @@ public void onDismiss() { @SuppressLint({"InflateParams", "ClickableViewAccessibility"}) private void showSubKeys(Context context) { + // TODO: we'll need to pre-select the default key if it exists here! if (subKeysList == null || subKeysWindow != null) { return; } @@ -1060,6 +1062,7 @@ private void showSubKeys(Context context) { // Helps keep things from totally breaking when the event handler triggering subkey menu // generation and the menu's event handler stop talking to each other. final ArrayList> subkeyList = subKeysList; + // NOTE: keys are currently only executed from an actual 'click'. button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -1117,6 +1120,7 @@ public boolean onTouch(View v, MotionEvent event) { break; } } + // TODO: Is there a default subkey? If so, and we're still within the key-grid or similar, trigger it. dismissSubKeysWindow(); return true; } else if (action == MotionEvent.ACTION_MOVE) { From 3adf4dc6ebbbb7de6f569047fd9b93107bc6cddd Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 21 Aug 2023 11:41:44 +0700 Subject: [PATCH 027/207] fix(web): stuck base-key highlighting upon default selection --- web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts | 2 ++ web/src/test/manual/web/default-subkey/default_subkey.js | 2 +- .../kbd_source/source/default_subkey.keyman-touch-layout | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts index 9d7f0030002..94dcf2a65cd 100644 --- a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts +++ b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts @@ -235,6 +235,8 @@ export default class SubkeyPopup implements RealizedGesture { } if(bk) { + // Prevent sticky-highlighting should the default key be selected. + vkbd.keyPending?.key.highlight(false); vkbd.keyPending = bk; this.currentSelection = bk; // Subkeys never get key previews, so we can directly highlight the subkey. diff --git a/web/src/test/manual/web/default-subkey/default_subkey.js b/web/src/test/manual/web/default-subkey/default_subkey.js index 1d656018754..556acda19a2 100644 --- a/web/src/test/manual/web/default-subkey/default_subkey.js +++ b/web/src/test/manual/web/default-subkey/default_subkey.js @@ -1 +1 @@ -if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_default_subkey());}function Keyboard_default_subkey(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_default_subkey";this.KN="default-subkey";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0000;this.KVKD="T_d_default";this.KVKL={"tablet":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_Q","text":"q"},{"id":"K_W","text":"w"},{"id":"K_E","text":"e"},{"id":"K_R","text":"r"},{"id":"K_T","text":"t"},{"id":"K_Y","text":"y"},{"id":"K_U","text":"u"},{"id":"K_I","text":"i"},{"id":"K_O","text":"o"},{"id":"K_P","text":"p"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"a","sk":[{"id":"U_00E1","text":"\u00E1"},{"id":"U_00E0","text":"\u00E0"},{"id":"K_A","text":"a"},{"id":"U_00E4","text":"\u00E4"},{"id":"U_00E5","text":"\u00E5"},{"id":"U_00E2","text":"\u00E2"},{"id":"U_00E3","text":"\u00E3"}]},{"id":"K_S","text":"s"},{"id":"K_D","text":"d","sk":[{"id":"U_010F","text":"\u010F"},{"id":"U_0111","text":"\u0111"},{"id":"U_0257","text":"\u0257"},{"id":"T_d_default","text":"default","default":true},{"id":"U_0221","text":"\u0221"}]},{"id":"K_F","text":"f"},{"id":"K_G","text":"g"},{"id":"K_H","text":"h"},{"id":"K_J","text":"j"},{"id":"K_K","text":"k"},{"id":"K_L","text":"l"},{"width":"10","id":"T_new_88","sp":"10"}]},{"id":"3","key":[{"nextlayer":"shift","width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_Z","text":"z"},{"id":"K_X","text":"x"},{"id":"K_C","text":"c"},{"id":"K_V","text":"v"},{"id":"K_B","text":"b"},{"id":"K_N","text":"n"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"Q"},{"id":"K_W","text":"W"},{"id":"K_E","text":"E"},{"id":"K_R","text":"R"},{"id":"K_T","text":"T"},{"id":"K_Y","text":"Y"},{"id":"K_U","text":"U"},{"id":"K_I","text":"I"},{"id":"K_O","text":"O"},{"id":"K_P","text":"P"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"A"},{"id":"K_S","text":"S"},{"id":"K_D","text":"D"},{"id":"K_F","text":"F"},{"id":"K_G","text":"G"},{"id":"K_H","text":"H"},{"id":"K_J","text":"J"},{"id":"K_K","text":"K"},{"id":"K_L","text":"L"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"nextlayer":"default","width":"110","id":"K_SHIFT","sp":"2","text":"*Shift*"},{"id":"K_Z","text":"Z"},{"id":"K_X","text":"X"},{"id":"K_C","text":"C"},{"id":"K_V","text":"V"},{"id":"K_B","text":"B"},{"id":"K_N","text":"N"},{"id":"K_M","text":"M"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"70","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_LBRKT","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"layer":"default","id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"layer":"default","id":"K_SLASH","text":"\/"},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"140","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="17.0.162.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"default");}}return r;};} \ No newline at end of file +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_default_subkey());}function Keyboard_default_subkey(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_default_subkey";this.KN="default-subkey";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0000;this.KVKD="T_d_default";this.KVKL={"tablet":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_Q","text":"q"},{"id":"K_W","text":"w"},{"id":"K_E","text":"e"},{"id":"K_R","text":"r"},{"id":"K_T","text":"t"},{"id":"K_Y","text":"y"},{"id":"K_U","text":"u"},{"id":"K_I","text":"i"},{"id":"K_O","text":"o"},{"id":"K_P","text":"p"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"a","sk":[{"id":"U_00E1","text":"\u00E1"},{"id":"U_00E0","text":"\u00E0"},{"id":"K_A","text":"a"},{"id":"U_00E4","text":"\u00E4"},{"id":"U_00E5","text":"\u00E5"},{"id":"U_00E2","text":"\u00E2"},{"id":"U_00E3","text":"\u00E3"}]},{"id":"K_S","text":"s"},{"id":"K_D","text":"d","sk":[{"id":"K_D","text":"d"},{"id":"U_010F","text":"\u010F"},{"id":"U_0111","text":"\u0111"},{"id":"U_0257","text":"\u0257"},{"id":"T_d_default","text":"default","default":true},{"id":"U_0221","text":"\u0221"}]},{"id":"K_F","text":"f"},{"id":"K_G","text":"g"},{"id":"K_H","text":"h"},{"id":"K_J","text":"j"},{"id":"K_K","text":"k"},{"id":"K_L","text":"l"},{"width":"10","id":"T_new_88","sp":"10"}]},{"id":"3","key":[{"nextlayer":"shift","width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_Z","text":"z"},{"id":"K_X","text":"x"},{"id":"K_C","text":"c"},{"id":"K_V","text":"v"},{"id":"K_B","text":"b"},{"id":"K_N","text":"n"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"Q"},{"id":"K_W","text":"W"},{"id":"K_E","text":"E"},{"id":"K_R","text":"R"},{"id":"K_T","text":"T"},{"id":"K_Y","text":"Y"},{"id":"K_U","text":"U"},{"id":"K_I","text":"I"},{"id":"K_O","text":"O"},{"id":"K_P","text":"P"}]},{"id":"2","key":[{"id":"K_A","pad":"70","text":"A"},{"id":"K_S","text":"S"},{"id":"K_D","text":"D"},{"id":"K_F","text":"F"},{"id":"K_G","text":"G"},{"id":"K_H","text":"H"},{"id":"K_J","text":"J"},{"id":"K_K","text":"K"},{"id":"K_L","text":"L"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"nextlayer":"default","width":"110","id":"K_SHIFT","sp":"2","text":"*Shift*"},{"id":"K_Z","text":"Z"},{"id":"K_X","text":"X"},{"id":"K_C","text":"C"},{"id":"K_V","text":"V"},{"id":"K_B","text":"B"},{"id":"K_N","text":"N"},{"id":"K_M","text":"M"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"140","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"70","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"width":"110","id":"K_SHIFT","sp":"1","text":"*Shift*"},{"id":"K_LBRKT","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"layer":"default","id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"layer":"default","id":"K_SLASH","text":"\/"},{"width":"90","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"140","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"630","id":"K_SPACE"},{"width":"140","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="17.0.162.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"default");}}return r;};} \ No newline at end of file diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.keyman-touch-layout b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.keyman-touch-layout index a0641c40791..41cbf16e1d9 100644 --- a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.keyman-touch-layout +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.keyman-touch-layout @@ -96,6 +96,10 @@ "id": "K_D", "text": "d", "sk": [ + { + "text": "d", + "id": "K_D" + }, { "text": "ď", "id": "U_010F" From bf713d10439fd56f31cff273f60933cacaf2ac89 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 21 Aug 2023 12:16:22 +0700 Subject: [PATCH 028/207] fix(web): test resource package compilation --- .../web/default-subkey/kbd_source/source/default_subkey.kps | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps index 291e349d760..97cbafecd72 100644 --- a/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps +++ b/web/src/test/manual/web/default-subkey/kbd_source/source/default_subkey.kps @@ -58,7 +58,9 @@ default-subkey default_subkey 1.0 - + + English + From 909331df62734410c11741c60d0b321632082430 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 21 Aug 2023 12:22:52 +0700 Subject: [PATCH 029/207] feat(web): allows small fudge-factor before cancelling default --- .../src/input/gestures/browser/subkeyPopup.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts index 94dcf2a65cd..8482aa85e85 100644 --- a/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts +++ b/web/src/engine/osk/src/input/gestures/browser/subkeyPopup.ts @@ -36,6 +36,9 @@ export default class SubkeyPopup implements RealizedGesture { public readonly baseKey: KeyElement; public readonly promise: Promise; + private initialX: number; + private initialY: number; + // Resolves the promise that generated this SubkeyPopup. private resolver: (keyEvent: KeyEvent) => void; @@ -269,7 +272,33 @@ export default class SubkeyPopup implements RealizedGesture { } updateTouch(input: InputEventCoordinate) { - this.currentSelection = null; + // For 'default' subkey handling, we want a small fudge factor. + if(this.initialX === undefined || this.initialY === undefined) { + this.initialX = input.x; + this.initialY = input.y; + } + + const deltaX = this.initialX - input.x; + const deltaY = this.initialY - input.y; + const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + if(dist > 5) { + this.initialX = Number.MAX_SAFE_INTEGER; // it'll always exceed the threshold hereafter. + this.currentSelection = null; + } else { + // The function that calls this to perform subkey updates auto-unhighlights the active selection; + // make sure that highlighting is maintained if no new key was selected, but we haven't cancelled + // default-selection mode yet. + this.currentSelection.key.highlight(true); + + // Even if we technically have a different subkey underneath the touchpoint, we're still in + // default-selection mode. Require more movement before cancelling default-selection mode. + // + // Can occur for large subkey menus or when subkey menus are "constrained" within OSK bounds, + // as with the iOS app. + return; + } + this.baseKey.key.highlight(false); for(let i=0; i < this.baseKey['subKeys'].length; i++) { From 1531e88f7083fc7773ea139bcbf943d90c42734f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 29 Aug 2023 15:37:31 +1000 Subject: [PATCH 030/207] feat(developer): eliminate source .model_info and .keyboard_info files Removes all references to source .keyboard_info and .model_info files in the kmc compiler and types. Updates the model info compiler to support building purely from model sources (isRTL, license fields are TODO). --- .../types/src/kpj/keyman-developer-project.ts | 46 +++++---- common/web/types/src/kpj/kpj-file-reader.ts | 1 - common/web/types/src/util/file-types.ts | 18 +--- .../types/test/kpj/test-kpj-file-reader.ts | 4 +- developer/src/README.md | 39 +++++--- developer/src/inst/kmdev.wxs | 4 - developer/src/inst/node/kmlmi.cmd | 4 - developer/src/kmc-keyboard-info/src/index.ts | 6 +- .../test/test-keyboard-info-compiler.ts | 2 +- .../kmc-model-info/src/model-info-compiler.ts | 94 +++++++------------ .../src/kmc-model-info/src/model-info-file.ts | 7 -- .../sil.cmo.bw/build/sil.cmo.bw.model_info | 3 +- .../test/fixtures/sil.cmo.bw/sil.cmo.bw.kpj | 7 -- .../fixtures/sil.cmo.bw/sil.cmo.bw.model_info | 7 -- .../sil.cmo.bw/source/sil.cmo.bw.model.kps | 1 + .../test/test-model-info-compiler.ts | 9 +- developer/src/kmc/README.md | 7 +- developer/src/kmc/build-bundler.js | 1 - developer/src/kmc/package.json | 6 +- developer/src/kmc/src/commands/build.ts | 4 - .../buildClasses/BuildKeyboardInfo.ts | 33 +++---- .../commands/buildClasses/BuildModelInfo.ts | 28 +++--- .../src/commands/buildClasses/BuildProject.ts | 18 +++- .../commands/buildClasses/buildActivities.ts | 7 +- developer/src/kmc/src/kmlmi.ts | 91 ------------------ developer/src/tike/Makefile | 1 - developer/src/tike/kmlmi.cmd | 20 ---- .../source/dmg.dv.test/dmg.dv.test.kpj | 7 -- .../source/dmg.dv.test/dmg.dv.test.model_info | 7 -- 29 files changed, 145 insertions(+), 337 deletions(-) delete mode 100644 developer/src/inst/node/kmlmi.cmd delete mode 100644 developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.model_info delete mode 100644 developer/src/kmc/src/kmlmi.ts delete mode 100644 developer/src/tike/kmlmi.cmd delete mode 100644 web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.model_info diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/common/web/types/src/kpj/keyman-developer-project.ts index 1eee3a18cce..a6814a5302f 100644 --- a/common/web/types/src/kpj/keyman-developer-project.ts +++ b/common/web/types/src/kpj/keyman-developer-project.ts @@ -1,5 +1,5 @@ // -// Version 1.0 of Keyman Developer Project .kpj file +// Version 1.0 and 2.0 of Keyman Developer Project .kpj file // import { KeymanFileTypes } from '../main.js'; @@ -9,11 +9,17 @@ export class KeymanDeveloperProject { options: KeymanDeveloperProjectOptions; files: KeymanDeveloperProjectFile[]; projectPath: string = ''; + readonly projectFile: KeymanDeveloperProjectFile; - constructor(private projectFilename: string, version: KeymanDeveloperProjectVersion, private callbacks: CompilerCallbacks) { - this.projectPath = this.callbacks.path.dirname(this.projectFilename); + get projectFilename() { + return this._projectFilename; + } + + constructor(private _projectFilename: string, version: KeymanDeveloperProjectVersion, private callbacks: CompilerCallbacks) { + this.projectPath = this.callbacks.path.dirname(this._projectFilename); this.options = new KeymanDeveloperProjectOptions(version); this.files = []; + this.projectFile = new KeymanDeveloperProjectFile20(_projectFilename, callbacks); } /** * Adds .kmn, .xml, .kps to project based on options.sourcePath @@ -38,8 +44,6 @@ export class KeymanDeveloperProject { this.files.push(file); } } - - this.addMetadataFile(); } public isKeyboardProject() { @@ -50,26 +54,32 @@ export class KeymanDeveloperProject { return !!this.files.find(file => file.fileType == KeymanFileTypes.Source.Model); } - public addMetadataFile() { - const ext = this.isLexicalModelProject() ? KeymanFileTypes.Source.ModelInfo : KeymanFileTypes.Source.KeyboardInfo; + private resolveProjectPath(p: string): string { + // Replace placeholders in the target path + return p.replace('$PROJECTPATH', this.projectPath); + } + + getOutputFilePath(type: KeymanFileTypes.Binary) { + // Roughly corresponds to Delphi TProject.GetTargetFileName + let p = this.options.version == '1.0' ? + this.options.buildPath || '$SOURCEPATH' : + this.options.buildPath; - if(this.files.find(file => KeymanFileTypes.filenameIs(file.filename, ext))) { - return; + // Replace placeholders in the target path + if(this.options.version == '1.0') { + // TODO: do we need to support $VERSION? + // $SOURCEPATH only supported in 1.0 projects + p = p.replace('$SOURCEPATH', this.callbacks.path.dirname(this._projectFilename)); } - const infoFile = - this.callbacks.path.join(this.projectPath, - this.callbacks.path.basename(this.projectFilename, KeymanFileTypes.Source.Project) + ext); - this.files.push(new KeymanDeveloperProjectFile20(infoFile, this.callbacks)); - } + p = this.resolveProjectPath(p); - private resolveProjectPath(p: string): string { - // Replace placeholders in the target path - return p.replace('$PROJECTPATH', this.projectPath); + const f = this.callbacks.path.basename(this._projectFilename, KeymanFileTypes.Source.Project) + type; + return this.callbacks.path.normalize(this.callbacks.path.join(p, f)); } resolveInputFilePath(file: KeymanDeveloperProjectFile): string { - return this.callbacks.resolveFilename(this.projectFilename, file.filePath); + return this.callbacks.resolveFilename(this._projectFilename, file.filePath); } resolveOutputFilePath(file: KeymanDeveloperProjectFile, sourceExt: string, targetExt: string): string { diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 5693d3a0b69..5472d5c8d24 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -77,7 +77,6 @@ export class KPJFileReader { if(result.options.version == '1.0') { this.transformFilesVersion10(project, result); - result.addMetadataFile(); } else { result.populateFiles(); } diff --git a/common/web/types/src/util/file-types.ts b/common/web/types/src/util/file-types.ts index 52aa7495457..e3d3bd54523 100644 --- a/common/web/types/src/util/file-types.ts +++ b/common/web/types/src/util/file-types.ts @@ -10,9 +10,7 @@ export const enum Source { LdmlKeyboard = '.xml', // Warning, also other possible uses Package = '.kps', VisualKeyboard = '.kvks', - TouchLayout = '.keyman-touch-layout', - KeyboardInfo = '.keyboard_info', - ModelInfo = '.model_info', + TouchLayout = '.keyman-touch-layout' }; /** @@ -26,9 +24,7 @@ export const ALL_SOURCE: ReadonlyArray = [ Source.LdmlKeyboard, Source.Package, Source.VisualKeyboard, - Source.TouchLayout, - Source.KeyboardInfo, - Source.ModelInfo, + Source.TouchLayout ] as const; /** @@ -146,16 +142,6 @@ export function filenameIs(filename: string, fileType: Source | Binary) { return filename.toLowerCase().endsWith(fileType); } -/** - * Returns true if the file is either a .keyboard_info file or a .model_info - * file - * @param filename - * @returns - */ -export function filenameIsMetadata(filename: string) { - return filenameIs(filename, Source.KeyboardInfo) || filenameIs(filename, Source.ModelInfo); -} - /** * Replaces a filename extension with the new extension. Returns `null` if the * filename does not end with oldExtension. diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index 0523bac078b..12c1e7a134e 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -23,7 +23,7 @@ describe('kpj-file-reader', function () { assert.equal(kpj.KeymanDeveloperProject.Options.CompilerWarningsAsErrors, 'True'); assert.equal(kpj.KeymanDeveloperProject.Options.ProjectType, 'keyboard'); assert.equal(kpj.KeymanDeveloperProject.Options.WarnDeprecatedCode, 'True'); - assert.equal(kpj.KeymanDeveloperProject.Options.SkipMetadataFiles, 'True'); // because this is a 1.0 version file + assert.isUndefined(kpj.KeymanDeveloperProject.Options.SkipMetadataFiles); // because this is a 1.0 version file assert.isUndefined(kpj.KeymanDeveloperProject.Options.Version); assert.lengthOf(kpj.KeymanDeveloperProject.Files.File, 21); @@ -77,7 +77,7 @@ describe('kpj-file-reader', function () { assert.isTrue(project.options.skipMetadataFiles); assert.equal(project.options.version, '1.0'); - assert.lengthOf(project.files, 3); + assert.lengthOf(project.files, 2); let f: KeymanDeveloperProjectFile10 = project.files[0]; assert.equal(f.id, 'id_f347675c33d2e6b1c705c787fad4941a'); diff --git a/developer/src/README.md b/developer/src/README.md index 95b33c36f44..7622abaca5d 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -68,31 +68,40 @@ details. ## src/kmc -node-based next generation compiler, hosts kmc, kmlmi, kmlmc, kmlmp +node-based next generation compiler, hosts kmc, (and legacy kmlmc, kmlmp) + +### src/kmc-analyze - Analysis tools + +File analysis tools for Keyman files. + +### src/kmc-keyboard-info - Keyboard Info Compiler + +Builds .keyboard_info files for use on the Keyman Cloud keyboard repository +at https://github.com/keymanapp/keyboards. Command line access through kmc. + +### src/kmc-kmn - Keyboard Compiler + +Builds .kmx files from .kmn. Command line access through kmc. ### src/kmc-ldml - LDML Keyboard Compiler -Next Generation keyboard compiler package - LDML keyboards only at present. -Command line access through kmc. +Next Generation keyboard compiler - LDML keyboards. Command line access through +kmc. ### src/kmc-model - Lexical Model Compiler -The Lexical Model Compiler, kmlmc, runs on nodeJS on all supported desktop -platforms. Command line access through kmc/kmlmc. +The Lexical Model Compiler, runs on nodeJS on all supported desktop platforms. +Command line access through kmc. -### src/kmc-package - Package Compiler +### src/kmc-model-info - Model Info Compiler -The package compiler is broadly compatible with the kmcomp .kps package -compiler. However at this stage it is only tested with lexical models, and use -with keyboards (either .js or .kmx) is not tested or supported. It is likely in -the future that the kmcomp .kps compiler will be deprecated in favour of this -one. Command line access through kmc/kmlmp. +Builds .model_info files for use on the Keyman Cloud lexical model repository at +https://github.com/keymanapp/lexical-models. Command line access through kmc. -### src/kmc-model-info - Model Info Compiler +### src/kmc-package - Package Compiler -Merges .model_info files for use on the Keyman Cloud lexical model repository at -https://github.com/keymanapp/lexical-models. Command line access through -kmc/kmlmi. +Compiles .kps packages into .kmp files. Works with both lexical model packages +and keyboard packages. Command line access through kmc. ## src/samples diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index 436d532f4f5..f44cd0b883f 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -253,10 +253,6 @@ - - - - diff --git a/developer/src/inst/node/kmlmi.cmd b/developer/src/inst/node/kmlmi.cmd deleted file mode 100644 index e674fe4d2c0..00000000000 --- a/developer/src/inst/node/kmlmi.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@rem This script avoids path dependencies for node for distribution -@rem with Keyman Developer. When used on platforms other than Windows, -@rem node can be used directly with the compiler (`npm link` will setup). -@"%~dp0\node.js\node.exe" "%~dp0\kmc\kmlmi.mjs" %* diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index ef86efed05d..0c0876cf247 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -62,7 +62,7 @@ export class KeyboardInfoCompiler { } /** - * Merges source .keyboard_info file with metadata from the keyboard and package source file. + * Builds a .keyboard_info file with metadata from the keyboard and package source file. * This function is intended for use within the keyboards repository. While many of the * parameters could be deduced from each other, they are specified here to reduce the * number of places the filenames are constructed. @@ -71,7 +71,7 @@ export class KeyboardInfoCompiler { * * @param sources Details on files from which to extract metadata */ - public writeMergedKeyboardInfoFile( + public writeKeyboardInfoFile( sources: KeyboardInfoSources ): Uint8Array { @@ -158,7 +158,7 @@ export class KeyboardInfoCompiler { // description if(kmpJsonData.info.description?.description) { - keyboard_info.description = kmpJsonData.info.description?.description.trim(); + keyboard_info.description = kmpJsonData.info.description.description.trim(); } // extract the language identifiers from the language metadata arrays for diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 27c2a383bea..939b76407ab 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -19,7 +19,7 @@ describe('keyboard-info-compiler', function () { const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.keyboard_info'); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile({ + const data = compiler.writeKeyboardInfoFile({ kmpFilename, sourcePath: 'release/k/khmer_angkor', kpsFilename, diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index 32730dd034f..04ebf6cef8d 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -1,5 +1,5 @@ /** - * Merges a source .model_info file with metadata extracted from .kps file and + * Builds a source .model_info file with metadata extracted from .kps file and * compiled files to produce a comprehensive .model_info file. */ @@ -8,6 +8,8 @@ import { ModelInfoFile } from "./model-info-file.js"; import { CompilerCallbacks, KmpJsonFile } from "@keymanapp/common-types"; import { ModelInfoCompilerMessages } from "./messages.js"; +const HelpRoot = 'https://help.keyman.com/model/'; + /* c8 ignore start */ export class ModelInfoOptions { /** The identifier for the model */ @@ -28,16 +30,15 @@ export class ModelInfoOptions { /* c8 ignore stop */ /** - * Merges source .model_info file with metadata from the model and package source file. + * Builds .model_info file with metadata from the model and package source file. * This function is intended for use within the lexical-models repository. While many of the * parameters could be deduced from each other, they are specified here to reduce the * number of places the filenames are constructed. * - * @param sourceModelInfoFileName Path for the source .model_info file + * @param callbacks * @param options Details on files from which to extract additional metadata */ -export function writeMergedModelMetadataFile( - sourceModelInfoFileName: string, +export function writeModelMetadataFile( callbacks: CompilerCallbacks, options: ModelInfoOptions ): Uint8Array { @@ -58,66 +59,44 @@ export function writeMergedModelMetadataFile( * For full documentation, see: * https://help.keyman.com/developer/cloud/model_info/1.0/ */ - const dataInput = callbacks.loadFile(sourceModelInfoFileName); - if(!dataInput) { - callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename: sourceModelInfoFileName})); - return null; - } - let model_info: ModelInfoFile = null; - try { - const jsonInput = new TextDecoder('utf-8', {fatal: true}).decode(dataInput); - model_info = JSON.parse(jsonInput); - } catch(e) { - callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileIsNotValid({filename: sourceModelInfoFileName, e})); - return null; - } + let model_info: ModelInfoFile = { + languages: [], + license: 'mit' + }; // - // Build merged .model_info file + // Build .model_info file -- some fields have "special" behaviours -- see below // https://api.keyman.com/schemas/model_info.source.json and // https://api.keyman.com/schemas/model_info.distribution.json // https://help.keyman.com/developer/cloud/model_info/1.0 // - function setModelMetadata(field: keyof ModelInfoFile, expected: unknown, warn: boolean = true) { - /* c8 ignore next 4 */ - if (model_info[field] && model_info[field] !== expected) { - if (warn ?? true) { - callbacks.reportMessage(ModelInfoCompilerMessages.Warn_MetadataFieldInconsistent({ - field, value:model_info[field], expected - })); - } - - } - // TypeScript gets upset with this assignment, because it cannot deduce - // the exact type of model_info[field] -- there are many possibilities! - // So we assert that it's unknown so that TypeScript can chill. - ( model_info[field]) = model_info[field] || expected; - } - - // - // Merge model info file -- some fields have "special" behaviours -- see below - // - - setModelMetadata('id', options.model_id); + // TODO: isrtl + // TODO: license - setModelMetadata('name', options.kmpJsonData.info.name.description); + model_info.id = options.model_id; + model_info.name = options.kmpJsonData.info.name.description; - let author = options.kmpJsonData.info.author; - setModelMetadata('authorName', author?.description); + const author = options.kmpJsonData.info.author; + model_info.authorName = author?.description ?? ''; if (author?.url) { // we strip the mailto: from the .kps file for the .model_info - let match = author.url.match(/^(mailto\:)?(.+)$/); + const match = author.url.match(/^(mailto\:)?(.+)$/); /* c8 ignore next 3 */ if (match === null) { callbacks.reportMessage(ModelInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url})); return null; } - let email = match[2]; - setModelMetadata('authorEmail', email, false); + model_info.authorEmail = match[2]; + } + + // description + + if(options.kmpJsonData.info.description?.description) { + model_info.description = options.kmpJsonData.info.description.description.trim(); } // extract the language identifiers from the language metadata @@ -125,37 +104,32 @@ export function writeMergedModelMetadataFile( // and merge into a single array of identifiers in the // .model_info file. - model_info.languages = model_info.languages || options.kmpJsonData.lexicalModels.reduce((a, e) => [].concat(a, e.languages.map((f) => f.id)), []); + model_info.languages = options.kmpJsonData.lexicalModels.reduce((a, e) => [].concat(a, e.languages.map((f) => f.id)), []); - setModelMetadata('lastModifiedDate', (new Date).toISOString()); - setModelMetadata('packageFilename', callbacks.path.basename(options.kmpFileName)); + // TODO: use git date + model_info.lastModifiedDate = (new Date).toISOString(); - // Always overwrite with actual file size + model_info.packageFilename = callbacks.path.basename(options.kmpFileName); model_info.packageFileSize = callbacks.fileSize(options.kmpFileName); if(model_info.packageFileSize === undefined) { callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:options.kmpFileName})); return null; } - setModelMetadata('jsFilename', callbacks.path.basename(options.modelFileName)); - - // Always overwrite with actual file size + model_info.jsFilename = callbacks.path.basename(options.modelFileName); model_info.jsFileSize = callbacks.fileSize(options.modelFileName); if(model_info.jsFileSize === undefined) { callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:options.modelFileName})); return null; } - // Always overwrite source data model_info.packageIncludes = options.kmpJsonData.files.filter((e) => !!e.name.match(/.[ot]tf$/i)).length ? ['fonts'] : []; - - setModelMetadata('version', options.kmpJsonData.info.version.description); - - // The minimum Keyman version detected in the package file may be manually set higher by the developer - setModelMetadata('minKeymanVersion', minKeymanVersion, false); + model_info.version = options.kmpJsonData.info.version.description; + model_info.minKeymanVersion = minKeymanVersion; + model_info.helpLink = HelpRoot + model_info.id; if(options.sourcePath) { - setModelMetadata('sourcePath', options.sourcePath); + model_info.sourcePath = options.sourcePath; } const jsonOutput = JSON.stringify(model_info, null, 2); diff --git a/developer/src/kmc-model-info/src/model-info-file.ts b/developer/src/kmc-model-info/src/model-info-file.ts index a70cfba20a0..c993242e349 100644 --- a/developer/src/kmc-model-info/src/model-info-file.ts +++ b/developer/src/kmc-model-info/src/model-info-file.ts @@ -7,7 +7,6 @@ export interface ModelInfoFile { license: "mit"; languages: Array; lastModifiedDate?: string; - links: ModelInfoFileLink[]; packageFilename?: string; packageFileSize?: number; jsFilename?: string; @@ -21,13 +20,7 @@ export interface ModelInfoFile { related?: {[id:string]:ModelInfoFileRelated}; } -export interface ModelInfoFileLink { - name: string; - url: string; -} - export interface ModelInfoFileRelated { deprecates?: string; deprecatedBy?: string; - note?: string; } \ No newline at end of file diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/build/sil.cmo.bw.model_info b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/build/sil.cmo.bw.model_info index a9f7bbe1548..1f8d5c03548 100644 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/build/sil.cmo.bw.model_info +++ b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/build/sil.cmo.bw.model_info @@ -3,7 +3,8 @@ "languages": [ "cmo-Khmr" ], - "description": "Bunong Wordlist is based on the Bible wordlist of 1,157 unique entries with their frequencies.", + "description": "

Bunong Wordlist is based on the Bible wordlist of 1,157 unique entries with their frequencies.

", + "helpLink": "https://help.keyman.com/model/sil.cmo.bw", "id": "sil.cmo.bw", "name": "Bunong Wordlist", "authorName": "", diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.kpj b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.kpj index 12aca7bbf28..11d0f84b757 100644 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.kpj +++ b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.kpj @@ -48,13 +48,6 @@ .md - - id_8146e45ce008c5c83d5a824f2121c46c - sil.cmo.bw.model_info - sil.cmo.bw.model_info - - .model_info - id_46ebc8fccf1bc10c92484653167449df sil.cmo.bw.model.js diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.model_info b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.model_info deleted file mode 100644 index c2d2b6070c5..00000000000 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/sil.cmo.bw.model_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "cmo-Khmr" - ], - "description": "Bunong Wordlist is based on the Bible wordlist of 1,157 unique entries with their frequencies." -} diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps index 4fd452897cb..f53475848e7 100644 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps +++ b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps @@ -19,6 +19,7 @@ © 2021 - 2022 SIL International 1.1 + Bunong Wordlist is based on the Bible wordlist of 1,157 unique entries with their frequencies. diff --git a/developer/src/kmc-model-info/test/test-model-info-compiler.ts b/developer/src/kmc-model-info/test/test-model-info-compiler.ts index 418b62955dc..ebfb82f4f89 100644 --- a/developer/src/kmc-model-info/test/test-model-info-compiler.ts +++ b/developer/src/kmc-model-info/test/test-model-info-compiler.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import 'mocha'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; -import { writeMergedModelMetadataFile } from '../src/model-info-compiler.js'; +import { writeModelMetadataFile } from '../src/model-info-compiler.js'; import { KmpCompiler } from '@keymanapp/kmc-package'; const callbacks = new TestCompilerCallbacks(); @@ -14,7 +14,6 @@ beforeEach(function() { describe('model-info-compiler', function () { it('compile a .model_info file correctly', function() { - const path = makePathToFixture('sil.cmo.bw', 'sil.cmo.bw.model_info'); const kpsFileName = makePathToFixture('sil.cmo.bw', 'source', 'sil.cmo.bw.model.kps'); const kmpFileName = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model.kmp'); const buildModelInfoFilename = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model_info'); @@ -23,7 +22,7 @@ describe('model-info-compiler', function () { const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFileName); const modelFileName = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model.js'); - const data = writeMergedModelMetadataFile(path, callbacks, { + const data = writeModelMetadataFile(callbacks, { kmpFileName, kmpJsonData, model_id: 'sil.cmo.bw', @@ -41,8 +40,4 @@ describe('model-info-compiler', function () { assert.deepEqual(actual, expected); }); - - it('compile a .model_info file correctly when no source .model_info exists', function() { - this.skip(); // TODO: support model_info when no source file exists (determine license from LICENSE.md) - }); }); diff --git a/developer/src/kmc/README.md b/developer/src/kmc/README.md index f0c816bbb54..448f42e4b43 100644 --- a/developer/src/kmc/README.md +++ b/developer/src/kmc/README.md @@ -9,13 +9,11 @@ This package provides the following Keyman **command line tools**: file. - `kmlmp` — uses a `.model.kmp` file to generate a redistributable **lexical model package**. - - `kmlmi` — merges Keyman lexical model `.model_info` files. `kmlmc` is intended to be used standalone, or as part of a build system. `kmlmp` -is used only by command line tools. `kmlmi` is used exclusively in the -[lexical-models repository][lexical models]. +is used only by command line tools. -Note: `kmc` will in the future replace `kmlmc`, `kmlmp`, and `kmlmi`. +Note: `kmc` will in the future replace `kmlmc` and `kmlmp`. In order to build [lexical models][], these tools must be built and compiled. @@ -51,7 +49,6 @@ To see more command line options by using the `--help` option: kmlmc --help kmlmp --help - kmlmi --help How to build from source ------------------------ diff --git a/developer/src/kmc/build-bundler.js b/developer/src/kmc/build-bundler.js index e24ca39da0f..9b5f9cdd9bd 100644 --- a/developer/src/kmc/build-bundler.js +++ b/developer/src/kmc/build-bundler.js @@ -8,7 +8,6 @@ await esbuild.build({ entryPoints: [ 'build/src/kmc.js', 'build/src/kmlmc.js', - 'build/src/kmlmi.js', 'build/src/kmlmp.js', ], bundle: true, diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index b3ae21c078e..892acd93d73 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -11,10 +11,9 @@ ], "scripts": { "build": "tsc -b", - "bundle": "npm run bundle-kmc && npm run bundle-kmlmc && npm run bundle-kmlmi && npm run bundle-kmlmp", + "bundle": "npm run bundle-kmc && npm run bundle-kmlmc && npm run bundle-kmlmp", "bundle-kmc": "esbuild build/src/kmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmc.cjs", "bundle-kmlmc": "esbuild build/src/kmlmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmc.cjs", - "bundle-kmlmi": "esbuild build/src/kmlmi.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmi.cjs", "bundle-kmlmp": "esbuild build/src/kmlmp.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmp.cjs", "test": "eslint . && cd test && tsc -b && cd .. && mocha", "prepublishOnly": "npm run build" @@ -33,8 +32,7 @@ "bin": { "kmc": "build/src/kmc.js", "kmlmc": "build/src/kmlmc.js", - "kmlmp": "build/src/kmlmp.js", - "kmlmi": "build/src/kmlmi.js" + "kmlmp": "build/src/kmlmp.js" }, "dependencies": { "@keymanapp/common-types": "*", diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 1047dd8208c..9568da70209 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -41,10 +41,6 @@ Supported file types: * .model.ts: Keyman lexical model * .kps: Keyman keyboard or lexical model package -The following two metadata file types are also supported: - * .model_info: lexical model metadata file - * .keyboard_info: keyboard metadata file - File lists can be referenced with @filelist.txt. If no input file is supplied, kmc will build the current folder.`) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index e392f1c62c4..0af8343b2da 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -9,45 +9,34 @@ import { getLastGitCommitDate } from '../../util/getLastGitCommitDate.js'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } - public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.KeyboardInfo; } + public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.Project; } public get compiledExtension(): KeymanFileTypes.Binary { return KeymanFileTypes.Binary.KeyboardInfo; } public get description(): string { return 'Build a keyboard metadata file'; } public async build(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { - if(KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.KeyboardInfo)) { - // We are given a .keyboard_info but need to use the project file in the - // same folder, so that we can find the related files. This also supports - // version 2.0 projects (where the .kpj file is optional). - infile = KeymanFileTypes.replaceExtension(infile, KeymanFileTypes.Source.KeyboardInfo, KeymanFileTypes.Source.Project); - } - - if(!callbacks.fs.existsSync(infile)) { - // We cannot build a .keyboard_info if we don't have a repository-style project - return false; + if(!KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.Project)) { + // Even if the project file does not exist, we use its name as our reference + // in order to avoid ambiguity + throw new Error(`BuildKeyboardInfo called with unexpected file type ${infile}`); } const project = loadProject(infile, callbacks); if(!project) { - return false; - } - - const metadata = findProjectFile(callbacks, project, KeymanFileTypes.Source.KeyboardInfo); - if(!metadata) { - // Project loader should always have added a metadata file + // Error messages written by loadProject return false; } const keyboard = findProjectFile(callbacks, project, KeymanFileTypes.Source.KeymanKeyboard); const kps = findProjectFile(callbacks, project, KeymanFileTypes.Source.Package); if(!keyboard || !kps) { + // Error messages written by findProjectFile return false; } - const jsFilename = project.resolveOutputFilePath(keyboard, KeymanFileTypes.Source.KeymanKeyboard, KeymanFileTypes.Binary.WebKeyboard); - const lastCommitDate = getLastGitCommitDate(callbacks.path.dirname(project.resolveInputFilePath(metadata))); + const lastCommitDate = getLastGitCommitDate(project.projectPath); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeMergedKeyboardInfoFile({ + const data = compiler.writeKeyboardInfoFile({ kmpFilename: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kpsFilename: project.resolveInputFilePath(kps), jsFilename: fs.existsSync(jsFilename) ? jsFilename : undefined, @@ -60,8 +49,10 @@ export class BuildKeyboardInfo extends BuildActivity { return false; } + const outputFilename = project.getOutputFilePath(KeymanFileTypes.Binary.KeyboardInfo); + fs.writeFileSync( - project.resolveOutputFilePath(metadata, KeymanFileTypes.Source.KeyboardInfo, KeymanFileTypes.Binary.KeyboardInfo), + outputFilename, data ); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts index 9dd4d476e7c..92a6a08c72a 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import { BuildActivity } from './BuildActivity.js'; import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types'; -import { writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info'; +import { writeModelMetadataFile } from '@keymanapp/kmc-model-info'; import { KmpCompiler } from '@keymanapp/kmc-package'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; @@ -9,7 +9,7 @@ import { calculateSourcePath } from '../../util/calculateSourcePath.js'; export class BuildModelInfo extends BuildActivity { public get name(): string { return 'Lexical model metadata'; } - public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.ModelInfo; } + public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.Project; } public get compiledExtension(): KeymanFileTypes.Binary { return KeymanFileTypes.Binary.ModelInfo; } public get description(): string { return 'Build a lexical model metadata file'; } @@ -24,23 +24,18 @@ export class BuildModelInfo extends BuildActivity { * @returns */ public async build(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { - if(KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.ModelInfo)) { - // We are given a .model_info but need to use the project file in the - // same folder, so that we can find the related files. - infile = KeymanFileTypes.replaceExtension(infile, KeymanFileTypes.Source.ModelInfo, KeymanFileTypes.Source.Project); + if(!KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.Project)) { + // Even if the project file does not exist, we use its name as our reference + // in order to avoid ambiguity + throw new Error(`BuildModelInfo called with unexpected file type ${infile}`); } + const project = loadProject(infile, callbacks); if(!project) { // Error messages will be reported by loadProject return false; } - const metadata = project.files.find(file => file.fileType == KeymanFileTypes.Source.ModelInfo); - if(!metadata) { - callbacks.reportMessage(InfrastructureMessages.Error_FileTypeNotFound({ext: KeymanFileTypes.Source.ModelInfo})); - return false; - } - const model = project.files.find(file => file.fileType == KeymanFileTypes.Source.Model); if(!model) { callbacks.reportMessage(InfrastructureMessages.Error_FileTypeNotFound({ext: KeymanFileTypes.Source.Model})); @@ -60,11 +55,10 @@ export class BuildModelInfo extends BuildActivity { return false; } - const data = writeMergedModelMetadataFile( - project.resolveInputFilePath(metadata), + const data = writeModelMetadataFile( callbacks, { - model_id: callbacks.path.basename(metadata.filename, KeymanFileTypes.Source.ModelInfo), + model_id: callbacks.path.basename(project.projectPath, KeymanFileTypes.Source.Project), kmpJsonData, sourcePath: calculateSourcePath(infile), modelFileName: project.resolveOutputFilePath(model, KeymanFileTypes.Source.Model, KeymanFileTypes.Binary.Model), @@ -73,12 +67,12 @@ export class BuildModelInfo extends BuildActivity { ); if(data == null) { - // Error messages have already been emitted by writeMergedModelMetadataFile + // Error messages have already been emitted by writeModelMetadataFile return false; } fs.writeFileSync( - project.resolveOutputFilePath(metadata, KeymanFileTypes.Source.ModelInfo, KeymanFileTypes.Binary.ModelInfo), + project.getOutputFilePath(KeymanFileTypes.Binary.ModelInfo), data ); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index fdced30e021..380be1b7245 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { CompilerCallbacks, CompilerFileCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanFileTypes } from '@keymanapp/common-types'; import { BuildActivity } from './BuildActivity.js'; -import { buildActivities } from './buildActivities.js'; +import { buildActivities, buildKeyboardInfoActivity, buildModelInfoActivity } from './buildActivities.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { loadProject } from '../../util/projectLoader.js'; @@ -47,11 +47,17 @@ class ProjectBuilder { continue; } - if(KeymanFileTypes.filenameIsMetadata(builder.sourceExtension) && this.project.options.skipMetadataFiles) { - continue; + if(!await this.buildProjectTargets(builder)) { + return false; } + } - if(!await this.buildProjectTargets(builder)) { + // Build project metadata + if(!this.project.options.skipMetadataFiles) { + if(!await (this.buildProjectTargets( + this.project.isKeyboardProject() + ? buildKeyboardInfoActivity + : buildModelInfoActivity))) { return false; } } @@ -60,6 +66,10 @@ class ProjectBuilder { } async buildProjectTargets(activity: BuildActivity): Promise { + if(activity.sourceExtension == KeymanFileTypes.Source.Project) { + return await this.buildTarget(this.project.projectFile, activity); + } + let result = true; for(let file of this.project.files) { if(file.fileType.toLowerCase() == activity.sourceExtension) { diff --git a/developer/src/kmc/src/commands/buildClasses/buildActivities.ts b/developer/src/kmc/src/commands/buildClasses/buildActivities.ts index aae271fbacb..d8d1a9b5d55 100644 --- a/developer/src/kmc/src/commands/buildClasses/buildActivities.ts +++ b/developer/src/kmc/src/commands/buildClasses/buildActivities.ts @@ -13,10 +13,13 @@ export const buildActivities: BuildActivity[] = [ new BuildLdmlKeyboard(), new BuildModel(), new BuildPackage(), - new BuildKeyboardInfo(), - new BuildModelInfo(), ]; +// These are built from the .kpj reference after all others +export const buildKeyboardInfoActivity = new BuildKeyboardInfo(); +export const buildModelInfoActivity = new BuildModelInfo(); + + // Note: BuildProject is not listed here to avoid circular references, // because it depends on the other activities here. This means that // BuildProject must be separately checked. diff --git a/developer/src/kmc/src/kmlmi.ts b/developer/src/kmc/src/kmlmi.ts deleted file mode 100644 index 219032d0446..00000000000 --- a/developer/src/kmc/src/kmlmi.ts +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env node -/** - * kmlmi - Keyman Lexical Model model_info Compiler - */ - -import * as fs from 'fs'; -import * as path from 'path'; -import { Command } from 'commander'; -import { KmpCompiler, PackageValidation } from '@keymanapp/kmc-package'; -import { ModelInfoOptions, writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info'; -import { SysExits } from './util/sysexits.js'; -import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; - -let inputFilename: string; -const program = new Command(); - -/* Arguments */ -program - .description('Merges Keyman lexical model model_info files. Intended for use within the keymanapp/lexical-models repository.') - .version(KEYMAN_VERSION.VERSION_WITH_TAG) - .arguments('') - .action(infile => inputFilename = infile) - .option('-o, --outFile ', 'where to save the resultant file') - .option('-m, --model ', 'model id, defaults to basename of input file sans .model_info extension') - .option('-s, --source ', 'path to source of model, relative to lexical-models repo root') - .option('--kpsFilename ', 'path to .model.kps file, defaults to source/.model.kps') - .option('--kmpFilename ', 'path to .model.kmp file, defaults to build/.model.kmp') - .option('--jsFilename ', 'path to .model.js file, defaults to build/.model.js'); - -program.parse(process.argv); - -// Deal with input arguments: - -if (!inputFilename) { - exitDueToUsageError('Must provide a lexical model .model_info source file.'); -} - -let model_id: string = program.opts().model ? program.opts().model : path.basename(inputFilename).replace(/\.model_info$/, ""); -let outputFilename: string = program.opts().outFile ? program.opts().outFile : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename)); -let kpsFilename = program.opts().kpsFilename ? program.opts().kpsFilename : path.join(path.dirname(inputFilename), 'source', path.basename(inputFilename).replace(/\.model_info$/, '.model.kps')); -let kmpFilename = program.opts().kmpFilename ? program.opts().kmpFilename : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename).replace(/\.model_info$/, '.model.kmp')); -let jsFilename = program.opts().jsFilename ? program.opts().jsFilename : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename).replace(/\.model_info$/, '.model.js')); - -// -// Load .kps source data -// - -const callbacks = new NodeCompilerCallbacks({logLevel: 'info'}); -let kmpCompiler = new KmpCompiler(callbacks); -let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); -if(!kmpJsonData) { - process.exit(SysExits.EX_DATAERR); -} - -// -// Validate the package file -// - -const validation = new PackageValidation(callbacks, {}); -if(!validation.validate(kpsFilename, kmpJsonData)) { - process.exit(SysExits.EX_DATAERR); -} - -// -// Write out the merged .model_info file -// - -let modelInfoOptions: ModelInfoOptions = { - model_id: model_id, - kmpJsonData: kmpJsonData, - sourcePath: program.opts().source, - modelFileName: jsFilename, - kmpFileName: kmpFilename -}; - -const data = writeMergedModelMetadataFile( - inputFilename, - callbacks, - modelInfoOptions); -if(!data) { - process.exit(SysExits.EX_DATAERR); -} -fs.writeFileSync(outputFilename, data); - -function exitDueToUsageError(message: string): never { - console.error(`${program.name()}: ${message}`); - console.error(); - program.outputHelp(); - return process.exit(SysExits.EX_USAGE); -} diff --git a/developer/src/tike/Makefile b/developer/src/tike/Makefile index ce20a3673c6..c6a8cb16044 100644 --- a/developer/src/tike/Makefile +++ b/developer/src/tike/Makefile @@ -11,7 +11,6 @@ build: version.res manifest.res icons dirs xml xsd pull-core $(TDS2DBG) $(WIN32_TARGET_PATH)\tike.exe $(COPY) $(WIN32_TARGET_PATH)\tike.exe $(DEVELOPER_PROGRAM) $(COPY) kmlmc.cmd $(DEVELOPER_PROGRAM) - $(COPY) kmlmi.cmd $(DEVELOPER_PROGRAM) $(COPY) kmlmp.cmd $(DEVELOPER_PROGRAM) $(COPY) kmc.cmd $(DEVELOPER_PROGRAM) $(COPY) $(KEYMAN_ROOT)\core\build\x86\$(TARGET_PATH)\src\kmnkbp0-0.dll $(DEVELOPER_PROGRAM) diff --git a/developer/src/tike/kmlmi.cmd b/developer/src/tike/kmlmi.cmd deleted file mode 100644 index 32f6a256a95..00000000000 --- a/developer/src/tike/kmlmi.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -setlocal -rem This script is based on /developer/src/node/inst/kmlmi.cmd. It is stored here -rem in order to allow TIKE to call out to the compiler while debugging. -if exist "%~dp0..\inst\node\dist\node.exe" ( - rem If running in developer/src/tike/: - set nodeexe="%~dp0..\inst\node\dist\node.exe" - set nodecli="%~dp0..\kmc\build\src\kmlmi.js" -) else if exist "%~dp0..\src\inst\node\dist\node.exe" ( - rem If running in developer/bin/: - set nodeexe="%~dp0..\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\src\kmc\build\src\kmlmi.js" -) else ( - rem Cannot find node or kmlmi.js relative to execution path - echo Error: node.exe or kmlmi.js not found. - exit /b 1 -) - -%nodeexe% --enable-source-maps %nodecli% %* -exit /b %errorlevel% diff --git a/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.kpj b/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.kpj index e4d5c9c567a..855646cf269 100644 --- a/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.kpj +++ b/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.kpj @@ -48,13 +48,6 @@ .md - - id_4b12eec2a4d0bfc7b5441a4a59691eab - dmg.dv.test.model_info - dmg.dv.test.model_info - - .model_info - id_6ee5fa96871fb96ecebf2cbe9ec7425b dmg.dv.test.model.js diff --git a/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.model_info b/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.model_info deleted file mode 100644 index 03399a9f8e5..00000000000 --- a/web/src/test/manual/web/issue2924/source/dmg.dv.test/dmg.dv.test.model_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "dv" - ], - "description": "test generated from template" -} From 232c918c314a859823b957938995efa8a7ec92b1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 29 Aug 2023 16:37:37 +1000 Subject: [PATCH 031/207] feat(developer): support isRTL and license fields in .model_info Adds support for calculating isRTL and license fields in .model_info compiler. Also: * establishes @keymanapp/developer-utils shared module * moves license validation into @keymanapp/developer-utils * refactors kmc-model-info to a class and general cleanup --- developer/build.sh | 1 + developer/src/common/web/utils/.gitignore | 1 + developer/src/common/web/utils/build.sh | 36 +++ developer/src/common/web/utils/index.ts | 1 + developer/src/common/web/utils/package.json | 32 +++ .../web/utils}/src/validate-mit-license.ts | 0 .../license/LICENSE-changed-clause.md | 0 .../license/LICENSE-missing-copyright.md | 0 .../fixtures/license/LICENSE-missing-title.md | 0 .../test/fixtures/license/LICENSE-too-long.md | 0 .../fixtures/license/LICENSE-too-short.md | 0 .../utils}/test/fixtures/license/LICENSE.md | 0 .../common/web/utils/test/helpers/index.ts | 18 ++ .../web/utils}/test/test-license.ts | 0 .../src/common/web/utils/test/tsconfig.json | 24 ++ developer/src/common/web/utils/tsconfig.json | 18 ++ developer/src/kmc-keyboard-info/package.json | 3 +- developer/src/kmc-keyboard-info/src/index.ts | 2 +- developer/src/kmc-keyboard-info/tsconfig.json | 2 + developer/src/kmc-model-info/package.json | 3 +- developer/src/kmc-model-info/src/messages.ts | 15 ++ .../kmc-model-info/src/model-info-compiler.ts | 254 +++++++++++------- .../src/kmc-model-info/src/model-info-file.ts | 2 +- .../test/fixtures/sil.cmo.bw/LICENSE.md | 2 +- .../sil.cmo.bw/source/sil.cmo.bw.model.kps | 7 + .../test/test-model-info-compiler.ts | 14 +- developer/src/kmc-model-info/tsconfig.json | 2 + .../commands/buildClasses/BuildModelInfo.ts | 5 + package.json | 2 + tsconfig.esm.json | 2 + 30 files changed, 345 insertions(+), 101 deletions(-) create mode 100644 developer/src/common/web/utils/.gitignore create mode 100755 developer/src/common/web/utils/build.sh create mode 100644 developer/src/common/web/utils/index.ts create mode 100644 developer/src/common/web/utils/package.json rename developer/src/{kmc-keyboard-info => common/web/utils}/src/validate-mit-license.ts (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE-changed-clause.md (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE-missing-copyright.md (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE-missing-title.md (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE-too-long.md (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE-too-short.md (100%) rename developer/src/{kmc-keyboard-info => common/web/utils}/test/fixtures/license/LICENSE.md (100%) create mode 100644 developer/src/common/web/utils/test/helpers/index.ts rename developer/src/{kmc-keyboard-info => common/web/utils}/test/test-license.ts (100%) create mode 100644 developer/src/common/web/utils/test/tsconfig.json create mode 100644 developer/src/common/web/utils/tsconfig.json diff --git a/developer/build.sh b/developer/build.sh index 2315cd3b34c..47cc05f533b 100755 --- a/developer/build.sh +++ b/developer/build.sh @@ -27,6 +27,7 @@ builder_describe \ configure \ build \ test \ + ":utils=src/common/web/utils Developer utils" \ ":kmcmplib=src/kmcmplib Compiler - .kmn compiler" \ ":kmc-analyze=src/kmc-analyze Compiler - Analysis Tools" \ ":kmc-keyboard-info=src/kmc-keyboard-info Compiler - .keyboard_info Module" \ diff --git a/developer/src/common/web/utils/.gitignore b/developer/src/common/web/utils/.gitignore new file mode 100644 index 00000000000..d16386367f7 --- /dev/null +++ b/developer/src/common/web/utils/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh new file mode 100755 index 00000000000..d0da84b56fe --- /dev/null +++ b/developer/src/common/web/utils/build.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +builder_describe "Build Keyman Developer web utility module" \ + "@/common/web/types" \ + "clean" \ + "configure" \ + "build" \ + "test" + +builder_describe_outputs \ + configure /node_modules \ + build /developer/src/common/web/utils/build/index.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +builder_run_action clean rm -rf ./build/ +builder_run_action configure verify_npm_setup +builder_run_action build tsc --build + +if builder_start_action test; then + eslint . + tsc --build test + c8 --reporter=lcov --reporter=text --exclude-after-remap mocha + builder_finish_action success test +fi diff --git a/developer/src/common/web/utils/index.ts b/developer/src/common/web/utils/index.ts new file mode 100644 index 00000000000..f18f2e90de5 --- /dev/null +++ b/developer/src/common/web/utils/index.ts @@ -0,0 +1 @@ +export { validateMITLicense } from './src/validate-mit-license.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json new file mode 100644 index 00000000000..277066b0464 --- /dev/null +++ b/developer/src/common/web/utils/package.json @@ -0,0 +1,32 @@ +{ + "name": "@keymanapp/developer-utils", + "description": "Keyman Developer utilities", + "type": "module", + "exports": { + ".": "./build/index.js" + }, + "files": [ + "/build/" + ], + "devDependencies": { + "@types/node": "^20.4.1", + "ts-node": "^9.1.1", + "typescript": "^4.9.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "mocha": "^8.4.0" + }, + "scripts": { + "build": "tsc -b" + }, + "license": "MIT", + "dependencies": { + "@keymanapp/common-types": "*" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + } +} diff --git a/developer/src/kmc-keyboard-info/src/validate-mit-license.ts b/developer/src/common/web/utils/src/validate-mit-license.ts similarity index 100% rename from developer/src/kmc-keyboard-info/src/validate-mit-license.ts rename to developer/src/common/web/utils/src/validate-mit-license.ts diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE-changed-clause.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-changed-clause.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE-changed-clause.md diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE-missing-copyright.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-copyright.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE-missing-copyright.md diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE-missing-title.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-missing-title.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE-missing-title.md diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE-too-long.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-long.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE-too-long.md diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE-too-short.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE-too-short.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE-too-short.md diff --git a/developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md b/developer/src/common/web/utils/test/fixtures/license/LICENSE.md similarity index 100% rename from developer/src/kmc-keyboard-info/test/fixtures/license/LICENSE.md rename to developer/src/common/web/utils/test/fixtures/license/LICENSE.md diff --git a/developer/src/common/web/utils/test/helpers/index.ts b/developer/src/common/web/utils/test/helpers/index.ts new file mode 100644 index 00000000000..9968fbfea6d --- /dev/null +++ b/developer/src/common/web/utils/test/helpers/index.ts @@ -0,0 +1,18 @@ +/** + * Helpers and utilities for the Mocha tests. + */ +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +/** + * Builds a path to the fixture with the given path components. + * + * e.g., makePathToFixture('example.qaa.trivial') + * e.g., makePathToFixture('example.qaa.trivial', 'model.ts') + * + * @param components One or more path components. + */ + export function makePathToFixture(...components: string[]): string { + return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); +} + diff --git a/developer/src/kmc-keyboard-info/test/test-license.ts b/developer/src/common/web/utils/test/test-license.ts similarity index 100% rename from developer/src/kmc-keyboard-info/test/test-license.ts rename to developer/src/common/web/utils/test/test-license.ts diff --git a/developer/src/common/web/utils/test/tsconfig.json b/developer/src/common/web/utils/test/tsconfig.json new file mode 100644 index 00000000000..a28eeb03ab2 --- /dev/null +++ b/developer/src/common/web/utils/test/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "esModuleInterop": true, + "moduleResolution": "node16", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "@keymanapp/developer-test-helpers": ["../../test-helpers/index"], + }, + }, + "include": [ + "**/test-*.ts", + "helpers/*.ts", + ], + "references": [ + { "path": "../" }, + { "path": "../../test-helpers/" }, + ] +} \ No newline at end of file diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json new file mode 100644 index 00000000000..dbb068a7a22 --- /dev/null +++ b/developer/src/common/web/utils/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../../tsconfig.esm-base.json", + + "compilerOptions": { + "rootDir": ".", + "outDir": "./build/", + "baseUrl": ".", + "paths": { + "@keymanapp/common-types": ["../../../../../common/web/types/src/main"], + }, + }, + "include": [ + "./*.ts", "src/validate-mit-license.ts", + ], + "references": [ + { "path": "../../../../../common/web/types/" }, + ] +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index ad3572ec1e8..7b028ec1294 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -25,7 +25,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "@keymanapp/kmc-package": "*" + "@keymanapp/kmc-package": "*", + "@keymanapp/developer-utils": "*" }, "devDependencies": { "@types/chai": "^4.3.5", diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index 0c0876cf247..a677f5f5a9f 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -8,7 +8,7 @@ import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFilePlatform } import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets } from "@keymanapp/common-types"; import { KeyboardInfoCompilerMessages } from "./messages.js"; import langtags from "./imports/langtags.js"; -import { validateMITLicense } from "./validate-mit-license.js"; +import { validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); diff --git a/developer/src/kmc-keyboard-info/tsconfig.json b/developer/src/kmc-keyboard-info/tsconfig.json index c10b82f8637..bf1ad29bd95 100644 --- a/developer/src/kmc-keyboard-info/tsconfig.json +++ b/developer/src/kmc-keyboard-info/tsconfig.json @@ -13,6 +13,7 @@ "paths": { "@keymanapp/common-types": ["../../../common/web/types/src/main"], "@keymanapp/kmc-package": ["../kmc-package/source"], + "@keymanapp/developer-utils": ["../common/web/utils/index"], } }, "include": [ @@ -22,5 +23,6 @@ "references": [ { "path": "../../../common/web/types" }, { "path": "../kmc-package/" }, + { "path": "../common/web/utils/" }, ] } diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index 624be5ff807..e0499ad0e60 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -32,7 +32,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "@keymanapp/models-types": "*" + "@keymanapp/models-types": "*", + "@keymanapp/developer-utils": "*" }, "devDependencies": { "@types/chai": "^4.1.7", diff --git a/developer/src/kmc-model-info/src/messages.ts b/developer/src/kmc-model-info/src/messages.ts index a4d61e8b232..03dce78613a 100644 --- a/developer/src/kmc-model-info/src/messages.ts +++ b/developer/src/kmc-model-info/src/messages.ts @@ -26,5 +26,20 @@ export class ModelInfoCompilerMessages { `Invalid author email: ${o.email}`); static ERROR_InvalidAuthorEmail = SevError | 0x0005; + static Error_LicenseFileDoesNotExist = (o:{filename:string}) => m(this.ERROR_LicenseFileIsMissing, + `License file ${o.filename} does not exist.`); + static ERROR_LicenseFileIsMissing = SevError | 0x0006; + + static Error_LicenseFileIsDamaged = (o:{filename:string}) => m(this.ERROR_LicenseFileIsDamaged, + `License file ${o.filename} could not be loaded or decoded.`); + static ERROR_LicenseFileIsDamaged = SevError | 0x0007; + + static Error_LicenseIsNotValid = (o:{filename:string,message:string}) => m(this.ERROR_LicenseIsNotValid, + `An error was encountered parsing license file ${o.filename}: ${o.message}.`); + static ERROR_LicenseIsNotValid = SevError | 0x0008; + + static Error_NoLicenseFound = () => m(this.ERROR_NoLicenseFound, + `No license for the model was found. MIT license is required for publication to Keyman lexical-models repository.`); + static ERROR_NoLicenseFound = SevError | 0x0009; } diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index 04ebf6cef8d..4563963aa13 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -7,11 +7,12 @@ import { minKeymanVersion } from "./min-keyman-version.js"; import { ModelInfoFile } from "./model-info-file.js"; import { CompilerCallbacks, KmpJsonFile } from "@keymanapp/common-types"; import { ModelInfoCompilerMessages } from "./messages.js"; +import { validateMITLicense } from "@keymanapp/developer-utils"; const HelpRoot = 'https://help.keyman.com/model/'; /* c8 ignore start */ -export class ModelInfoOptions { +export class ModelInfoSources { /** The identifier for the model */ model_id: string; @@ -19,119 +20,190 @@ export class ModelInfoOptions { kmpJsonData: KmpJsonFile.KmpJsonFile; /** The path in the keymanapp/lexical-models repo where this model may be found (optional) */ - sourcePath?: string; + sourcePath: string; /** The compiled model filename and relative path (.js) */ modelFileName: string; /** The compiled package filename and relative path (.kmp) */ kmpFileName: string; + + /** The source package filename and relative path (.kps) */ + kpsFilename: string; + + /** Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' */ + lastCommitDate?: string; }; /* c8 ignore stop */ -/** - * Builds .model_info file with metadata from the model and package source file. - * This function is intended for use within the lexical-models repository. While many of the - * parameters could be deduced from each other, they are specified here to reduce the - * number of places the filenames are constructed. - * - * @param callbacks - * @param options Details on files from which to extract additional metadata - */ -export function writeModelMetadataFile( - callbacks: CompilerCallbacks, - options: ModelInfoOptions +export class ModelInfoCompiler { + constructor(private callbacks: CompilerCallbacks) { + } + + /** + * Builds .model_info file with metadata from the model and package source file. + * This function is intended for use within the lexical-models repository. While many of the + * parameters could be deduced from each other, they are specified here to reduce the + * number of places the filenames are constructed. + * + * @param sources Details on files from which to extract additional metadata + */ + writeModelMetadataFile( + sources: ModelInfoSources ): Uint8Array { - /* - * Model info looks like this: - * - * { - * "name": "Example Template Model" - * "license": "mit", - * "version": "1.0.0", - * "languages": ["en"], - * "authorName": "Example Author", - * "authorEmail": "nobody@example.com", - * "description": "Example wordlist model" - * } - * - * For full documentation, see: - * https://help.keyman.com/developer/cloud/model_info/1.0/ - */ - - let model_info: ModelInfoFile = { - languages: [], - license: 'mit' - }; - - // - // Build .model_info file -- some fields have "special" behaviours -- see below - // https://api.keyman.com/schemas/model_info.source.json and - // https://api.keyman.com/schemas/model_info.distribution.json - // https://help.keyman.com/developer/cloud/model_info/1.0 - // - - // TODO: isrtl - // TODO: license - - model_info.id = options.model_id; - model_info.name = options.kmpJsonData.info.name.description; - - const author = options.kmpJsonData.info.author; - model_info.authorName = author?.description ?? ''; - - if (author?.url) { - // we strip the mailto: from the .kps file for the .model_info - const match = author.url.match(/^(mailto\:)?(.+)$/); - /* c8 ignore next 3 */ - if (match === null) { - callbacks.reportMessage(ModelInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url})); + /* + * Model info looks like this: + * + * { + * "name": "Example Template Model" + * "license": "mit", + * "version": "1.0.0", + * "languages": ["en"], + * "authorName": "Example Author", + * "authorEmail": "nobody@example.com", + * "description": "Example wordlist model" + * } + * + * For full documentation, see: + * https://help.keyman.com/developer/cloud/model_info/1.0/ + */ + + let jsFile: string = null; + + if(sources.modelFileName) { + jsFile = this.loadJsFile(sources.modelFileName); + if(!jsFile) { + return null; + } + } + + + let model_info: ModelInfoFile = { + languages: [], + }; + + // + // Build .model_info file -- some fields have "special" behaviours -- see below + // https://api.keyman.com/schemas/model_info.source.json and + // https://api.keyman.com/schemas/model_info.distribution.json + // https://help.keyman.com/developer/cloud/model_info/1.0 + // + + model_info.id = sources.model_id; + model_info.name = sources.kmpJsonData.info.name.description; + + // License + + if(!sources.kmpJsonData.options?.licenseFile) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_NoLicenseFound()); return null; } - model_info.authorEmail = match[2]; - } + if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, sources.kmpJsonData.options.licenseFile))) { + return null; + } - // description + model_info.license = 'mit'; - if(options.kmpJsonData.info.description?.description) { - model_info.description = options.kmpJsonData.info.description.description.trim(); - } + const author = sources.kmpJsonData.info.author; + model_info.authorName = author?.description ?? ''; - // extract the language identifiers from the language metadata - // arrays for each of the lexical models in the kmp.json file, - // and merge into a single array of identifiers in the - // .model_info file. + if (author?.url) { + // we strip the mailto: from the .kps file for the .model_info + const match = author.url.match(/^(mailto\:)?(.+)$/); + /* c8 ignore next 3 */ + if (match === null) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url})); + return null; + } - model_info.languages = options.kmpJsonData.lexicalModels.reduce((a, e) => [].concat(a, e.languages.map((f) => f.id)), []); + model_info.authorEmail = match[2]; + } - // TODO: use git date - model_info.lastModifiedDate = (new Date).toISOString(); + // description - model_info.packageFilename = callbacks.path.basename(options.kmpFileName); - model_info.packageFileSize = callbacks.fileSize(options.kmpFileName); - if(model_info.packageFileSize === undefined) { - callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:options.kmpFileName})); - return null; - } + if(sources.kmpJsonData.info.description?.description) { + model_info.description = sources.kmpJsonData.info.description.description.trim(); + } + + // isRTL -- this is a little bit of a heuristic from a compiled .js + // which may need modification if compilers change - model_info.jsFilename = callbacks.path.basename(options.modelFileName); - model_info.jsFileSize = callbacks.fileSize(options.modelFileName); - if(model_info.jsFileSize === undefined) { - callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:options.modelFileName})); - return null; + if(jsFile?.match(/("?)isRTL("?):\s*true/)) { + model_info.isRTL = true; + } + + // extract the language identifiers from the language metadata + // arrays for each of the lexical models in the kmp.json file, + // and merge into a single array of identifiers in the + // .model_info file. + + model_info.languages = sources.kmpJsonData.lexicalModels.reduce((a, e) => [].concat(a, e.languages.map((f) => f.id)), []); + + // If a last commit date is not given, then just use the current time + model_info.lastModifiedDate = sources.lastCommitDate ?? (new Date).toISOString(); + + model_info.packageFilename = this.callbacks.path.basename(sources.kmpFileName); + model_info.packageFileSize = this.callbacks.fileSize(sources.kmpFileName); + if(model_info.packageFileSize === undefined) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.kmpFileName})); + return null; + } + + model_info.jsFilename = this.callbacks.path.basename(sources.modelFileName); + model_info.jsFileSize = this.callbacks.fileSize(sources.modelFileName); + if(model_info.jsFileSize === undefined) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename:sources.modelFileName})); + return null; + } + + model_info.packageIncludes = sources.kmpJsonData.files.filter((e) => !!e.name.match(/.[ot]tf$/i)).length ? ['fonts'] : []; + model_info.version = sources.kmpJsonData.info.version.description; + model_info.minKeymanVersion = minKeymanVersion; + model_info.helpLink = HelpRoot + model_info.id; + + if(sources.sourcePath) { + model_info.sourcePath = sources.sourcePath; + } + + const jsonOutput = JSON.stringify(model_info, null, 2); + return new TextEncoder().encode(jsonOutput); } - model_info.packageIncludes = options.kmpJsonData.files.filter((e) => !!e.name.match(/.[ot]tf$/i)).length ? ['fonts'] : []; - model_info.version = options.kmpJsonData.info.version.description; - model_info.minKeymanVersion = minKeymanVersion; - model_info.helpLink = HelpRoot + model_info.id; + private isLicenseMIT(filename: string) { + const data = this.callbacks.loadFile(filename); + if(!data) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_LicenseFileDoesNotExist({filename})); + return false; + } - if(options.sourcePath) { - model_info.sourcePath = options.sourcePath; + let license = null; + try { + license = new TextDecoder().decode(data); + } catch(e) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_LicenseFileIsDamaged({filename})); + return false; + } + if(!license) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_LicenseFileIsDamaged({filename})); + return false; + } + const message = validateMITLicense(license); + if(message != null) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_LicenseIsNotValid({filename, message})); + return false; + } + return true; } - const jsonOutput = JSON.stringify(model_info, null, 2); - return new TextEncoder().encode(jsonOutput); -} + private loadJsFile(filename: string) { + const data = this.callbacks.loadFile(filename); + if(!data) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_FileDoesNotExist({filename})); + return null; + } + const text = new TextDecoder('utf-8', {fatal: true}).decode(data); + return text; + } +} \ No newline at end of file diff --git a/developer/src/kmc-model-info/src/model-info-file.ts b/developer/src/kmc-model-info/src/model-info-file.ts index c993242e349..c038c67ca95 100644 --- a/developer/src/kmc-model-info/src/model-info-file.ts +++ b/developer/src/kmc-model-info/src/model-info-file.ts @@ -4,7 +4,7 @@ export interface ModelInfoFile { authorName?: string; authorEmail?: string; description?: string; - license: "mit"; + license?: "mit"; languages: Array; lastModifiedDate?: string; packageFilename?: string; diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/LICENSE.md b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/LICENSE.md index fefb033d6c1..3a58ba2ae32 100644 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/LICENSE.md +++ b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -© 2021 - 2022 SIL International +Copyright © 2021 - 2022 SIL International Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps index f53475848e7..57b610c83ab 100644 --- a/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps +++ b/developer/src/kmc-model-info/test/fixtures/sil.cmo.bw/source/sil.cmo.bw.model.kps @@ -7,6 +7,7 @@ readme.htm + ..\LICENSE.md @@ -28,6 +29,12 @@ 0 .js + + ..\LICENSE.md + + 0 + .md + welcome.htm diff --git a/developer/src/kmc-model-info/test/test-model-info-compiler.ts b/developer/src/kmc-model-info/test/test-model-info-compiler.ts index ebfb82f4f89..aa0c2ed65b7 100644 --- a/developer/src/kmc-model-info/test/test-model-info-compiler.ts +++ b/developer/src/kmc-model-info/test/test-model-info-compiler.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import 'mocha'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; -import { writeModelMetadataFile } from '../src/model-info-compiler.js'; +import { ModelInfoCompiler } from '../src/model-info-compiler.js'; import { KmpCompiler } from '@keymanapp/kmc-package'; const callbacks = new TestCompilerCallbacks(); @@ -14,21 +14,25 @@ beforeEach(function() { describe('model-info-compiler', function () { it('compile a .model_info file correctly', function() { - const kpsFileName = makePathToFixture('sil.cmo.bw', 'source', 'sil.cmo.bw.model.kps'); + const kpsFilename = makePathToFixture('sil.cmo.bw', 'source', 'sil.cmo.bw.model.kps'); const kmpFileName = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model.kmp'); const buildModelInfoFilename = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model_info'); const kmpCompiler = new KmpCompiler(callbacks); - const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFileName); + const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsFilename); const modelFileName = makePathToFixture('sil.cmo.bw', 'build', 'sil.cmo.bw.model.js'); - const data = writeModelMetadataFile(callbacks, { + const data = (new ModelInfoCompiler(callbacks)).writeModelMetadataFile({ kmpFileName, kmpJsonData, model_id: 'sil.cmo.bw', modelFileName, - sourcePath: 'release/sil/sil.cmo.bw' + sourcePath: 'release/sil/sil.cmo.bw', + kpsFilename, }); + if(data == null) { + callbacks.printMessages(); + } assert.isNotNull(data); const actual = JSON.parse(new TextDecoder().decode(data)); diff --git a/developer/src/kmc-model-info/tsconfig.json b/developer/src/kmc-model-info/tsconfig.json index c7edb11615d..e751bfb5eaf 100644 --- a/developer/src/kmc-model-info/tsconfig.json +++ b/developer/src/kmc-model-info/tsconfig.json @@ -6,6 +6,7 @@ "rootDir": "src/", "paths": { "@keymanapp/common-types": ["../../../common/web/types/src/main"], + "@keymanapp/developer-utils": ["../developer/web/utils/index"], } }, "include": [ @@ -14,5 +15,6 @@ "references": [ { "path": "../../../common/web/types" }, { "path": "../../../common/models/types" }, + { "path": "../common/web/utils" }, ] } diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts index 92a6a08c72a..5f9759aa54f 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts @@ -6,6 +6,7 @@ import { KmpCompiler } from '@keymanapp/kmc-package'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; +import { getLastGitCommitDate } from 'src/util/getLastGitCommitDate.js'; export class BuildModelInfo extends BuildActivity { public get name(): string { return 'Lexical model metadata'; } @@ -55,6 +56,8 @@ export class BuildModelInfo extends BuildActivity { return false; } + const lastCommitDate = getLastGitCommitDate(project.projectPath); + const data = writeModelMetadataFile( callbacks, { @@ -63,6 +66,8 @@ export class BuildModelInfo extends BuildActivity { sourcePath: calculateSourcePath(infile), modelFileName: project.resolveOutputFilePath(model, KeymanFileTypes.Source.Model, KeymanFileTypes.Binary.Model), kmpFileName: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), + kpsFilename: project.resolveInputFilePath(kps), + lastCommitDate } ); diff --git a/package.json b/package.json index 0afd9ade6eb..7f187b9a884 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "developer/src/kmc-package", "developer/src/kmc", "developer/src/server", + "developer/utils", "common/models/*", "common/test/resources", "common/tools/*", @@ -43,6 +44,7 @@ "dependencies": { "@keymanapp/common-types": "file:common/web/types", "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers", + "@keymanapp/developer-utils": "file:developer/src/common/web/utils", "@keymanapp/hextobin": "file:common/tools/hextobin", "@keymanapp/keyman-version": "file:common/web/keyman-version", "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" diff --git a/tsconfig.esm.json b/tsconfig.esm.json index e0f72d8f8d9..d0df9dbdc55 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -9,6 +9,8 @@ //{ "path": "./developer/src/kmc/test/tsconfig.json" }, { "path": "./developer/src/common/web/test-helpers/tsconfig.json" }, + { "path": "./developer/src/common/web/utils/tsconfig.json" }, + { "path": "./developer/src/common/web/utils/test/tsconfig.json" }, { "path": "./developer/src/kmc/tsconfig.json" }, From 56d64da9a77930b2f7343cd9310534409ba60de3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 30 Aug 2023 16:31:43 +1000 Subject: [PATCH 032/207] chore(developer): npm install and fix import --- .../commands/buildClasses/BuildModelInfo.ts | 27 +++++++++---------- package-lock.json | 12 ++++++++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts index 5f9759aa54f..490543c0e6a 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts @@ -1,12 +1,12 @@ import * as fs from 'fs'; import { BuildActivity } from './BuildActivity.js'; import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types'; -import { writeModelMetadataFile } from '@keymanapp/kmc-model-info'; +import { ModelInfoCompiler } from '@keymanapp/kmc-model-info'; import { KmpCompiler } from '@keymanapp/kmc-package'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; -import { getLastGitCommitDate } from 'src/util/getLastGitCommitDate.js'; +import { getLastGitCommitDate } from '../../util/getLastGitCommitDate.js'; export class BuildModelInfo extends BuildActivity { public get name(): string { return 'Lexical model metadata'; } @@ -57,19 +57,16 @@ export class BuildModelInfo extends BuildActivity { } const lastCommitDate = getLastGitCommitDate(project.projectPath); - - const data = writeModelMetadataFile( - callbacks, - { - model_id: callbacks.path.basename(project.projectPath, KeymanFileTypes.Source.Project), - kmpJsonData, - sourcePath: calculateSourcePath(infile), - modelFileName: project.resolveOutputFilePath(model, KeymanFileTypes.Source.Model, KeymanFileTypes.Binary.Model), - kmpFileName: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), - kpsFilename: project.resolveInputFilePath(kps), - lastCommitDate - } - ); + const compiler = new ModelInfoCompiler(callbacks); + const data = compiler.writeModelMetadataFile({ + model_id: callbacks.path.basename(project.projectPath, KeymanFileTypes.Source.Project), + kmpJsonData, + sourcePath: calculateSourcePath(infile), + modelFileName: project.resolveOutputFilePath(model, KeymanFileTypes.Source.Model, KeymanFileTypes.Binary.Model), + kmpFileName: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), + kpsFilename: project.resolveInputFilePath(kps), + lastCommitDate + }); if(data == null) { // Error messages have already been emitted by writeModelMetadataFile diff --git a/package-lock.json b/package-lock.json index 7caa96d15ac..4b18a604978 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "developer/src/kmc-package", "developer/src/kmc", "developer/src/server", + "developer/utils", "common/models/*", "common/test/resources", "common/tools/*", @@ -30,6 +31,7 @@ "dependencies": { "@keymanapp/common-types": "file:common/web/types", "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers", + "@keymanapp/developer-utils": "file:developer/src/common/web/utils", "@keymanapp/hextobin": "file:common/tools/hextobin", "@keymanapp/keyman-version": "file:common/web/keyman-version", "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" @@ -716,7 +718,6 @@ "bin": { "kmc": "build/src/kmc.js", "kmlmc": "build/src/kmlmc.js", - "kmlmi": "build/src/kmlmi.js", "kmlmp": "build/src/kmlmp.js" }, "devDependencies": { @@ -998,6 +999,7 @@ "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", + "@keymanapp/developer-utils": "*", "@keymanapp/kmc-package": "*" }, "devDependencies": { @@ -1855,6 +1857,7 @@ "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", + "@keymanapp/developer-utils": "*", "@keymanapp/models-types": "*" }, "devDependencies": { @@ -2960,6 +2963,13 @@ "resolved": "developer/src/common/web/test-helpers", "link": true }, + "node_modules/@keymanapp/developer-utils": { + "resolved": "file:developer/src/common/web/utils", + "license": "MIT", + "dependencies": { + "@keymanapp/common-types": "*" + } + }, "node_modules/@keymanapp/hextobin": { "resolved": "common/tools/hextobin", "link": true From 7f76de47811bdc8a8fcfe45e899aba8629300b25 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 10:11:15 +0700 Subject: [PATCH 033/207] chore(developer): add developer-utils as workspace --- package-lock.json | 164 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b18a604978..1aed891ee7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "resources/build/version", "core/include/ldml", "developer/src/common/web/test-helpers", + "developer/src/common/web/utils", "developer/src/kmc-analyze", "developer/src/kmc-keyboard-info", "developer/src/kmc-kmn", @@ -696,6 +697,162 @@ "typescript": ">=2.7" } }, + "developer/src/common/web/utils": { + "license": "MIT", + "dependencies": { + "@keymanapp/common-types": "*" + }, + "devDependencies": { + "@types/node": "^20.4.1", + "c8": "^7.12.0", + "chai": "^4.3.4", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.9.5" + } + }, + "developer/src/common/web/utils/node_modules/@types/node": { + "version": "20.5.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", + "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", + "dev": true + }, + "developer/src/common/web/utils/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "developer/src/common/web/utils/node_modules/js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "developer/src/common/web/utils/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "developer/src/common/web/utils/node_modules/mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "developer/src/common/web/utils/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "developer/src/common/web/utils/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "developer/src/common/web/utils/node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "developer/src/common/web/utils/node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "developer/src/kmc": { "name": "@keymanapp/kmc", "license": "MIT", @@ -2964,11 +3121,8 @@ "link": true }, "node_modules/@keymanapp/developer-utils": { - "resolved": "file:developer/src/common/web/utils", - "license": "MIT", - "dependencies": { - "@keymanapp/common-types": "*" - } + "resolved": "developer/src/common/web/utils", + "link": true }, "node_modules/@keymanapp/hextobin": { "resolved": "common/tools/hextobin", diff --git a/package.json b/package.json index 7f187b9a884..ac8c23dec5d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "resources/build/version", "core/include/ldml", "developer/src/common/web/test-helpers", + "developer/src/common/web/utils", "developer/src/kmc-analyze", "developer/src/kmc-keyboard-info", "developer/src/kmc-kmn", From 6590658da400987f7792f2512bb5c98f8103c539 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 10:12:12 +0700 Subject: [PATCH 034/207] chore(developer): add developer-utils package to publish --- developer/src/kmc/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 2f8045f455e..4d7e50fd794 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -20,6 +20,7 @@ builder_describe "Build Keyman Keyboard Compiler kmc" \ "@/common/include" \ "@/common/web/keyman-version" \ "@/common/web/types" \ + "@/developer/src/common/web/utils" \ "@/developer/src/kmc-analyze" \ "@/developer/src/kmc-keyboard-info" \ "@/developer/src/kmc-kmn" \ @@ -93,6 +94,7 @@ readonly PACKAGES=( developer/src/kmc-model developer/src/kmc-model-info developer/src/kmc-package + developer/src/common/web/utils ) if builder_start_action bundle; then From 508588ef260a107aff36e18d17af00e84f708f06 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 15:58:22 +0700 Subject: [PATCH 035/207] chore(developer): developer-utils as private package --- developer/src/common/web/utils/package.json | 2 +- developer/src/kmc-keyboard-info/package.json | 3 +++ developer/src/kmc-model-info/package.json | 3 +++ developer/src/kmc/build.sh | 1 - package-lock.json | 8 +++++++- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 277066b0464..b09b667e714 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -1,5 +1,6 @@ { "name": "@keymanapp/developer-utils", + "private": true, "description": "Keyman Developer utilities", "type": "module", "exports": { @@ -19,7 +20,6 @@ "scripts": { "build": "tsc -b" }, - "license": "MIT", "dependencies": { "@keymanapp/common-types": "*" }, diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 7b028ec1294..f1f787b9cd2 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -28,6 +28,9 @@ "@keymanapp/kmc-package": "*", "@keymanapp/developer-utils": "*" }, + "bundleDependencies": [ + "@keymanapp/developer-utils" + ], "devDependencies": { "@types/chai": "^4.3.5", "@types/mocha": "^5.2.7", diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index e0499ad0e60..cddc61af63a 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -35,6 +35,9 @@ "@keymanapp/models-types": "*", "@keymanapp/developer-utils": "*" }, + "bundleDependencies": [ + "@keymanapp/developer-utils" + ], "devDependencies": { "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 4d7e50fd794..8b6a87de8a4 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -94,7 +94,6 @@ readonly PACKAGES=( developer/src/kmc-model developer/src/kmc-model-info developer/src/kmc-package - developer/src/common/web/utils ) if builder_start_action bundle; then diff --git a/package-lock.json b/package-lock.json index 1aed891ee7e..3e0c5a43c14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -698,7 +698,7 @@ } }, "developer/src/common/web/utils": { - "license": "MIT", + "name": "@keymanapp/developer-utils", "dependencies": { "@keymanapp/common-types": "*" }, @@ -1153,6 +1153,9 @@ }, "developer/src/kmc-keyboard-info": { "name": "@keymanapp/kmc-keyboard-info", + "bundleDependencies": [ + "@keymanapp/developer-utils" + ], "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", @@ -2011,6 +2014,9 @@ }, "developer/src/kmc-model-info": { "name": "@keymanapp/kmc-model-info", + "bundleDependencies": [ + "@keymanapp/developer-utils" + ], "license": "MIT", "dependencies": { "@keymanapp/common-types": "*", From 0d96437cb8bf6efc38834b0c21318d977c3e8dce Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 20:30:43 +0800 Subject: [PATCH 036/207] chore: workaround npm/cli#3466 when bundling internal deps Works around npm/cli#3466 when bundling internal dependencies using the bundleDependencies package.json property. This change works in tandem with the npm pack/publish process -- when we run `developer/src/kmc/build.sh publish` (or `pack`), we end up with `npm version` stomping on all our package.json files, so the repo is dirty after this. We need a copy of the top-level package.json before this stomping happens, in order to get a simple map of the location of each of our internal dependencies, from the `dependencies` property (it would be possible to figure this out with a lot more parsing of our package.json files, but this is simpler). This means, in future, we should avoid publishing our internal dependencies such as those under common/ to npm, as they serve no practical purpose there. --- developer/src/kmc/build.sh | 10 +++++ resources/build/build-utils-ci.inc.sh | 63 ++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 8b6a87de8a4..b9f4ea32dd6 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -133,6 +133,10 @@ fi if builder_start_action publish; then . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" + # To ensure that we cache the top-level package.json, we must call this before + # the global publish + builder_publish_cleanup + # For now, kmc will have responsibility for publishing keyman-version and # common-types, as well as all the other dependent modules. In the future, we # should probably have a top-level npm publish script that publishes all @@ -143,14 +147,20 @@ if builder_start_action publish; then # Finally, publish kmc builder_publish_to_npm + builder_publish_cleanup builder_finish_action success publish elif builder_start_action pack; then . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" + # To ensure that we cache the top-level package.json, we must call this before + # the global pack + builder_publish_cleanup + for package in "${PACKAGES[@]}"; do "$KEYMAN_ROOT/$package/build.sh" pack $DRY_RUN done builder_publish_to_pack + builder_publish_cleanup builder_finish_action success pack fi diff --git a/resources/build/build-utils-ci.inc.sh b/resources/build/build-utils-ci.inc.sh index cab449a4636..973f6f8aca7 100644 --- a/resources/build/build-utils-ci.inc.sh +++ b/resources/build/build-utils-ci.inc.sh @@ -100,7 +100,9 @@ function _builder_publish_npm_package() { dry_run=--dry-run fi + _builder_publish_cache_package_json _builder_write_npm_version + _builder_prepublish # Note: In either case, npm publish MUST be given --access public to publish a # package in the @keymanapp scope on the public npm package index. @@ -141,9 +143,68 @@ function _builder_write_npm_version() { . + (try { dependencies: (.dependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + (try { devDependencies: (.devDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { bundleDependencies: (.bundleDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + (try { optionalDependencies: (.optionalDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) ' > "${line}_" mv -f "${line}_" "$line" done +} + +# +# Due to https://github.com/npm/cli/issues/3466, we manually create all +# bundleDependencies (__NOT__ bundledDependencies, beware typos) from +# the target's package.json in its node_modules folder. Must run from +# the target's folder. +# +function _builder_prepublish() { + mkdir -p node_modules/@keymanapp + local packages=($(cat package.json | "$JQ" --raw-output '.bundleDependencies | join(" ")')) + local package + + # For each @keymanapp/ package, we'll do a local symlink, note that Windows + # mklink is internal to cmd! + for package in "${packages[@]}"; do + if [[ $package =~ ^@keymanapp/ ]]; then + # Creating local symlink under node_modules + local link_source=node_modules/$package + + # lookup the link_target from top-level package.json/dependencies + local link_target="$(cat "$KEYMAN_ROOT/builder_package_publish.json" | jq -r .dependencies.\"$package\")" + + if [[ $link_target =~ ^file: ]]; then + link_target="$KEYMAN_ROOT"/${link_target#file:} + + builder_echo "Manually linking $link_source -> $link_target (see https://github.com/npm/cli/issues/3466)" + rm -rf $link_source + if [[ $BUILDER_OS == win ]]; then + link_source="$(cygpath -w "$link_source")" + link_target="$(cygpath -w "$link_target")" + cmd //c mklink //j "$link_source" "$link_target" + else + ln -sr "$link_target" "$link_source" + fi + fi + fi + done +} + +# +# We need to cache /package.json before npm version gets its sticky fingers on +# it, because afterwards, we lose the file: paths that help us to resolve +# dependencies easily. Part of the https://github.com/npm/cli/issues/3466 +# workaround. +# +function _builder_publish_cache_package_json() { + if [[ -f "$KEYMAN_ROOT/builder_package_publish.json" ]]; then + return 0 + fi + + if "$JQ" -e '.version' "$KEYMAN_ROOT/developer/src/kmc/package.json" > /dev/null; then + builder_die "npm version has already been run. Revert the version changes to all package.json files before re-running" + fi + + cp "$KEYMAN_ROOT/package.json" "$KEYMAN_ROOT/builder_package_publish.json" +} + +function builder_publish_cleanup() { + rm -f "$KEYMAN_ROOT/builder_package_publish.json" } \ No newline at end of file From d2f4e5f2c73efbd1e77b857cdd0750cdd1128726 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 18:00:13 +0400 Subject: [PATCH 037/207] feat(developer): add --log-format to kmc Adds logFormat of tsv vs formatted for kmc, in order to allow for "porcelain" style log formatting when we interop with TIKE. --- common/web/types/src/main.ts | 4 ++- .../web/types/src/util/compiler-interfaces.ts | 25 ++++++++++++++++++- developer/src/kmc/src/commands/build.ts | 13 +++++----- .../src/commands/buildClasses/BuildProject.ts | 6 ++--- .../src/messages/infrastructureMessages.ts | 20 +++++++-------- .../src/kmc/src/util/NodeCompilerCallbacks.ts | 23 ++++++++++++++++- developer/src/kmc/src/util/baseOptions.ts | 7 +++++- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 430b88cdc3d..a814903ab75 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -25,7 +25,9 @@ export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, Compile CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions, CompilerError, CompilerMessageSpec, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName, compilerExceptionToString, compilerErrorFormatCode, - compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS } from './util/compiler-interfaces.js'; + compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS, + ALL_COMPILER_LOG_FORMATS, CompilerLogFormat, + } from './util/compiler-interfaces.js'; export { CommonTypesMessages } from './util/common-events.js'; export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; diff --git a/common/web/types/src/util/compiler-interfaces.ts b/common/web/types/src/util/compiler-interfaces.ts index 3283fb25981..b27a170ab34 100644 --- a/common/web/types/src/util/compiler-interfaces.ts +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -74,11 +74,20 @@ export class CompilerError { * @param filename * @returns */ - static formatFilename(filename: string): string { + static formatFilename(filename: string, options?: { + fullPath?: boolean, + forwardSlashes?: boolean + }): string { if(!filename) { return ''; } + if(options?.fullPath) { + return options?.forwardSlashes ? + filename.replaceAll(/\\/g, '/') : + filename.replaceAll(/\//g, '\\'); + } + let x = filename.lastIndexOf('/'); if(x < 0) { x = filename.lastIndexOf('\\'); @@ -239,6 +248,7 @@ export interface CompilerFileSystemCallbacks { export interface CompilerCallbackOptions { logLevel?: CompilerLogLevel; + logFormat?: CompilerLogFormat; color?: boolean; // null or undefined == use console default compilerWarningsAsErrors?: boolean; }; @@ -355,6 +365,10 @@ export interface CompilerBaseOptions { * all messages are still reported to the internal log) */ logLevel?: CompilerLogLevel; + /** + * Format of output for log to console + */ + logFormat?: CompilerLogFormat; /** * Optional output file for activities that generate output */ @@ -390,6 +404,7 @@ export interface CompilerOptions extends CompilerBaseOptions { export const defaultCompilerOptions: CompilerOptions = { logLevel: 'info', + logFormat: 'formatted', // outFile: (undefined) saveDebug: false, shouldAddCompilerVersion: true, @@ -443,3 +458,11 @@ export const compilerLogLevelToSeverity: {[index in CompilerLogLevel]: number} = 'info': CompilerErrorSeverity.Info, 'debug': CompilerErrorSeverity.Info }; + +export const ALL_COMPILER_LOG_FORMATS = [ + 'tsv', + 'formatted' +] as const; + +type CompilerLogFormatTuple = typeof ALL_COMPILER_LOG_FORMATS; +export type CompilerLogFormat = CompilerLogFormatTuple[number]; diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 9568da70209..931833c714a 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -19,6 +19,7 @@ function commandOptionsToCompilerOptions(options: any): CompilerOptions { // CompilerBaseOptions outFile: options.outFile, logLevel: options.logLevel, + logFormat: options.logFormat, color: options.color, // CompilerOptions shouldAddCompilerVersion: options.compilerVersion, @@ -112,22 +113,22 @@ async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, o if(fs.statSync(filename).isDirectory()) { buildFilename = path.join(buildFilename, path.basename(buildFilename) + KeymanFileTypes.Source.Project); } - buildFilename = path.relative(process.cwd(), buildFilename).replace(/\\/g, '/'); + const relativeFilename = path.relative(process.cwd(), buildFilename).replace(/\\/g, '/'); const callbacks = new CompilerFileCallbacks(buildFilename, options, parentCallbacks); - callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename:buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename:buildFilename, relativeFilename})); let result = await builder.build(filename, callbacks, options); result = result && !callbacks.hasFailureMessage(); if(result) { callbacks.reportMessage(builder instanceof BuildProject - ? InfrastructureMessages.Info_ProjectBuiltSuccessfully({filename:buildFilename}) - : InfrastructureMessages.Info_FileBuiltSuccessfully({filename:buildFilename}) + ? InfrastructureMessages.Info_ProjectBuiltSuccessfully({filename:buildFilename, relativeFilename}) + : InfrastructureMessages.Info_FileBuiltSuccessfully({filename:buildFilename, relativeFilename}) ); } else { callbacks.reportMessage(builder instanceof BuildProject - ? InfrastructureMessages.Info_ProjectNotBuiltSuccessfully({filename:buildFilename}) - : InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename:buildFilename}) + ? InfrastructureMessages.Info_ProjectNotBuiltSuccessfully({filename:buildFilename, relativeFilename}) + : InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename:buildFilename, relativeFilename}) ); } diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 380be1b7245..db74ccec066 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -87,7 +87,7 @@ class ProjectBuilder { const buildFilename = path.relative(process.cwd(), infile).replace(/\\/g, '/'); const callbacks = new CompilerFileCallbacks(buildFilename, options, this.callbacks); - callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: infile, relativeFilename:buildFilename})); fs.mkdirSync(path.dirname(options.outFile), {recursive:true}); @@ -98,9 +98,9 @@ class ProjectBuilder { result = result && !callbacks.hasFailureMessage(this.options.compilerWarningsAsErrors ?? this.project.options.compilerWarningsAsErrors); if(result) { - callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: infile, relativeFilename:buildFilename})); } else { - callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: infile, relativeFilename: buildFilename})); } return result; diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index ee828dae751..49b85404dd4 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -12,8 +12,8 @@ export class InfrastructureMessages { static FATAL_UnexpectedException = SevFatal | 0x0001; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_BuildingFile = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_BuildingFile, - `Building ${o.filename}`)}); + static Info_BuildingFile = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_BuildingFile, + `Building ${o.relativeFilename}`)}); static INFO_BuildingFile = SevInfo | 0x0002; static Error_FileDoesNotExist = (o:{filename:string}) => m(this.ERROR_FileDoesNotExist, @@ -29,13 +29,13 @@ export class InfrastructureMessages { static ERROR_OutFileNotValidForProjects = SevError | 0x0005; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_FileBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_FileBuiltSuccessfully, - `${o.filename} built successfully.`)}); + static Info_FileBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_FileBuiltSuccessfully, + `${o.relativeFilename} built successfully.`)}); static INFO_FileBuiltSuccessfully = SevInfo | 0x0006; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_FileNotBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_FileNotBuiltSuccessfully, - `${o.filename} failed to build.`)}); + static Info_FileNotBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_FileNotBuiltSuccessfully, + `${o.relativeFilename} failed to build.`)}); static INFO_FileNotBuiltSuccessfully = SevInfo | 0x0007; static Error_InvalidProjectFile = (o:{message:string}) => m(this.ERROR_InvalidProjectFile, @@ -51,13 +51,13 @@ export class InfrastructureMessages { static ERROR_UnknownFileFormat = SevError | 0x000A; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_ProjectBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectBuiltSuccessfully, - `Project ${o.filename} built successfully.`)}); + static Info_ProjectBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectBuiltSuccessfully, + `Project ${o.relativeFilename} built successfully.`)}); static INFO_ProjectBuiltSuccessfully = SevInfo | 0x000B; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_ProjectNotBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectNotBuiltSuccessfully, - `Project ${o.filename} failed to build.`)}); + static Info_ProjectNotBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectNotBuiltSuccessfully, + `Project ${o.relativeFilename} failed to build.`)}); static INFO_ProjectNotBuiltSuccessfully = SevInfo | 0x000C; static Info_TooManyMessages = (o:{count:number}) => m(this.INFO_TooManyMessages, diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts index d5e15e0bccc..015e00b9c1e 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -154,6 +154,28 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { event.filename = this.messageFilename; } + this.printMessage(event); + } + + private printMessage(event: CompilerEvent) { + if(this.options.logFormat == 'tsv') { + this.printTsvMessage(event); + } else { + this.printFormattedMessage(event); + } + } + + private printTsvMessage(event: CompilerEvent) { + process.stdout.write([ + CompilerError.formatFilename(event.filename, {fullPath:true, forwardSlashes:false}), + CompilerError.formatLine(event.line), + CompilerError.formatSeverity(event.code), + CompilerError.formatCode(event.code), + CompilerError.formatMessage(event.message) + ].join('\t') + '\n'); + } + + private printFormattedMessage(event: CompilerEvent) { const severityColor = severityColors[CompilerError.severity(event.code)] ?? color.reset; const messageColor = this.messageSpecialColor(event) ?? color.reset; process.stdout.write( @@ -172,7 +194,6 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { // Special case: we'll add a blank line after project builds process.stdout.write('\n'); } - } /** diff --git a/developer/src/kmc/src/util/baseOptions.ts b/developer/src/kmc/src/util/baseOptions.ts index a53539a124b..67c81032946 100644 --- a/developer/src/kmc/src/util/baseOptions.ts +++ b/developer/src/kmc/src/util/baseOptions.ts @@ -1,4 +1,4 @@ -import { ALL_COMPILER_LOG_LEVELS } from "@keymanapp/common-types"; +import { ALL_COMPILER_LOG_FORMATS, ALL_COMPILER_LOG_LEVELS } from "@keymanapp/common-types"; import { Command, Option } from "commander"; import KEYMAN_VERSION from "@keymanapp/keyman-version"; @@ -23,6 +23,10 @@ export class BaseOptions { return program.addOption(new Option('-l, --log-level ', 'Log level').choices(ALL_COMPILER_LOG_LEVELS).default('info')); } + public static addLogFormat(program: Command) { + return program.addOption(new Option('-l, --log-format ', 'Log format').choices(ALL_COMPILER_LOG_FORMATS).default('formatted')); + } + public static addOutFile(program: Command) { return program.option('-o, --out-file ', 'Override the default path and filename for the output file') } @@ -31,6 +35,7 @@ export class BaseOptions { return [ this.addVersion, this.addLogLevel, + this.addLogFormat, this.addOutFile, ].reduce((p,f) => f(p), program); } From fb165a3f95099f8b0a33df7d98355cb30157bd44 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 18:16:25 +0400 Subject: [PATCH 038/207] feat(developer): use kmc instead of kmlmc in TIKE First step of integration of kmc into TIKE, establishing base classes and replacing the lexical model compiler wrapper with them. --- ...n.Developer.System.LexicalModelCompile.pas | 83 +----------- .../Keyman.Developer.System.KmcWrapper.pas | 121 ++++++++++++++++++ ....Developer.System.KeymanDeveloperPaths.pas | 14 ++ developer/src/tike/main/UfrmMessages.pas | 2 +- ...n.Developer.System.Project.ProjectFile.pas | 2 +- developer/src/tike/tike.dpr | 3 +- developer/src/tike/tike.dproj | 7 +- 7 files changed, 146 insertions(+), 86 deletions(-) create mode 100644 developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas diff --git a/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas b/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas index a3e07f03b23..e877e84f75a 100644 --- a/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas +++ b/developer/src/common/delphi/lexicalmodels/Keyman.Developer.System.LexicalModelCompile.pas @@ -3,95 +3,24 @@ interface uses - Keyman.Developer.System.Project.ProjectFile, - Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.KeymanDeveloperPaths; + Keyman.Developer.System.Project.ProjectFile; function CompileModelFile(ProjectFile: TProjectFile; infile, outfile: string; debug: Boolean): Boolean; implementation uses - System.Classes, - System.RegularExpressions, - System.SysUtils, - compile, - utilexecute; + Keyman.Developer.System.KmcWrapper; function CompileModelFile(ProjectFile: TProjectFile; infile, outfile: string; debug: Boolean): Boolean; var - s: TStringList; - logtext, cmdline: string; - ec: Integer; - line: string; - messageLine: TRegEx; - m: TMatch; - state: TProjectLogState; - msgFilename: string; - msgText: string; - msgCode: Integer; - msgLine: Integer; - msgType: string; + w: TKmcWrapper; begin - ec := 0; - logtext := ''; - - cmdline := Format('"%s" "%s" -o "%s"', [TKeymanDeveloperPaths.LexicalModelCompilerPath, infile, outfile]); - Result := TUtilExecute.Console(cmdline, ExtractFileDir(infile), logtext, ec); - - logtext := UTF8ToString(AnsiString(logtext)); - - if not Result then - begin - ProjectFile.Project.Log(plsError, infile, - Format('Compiler failed to start with error %d: %s', [GetLastError, SysErrorMessage(GetLastError)]), CERR_ERROR, 0); - end; - - Result := Result and (ec = 0); - - // Format of messages emitted from kmlmc, see errors.ts:printLogs: - // ' (): : ' - messageLine := TRegEx.Create('^(?:(.+) \((\d+)\): )?(Hint|Warning|Error|Fatal Error): ([a-fA-F0-9]+) (.+)$'); - s := TStringList.Create; + w := TKmcWrapper.Create; try - s.Text := logtext; - for line in s do - begin - m := messageLine.Match(line); - if m.Success then - begin - msgFilename := m.Groups[1].Value; - msgLine := StrToIntDef(m.Groups[2].Value, 0); - msgType := m.Groups[3].Value; - msgCode := StrToInt('$'+m.Groups[4].Value); - msgText := m.Groups[5].Value; - if msgType = 'Hint' then - state := plsHint - else if msgType = 'Warning' then - begin - state := plsWarning; - if ProjectFile.Project.Options.CompilerWarningsAsErrors then - Result := False; - end - else if msgType = 'Error' then - begin - state := plsError; - Result := False; - end - else if msgType = 'Fatal Error' then - begin - state := plsFatal; - Result := False; - end - else - state := plsInfo; - ProjectFile.Project.Log(state, msgFilename, msgText, msgCode, msgLine); - end - else - ProjectFile.Project.Log(plsInfo, infile, line, 0, 0); - end; + Result := w.Compile(ProjectFile, infile, outfile, debug); finally - s.Free; + w.Free; end; end; diff --git a/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas new file mode 100644 index 00000000000..60df52ee360 --- /dev/null +++ b/developer/src/tike/compile/Keyman.Developer.System.KmcWrapper.pas @@ -0,0 +1,121 @@ +unit Keyman.Developer.System.KmcWrapper; + +interface + +uses + Keyman.Developer.System.Project.ProjectFile; + +type + TKmcWrapper = class + public + function Compile(ProjectFile: TProjectFile; const infile, outfile: string; debug: Boolean): Boolean; + end; + +implementation + +uses + System.Classes, + System.SysUtils, + + Keyman.Developer.System.Project.ProjectLog, + Keyman.Developer.System.KeymanDeveloperPaths, + compile, + utilexecute; + +{ TKmcWrapper } + +function TKmcWrapper.Compile( + ProjectFile: TProjectFile; + const infile: string; + const outfile: string; + debug: Boolean +): Boolean; +var + s: TStringList; + logtext, cmdline: string; + ec: Integer; + line: string; + messageLine: TArray; + state: TProjectLogState; + msgFilename: string; + msgText: string; + msgCode: Integer; + msgLine: Integer; + msgType: string; +begin + ec := 0; + logtext := ''; + + // TODO: log-level as opt? + cmdline := Format('"%s" build --log-format tsv --log-level info "%s"', [TKeymanDeveloperPaths.KmcPath, infile]); + if outfile <> '' then + cmdline := cmdline + Format(' --out-file "%s"', [outfile]); + if ProjectFile.Project.Options.CompilerWarningsAsErrors then + cmdline := cmdline + ' --compiler-warnings-as-errors' + else + cmdline := cmdline + ' --no-compiler-warnings-as-errors'; + if not ProjectFile.Project.Options.WarnDeprecatedCode then + cmdline := cmdline + ' --no-warn-deprecated-code'; + + Result := TUtilExecute.Console(cmdline, ExtractFileDir(infile), logtext, ec); + + logtext := UTF8ToString(AnsiString(logtext)); + + if not Result then + begin + ProjectFile.Project.Log(plsError, infile, + Format('Compiler failed to start with error %d: %s', [GetLastError, SysErrorMessage(GetLastError)]), CERR_ERROR, 0); + end; + + Result := Result and (ec = 0); + + // TSV Format of messages emitted from kmc, see NodeCompilerCallbacks.ts:printTsvMessage: + // file line severity code message + // (tab separated) + s := TStringList.Create; + try + s.Text := logtext; + for line in s do + begin + if line.Trim = '' then + Continue; + messageLine := line.Split([#9]); + if Length(messageLine) = 5 then + begin + msgFilename := messageLine[0]; + msgLine := StrToIntDef(messageLine[1], 0); + msgType := messageLine[2]; + msgCode := StrToInt('$'+messageLine[3].Substring(2)); // KM12345 + msgText := messageLine[4]; + + if msgType = 'hint' then + state := plsHint + else if msgType = 'warn' then + begin + state := plsWarning; + if ProjectFile.Project.Options.CompilerWarningsAsErrors then + Result := False; + end + else if msgType = 'error' then + begin + state := plsError; + Result := False; + end + else if msgType = 'fatal' then + begin + state := plsFatal; + Result := False; + end + else // assume msgType = 'info' + state := plsInfo; + ProjectFile.Project.Log(state, msgFilename, msgText, msgCode, msgLine); + end + else + ProjectFile.Project.Log(plsInfo, infile, line, 0, 0); + end; + finally + s.Free; + end; +end; + +end. diff --git a/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas b/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas index 0e6b63c3794..2bae4475182 100644 --- a/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas +++ b/developer/src/tike/main/Keyman.Developer.System.KeymanDeveloperPaths.pas @@ -16,6 +16,9 @@ TKeymanDeveloperPaths = class sealed const S_LexicalModelCompiler = 'kmlmc.cmd'; class function LexicalModelCompilerPath: string; static; + const S_Kmc = 'kmc.cmd'; + class function KmcPath: string; static; + const S_ServerConfigJson = 'config.json'; class function ServerDataPath: string; static; class function ServerPath: string; static; @@ -54,6 +57,17 @@ class function TKeymanDeveloperPaths.ServerPath: string; else Result := ExtractFilePath(ParamStr(0)) + 'server\'; end; +class function TKeymanDeveloperPaths.KmcPath: string; +var + KeymanRoot: string; +begin + if TKeymanPaths.RunningFromSource(KeymanRoot) + then Result := KeymanRoot + 'developer\src\tike\' + else Result := ExtractFilePath(ParamStr(0)); + + Result := Result + S_Kmc; +end; + class function TKeymanDeveloperPaths.LexicalModelCompilerPath: string; var KeymanRoot: string; diff --git a/developer/src/tike/main/UfrmMessages.pas b/developer/src/tike/main/UfrmMessages.pas index be0e38a90a4..9db9586855d 100644 --- a/developer/src/tike/main/UfrmMessages.pas +++ b/developer/src/tike/main/UfrmMessages.pas @@ -188,7 +188,7 @@ procedure TfrmMessages.memoMessageDblClick(Sender: TObject); begin pf := FGlobalProject.FindFile(FFileName); if Assigned(pf) then (pf.UI as TProjectFileUI).DefaultEvent(Self) // I4687 - else frmKeymanDeveloper.OpenFile(FFileName, False); + else if FileExists(FFileName) then frmKeymanDeveloper.OpenFile(FFileName, False); frm := frmKeymanDeveloper.FindEditorByFileName(FFileName); if not Assigned(frm) then begin diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFile.pas b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFile.pas index 933d6282ba3..84fd6f6ebc9 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFile.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFile.pas @@ -1096,7 +1096,7 @@ function TProject.FindFile(AFileName: string): TProjectFile; i: Integer; begin for i := 0 to Files.Count - 1 do - if Files[i].FileName = AFileName then + if SameText(Files[i].FileName, AFileName) then begin Result := Files[i]; Exit; diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index df770511b9c..3fae5dadf1d 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -287,7 +287,8 @@ uses Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}, Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}, - Keyman.Developer.UI.UfrmEditPackageWebFonts in 'dialogs\packageWebFonts\Keyman.Developer.UI.UfrmEditPackageWebFonts.pas' {frmEditPackageWebFonts}; + Keyman.Developer.UI.UfrmEditPackageWebFonts in 'dialogs\packageWebFonts\Keyman.Developer.UI.UfrmEditPackageWebFonts.pas' {frmEditPackageWebFonts}, + Keyman.Developer.System.KmcWrapper in 'compile\Keyman.Developer.System.KmcWrapper.pas'; {$R *.RES} {$R ICONS.RES} diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index 88564a6ce45..c7ac6c36953 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -565,6 +565,7 @@
frmEditPackageWebFonts
dfm + Cfg_2 @@ -626,12 +627,6 @@ False - - - tike.exe - true - - tike.exe From c91b271e6795935106eac5ac6a1d2221d7871b57 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 19:44:38 +0400 Subject: [PATCH 039/207] feat(developer): use kmc to compile packages in TIKE Also removes unused FSilent flag (was always False). --- ...er.System.Project.kpsProjectFileAction.pas | 57 +++---------------- ....Developer.UI.Project.kpsProjectFileUI.pas | 46 ++++++--------- 2 files changed, 26 insertions(+), 77 deletions(-) diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas index 5a88a47922b..a8a72783c1c 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas @@ -10,6 +10,7 @@ interface Keyman.Developer.System.Project.ProjectFiles, Keyman.Developer.System.Project.ProjectFileType, Keyman.Developer.System.Project.ProjectLog, + Keyman.Developer.System.KmcWrapper, KPSFile; type @@ -19,7 +20,7 @@ TkpsProjectFileAction = class(TkpsProjectFile) public function CompilePackageInstaller(APack: TKPSFile; FSilent: Boolean): Boolean; - function CompilePackage(APack: TKPSFile; FSilent: Boolean): Boolean; + function CompilePackage: Boolean; function Clean: Boolean; end; @@ -27,63 +28,21 @@ implementation uses compile, - CompilePackage, CompilePackageInstaller, Keyman.Developer.System.ValidateKpsFile, PackageInfo, utilexecute; -function TkpsProjectFileAction.CompilePackage(APack: TKPSFile; FSilent: Boolean): Boolean; +function TkpsProjectFileAction.CompilePackage: Boolean; var - pack: TKPSFile; - ASchemaPath: string; + w: TKmcWrapper; begin - HasCompileWarning := False; // I4706 - if APack = nil then - begin - pack := TKPSFile.Create; - pack.FileName := FileName; - pack.LoadXML; - end - else - pack := APack; - - ASchemaPath := ExtractFilePath(ParamStr(0)); - if (ASchemaPath <> '') and (FileExists(ASchemaPath + 'kps.xsd')) then - begin - if not TValidateKpsFile.Execute(FileName, ASchemaPath + 'kps.xsd', OwnerProject.Log) then - begin - Log(plsFailure, 'Package '+FileName+' had validation errors.', 0, 0); - Exit(False); - end; - end; - + w := TKmcWrapper.Create; try - try - Result := DoCompilePackage(pack, SelfMessage, FSilent, OwnerProject.Options.CheckFilenameConventions, TargetFilename); - if HasCompileWarning and (WarnAsError or OwnerProject.Options.CompilerWarningsAsErrors) then // I4706 - Result := False; - - if Result then - Log(plsSuccess, '''' + FileName + ''' compiled successfully to '''+TargetFileName+'''.', 0, 0) - else - begin - if FileExists(TargetFilename) then - System.SysUtils.DeleteFile(TargetFilename); - Log(plsFailure, '''' + FileName + ''' was not compiled successfully.', 0, 0); - end; - except - on E:Exception do - begin - Log(plsError, E.Message, CERR_Error, 0); - Log(plsFailure, '''' + FileName + ''' was not compiled successfully.', 0, 0); - Result := False; - end; - end; - + Result := w.Compile(Self, FileName, OutputFilename, False); + // TODO(lowpri): FDebug flag finally - if APack = nil then - pack.Free; + w.Free; end; end; diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas index da743acd67f..672a8c92bfa 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas @@ -34,12 +34,12 @@ TkpsProjectFileUI = class(TOpenableProjectFileUI) function TestPackageOnline: Boolean; function InstallPackage: Boolean; function UninstallPackage: Boolean; - function CompilePackage(FSilent: Boolean): Boolean; + function CompilePackage: Boolean; function CompilePackageInstaller(FSilent: Boolean): Boolean; function GetPack: TKPSFile; function GetProjectFile: TkpsProjectFileAction; - function TestPackageState(FCompiledName: string; FSilent: Boolean): Boolean; + function TestPackageState(FCompiledName: string): Boolean; public function DoAction(action: TProjectFileAction; FSilent: Boolean): Boolean; override; property ProjectFile: TkpsProjectFileAction read GetProjectFile; @@ -69,13 +69,13 @@ implementation KeymanDeveloperUtils, PackageInfo; -function TkpsProjectFileUI.CompilePackage(FSilent: Boolean): Boolean; +function TkpsProjectFileUI.CompilePackage: Boolean; begin Result := False; if ProjectFile.Modified then if not modActionsMain.actFileSave.Execute then Exit; - Result := ProjectFile.CompilePackage(GetPack, FSilent); + Result := ProjectFile.CompilePackage; if Result and TServerDebugAPI.Running and @@ -95,7 +95,7 @@ function TkpsProjectFileUI.CompilePackageInstaller(FSilent: Boolean): Boolean; function TkpsProjectFileUI.DoAction(action: TProjectFileAction; FSilent: Boolean): Boolean; begin case action of - pfaCompile: Result := CompilePackage(FSilent); + pfaCompile: Result := CompilePackage; pfaInstall: Result := InstallPackage; pfaUninstall: Result := UninstallPackage; pfaCompileInstaller: Result := CompilePackageInstaller(FSilent); @@ -124,7 +124,7 @@ function TkpsProjectFileUI.InstallPackage: Boolean; begin Result := False; FCompiledName := ProjectFile.TargetFilename; - if not TestPackageState(FCompiledName, False) then Exit; + if not TestPackageState(FCompiledName) then Exit; KeymanDeveloperUtils.InstallPackage(FCompiledName, True); Result := True; end; @@ -158,39 +158,29 @@ function TkpsProjectFileUI.UninstallPackage: Boolean; Result := KeymanDeveloperUtils.UninstallPackage(ChangeFileExt(ExtractFileName(ProjectFile.FileName), '')); end; -function TkpsProjectFileUI.TestPackageState(FCompiledName: string; FSilent: Boolean): Boolean; +function TkpsProjectFileUI.TestPackageState(FCompiledName: string): Boolean; var ftkps, ftkmp: TDateTime; begin Result := False; if not FileExists(FCompiledName) then - if FSilent then - begin - if not CompilePackage(FSilent) then Exit; - end - else - case MessageDlg('You need to compile the keyboard before you can test it. Compile now?', - mtConfirmation, mbOkCancel, 0) of - mrOk: if not CompilePackage(FSilent) then Exit; - mrCancel: Exit; - end; + case MessageDlg('You need to compile the keyboard before you can test it. Compile now?', + mtConfirmation, mbOkCancel, 0) of + mrOk: if not CompilePackage then Exit; + mrCancel: Exit; + end; FileAge(ProjectFile.FileName, ftkps); FileAge(FCompiledName, ftkmp); if ProjectFile.Modified or (ftkps > ftkmp) then - if FSilent then - begin - if not CompilePackage(FSilent) then Exit; - end - else - case MessageDlg('The source file has changed. Recompile before testing?', - mtConfirmation, mbYesNoCancel, 0) of - mrYes: if not CompilePackage(FSilent) then Exit; - mrNo: ; - mrCancel: Exit; - end; + case MessageDlg('The source file has changed. Recompile before testing?', + mtConfirmation, mbYesNoCancel, 0) of + mrYes: if not CompilePackage then Exit; + mrNo: ; + mrCancel: Exit; + end; Result := True; end; From 41f370ede6792a927d90661defb3252ee25bb73c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 20:23:43 +0400 Subject: [PATCH 040/207] chore(developer): remove duplicate outFile parameter Consolidates the outFile parameter that was duplicated in kmc-kmn. --- .../src/util/get-osk-from-kmn-file.ts | 4 +--- developer/src/kmc-kmn/src/compiler/compiler.ts | 15 ++++++++------- .../src/kmc-kmn/test/kmw/test-kmw-compiler.ts | 3 +-- developer/src/kmc-kmn/test/test-compiler.ts | 18 +++++++++--------- developer/src/kmc-kmn/test/test-messages.ts | 6 ++---- .../commands/buildClasses/BuildKmnKeyboard.ts | 9 ++++----- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts b/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts index 686dcbd4f90..2df34e9dae0 100644 --- a/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts +++ b/developer/src/kmc-analyze/src/util/get-osk-from-kmn-file.ts @@ -14,9 +14,7 @@ export async function getOskFromKmnFile(callbacks: CompilerCallbacks, filename: return null; } - // Note, output filename here is just to provide path data, - // as nothing is written to disk - let result = kmnCompiler.runCompiler(filename, filename + '.tmp', { + let result = kmnCompiler.runCompiler(filename, { shouldAddCompilerVersion: false, saveDebug: false, }); diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 33d1b545f57..ccea91b99bf 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -140,9 +140,8 @@ export class KmnCompiler implements UnicodeSetParser { return true; } - // TODO: use outFile from options - public run(infile: string, outfile: string, options?: KmnCompilerOptions): boolean { - let result = this.runCompiler(infile, outfile, options); + public run(infile: string, options?: KmnCompilerOptions): boolean { + let result = this.runCompiler(infile, options); if(result) { if(result.kmx) { this.callbacks.fs.writeFileSync(result.kmx.filename, result.kmx.data); @@ -235,7 +234,7 @@ export class KmnCompiler implements UnicodeSetParser { return new Uint8Array(new Uint8Array(Module.HEAP8.buffer, offset, size)); } - public runCompiler(infile: string, outfile: string, options: KmnCompilerOptions): CompilerResult { + public runCompiler(infile: string, options: KmnCompilerOptions): CompilerResult { if(!this.verifyInitialized()) { /* c8 ignore next 2 */ return null; @@ -243,6 +242,8 @@ export class KmnCompiler implements UnicodeSetParser { options = {...baseOptions, ...options}; + options.outFile = options.outFile ?? infile.replace(/\.kmn$/i, '.kmx'); + (globalThis as any)[this.callbackID] = { message: this.compilerMessageCallback, loadFile: this.loadFileCallback @@ -267,7 +268,7 @@ export class KmnCompiler implements UnicodeSetParser { if(result.extra.targets & COMPILETARGETS_KMX) { result.kmx = { - filename: outfile, + filename: options.outFile, data: this.copyWasmBuffer(wasm_result.kmx, wasm_result.kmxSize) }; } @@ -282,7 +283,7 @@ export class KmnCompiler implements UnicodeSetParser { } if(result.extra.kvksFilename) { - result.kvk = this.runKvkCompiler(result.extra.kvksFilename, infile, outfile, result.displayMap); + result.kvk = this.runKvkCompiler(result.extra.kvksFilename, infile, options.outFile, result.displayMap); if(!result.kvk) { return null; } @@ -302,7 +303,7 @@ export class KmnCompiler implements UnicodeSetParser { kmw_result.displayMap = result.displayMap; // we can safely re-use the kmx compile displayMap const web_kmx = this.copyWasmBuffer(wasm_result.kmx, wasm_result.kmxSize); - result.js = this.runWebCompiler(infile, outfile, web_kmx, result.kvk?.data, kmw_result, options); + result.js = this.runWebCompiler(infile, options.outFile, web_kmx, result.kvk?.data, kmw_result, options); if(!result.js) { return null; } diff --git a/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts b/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts index cb9e101c491..42aa489a911 100644 --- a/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts +++ b/developer/src/kmc-kmn/test/kmw/test-kmw-compiler.ts @@ -19,7 +19,6 @@ const debug=false; const generateTestFilenames = (id: string) => ({ fixture: fixturesDir + id + KeymanFileTypes.Binary.WebKeyboard, source: fixturesDir + id + KeymanFileTypes.Source.KeymanKeyboard, - intermediate: fixturesDir + id + KeymanFileTypes.Binary.Keyboard, binary: fixturesDir + id + '.test' + KeymanFileTypes.Binary.WebKeyboard }); @@ -67,7 +66,7 @@ describe('KeymanWeb Compiler', function() { function run_test_keyboard(kmnCompiler: KmnCompiler, id: string): { result: CompilerResult, actualCode: string, actual: ETLResult, expectedCode: string, expected: ETLResult } { const filenames = generateTestFilenames(id); - let result = kmnCompiler.runCompiler(filenames.source, filenames.intermediate, { + let result = kmnCompiler.runCompiler(filenames.source, { shouldAddCompilerVersion: false, saveDebug: true, }); diff --git a/developer/src/kmc-kmn/test/test-compiler.ts b/developer/src/kmc-kmn/test/test-compiler.ts index 9f97560679d..c69ae89a95a 100644 --- a/developer/src/kmc-kmn/test/test-compiler.ts +++ b/developer/src/kmc-kmn/test/test-compiler.ts @@ -38,12 +38,12 @@ describe('Compiler class', function() { const fixtureName = baselineDir + 'k_000___null_keyboard.kmx'; const infile = baselineDir + 'k_000___null_keyboard.kmn'; - const outfile = __dirname + '/k_000___null_keyboard.kmx'; + const outFile = __dirname + '/k_000___null_keyboard.kmx'; - assert(compiler.run(infile, outfile, {saveDebug: true, shouldAddCompilerVersion: false})); + assert(compiler.run(infile, {saveDebug: true, outFile, shouldAddCompilerVersion: false})); - assert(fs.existsSync(outfile)); - const outfileData = fs.readFileSync(outfile); + assert(fs.existsSync(outFile)); + const outfileData = fs.readFileSync(outFile); const fixtureData = fs.readFileSync(fixtureName); assert.equal(outfileData.byteLength, fixtureData.byteLength); assert.deepEqual(outfileData, fixtureData); @@ -62,12 +62,12 @@ describe('Compiler class', function() { if(file.match(/\.kmx$/)) { const fixtureName = baselineDir + file; const infile = baselineDir + file.replace(/x$/, 'n'); - const outfile = __dirname + '/' + file; + const outFile = __dirname + '/' + file; - assert(compiler.run(infile, outfile, {saveDebug: true, shouldAddCompilerVersion: false})); + assert(compiler.run(infile, {saveDebug: true, outFile, shouldAddCompilerVersion: false})); - assert(fs.existsSync(outfile)); - const outfileData = fs.readFileSync(outfile); + assert(fs.existsSync(outFile)); + const outfileData = fs.readFileSync(outFile); const fixtureData = fs.readFileSync(fixtureName); assert.equal(outfileData.byteLength, fixtureData.byteLength); assert.deepEqual(outfileData, fixtureData); @@ -89,7 +89,7 @@ describe('Compiler class', function() { const resultingKmxfile = __dirname + '/caps_lock_layer_3620.kmx'; const resultingKvkfile = __dirname + '/caps_lock_layer_3620.kvk'; - assert.isTrue(compiler.run(infile, resultingKmxfile, {saveDebug: true, shouldAddCompilerVersion: false})); + assert.isTrue(compiler.run(infile, {saveDebug: true, shouldAddCompilerVersion: false})); assert.isTrue(fs.existsSync(resultingKmxfile)); assert.isTrue(fs.existsSync(resultingKvkfile)); diff --git a/developer/src/kmc-kmn/test/test-messages.ts b/developer/src/kmc-kmn/test/test-messages.ts index 88edc9b8f09..72624f9599c 100644 --- a/developer/src/kmc-kmn/test/test-messages.ts +++ b/developer/src/kmc-kmn/test/test-messages.ts @@ -1,11 +1,10 @@ import 'mocha'; -import path from 'path'; import { assert } from 'chai'; import { CompilerMessages } from '../src/compiler/messages.js'; import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; import { makePathToFixture } from './helpers/index.js'; import { KmnCompiler } from '../src/main.js'; -import { CompilerErrorNamespace, KeymanFileTypes } from '@keymanapp/common-types'; +import { CompilerErrorNamespace } from '@keymanapp/common-types'; describe('CompilerMessages', function () { const callbacks = new TestCompilerCallbacks(); @@ -28,10 +27,9 @@ describe('CompilerMessages', function () { assert(compiler.verifyInitialized()); const kmnPath = makePathToFixture(...fixture); - const outfile = path.basename(kmnPath, KeymanFileTypes.Source.KeymanKeyboard) + KeymanFileTypes.Binary.Keyboard; // Note: throwing away compile results (just to memory) - compiler.runCompiler(kmnPath, outfile, {saveDebug: true, shouldAddCompilerVersion: false}); + compiler.runCompiler(kmnPath, {saveDebug: true, shouldAddCompilerVersion: false}); if(messageId) { assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts index 0cf9d7d152c..e78291fdf9d 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts @@ -16,14 +16,13 @@ export class BuildKmnKeyboard extends BuildActivity { } // We need to resolve paths to absolute paths before calling kmc-kmn - let outfile = this.getOutputFilename(infile, options); + if(options.outFile) { + options.outFile = getPosixAbsolutePath(options.outFile); + } infile = getPosixAbsolutePath(infile); - outfile = getPosixAbsolutePath(outfile); - // TODO: Currently this only builds .kmn->.kmx, and targeting .js is as-yet unsupported - // TODO: outfile should be set in options only? - return compiler.run(infile, outfile, options); + return compiler.run(infile, options); } } From 44d57e80417c8102acc4bcd8301a72dbbd0b6546 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 20:40:16 +0400 Subject: [PATCH 041/207] feat(developer): compile kmn files with kmc in tike --- .../commands/buildClasses/BuildKmnKeyboard.ts | 8 +- ....Developer.System.Project.ProjectFiles.pas | 6 - ...er.System.Project.kmnProjectFileAction.pas | 132 +----------------- ...er.System.Project.kpsProjectFileAction.pas | 2 +- ....Developer.UI.Project.kmnProjectFileUI.pas | 3 - 5 files changed, 15 insertions(+), 136 deletions(-) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts index e78291fdf9d..c2a74b256b6 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts @@ -3,6 +3,7 @@ import { platform } from 'os'; import { KmnCompiler } from '@keymanapp/kmc-kmn'; import { CompilerOptions, CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types'; import { BuildActivity } from './BuildActivity.js'; +import * as fs from 'fs'; export class BuildKmnKeyboard extends BuildActivity { public get name(): string { return 'Keyman keyboard'; } @@ -21,7 +22,12 @@ export class BuildKmnKeyboard extends BuildActivity { } infile = getPosixAbsolutePath(infile); - + try { + fs.mkdirSync(path.dirname(options.outFile), {recursive: true}); + } catch(e) { + // TODO: error + return false; + } return compiler.run(infile, options); } } diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas index 87db87850ed..e17f366fb46 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas @@ -29,8 +29,6 @@ interface Keyman.Developer.System.Project.ProjectFile; type - TProjectFileActionCompileSuccessEvent = procedure(Sender: TObject; const SourceFilename, DestFilename: string) of object; - TShellProjectFile = class(TProjectFile) protected function GetRelativeOrder: Integer; override; @@ -38,12 +36,8 @@ TShellProjectFile = class(TProjectFile) end; TOpenableProjectFile = class(TShellProjectFile) - private - class var FOnCompileSuccess: TProjectFileActionCompileSuccessEvent; protected function GetRelativeOrder: Integer; override; - public - class property OnCompileSuccess: TProjectFileActionCompileSuccessEvent read FOnCompileSuccess write FOnCompileSuccess; end; implementation diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas index 2b6dea94ec5..e490bd7821e 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas @@ -14,13 +14,10 @@ interface type TkmnProjectFileAction = class(TkmnProjectFile) private - function CompileVisualKeyboard(const AKVKSourceFile, AKVKTargetFile: string): Boolean; procedure CheckFilenameConventions; public function CompileKeyboard: Boolean; function Clean: Boolean; - function DoSetCompilerOptions(addVersion, - useLegacyCompiler: Boolean): Boolean; end; implementation @@ -31,41 +28,10 @@ implementation System.Variants, Winapi.Windows, - CompileKeymanWeb, compile, Keyman.Developer.System.Project.ProjectLog, Keyman.System.KeyboardUtils, - VisualKeyboard; - -function TkmnProjectFileAction.CompileVisualKeyboard(const AKVKSourceFile, AKVKTargetFile: string): Boolean; -begin - with TVisualKeyboard.Create do - try - try - LoadFromFile(AKVKSourceFile); - except - on E:Exception do - begin - OwnerProject.Log(plsError, AKVKSourceFile, 'Invalid visual keyboard: '+E.Message, CERR_ERROR, 0); - Exit(False); - end; - end; - if not SameFileName(AKVKSourceFile, AKVKTargetFile) then - try - Header.AssociatedKeyboard := ChangeFileExt(ExtractFileName(Self.FileName), ''); - SaveToFile(AKVKTargetFile, kvksfBinary); - except - on E:Exception do - begin - OwnerProject.Log(plsError, AKVKSourceFile, 'Could not save visual keyboard '+AKVKSourceFile+' to '+AKVKTargetFile+': '+E.ClassName+','+E.Message, CERR_ERROR, 0); - Exit(False); - end; - end; - finally - Free; - end; - Result := True; -end; + Keyman.Developer.System.KmcWrapper; function TkmnProjectFileAction.Clean: Boolean; var @@ -80,8 +46,8 @@ function TkmnProjectFileAction.Clean: Boolean; begin FJS := TKeyboardUtils.GetKeymanWebCompiledFileName(FileName); CleanFile(FJS); // keyboard-x.y.js - CleanFile(ChangeFileExt(FJS, '') + '_load.js'); // keyboard-x.y_load.js - CleanFile(ChangeFileExt(FJS, '.json'), True); // keyboard-x.y_load.js +// CleanFile(ChangeFileExt(FJS, '') + '_load.js'); // keyboard-x.y_load.js +// CleanFile(ChangeFileExt(FJS, '.json'), True); // keyboard-x.y_load.js end; Result := True; @@ -108,99 +74,15 @@ procedure TkmnProjectFileAction.CheckFilenameConventions; end; end; -function TkmnProjectFileAction.DoSetCompilerOptions(addVersion: Boolean; useLegacyCompiler: Boolean): Boolean; -var - options: COMPILER_OPTIONS; -begin - TProject.CompilerMessageFile := Self; - options.dwSize := sizeof(COMPILER_OPTIONS); - options.ShouldAddCompilerVersion := addVersion; - // TODO: useLegacyCompiler means we switch to kmcmpdll vs kmc - if not SetCompilerOptions(@options, ProjectCompilerMessage) then - begin - Log(plsFatal, 'Unable to set compiler options', CERR_FATAL, 0); - Exit(False); - end; - Result := True; -end; - function TkmnProjectFileAction.CompileKeyboard: Boolean; var - KMXFileName: String; - FOutFileName: string; - ckw: TCompileKeymanWeb; - FKVKSourceFile: string; - FKVKTargetFile: string; - TargetNames: string; + w: TKmcWrapper; begin - TProject.CompilerMessageFile := Self; - HasCompileWarning := False; // I4706 - - FKVKSourceFile := ExtractFilePath(FileName) + ExtractFileName(KVKFileName); - FKVKTargetFile := OwnerProject.GetTargetFileName(ChangeFileExt(FKVKSourceFile, '.kvk'), FileName, FileVersion); - + w := TKmcWrapper.Create; try - CheckFilenameConventions; - - if Targets * KMXKeymanTargets <> [] then - begin - TargetNames := KeymanTargetsToNames(Targets * KMXKeymanTargets); - Log(plsInfo, Format('Compiling ''%s'' %sfor %s...', [Filename, IfThen(Debug, 'with debug symbols ', ''), TargetNames]), 0, 0); - - //compile the keyboard - KMXFileName := TargetFileName; - ForceDirectories(ExtractFileDir(KMXFileName)); - //KMXFileName := ChangeFileExt(FileName, '.kmx'); - Result := CompileKeyboardFile(PChar(FileName), PChar(KMXFileName), Debug, - OwnerProject.Options.CompilerWarningsAsErrors, OwnerProject.Options.WarnDeprecatedCode, // I4865 // I4866 - ProjectCompilerMessage) > 0; - - if Result then - begin - if KVKFileName <> '' then - Result := CompileVisualKeyboard(FKVKSourceFile, FKVKTargetFile); - end; - - if HasCompileWarning and (WarnAsError or OwnerProject.Options.CompilerWarningsAsErrors) then Result := False; // I4706 - - if Result - then Log(plsSuccess, Format('''%s'' was compiled successfully for %s to ''%s''.', [FileName, TargetNames, KMXFileName]), 0, 0) // I4504 - else Log(plsFailure, Format('''%s'' was not compiled successfully for %s.', [FileName, TargetNames]), 0, 0); // I4504 - end - else - Result := True; // I4564 - - // compile keyboard to web - if Result and (Targets * KMWKeymanTargets <> []) then - begin - TargetNames := KeymanTargetsToNames(Targets * KMWKeymanTargets); - Log(plsInfo, Format('Compiling ''%s'' %sfor %s...', [Filename, IfThen(Debug, 'with debug symbols ', ''), TargetNames]), 0, 0); - - ckw := TCompileKeymanWeb.Create; - try - FOutFilename := JSTargetFileName; - ForceDirectories(ExtractFileDir(FOutFileName)); - - if KVKFileName <> '' then - Result := CompileVisualKeyboard(FKVKSourceFile, FKVKTargetFile); - - if Result then - Result := ckw.Compile(OwnerProject, FileName, FOutFileName, Debug, ProjectCompilerMessageW); // I3681 // I4140 // I4865 // I4866 - - if HasCompileWarning and (WarnAsError or OwnerProject.Options.CompilerWarningsAsErrors) then Result := False; // I4706 - - if Result - then Log(plsSuccess, Format('''%s'' was compiled successfully for %s to ''%s''.', [FileName, TargetNames, FOutFileName]), 0, 0) // I4504 - else Log(plsFailure, Format('''%s'' was not compiled successfully for %s.', [FileName, TargetNames]), 0, 0); // I4504 - - if Result and Assigned(OnCompileSuccess) then - OnCompileSuccess(Self, FileName, FOutFileName); - finally - ckw.Free; - end; - end; + Result := w.Compile(Self, FileName, TargetFilename, Debug); finally - TProject.CompilerMessageFile := nil; + w.Free; end; end; diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas index a8a72783c1c..1f681f28c07 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas @@ -39,7 +39,7 @@ function TkpsProjectFileAction.CompilePackage: Boolean; begin w := TKmcWrapper.Create; try - Result := w.Compile(Self, FileName, OutputFilename, False); + Result := w.Compile(Self, FileName, TargetFilename, False); // TODO(lowpri): FDebug flag finally w.Free; diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.kmnProjectFileUI.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.kmnProjectFileUI.pas index 72c9b43fd47..24cfdd9e884 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.kmnProjectFileUI.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.kmnProjectFileUI.pas @@ -131,9 +131,6 @@ function TkmnProjectFileUI.CompileKeyboard(FSilent: Boolean): Boolean; frmMessages.DoShowForm; Result := - // Note: we do not surface the ability to exclude version information from - // the TIKE compiler; this must be done from kmcomp.exe. - ProjectFile.DoSetCompilerOptions(True, FKeymanDeveloperOptions.UseLegacyCompiler) and ProjectFile.CompileKeyboard; if Result and From ea4af39b13b307f0f7cfdf7b5a3dd06458b77ef7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 2 Sep 2023 07:11:18 +0400 Subject: [PATCH 042/207] chore(developer): kmcomp to build with kmcwrapper --- developer/src/kmcomp/kccompileproject.pas | 2 +- developer/src/kmcomp/kmcomp.dpr | 5 +++-- developer/src/kmcomp/kmcomp.dproj | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/developer/src/kmcomp/kccompileproject.pas b/developer/src/kmcomp/kccompileproject.pas index 2c401888b7d..1bc4a5cc904 100644 --- a/developer/src/kmcomp/kccompileproject.pas +++ b/developer/src/kmcomp/kccompileproject.pas @@ -109,7 +109,7 @@ function DoKCCompileProject(AProjectFilename: string; ADebug, AClean, AWarnAsErr if not kps.Clean then Exit; end else - if not kps.CompilePackage(nil, False) then Exit; + if not kps.CompilePackage then Exit; Found := True; end; diff --git a/developer/src/kmcomp/kmcomp.dpr b/developer/src/kmcomp/kmcomp.dpr index 30204a63dba..dd7fff9c2e1 100644 --- a/developer/src/kmcomp/kmcomp.dpr +++ b/developer/src/kmcomp/kmcomp.dpr @@ -28,7 +28,7 @@ uses utilsystem in '..\..\..\common\windows\delphi\general\utilsystem.pas', utildir in '..\..\..\common\windows\delphi\general\utildir.pas', utilkeyboard in '..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', - unicode in '..\..\..\common\windows\delphi\general\Unicode.pas', + Unicode in '..\..\..\common\windows\delphi\general\Unicode.pas', utilhttp in '..\..\..\common\windows\delphi\general\utilhttp.pas', GetOsVersion in '..\..\..\common\windows\delphi\general\GetOsVersion.pas', UfrmTike in '..\tike\main\UfrmTike.pas' {TikeForm: TTntForm}, @@ -116,7 +116,8 @@ uses Keyman.Developer.System.Project.UrlRenderer in '..\tike\project\Keyman.Developer.System.Project.UrlRenderer.pas', Keyman.Developer.System.ValidateRepoChanges in 'Keyman.Developer.System.ValidateRepoChanges.pas', Keyman.Developer.System.KeymanDeveloperPaths in '..\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas', - Keyman.Developer.System.ValidateKpsFile in '..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas'; + Keyman.Developer.System.ValidateKpsFile in '..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas', + Keyman.Developer.System.KmcWrapper in '..\tike\compile\Keyman.Developer.System.KmcWrapper.pas'; {$R icons.RES} {$R version.res} diff --git a/developer/src/kmcomp/kmcomp.dproj b/developer/src/kmcomp/kmcomp.dproj index 36c16e35356..bb802bc5f7c 100644 --- a/developer/src/kmcomp/kmcomp.dproj +++ b/developer/src/kmcomp/kmcomp.dproj @@ -266,6 +266,7 @@ + Cfg_2 Base From a14abd7b0d9cae08fc9ae192f5a1c1e992032b4e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 2 Sep 2023 07:54:07 +0400 Subject: [PATCH 043/207] chore(developer): add kmcwrapper to unit tests --- .../KeyboardPackageVersionsTestSuite.dpr | 3 ++- .../KeyboardPackageVersionsTestSuite.dproj | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr index 6122f21916e..bb50c6f6545 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr @@ -92,7 +92,8 @@ uses Keyman.Developer.System.Project.UrlRenderer in '..\..\..\tike\project\Keyman.Developer.System.Project.UrlRenderer.pas', KeymanPaths in '..\..\..\..\..\common\windows\delphi\general\KeymanPaths.pas', Keyman.Developer.System.ValidateKpsFile in '..\..\..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas', - Keyman.Developer.System.KeymanDeveloperPaths in '..\..\..\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas'; + Keyman.Developer.System.KeymanDeveloperPaths in '..\..\..\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas', + Keyman.Developer.System.KmcWrapper in '..\..\..\tike\compile\Keyman.Developer.System.KmcWrapper.pas'; var runner : ITestRunner; diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj index 3bbafa79bf8..26c431d4724 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj @@ -174,6 +174,7 @@ + Cfg_2 Base From ec134f61e6fad5dc1361d7e8a86653a48beec38e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 2 Sep 2023 10:51:30 +0400 Subject: [PATCH 044/207] chore(developer): update tests for new compiler --- .../src/kmc/src/util/NodeCompilerCallbacks.ts | 14 ++++++++++++-- developer/src/test/auto/kmcomp/test.bat | 4 ++-- .../auto/kmcomp/test_194_filename_case.kmn | 4 ++-- .../kmcomp/test_194_filename_case.out.txt | 19 +++++++++++-------- developer/src/test/auto/kmcomp/test_valid.kps | 6 ++++++ 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts index 015e00b9c1e..dc048317deb 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import { platform } from 'os'; import { CompilerCallbacks, CompilerEvent, CompilerPathCallbacks, CompilerFileSystemCallbacks, compilerLogLevelToSeverity, CompilerErrorSeverity, @@ -71,8 +72,17 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { // Note, we only check this if the file exists, because // if it is not found, that will be returned as an error // from loadFile anyway. - const filename = fs.realpathSync(originalFilename); - const nativeFilename = fs.realpathSync.native(filename); + let filename = fs.realpathSync(originalFilename); + let nativeFilename = fs.realpathSync.native(filename); + if(platform() == 'win32' && originalFilename.match(/^.:/)) { + // When an absolute path is passed in, it includes a drive letter. + // Drive letter case can differ but we don't care about that on win32. + // Typically absolute paths only appear for input parameters, as absolute + // paths are flagged as warnings when they appear in source files anyway. + // Upper casing the drive letter just avoids the issue. + filename = filename[0].toUpperCase() + filename.substring(1); + nativeFilename = nativeFilename[0].toUpperCase() + nativeFilename.substring(1); + } if(filename != nativeFilename) { this.reportMessage(InfrastructureMessages.Hint_FilenameHasDifferingCase({ reference: path.basename(originalFilename), diff --git a/developer/src/test/auto/kmcomp/test.bat b/developer/src/test/auto/kmcomp/test.bat index 808c2f9068b..d33b389d162 100644 --- a/developer/src/test/auto/kmcomp/test.bat +++ b/developer/src/test/auto/kmcomp/test.bat @@ -23,7 +23,7 @@ if "%1"=="-t" ( ) if "%1"=="" ( - set compiler=..\..\..\..\bin\developer\kmcomp.exe + set compiler=..\..\..\..\bin\kmcomp.exe ) else ( set compiler=%1 shift @@ -186,4 +186,4 @@ exit /b 0 :usage echo Usage: test.bat [-c] [path-to-kmcomp.exe] echo -c will add colour via ANSI escapes (don't use in redirected scripts) -echo path-to-kmcomp.exe, if not included will default to ..\..\..\..\bin\developer\kmcomp.exe +echo path-to-kmcomp.exe, if not included will default to ..\..\..\..\bin\kmcomp.exe diff --git a/developer/src/test/auto/kmcomp/test_194_filename_case.kmn b/developer/src/test/auto/kmcomp/test_194_filename_case.kmn index 6ef3940e4cc..4c128f7b50e 100644 --- a/developer/src/test/auto/kmcomp/test_194_filename_case.kmn +++ b/developer/src/test/auto/kmcomp/test_194_filename_case.kmn @@ -3,8 +3,8 @@ c c Test all the different types of files that may be referenced by a .kmn c for case mismatches -store(&VERSION) '10.0' -store(&TARGETS) 'windows' +store(&VERSION) '14.0' +store(&TARGETS) 'any' store(&BITMAP) 'test_194_Filename_case' store(&KMW_HELPFILE) 'test_194_Filename_case.html' store(&KMW_EMBEDJS) 'test_194_Filename_case.embed_js' c .embed_js just to avoid .js being deleted by nmake clean... diff --git a/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt b/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt index e95a2c50229..4bd39db1a8b 100644 --- a/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt +++ b/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt @@ -1,8 +1,11 @@ -test_194_filename_case.kmn (8): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.bmp' does not match actual filename 'test_194_filename_case.bmp' -test_194_filename_case.kmn (9): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.html' does not match actual filename 'test_194_filename_case.html' -test_194_filename_case.kmn (10): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.embed_js' does not match actual filename 'test_194_filename_case.embed_js' -test_194_filename_case.kmn (11): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.embed.css' does not match actual filename 'test_194_filename_case.embed.css' -test_194_filename_case.kmn (12): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.keyman-touch-layout' does not match actual filename 'test_194_filename_case.keyman-touch-layout' -test_194_filename_case.kmn (14): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case.txt' does not match actual filename 'test_194_filename_case.txt' -test_194_filename_case.kmn (23): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case_call_0.call_js' does not match actual filename 'test_194_filename_case_call_0.call_js' -test_194_filename_case.kmn (23): Hint: 10AF Casing differences may fail on some platforms: reference 'test_194_Filename_case_call_1.call_js' does not match actual filename 'test_194_filename_case_call_1.call_js' +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.kvks' does not match case of 'test_194_Filename_case.kvks' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.html' does not match case of 'test_194_Filename_case.html' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.embed_js' does not match case of 'test_194_Filename_case.embed_js' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.embed.css' does not match case of 'test_194_Filename_case.embed.css' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.keyman-touch-layout' does not match case of 'test_194_Filename_case.keyman-touch-layout' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case_call_0.call_js' does not match case of 'test_194_Filename_case_call_0.call_js' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case_call_1.call_js' does not match case of 'test_194_Filename_case_call_1.call_js' in source file; this is an error on platforms with case-sensitive filesystems. \ No newline at end of file diff --git a/developer/src/test/auto/kmcomp/test_valid.kps b/developer/src/test/auto/kmcomp/test_valid.kps index d64ee42848e..8877759a281 100644 --- a/developer/src/test/auto/kmcomp/test_valid.kps +++ b/developer/src/test/auto/kmcomp/test_valid.kps @@ -24,6 +24,12 @@ 0 .kmx
+ + test_valid.js + Keyboard Test Valid + 0 + .js +
From 4c07b6c49cad27e4d9ff84af427952de2a5e2c66 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 2 Sep 2023 12:25:29 +0400 Subject: [PATCH 045/207] feat(developer): remove kmcmpdll --- .github/labeler.yml | 1 - .gitignore | 7 - developer/src/.gitignore | 5 - developer/src/Makefile | 12 +- developer/src/README.md | 11 - .../delphi/compiler/CompileErrorCodes.pas | 2 - .../src/common/delphi/compiler/compile.pas | 180 +- .../src/common/include/kmn_compiler_errors.h | 2 - developer/src/developer.sln | 26 +- developer/src/inst/download.in.mak | 4 +- developer/src/inst/kmdev.wxs | 8 - developer/src/kmcmpdll/CasedKeys.cpp | 149 - developer/src/kmcmpdll/CasedKeys.h | 4 - .../src/kmcmpdll/CharToKeyConversion.cpp | 187 - developer/src/kmcmpdll/CharToKeyConversion.h | 20 - .../src/kmcmpdll/CheckFilenameConsistency.cpp | 121 - .../src/kmcmpdll/CheckFilenameConsistency.h | 11 - developer/src/kmcmpdll/CheckForDuplicates.cpp | 44 - developer/src/kmcmpdll/CheckForDuplicates.h | 4 - .../src/kmcmpdll/CheckNCapsConsistency.cpp | 98 - .../src/kmcmpdll/CheckNCapsConsistency.h | 6 - developer/src/kmcmpdll/Compiler.cpp | 3795 ----------------- developer/src/kmcmpdll/Compiler.rc | 252 -- developer/src/kmcmpdll/DeprecationChecks.cpp | 52 - developer/src/kmcmpdll/DeprecationChecks.h | 7 - developer/src/kmcmpdll/Edition.cpp | 8 - developer/src/kmcmpdll/Edition.h | 12 - developer/src/kmcmpdll/Makefile | 49 - developer/src/kmcmpdll/NamedCodeConstants.cpp | 316 -- developer/src/kmcmpdll/NamedCodeConstants.h | 36 - developer/src/kmcmpdll/UnreachableRules.cpp | 52 - developer/src/kmcmpdll/UnreachableRules.h | 3 - developer/src/kmcmpdll/compfile.h | 226 - developer/src/kmcmpdll/debugstore.h | 20 - developer/src/kmcmpdll/json-validation.cpp | 83 - developer/src/kmcmpdll/kcframe/kcframe.cpp | 52 - .../src/kmcmpdll/kcframe/kcframe.vcxproj | 267 -- .../kmcmpdll/kcframe/kcframe.vcxproj.filters | 22 - developer/src/kmcmpdll/kmcmpdll.h | 20 - developer/src/kmcmpdll/kmcmpdll.sln | 41 - developer/src/kmcmpdll/kmcmpdll.vcxproj | 450 -- .../src/kmcmpdll/kmcmpdll.vcxproj.filters | 119 - developer/src/kmcmpdll/pch.cpp | 1 - developer/src/kmcmpdll/pch.h | 24 - developer/src/kmcmpdll/version.rc | 32 - developer/src/kmcmpdll/versioning.cpp | 28 - developer/src/kmcmpdll/versioning.h | 12 - developer/src/kmcmpdll/virtualcharkeys.cpp | 274 -- developer/src/kmcmpdll/virtualcharkeys.h | 9 - developer/src/kmcmpdll/xstring.cpp | 48 - developer/src/kmcmplib/Makefile | 2 +- developer/src/kmcomp/kmcomp.dpr | 7 - developer/src/kmcomp/kmcomp.dproj | 2 - developer/src/kmcomp/main.pas | 85 +- ...st.System.CompilePackageVersioningTest.pas | 8 - .../test/auto/kmcomp-x64-structures/Makefile | 2 +- .../cppstructsize/cppstructsize.cpp | 2 +- .../cppstructsize/cppstructsize.vcxproj | 2 +- .../cppstructsize.vcxproj.filters | 2 +- developer/src/tike/child/UfrmEditor.pas | 1 - developer/src/tike/child/UfrmKeymanWizard.pas | 18 +- .../src/tike/compile/CompileKeymanWeb.pas | 2844 ------------ .../src/tike/compile/ValidateKeyboardInfo.pas | 189 - developer/src/tike/main/RedistFiles.pas | 19 - developer/src/tike/main/UfrmMain.pas | 8 - ...ystem.Project.modelTsProjectFileAction.pas | 1 - developer/src/tike/tike.dpr | 1 - developer/src/tike/tike.dproj | 1 - developer/src/tools/sentry-upload-difs.sh | 4 - .../manual/web/regression-tests/README.md | 2 +- .../test/manual/web/regression-tests/test.js | 10 +- .../delphi/general/Keyman.System.Settings.pas | 11 +- windows/src/support/kdebug/Makefile | 2 +- .../test_httpuploader/test_httpuploader.dproj | 3 - 74 files changed, 34 insertions(+), 10404 deletions(-) delete mode 100644 developer/src/kmcmpdll/CasedKeys.cpp delete mode 100644 developer/src/kmcmpdll/CasedKeys.h delete mode 100644 developer/src/kmcmpdll/CharToKeyConversion.cpp delete mode 100644 developer/src/kmcmpdll/CharToKeyConversion.h delete mode 100644 developer/src/kmcmpdll/CheckFilenameConsistency.cpp delete mode 100644 developer/src/kmcmpdll/CheckFilenameConsistency.h delete mode 100644 developer/src/kmcmpdll/CheckForDuplicates.cpp delete mode 100644 developer/src/kmcmpdll/CheckForDuplicates.h delete mode 100644 developer/src/kmcmpdll/CheckNCapsConsistency.cpp delete mode 100644 developer/src/kmcmpdll/CheckNCapsConsistency.h delete mode 100644 developer/src/kmcmpdll/Compiler.cpp delete mode 100644 developer/src/kmcmpdll/Compiler.rc delete mode 100644 developer/src/kmcmpdll/DeprecationChecks.cpp delete mode 100644 developer/src/kmcmpdll/DeprecationChecks.h delete mode 100644 developer/src/kmcmpdll/Edition.cpp delete mode 100644 developer/src/kmcmpdll/Edition.h delete mode 100644 developer/src/kmcmpdll/Makefile delete mode 100644 developer/src/kmcmpdll/NamedCodeConstants.cpp delete mode 100644 developer/src/kmcmpdll/NamedCodeConstants.h delete mode 100644 developer/src/kmcmpdll/UnreachableRules.cpp delete mode 100644 developer/src/kmcmpdll/UnreachableRules.h delete mode 100644 developer/src/kmcmpdll/compfile.h delete mode 100644 developer/src/kmcmpdll/debugstore.h delete mode 100644 developer/src/kmcmpdll/json-validation.cpp delete mode 100644 developer/src/kmcmpdll/kcframe/kcframe.cpp delete mode 100644 developer/src/kmcmpdll/kcframe/kcframe.vcxproj delete mode 100644 developer/src/kmcmpdll/kcframe/kcframe.vcxproj.filters delete mode 100644 developer/src/kmcmpdll/kmcmpdll.h delete mode 100644 developer/src/kmcmpdll/kmcmpdll.sln delete mode 100644 developer/src/kmcmpdll/kmcmpdll.vcxproj delete mode 100644 developer/src/kmcmpdll/kmcmpdll.vcxproj.filters delete mode 100644 developer/src/kmcmpdll/pch.cpp delete mode 100644 developer/src/kmcmpdll/pch.h delete mode 100644 developer/src/kmcmpdll/version.rc delete mode 100644 developer/src/kmcmpdll/versioning.cpp delete mode 100644 developer/src/kmcmpdll/versioning.h delete mode 100644 developer/src/kmcmpdll/virtualcharkeys.cpp delete mode 100644 developer/src/kmcmpdll/virtualcharkeys.h delete mode 100644 developer/src/kmcmpdll/xstring.cpp delete mode 100644 developer/src/tike/compile/CompileKeymanWeb.pas delete mode 100644 developer/src/tike/compile/ValidateKeyboardInfo.pas diff --git a/.github/labeler.yml b/.github/labeler.yml index d831af12b66..c7406d0f6fb 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -36,7 +36,6 @@ developer/: developer/compilers/: - developer/src/kmcomp/** - - developer/src/kmcmpdll/** - developer/src/kmc/** - developer/src/kmc-*/** diff --git a/.gitignore b/.gitignore index 0abec10c3b1..7efdd873b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,6 @@ # /developer/TIKE/ /windows/src/developer/TIKE/stock.kct -/windows/src/developer/TIKE/kmcmpdll.dll # /developer/TIKE/redist/ /windows/src/developer/TIKE/redist/Addins @@ -85,12 +84,6 @@ /windows/src/developer/inst/download.mak /windows/src/developer/inst/copydev.mak -# /developer/kmcmpdll/ -/windows/src/developer/kmcmpdll/*.lastbuildstate - -# /developer/kmcmpdll/Debug/ -/windows/src/developer/kmcmpdll/Debug/*.idb - /windows/src/developer/stock/stock.kct /windows/src/developer/uitemplates/keyman.kct diff --git a/developer/src/.gitignore b/developer/src/.gitignore index 5e1280d2cad..0a4cbf81ca8 100644 --- a/developer/src/.gitignore +++ b/developer/src/.gitignore @@ -35,7 +35,6 @@ inst/kmc.wxs # /developer/TIKE/ tike/stock.kct -tike/kmcmpdll.dll # /developer/TIKE/redist/ tike/redist/Addins @@ -47,9 +46,6 @@ inst/*.wixpdb inst/download.mak inst/copydev.mak -# /developer/kmcmpdll/ -kmcmpdll/*.lastbuildstate - stock/stock.kct uitemplates/keyman.kct @@ -76,7 +72,6 @@ tike/xml/kmw/resource # These files are generated during build tike/icons.res -kmcmpdll/Compiler.res kmcomp/icons.res tds_file.txt diff --git a/developer/src/Makefile b/developer/src/Makefile index 0c6c81ece6b..cfecca71b8f 100644 --- a/developer/src/Makefile +++ b/developer/src/Makefile @@ -3,9 +3,9 @@ # !ifdef NODELPHI -TARGETS=build-tools kmcmplib kmcmpdll kmanalyze kmdecomp server kmc +TARGETS=build-tools kmcmplib kmanalyze kmdecomp server kmc !else -TARGETS=build-tools kmcmplib kmcmpdll kmcomp kmanalyze kmconvert tike samples setup inst kmdecomp server kmc +TARGETS=build-tools kmcmplib kmcomp kmanalyze kmconvert tike samples setup inst kmdecomp server kmc !endif EXCLUDEPATHDEFINES=1 @@ -33,15 +33,11 @@ kmcmplib: global-versions .virtual cd $(DEVELOPER_ROOT)\src\kmcmplib $(MAKE) $(TARGET) -kmcmpdll: kmcmplib .virtual - cd $(DEVELOPER_ROOT)\src\kmcmpdll - $(MAKE) $(TARGET) - kmdecomp: .virtual cd $(DEVELOPER_ROOT)\src\kmdecomp $(MAKE) $(TARGET) -kmcomp: kmcmpdll +kmcomp: cd $(DEVELOPER_ROOT)\src\kmcomp $(MAKE) $(TARGET) @@ -53,7 +49,7 @@ kmconvert: .virtual cd $(DEVELOPER_ROOT)\src\kmconvert $(MAKE) $(TARGET) -tike: kmcmpdll redist +tike: redist cd $(DEVELOPER_ROOT)\src\tike $(MAKE) $(TARGET) diff --git a/developer/src/README.md b/developer/src/README.md index 7622abaca5d..7745c3a0abc 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -37,17 +37,6 @@ a full node.js install. A keyboard source analysis and automated regression test tool. Testing for logical errors of a keyboard. -## src/kmcmpdll - kmcmpdll.dll & kmcmpdll.x64.dll - -This is the main source code for the keyboard compiler, packaged as a dynamic -linked library for Windows. The consumers will call 'CompileKeyboardFile'. - -### src/kmcmpdll/kcframe - kcframe.exe & kcframe.x64.exe - -Located under kmcmpdll folder this is used for debugging in Visual Studio. A -command line program compiler for a keyboard file 'kmn' into a binary format -'kmx'. Has hardcoded default arguments. - ## src/kmcomp - kmcomp.exe & kmcomp.x64.exe The main command line compiler for a keyboard. It compiles keyboards (.kmn), diff --git a/developer/src/common/delphi/compiler/CompileErrorCodes.pas b/developer/src/common/delphi/compiler/CompileErrorCodes.pas index 6c9cb9a11eb..557c5276a06 100644 --- a/developer/src/common/delphi/compiler/CompileErrorCodes.pas +++ b/developer/src/common/delphi/compiler/CompileErrorCodes.pas @@ -228,8 +228,6 @@ interface CWARN_KeyShouldIncludeNCaps = $20AD; CHINT_UnreachableRule = $10AE; - CHINT_FilenameHasDifferingCase = $10AF; - CWARN_MissingFile = $20B0; implementation diff --git a/developer/src/common/delphi/compiler/compile.pas b/developer/src/common/delphi/compiler/compile.pas index 0124150ef62..4dcf1e191f7 100644 --- a/developer/src/common/delphi/compiler/compile.pas +++ b/developer/src/common/delphi/compiler/compile.pas @@ -32,16 +32,8 @@ interface uses Winapi.Windows, - kmxfileconsts; -const -{$IFDEF WIN64} - kmcmpdll_lib = 'kmcmpdll.x64.dll'; -{$ELSE} - kmcmpdll_lib = 'kmcmpdll.dll'; -{$ENDIF} - {$IFDEF WIN64} {$A16} {$ENDIF} @@ -137,13 +129,13 @@ FILE_KEYBOARD = record {$ENDIF} type - TCompilerCallback = function( line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; // I3310 TCompilerCallbackW = function( line: Integer; msgcode: LongWord; const text: string): Integer; // not available to C++ in this form const CKF_KEYMAN = 0; CKF_KEYMANWEB = 1; +// TODO: REMOVE THESE: const CERR_FATAL = $00008000; CERR_ERROR = $00004000; @@ -167,176 +159,12 @@ FILE_KEYBOARD = record FILE_DEADKEY_SIZE = 160; {$ENDIF} -function CompileKeyboardFile(kmnFile, kmxFile: PChar; FSaveDebug, CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; CallBack: TCompilerCallback): Integer; cdecl; // I4865 // I4866 -function CompileKeyboardFileToBuffer(kmnFile: PChar; buf: PFILE_KEYBOARD; CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; CallBack: TCompilerCallback; Target: Integer): Integer; cdecl; // I4865 // I4866 -function Compiler_Diagnostic(mode: Integer): Integer; -procedure Compiler_Diagnostic_Console(mode: Integer); - -type - TCompilerOptions = record - dwSize: DWORD; - ShouldAddCompilerVersion: BOOL; - end; - - COMPILER_OPTIONS = TCompilerOptions; - PCOMPILER_OPTIONS = ^COMPILER_OPTIONS; - -function SetCompilerOptions(options: PCOMPILER_OPTIONS; CallBack: TCompilerCallback): BOOL; cdecl; - -// -// For unit tests, point to a known-current version of kmcmpdll.dll -// -var - FUnitTestKMCmpDllPath: string = ''; - implementation uses - System.SysUtils, - - RegistryKeys, - RedistFiles; - -var - HKMCmpDll: THandle = 0; - -type - TCompileKeyboardFile = function (kmnFile, kmxFile: PAnsiChar; FSaveDebug, CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; // I3310 // I4865 // I4866 - CallBack: TCompilerCallback): Integer; cdecl; // TODO: K9: Convert to Unicode - - TCompileKeyboardFileToBuffer = function (kmnFile: PAnsiChar; buf: PFILE_KEYBOARD; // I3310 - CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; // I4865 // I4866 - CallBack: TCompilerCallback; Target: Integer): Integer; cdecl; // TODO: K9: Convert to Unicode - - TSetCompilerOptions = function (options: PCOMPILER_OPTIONS): BOOL; cdecl; - -function LoadCompiler(CallBack: TCompilerCallback = nil): Boolean; -var - s: string; -begin - if HKMCmpDll = 0 then - begin - s := FUnitTestKMCmpDllPath; - if s = '' then - begin - s := GetDebugKMCmpDllPath; - if (s <> '') and not FileExists(s + kmcmpdll_lib) then // I4770 - s := ''; - if s = '' then - begin - try - s := GetDeveloperRootPath; - except - s := ''; - end; - if s = '' then s := ExtractFilePath(ParamStr(0)); - end; - end; - - HKMCmpDll := LoadLibrary(PChar(s+kmcmpdll_lib)); - if HKMCmpDll = 0 then - begin - if Assigned(Callback) then - Callback(0, $8000, PAnsiChar(AnsiString('Could not load the compiler library '+s+kmcmpdll_lib+'. '+ // I4706 - 'Check that '+kmcmpdll_lib+' is in the program directory '+ - 'and that it is not corrupt.'#13#10+'Windows error message: '+SysErrorMessage(GetLastError)))); - Exit(False); - end; - end; - Result := True; -end; - -function SetCompilerOptions(options: PCOMPILER_OPTIONS; CallBack: TCompilerCallback): BOOL; -var - sco: TSetCompilerOptions; -begin - if not LoadCompiler(Callback) then - Exit(False); - - @sco := GetProcAddress(HKMCmpDll, 'SetCompilerOptions'); - if not Assigned(@sco) then - begin - Callback(0, $8000, PAnsiChar(AnsiString('Could not access the compiler. Check that '+kmcmpdll_lib+' is in the program directory '+ // I4706 - 'and that it is not corrupt.'#13#10+'Windows error message: '+SysErrorMessage(GetLastError)))); - Exit(False); - end; - - Result := sco(options); -end; - -function CompileKeyboardFile(kmnFile, kmxFile: PChar; FSaveDebug, CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; CallBack: TCompilerCallback): Integer; // I4865 // I4866 -var - ckf: TCompileKeyboardFile; -begin - //Result := 0; - - if not LoadCompiler(Callback) then Exit(-1); - - @ckf := GetProcAddress(HKMCmpDll, 'CompileKeyboardFile'); - if not Assigned(@ckf) then - begin - Callback(0, $8000, PAnsiChar(AnsiString('Could not access the compiler. Check that '+kmcmpdll_lib+' is in the program directory '+ // I4706 - 'and that it is not corrupt.'#13#10+'Windows error message: '+SysErrorMessage(GetLastError)))); - Result := -1; - Exit; - end; - - Result := ckf(PAnsiChar(AnsiString(kmnFile)), PAnsiChar(AnsiString(kmxFile)), FSaveDebug, CompilerWarningsAsErrors, WarnDeprecatedCode, Callback); // I3310 // I4865 // I4866 -end; - -function CompileKeyboardFileToBuffer(kmnFile: PChar; buf: PFILE_KEYBOARD; CompilerWarningsAsErrors, WarnDeprecatedCode: BOOL; CallBack: TCompilerCallback; Target: Integer): Integer; // I4865 // I4866 -var - ckf: TCompileKeyboardFileToBuffer; -begin - //Result := 0; - - if not LoadCompiler(Callback) then Exit(-1); - - @ckf := GetProcAddress(HKMCmpDll, 'CompileKeyboardFileToBuffer'); - if not Assigned(@ckf) then - begin - Callback(0, $8000, PAnsiChar(AnsiString('Could not access the compiler. Check that '+kmcmpdll_lib+' is in the program directory '+ // I4706 - 'and that it is not corrupt.'#13#10+'Windows error message: '+SysErrorMessage(GetLastError)))); - Result := -1; - Exit; - end; - - Result := ckf(PAnsiChar(AnsiString(kmnFile)), buf, CompilerWarningsAsErrors, WarnDeprecatedCode, Callback, Target); // I3310 // I4865 // I4866 -end; - -type - TKeyman_Diagnostic = procedure(mode: Integer); cdecl; - -function Compiler_Diagnostic(mode: Integer): Integer; -var - keyman_diagnostic: TKeyman_Diagnostic; -begin - if not LoadCompiler then Exit(-1); - - @keyman_diagnostic := GetProcAddress(HKMCmpDll, 'Keyman_Diagnostic'); - if Assigned(@keyman_diagnostic) then - begin - keyman_diagnostic(mode); - Exit(0); - end; - - Result := 2; -end; - -procedure Compiler_Diagnostic_Console(mode: Integer); -begin - case compile.Compiler_Diagnostic(0) of - 0: writeln('Should not have got here'); - 1: writeln('Test failed: could not find kmcmpdll.dll'); - 2: writeln('Test failed: could not find keyman_diagnostic in kmcmpdll.dll'); - else writeln('This should not be possible'); - end; -end; + System.SysUtils; initialization - // We want to early load the compiler because we need it loaded for - // sentry symbolication: https://github.com/getsentry/sentry-native/issues/213 - LoadCompiler; try Assert(sizeof(FILE_KEYBOARD) = FILE_KEYBOARD_SIZE, 'Assertion failure: sizeof(FILE_KEYBOARD) = FILE_KEYBOARD_SIZE'); Assert(sizeof(FILE_GROUP) = FILE_GROUP_SIZE, 'Assertion failure: sizeof(FILE_GROUP) = FILE_GROUP_SIZE'); @@ -352,8 +180,4 @@ initialization raise; end; end; -finalization - if HKMCmpDll > 0 then - FreeLibrary(HKMCmpDll); - HKMCmpDll := 0; end. diff --git a/developer/src/common/include/kmn_compiler_errors.h b/developer/src/common/include/kmn_compiler_errors.h index 13ba4a261a5..9944c61304e 100644 --- a/developer/src/common/include/kmn_compiler_errors.h +++ b/developer/src/common/include/kmn_compiler_errors.h @@ -239,8 +239,6 @@ #define CWARN_KeyShouldIncludeNCaps 0x000020AD #define CHINT_UnreachableRule 0x000010AE -#define CHINT_FilenameHasDifferingCase 0x000010AF // only used in kmcmpdll -#define CWARN_MissingFile 0x000020B0 // only used in kmcmpdll #define CERR_BufferOverflow 0x000080C0 #define CERR_Break 0x000080C1 diff --git a/developer/src/developer.sln b/developer/src/developer.sln index 33dbf505129..3766c311c4f 100644 --- a/developer/src/developer.sln +++ b/developer/src/developer.sln @@ -1,17 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31313.79 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1259 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmdecomp", "kmdecomp\kmdecomp.vcxproj", "{963D608A-6689-469C-AE42-F56696DD42CC}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmcmpdll", "kmcmpdll\kmcmpdll.vcxproj", "{7E26FE08-721E-424B-9DA0-4A0DCA88A86E}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmanalyze", "kmanalyze\kmanalyze.vcxproj", "{4EAE6DFD-E18A-493D-A00F-4846A2593F56}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IMSample", "samples\imsample\IMSample.vcxproj", "{0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kcframe", "kmcmpdll\kcframe\kcframe.vcxproj", "{E92CC897-8228-4728-8110-028088D7A99C}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imsample", "samples\imsample\IMSample.vcxproj", "{0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,14 +23,6 @@ Global {963D608A-6689-469C-AE42-F56696DD42CC}.Release|x64.ActiveCfg = Release|Win32 {963D608A-6689-469C-AE42-F56696DD42CC}.Release|x86.ActiveCfg = Release|Win32 {963D608A-6689-469C-AE42-F56696DD42CC}.Release|x86.Build.0 = Release|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x64.ActiveCfg = Debug|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x64.Build.0 = Debug|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x86.ActiveCfg = Debug|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x86.Build.0 = Debug|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x64.ActiveCfg = Release|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x64.Build.0 = Release|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x86.ActiveCfg = Release|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x86.Build.0 = Release|Win32 {4EAE6DFD-E18A-493D-A00F-4846A2593F56}.Debug|x64.ActiveCfg = Debug|x64 {4EAE6DFD-E18A-493D-A00F-4846A2593F56}.Debug|x64.Build.0 = Debug|x64 {4EAE6DFD-E18A-493D-A00F-4846A2593F56}.Debug|x86.ActiveCfg = Debug|Win32 @@ -49,14 +37,6 @@ Global {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x64.ActiveCfg = Release|Win32 {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x86.ActiveCfg = Release|Win32 {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E}.Release|x86.Build.0 = Release|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.ActiveCfg = Debug|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.Build.0 = Debug|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x86.ActiveCfg = Debug|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x86.Build.0 = Debug|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.ActiveCfg = Release|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.Build.0 = Release|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x86.ActiveCfg = Release|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index 2bda1ac4bbd..e70621a81f0 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -114,8 +114,8 @@ make-kmcomp-install-zip: copy-schemas cd $(DEVELOPER_ROOT)\bin $(WZZIP) -bd -bb0 $(KMCOMP_ZIP) \ - kmcomp.exe kmcmpdll.dll \ - kmcomp.x64.exe kmcmpdll.x64.dll \ + kmcomp.exe \ + kmcomp.x64.exe \ kmconvert.exe \ sentry.dll sentry.x64.dll \ kmdecomp.exe \ diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index f44cd0b883f..0766b5603ac 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -167,14 +167,6 @@ - - - - - - - - diff --git a/developer/src/kmcmpdll/CasedKeys.cpp b/developer/src/kmcmpdll/CasedKeys.cpp deleted file mode 100644 index 3118f1be150..00000000000 --- a/developer/src/kmcmpdll/CasedKeys.cpp +++ /dev/null @@ -1,149 +0,0 @@ - -#include "pch.h" - -#include -#include -#include "../../../common/windows/cpp/include/vkeys.h" -#include - -#include "CharToKeyConversion.h" - -extern BOOL FMnemonicLayout; // TODO: these globals should be consolidated one day - -DWORD ExpandCapsRule(PFILE_GROUP gp, PFILE_KEY kpp, PFILE_STORE sp); - -DWORD VerifyCasedKeys(PFILE_STORE sp) { - assert(sp != NULL); - - if (FMnemonicLayout) { - // The &CasedKeys system store is not supported for - // mnemonic layouts in 14.0 - return CERR_CasedKeysNotSupportedWithMnemonicLayout; - } - - // We will rewrite this store with virtual keys - - PWSTR p = sp->dpString; - PWSTR buf = new WCHAR[wcslen(p) * 5 + 1]; // extended keys are 5 units long, so this is the max length - PWSTR q = buf; - - while (*p) { - UINT key = 0, shift = 0; - if (*p != UC_SENTINEL) { - if (!MapUSCharToVK(*p, &key, &shift)) { - return CERR_CasedKeysMustContainOnlyVirtualKeys; - } - if (shift & K_SHIFTFLAG) { - return CERR_CasedKeysMustNotIncludeShiftStates; - } - } - else { - if (*(p + 1) != CODE_EXTENDED) { - return CERR_CasedKeysMustContainOnlyVirtualKeys; - } - shift = *(p + 2); - key = *(p + 3); - if (shift != ISVIRTUALKEY) { - return CERR_CasedKeysMustNotIncludeShiftStates; - } - } - *q++ = UC_SENTINEL; - *q++ = CODE_EXTENDED; - *q++ = shift; - *q++ = key; - *q++ = UC_SENTINEL_EXTENDEDEND; - *q = 0; - - p = incxstr(p); - } - - delete[] sp->dpString; - sp->dpString = buf; - - return CERR_None; -} - -DWORD ExpandCapsRulesForGroup(PFILE_KEYBOARD fk, PFILE_GROUP gp) { - assert(fk != NULL); - assert(gp != NULL); - - if (FMnemonicLayout) { - // The &CasedKeys system store is not supported for - // mnemonic layouts in 14.0 - return CERR_None; - } - - PFILE_STORE sp = FindSystemStore(fk, TSS_CASEDKEYS); - if (!sp) { - // If there is no &CasedKeys system store, then we do not - // process the key - return CERR_None; - } - - DWORD msg; - // ExpandCapsRule may add extra rules at the end of gp->dpKeyArray, - // reallocating it, so we (a) cache the original length, and (b) - // dereference the array every call - int cxKeyArray = gp->cxKeyArray; - for (int i = 0; i < cxKeyArray; i++) { - if ((msg = ExpandCapsRule(gp, &gp->dpKeyArray[i], sp)) != CERR_None) { - return msg; - } - } - return CERR_None; -} - -DWORD ExpandCapsRule(PFILE_GROUP gp, PFILE_KEY kpp, PFILE_STORE sp) { - UINT key = kpp->Key; - UINT shift = kpp->ShiftFlags; - - if (shift == 0) { - // Convert US key cap to a virtual key - if (!MapUSCharToVK(kpp->Key, &key, &shift)) { - return CERR_None; - } - } - - if (shift & (CAPITALFLAG | NOTCAPITALFLAG)) { - // Don't attempt expansion if either Caps Lock flag is specified in the key rule - return CERR_None; - } - - PWSTR p = sp->dpString; - for (; *p; p = incxstr(p)) { - // We've already verified that the store contains only virtual keys in VerifyCasedKeys - if (*(p + 3) == key) { - break; - } - } - - if (!*p) { - // This key is not modified by Caps Lock - return CERR_None; - } - - // This key is modified by Caps Lock, so we need to duplicate this rule - PFILE_KEY k = new FILE_KEY[gp->cxKeyArray + 1]; - if (!k) return CERR_CannotAllocateMemory; - memcpy(k, gp->dpKeyArray, gp->cxKeyArray * sizeof(FILE_KEY)); - - kpp = &k[(INT_PTR)(kpp - gp->dpKeyArray)]; - - delete gp->dpKeyArray; - gp->dpKeyArray = k; - gp->cxKeyArray++; - - k = &k[gp->cxKeyArray - 1]; - k->dpContext = new WCHAR[wcslen(kpp->dpContext) + 1]; - k->dpOutput = new WCHAR[wcslen(kpp->dpOutput) + 1]; - wcscpy_s(k->dpContext, wcslen(kpp->dpContext) + 1, kpp->dpContext); // copy the context. - wcscpy_s(k->dpOutput, wcslen(kpp->dpOutput) + 1, kpp->dpOutput); // copy the output. - k->Key = key; - k->Line = kpp->Line; - // Add the CAPITAL FLAG, invert shift flag for the rule - k->ShiftFlags = shift ^ K_SHIFTFLAG | CAPITALFLAG; - kpp->Key = key; - kpp->ShiftFlags = shift | NOTCAPITALFLAG; - - return CERR_None; -} diff --git a/developer/src/kmcmpdll/CasedKeys.h b/developer/src/kmcmpdll/CasedKeys.h deleted file mode 100644 index 66ba8a312dc..00000000000 --- a/developer/src/kmcmpdll/CasedKeys.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -DWORD VerifyCasedKeys(PFILE_STORE sp); -DWORD ExpandCapsRulesForGroup(PFILE_KEYBOARD fk, PFILE_GROUP gp); diff --git a/developer/src/kmcmpdll/CharToKeyConversion.cpp b/developer/src/kmcmpdll/CharToKeyConversion.cpp deleted file mode 100644 index 24723c980f6..00000000000 --- a/developer/src/kmcmpdll/CharToKeyConversion.cpp +++ /dev/null @@ -1,187 +0,0 @@ - -#include "pch.h" -#include - -/* Following code lifted from syskbd.cpp and tweaked for compiler use. Todo: consolidate */ - -WCHAR VKToChar(WORD keyCode, UINT shiftFlags) -{ - char shiftedDigit[] = ")!@#$%^&*("; - int n, Shift; - - if (!(shiftFlags & ISVIRTUALKEY)) return keyCode; - - if (shiftFlags & (LCTRLFLAG | RCTRLFLAG | LALTFLAG | RALTFLAG)) return 0; - - if (keyCode >= '0' && keyCode <= '9') - { - n = keyCode - '0'; - return ((shiftFlags & K_SHIFTFLAG) ? shiftedDigit[n] : keyCode); - } - - if (keyCode >= 'A' && keyCode <= 'Z') - { - Shift = (shiftFlags & K_SHIFTFLAG); - if (shiftFlags & (CAPITALFLAG)) Shift = !Shift; - return (Shift ? keyCode : keyCode + 32); - } - - if (keyCode >= VK_NUMPAD0 && keyCode <= VK_NUMPAD9) - { - if (!(shiftFlags & NUMLOCKFLAG)) return 0; - return keyCode - (VK_NUMPAD0 - '0'); - } - - Shift = (shiftFlags & K_SHIFTFLAG); - - switch (keyCode) - { - case VK_ACCENT: - return Shift ? '~' : '`'; - case VK_HYPHEN: - return Shift ? '_' : '-'; - case VK_EQUAL: - return Shift ? '+' : '='; - case VK_BKSLASH: - return Shift ? '|' : 92; - case VK_LBRKT: - return Shift ? '{' : '['; - case VK_RBRKT: - return Shift ? '}' : ']'; - case VK_COLON: - return Shift ? ':' : ';'; - case VK_QUOTE: - return Shift ? '"' : 39; - case VK_COMMA: - return Shift ? '<' : ','; - case VK_PERIOD: - return Shift ? '>' : '.'; - case VK_SLASH: - return Shift ? '?' : '/'; - case VK_SPACE: - return ' '; - } - return 0; - //keyCode; -} - - -/* This array is lifted from preservedkeymap.cpp */ -// TODO: share this -const struct -{ - UINT key; - BOOL shift; -} USCharMap[] = { - { VK_SPACE, FALSE }, // 20 ' ' - { '1', TRUE }, // 21 '!' - { VK_QUOTE, TRUE }, // 22 '"' - { '3', TRUE }, // 23 '#' - { '4', TRUE }, // 24 '$' - { '5', TRUE }, // 25 '%' - { '7', TRUE }, // 26 '&' - { VK_QUOTE, FALSE }, // 27 ''' - { '9', TRUE }, // 28 '(' - { '0', TRUE }, // 29 ')' - { '8', TRUE }, // 2A '*' - { VK_EQUAL, TRUE }, // 2B '+' - { VK_COMMA, FALSE }, // 2C ',' - { VK_HYPHEN, FALSE }, // 2D '-' - { VK_PERIOD, FALSE }, // 2E '.' - { VK_SLASH, FALSE }, // 2F '/' - - { '0', FALSE }, // 30 '0' - { '1', FALSE }, // 31 '1' - { '2', FALSE }, // 32 '2' - { '3', FALSE }, // 33 '3' - { '4', FALSE }, // 34 '4' - { '5', FALSE }, // 35 '5' - { '6', FALSE }, // 36 '6' - { '7', FALSE }, // 37 '7' - { '8', FALSE }, // 38 '8' - { '9', FALSE }, // 39 '9' - { VK_COLON, TRUE }, // 3A ':' - { VK_COLON, FALSE }, // 3B ';' - { VK_COMMA, TRUE }, // 3C '<' - { VK_EQUAL, FALSE }, // 3D '=' - { VK_PERIOD, TRUE }, // 3E '>' - { VK_SLASH, TRUE }, // 3F '?' - - { '2', TRUE }, // 40 '@' - { 'A', TRUE }, // 41 'A' - { 'B', TRUE }, // 42 'B' - { 'C', TRUE }, // 43 'C' - { 'D', TRUE }, // 44 'D' - { 'E', TRUE }, // 45 'E' - { 'F', TRUE }, // 46 'F' - { 'G', TRUE }, // 47 'G' - { 'H', TRUE }, // 48 'H' - { 'I', TRUE }, // 49 'I' - { 'J', TRUE }, // 4A 'J' - { 'K', TRUE }, // 4B 'K' - { 'L', TRUE }, // 4C 'L' - { 'M', TRUE }, // 4D 'M' - { 'N', TRUE }, // 4E 'N' - { 'O', TRUE }, // 4F 'O' - - { 'P', TRUE }, // 50 'P' - { 'Q', TRUE }, // 51 'Q' - { 'R', TRUE }, // 52 'R' - { 'S', TRUE }, // 53 'S' - { 'T', TRUE }, // 54 'T' - { 'U', TRUE }, // 55 'U' - { 'V', TRUE }, // 56 'V' - { 'W', TRUE }, // 57 'W' - { 'X', TRUE }, // 58 'X' - { 'Y', TRUE }, // 59 'Y' - { 'Z', TRUE }, // 5A 'Z' - { VK_LBRKT, FALSE }, // 5B '[' - { VK_BKSLASH, FALSE }, // 5C '\' - { VK_RBRKT, FALSE }, // 5D ']' - { '6', TRUE }, // 5E '^' - { VK_HYPHEN, TRUE }, // 5F '_' - - { VK_ACCENT, FALSE }, // 60 '`' - { 'A', FALSE }, // 61 'a' - { 'B', FALSE }, // 62 'b' - { 'C', FALSE }, // 63 'c' - { 'D', FALSE }, // 64 'd' - { 'E', FALSE }, // 65 'e' - { 'F', FALSE }, // 66 'f' - { 'G', FALSE }, // 67 'g' - { 'H', FALSE }, // 68 'h' - { 'I', FALSE }, // 69 'i' - { 'J', FALSE }, // 6A 'j' - { 'K', FALSE }, // 6B 'k' - { 'L', FALSE }, // 6C 'l' - { 'M', FALSE }, // 6D 'm' - { 'N', FALSE }, // 6E 'n' - { 'O', FALSE }, // 6F 'o' - - { 'P', FALSE }, // 70 'p' - { 'Q', FALSE }, // 71 'q' - { 'R', FALSE }, // 72 'r' - { 'S', FALSE }, // 73 's' - { 'T', FALSE }, // 74 't' - { 'U', FALSE }, // 75 'u' - { 'V', FALSE }, // 76 'v' - { 'W', FALSE }, // 77 'w' - { 'X', FALSE }, // 78 'x' - { 'Y', FALSE }, // 79 'y' - { 'Z', FALSE }, // 7A 'z' - { VK_LBRKT, TRUE }, // 7B '{' - { VK_BKSLASH, TRUE }, // 7C '|' - { VK_RBRKT, TRUE }, // 7D '}' - { VK_ACCENT, TRUE } // 7E '~' -}; - -BOOL MapUSCharToVK(UINT ch, UINT *puKey, UINT *puShiftFlags) { - assert(puKey != NULL); - assert(puShiftFlags != NULL); - if (ch >= 0x20 && ch < 0x7F) { - *puKey = USCharMap[ch - 0x20].key; - *puShiftFlags = ISVIRTUALKEY | (USCharMap[ch - 0x20].shift ? K_SHIFTFLAG : 0); - return TRUE; - } - return FALSE; -} diff --git a/developer/src/kmcmpdll/CharToKeyConversion.h b/developer/src/kmcmpdll/CharToKeyConversion.h deleted file mode 100644 index 6524a73f995..00000000000 --- a/developer/src/kmcmpdll/CharToKeyConversion.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -#define VK_COLON 0xBA -#define VK_EQUAL 0xBB -#define VK_COMMA 0xBC -#define VK_HYPHEN 0xBD -#define VK_PERIOD 0xBE -#define VK_SLASH 0xBF -#define VK_ACCENT 0xC0 -#define VK_LBRKT 0xDB -#define VK_BKSLASH 0xDC -#define VK_RBRKT 0xDD -#define VK_QUOTE 0xDE -#define VK_xDF 0xDF - -BOOL MapUSCharToVK(UINT ch, UINT *puKey, UINT *puShiftFlags); -WCHAR VKToChar(WORD keyCode, UINT shiftFlags); - diff --git a/developer/src/kmcmpdll/CheckFilenameConsistency.cpp b/developer/src/kmcmpdll/CheckFilenameConsistency.cpp deleted file mode 100644 index 1d553839f49..00000000000 --- a/developer/src/kmcmpdll/CheckFilenameConsistency.cpp +++ /dev/null @@ -1,121 +0,0 @@ - -#include "pch.h" - -#include -#include -#include -#include -#include -#include "CheckFilenameConsistency.h" - -extern char CompileDir[MAX_PATH]; - -BOOL FileExists(char const * filename) { - _finddata_t fi; - intptr_t n; - - if ((n = _findfirst(filename, &fi)) != -1) { - _findclose(n); - return TRUE; - } - return FALSE; -} - -BOOL IsRelativePath(char const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if (*p == '\\') return FALSE; - if (*p && *(p + 1) == ':') return FALSE; - - return TRUE; -} - -BOOL IsRelativePath(wchar_t const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if (*p == L'\\') return FALSE; - if (*p && *(p + 1) == L':') return FALSE; - - return TRUE; -} - -DWORD CheckFilenameConsistency(char const * Filename, BOOL ReportMissingFile) { - PWCHAR WFilename = strtowstr((char *)Filename); - DWORD const result = CheckFilenameConsistency(WFilename, ReportMissingFile); - delete WFilename; - return result; -} - -DWORD CheckFilenameConsistency(wchar_t const * Filename, BOOL ReportMissingFile) { - WCHAR Name[_MAX_PATH], FName[_MAX_FNAME], Ext[_MAX_EXT]; - _wfinddata_t fi; - intptr_t n; - - if (IsRelativePath(Filename)) { - PWCHAR WCompileDir = strtowstr(CompileDir); - wcscpy_s(Name, _countof(Name), WCompileDir); // I3481 - wcscat_s(Name, _countof(Name), Filename); // I3481 - } - else { - wcscpy_s(Name, _countof(Name), Filename); // I3481 - } - - if ((n = _wfindfirst(Name, &fi)) == -1) { - if (ReportMissingFile) { - wsprintf(ErrExtra, "referenced file '%ls'", Filename); - AddWarning(CWARN_MissingFile); - } - return CERR_None; - } - - _wsplitpath_s(Filename, nullptr, 0, nullptr, 0, FName, _MAX_FNAME, Ext, _MAX_EXT); - _wmakepath_s(Name, _MAX_PATH, nullptr, nullptr, FName, Ext); - if (wcscmp(Name, fi.name) != 0) { - wsprintf(ErrExtra, "reference '%ls' does not match actual filename '%ls'", Name, fi.name); - AddWarning(CHINT_FilenameHasDifferingCase); - } - return CERR_None; -} - -DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk) { - // call() statements depend on a fairly ugly hack for js, - // where store(DllFunction) "my.dll:func" will look for a - // file called function.call_js. This is ripe for rewrite! - // But let's check what we have anyway - PFILE_STORE sp; - DWORD i, msg; - for (i = 0, sp = fk->dpStoreArray; i < fk->cxStoreArray; i++, sp++) { - if (!sp->fIsCall) continue; - - const std::wstring callsite(sp->dpString); - const auto colon = callsite.find(':'); - if (colon == std::wstring::npos) continue; - - auto func = callsite.substr(colon + 1); - func.append(L".call_js"); - if ((msg = CheckFilenameConsistency(func.c_str(), FALSE)) != CERR_None) { - return msg; - } - } - return CERR_None; -} diff --git a/developer/src/kmcmpdll/CheckFilenameConsistency.h b/developer/src/kmcmpdll/CheckFilenameConsistency.h deleted file mode 100644 index fc9a645f1e8..00000000000 --- a/developer/src/kmcmpdll/CheckFilenameConsistency.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include -#include - -DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk); -DWORD CheckFilenameConsistency(char const * Filename, BOOL ReportMissingFile); -DWORD CheckFilenameConsistency(wchar_t const * Filename, BOOL ReportMissingFile); -BOOL FileExists(char const * filename); -BOOL IsRelativePath(char const * p); -BOOL IsRelativePath(wchar_t const * p); diff --git a/developer/src/kmcmpdll/CheckForDuplicates.cpp b/developer/src/kmcmpdll/CheckForDuplicates.cpp deleted file mode 100644 index d22bfa80e6f..00000000000 --- a/developer/src/kmcmpdll/CheckForDuplicates.cpp +++ /dev/null @@ -1,44 +0,0 @@ - -#include "pch.h" - -#include -#include -#include - -#include "CheckForDuplicates.h" - - -DWORD CheckForDuplicateGroup(PFILE_KEYBOARD fk, PFILE_GROUP gp) noexcept { - DWORD i; - PFILE_GROUP gp0 = fk->dpGroupArray; - for (i = 0; i < fk->cxGroupArray; i++, gp0++) { - if (gp0 == gp) { - continue; - } - if (_wcsicmp(gp0->szName, gp->szName) == 0) { - wsprintf(ErrExtra, "Group '%ls' declared on line %d", gp0->szName, gp0->Line); - return CERR_DuplicateGroup; - } - } - return CERR_None; -} - -DWORD CheckForDuplicateStore(PFILE_KEYBOARD fk, PFILE_STORE sp) noexcept { - if (!sp->szName[0]) { - // Stores with zero length names are reserved system stores. - // They cannot be defined in user code. This is not an issue. - return CERR_None; - } - DWORD i; - PFILE_STORE sp0 = fk->dpStoreArray; - for (i = 0; i < fk->cxStoreArray; i++, sp0++) { - if (sp0 == sp) { - continue; - } - if (_wcsicmp(sp0->szName, sp->szName) == 0) { - wsprintf(ErrExtra, "Store '%ls' declared on line %d", sp0->szName, sp0->line); - return CERR_DuplicateStore; - } - } - return CERR_None; -} diff --git a/developer/src/kmcmpdll/CheckForDuplicates.h b/developer/src/kmcmpdll/CheckForDuplicates.h deleted file mode 100644 index 2dabf76edf6..00000000000 --- a/developer/src/kmcmpdll/CheckForDuplicates.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -DWORD CheckForDuplicateGroup(PFILE_KEYBOARD fk, PFILE_GROUP gp) noexcept; -DWORD CheckForDuplicateStore(PFILE_KEYBOARD fk, PFILE_STORE sp) noexcept; diff --git a/developer/src/kmcmpdll/CheckNCapsConsistency.cpp b/developer/src/kmcmpdll/CheckNCapsConsistency.cpp deleted file mode 100644 index 4c9e6853f99..00000000000 --- a/developer/src/kmcmpdll/CheckNCapsConsistency.cpp +++ /dev/null @@ -1,98 +0,0 @@ - -#include "pch.h" - -#include -#include -#include -#include "CharToKeyConversion.h" - -/** - * If any rule uses CAPS or NCAPS for a given key, then every rule that - * uses that key must also use CAPS or NCAPS, as otherwise the results are - * inconsistent. For example, if Caps Lock is on, then [K_F] may still be - * matched: - * - * + [K_F] > 'foo' - * + [CAPS K_F] > 'bar' - * - * Because of the way that KeymanWeb compiles rules, this may even apply when - * we have a key rule that would otherwise be ignored due to context. In the - * following example, [Caps Lock] + [y] would result in no output (or default - * output) rather than 'bar', because the preceding rule would capture the - * Y key, ignoring Caps Lock, meaning that the subsequent rule would never - * even get tested. (Note that this was introduced in the KeymanWeb compiler - * fix for extremely long if/else ladders in Keyman 12 in #1561, and this is - * technically slightly inconsistent with Keyman Core, although in my analysis - * only in this already ambiguous situation. - * - * 'x' + [K_Y] > 'foo' - * [CAPS K_Y] > 'bar' - * - * Given all this, we'll warn any time we find a key that has inconsistent use - * of CAPS/NCAPS in its rules. - * - * @param fk Keyboard to check - */ -BOOL CheckNCapsConsistency(PFILE_KEYBOARD fk) { - struct CapsUsage { - int ncaps_line, caps_line, neither_line; - }; - - // 256 virtual key codes + sizeof the virtual key dictionary is max key code possible - const int nkeys = 256 + fk->cxVKDictionary; - const int oldCurrentLine = currentLine; - auto caps_ncaps_usage = new CapsUsage [nkeys]; - - memset(caps_ncaps_usage, 0, nkeys * sizeof(CapsUsage)); - - PFILE_GROUP gp; - DWORD gn; - for (gn = 0, gp = fk->dpGroupArray; gn < fk->cxGroupArray; gn++, gp++) { - if (!gp->fUsingKeys) { - continue; - } - - PFILE_KEY kp; - DWORD kn; - for (kn = 0, kp = gp->dpKeyArray; kn < gp->cxKeyArray; kn++, kp++) { - UINT key; - UINT shift; - if (kp->ShiftFlags & ISVIRTUALKEY) { - if (kp->Key >= nkeys) { - assert(false); - continue; - } - key = kp->Key; - shift = kp->ShiftFlags; - } - else if (!MapUSCharToVK(kp->Key, &key, &shift)) { - // Not a valid key - continue; - } - - if (shift & NOTCAPITALFLAG) { - if (!caps_ncaps_usage[key].ncaps_line) caps_ncaps_usage[key].ncaps_line = (kp->Line == 0 ? 1 : kp->Line); - } - else if (shift & CAPITALFLAG) { - if (!caps_ncaps_usage[key].caps_line) caps_ncaps_usage[key].caps_line = (kp->Line == 0 ? 1 : kp->Line); - } - else { - if (!caps_ncaps_usage[key].neither_line) caps_ncaps_usage[key].neither_line = (kp->Line == 0 ? 1 : kp->Line); - } - } - } - - for (int i = 0; i < nkeys; i++) { - if (caps_ncaps_usage[i].neither_line && (caps_ncaps_usage[i].caps_line || caps_ncaps_usage[i].ncaps_line)) { - // We set the current line to one needing work: the developer should add the NCAPS flag - currentLine = caps_ncaps_usage[i].neither_line; - AddWarning(CWARN_KeyShouldIncludeNCaps); - } - } - - delete[] caps_ncaps_usage; - - currentLine = oldCurrentLine; - - return TRUE; -} diff --git a/developer/src/kmcmpdll/CheckNCapsConsistency.h b/developer/src/kmcmpdll/CheckNCapsConsistency.h deleted file mode 100644 index f6a11b5fe26..00000000000 --- a/developer/src/kmcmpdll/CheckNCapsConsistency.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -BOOL CheckNCapsConsistency(PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmpdll/Compiler.cpp b/developer/src/kmcmpdll/Compiler.cpp deleted file mode 100644 index 100e43fc5bd..00000000000 --- a/developer/src/kmcmpdll/Compiler.cpp +++ /dev/null @@ -1,3795 +0,0 @@ -/* - Name: Compiler - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 20 Jun 2006 - - Modified Date: 25 Oct 2016 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 20 Jun 2006 - mcdurdin - Initial version - 23 Aug 2006 - mcdurdin - Add VISUALKEYBOARD, KMW_RTL, KMW_HELPFILE, KMW_HELPTEXT, KMW_EMBEDJS system stores - 14 Sep 2006 - mcdurdin - Support icons in version 7 - 28 Sep 2006 - mcdurdin - Added product validation - 28 Sep 2006 - mcdurdin - Added test for version 7.0 icon support - 06 Oct 2006 - mcdurdin - Fix buffer overflow in UTF8 conversion - 04 Dec 2006 - mcdurdin - Fix readfile buffer bug - 04 Jan 2007 - mcdurdin - Add notany support - 22 Jan 2007 - mcdurdin - Add K_NPENTER reference - 25 Jan 2007 - mcdurdin - Resize buffers to 4095 - 30 May 2007 - mcdurdin - I786 - Compiler crash if zero length string in keystroke any - 23 Aug 2007 - mcdurdin - I1011 - Fix buffer clobbering for UTF8 conversion of large files - 27 Mar 2008 - mcdurdin - I1358 - Support for multiple languages for office config - 14 Jun 2008 - mcdurdin - Support documenting language id as a single WORD instead of by PRIMARY/SUB - 14 Jun 2008 - mcdurdin - Support Windows languages list - 28 Jul 2008 - mcdurdin - I1569 - line prefixes - 22 Mar 2010 - mcdurdin - Compiler fixup - x64 support - 25 May 2010 - mcdurdin - I1632 - Keyboard Options - 24 Jun 2010 - mcdurdin - I2432 - Use local buffers so GetXString can be re-entrant (used by if()) - 26 Jul 2010 - mcdurdin - I2467 - 8.0 renumber - 18 Mar 2011 - mcdurdin - I2646 - Compiler warning on invalid Ethnologue codes - 18 Mar 2011 - mcdurdin - I2525 - Unterminated string can crash compiler - 19 Jul 2011 - mcdurdin - I2993 - Named code constants cause a warning 0x208D to appear - 26 Jun 2012 - mcdurdin - I3377 - KM9 - Update code references from 8.0 to 9.0 - 17 Aug 2012 - mcdurdin - I3431 - V9.0 - Spaces in values in if comparisons break the compiler parser - 27 Aug 2012 - mcdurdin - I3439 - V9.0 - Refactor xstring support in C++ code - 27 Aug 2012 - mcdurdin - I3437 - V9.0 - Add support for set(&layer) and layer() - 27 Aug 2012 - mcdurdin - I3438 - V9.0 - Add support for custom virtual keys - 27 Aug 2012 - mcdurdin - I3430 - V9.0 - Add support for if(&platform) and if(&baselayout) to compilers - 27 Aug 2012 - mcdurdin - I3440 - V9.0 - Tidy up set statement delimiter recognition - 24 Oct 2012 - mcdurdin - I3483 - V9.0 - Add support for compiling in the layout file - 24 Oct 2012 - mcdurdin - I3481 - V9.0 - Eliminate unsafe calls in C++ - 24 Jan 2012 - mcdurdin - I3137 - If key part of VK rule is missing, compiler generates invalid file - 06 Feb 2012 - mcdurdin - I3228 - kmcmpdll sometimes tries to write temp files to Program Files - 03 Nov 2012 - mcdurdin - I3510 - V9.0 - Merge of I3228 - kmcmpdll sometimes tries to write temp files to Program Files - 03 Nov 2012 - mcdurdin - I3511 - V9.0 - Merge of I3137 - If key part of VK rule is missing, compiler generates invalid file - 13 Dec 2012 - mcdurdin - I3654 - V9.0 - Compiler appears to create unregistered keyboards even when registered - 13 Dec 2012 - mcdurdin - I3641 - V9.0 - compiler dll buffer overrun bugs - 13 Dec 2012 - mcdurdin - I3681 - V9.0 - KeymanWeb compiler should output formatted js when debug=1 - 13 Dec 2012 - mcdurdin - I3686 - V9.0 - AddStore attaches property flags to wrong store structure - 19 Mar 2014 - mcdurdin - I4140 - V9.0 - Add keyboard version information to keyboards - 04 Nov 2014 - mcdurdin - I4504 - V9.0 - Consolidate the compile action into single command - 02 Jul 2015 - mcdurdin - I4784 - Compiler does not recognise baselayout, layer or platform at the start of a line - 02 Jul 2015 - mcdurdin - I4785 - baselayout(), layer() and platform() produce incorrect compiled code - 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - 24 Aug 2015 - mcdurdin - I4867 - Add test for code after use warning to compile - 06 Nov 2015 - mcdurdin - I4914 - kmcmpdll does not pick an index() statement that has an offset one past the key - 23 Feb 2016 - mcdurdin - I4982 - Defined character constants cannot be referenced correctly in other stores - 25 Oct 2016 - mcdurdin - I5135 - Remove product and licensing references from Developer projects -*/ -#include "pch.h" - -#include -#include -#include "../../../common/windows/cpp/include/vkeys.h" -#include -#include -#include - -#include "virtualcharkeys.h" - -#include "../../../common/windows/cpp/include/crc32.h" -#include "../../../common/windows/cpp/include/ConvertUTF.h" -#include "debugstore.h" -#include "namedcodeconstants.h" -#include "../../../common/windows/cpp/include/unicode.h" - -#include "edition.h" - -#include "CharToKeyConversion.h" -#include "CasedKeys.h" -#include "CheckNCapsConsistency.h" -#include "CheckFilenameConsistency.h" -#include "UnreachableRules.h" -#include "CheckForDuplicates.h" - -int xatoi(PWSTR *p); -int atoiW(PWSTR p); -void safe_wcsncpy(PWSTR out, PWSTR in, int cbMax); -int UTF32ToUTF16(int n, int *n1, int *n2); -int GetDeadKey(PFILE_KEYBOARD fk, PWSTR p); - -BOOL IsValidCallStore(PFILE_STORE fs); -BOOL IsSameToken(PWSTR *p, PWSTR token); -DWORD GetRHS(PFILE_KEYBOARD fk, PWSTR p, PWSTR buf, int bufsize, int offset, int IsUnicode); -PWSTR GetDelimitedString(PWSTR *p, PWSTR Delimiters, WORD Flags); -DWORD GetXString(PFILE_KEYBOARD fk, PWSTR str, PWSTR token, PWSTR output, int max, int offset, PWSTR *newp, int isVKey, - int isUnicode); - -int GetGroupNum(PFILE_KEYBOARD fk, PWSTR p); -int LineTokenType(PWSTR *str); - -DWORD ParseLine(PFILE_KEYBOARD fk, PWSTR str); - -DWORD ProcessGroupFinish(PFILE_KEYBOARD fk); -DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PWSTR p); -DWORD ProcessStoreLine(PFILE_KEYBOARD fk, PWSTR p); -DWORD AddDebugStore(PFILE_KEYBOARD fk, PWSTR str); -DWORD ProcessKeyLine(PFILE_KEYBOARD fk, PWSTR str, BOOL IsUnicode); -DWORD ProcessEthnologueStore(PWSTR p); // I2646 -DWORD ProcessHotKey(PWSTR p, DWORD *hk); -DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PWSTR szName, PDWORD FileSize, PBYTE *Buf); - -DWORD ExpandKp(PFILE_KEYBOARD fk, PFILE_KEY kpp, DWORD storeIndex); - -DWORD ReadLine(HANDLE hInfile, PWSTR str, BOOL PreProcess); - -DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, HANDLE hOutfile); -BOOL CompileKeyboardHandle(HANDLE hInfile, PFILE_KEYBOARD fk); - -int GetVKCode(PFILE_KEYBOARD fk, PWSTR p); // I3438 // TODO: Consolidate GetDeadKey and GetVKCode? -DWORD BuildVKDictionary(PFILE_KEYBOARD fk); // I3438 -DWORD AddStore(PFILE_KEYBOARD fk, DWORD SystemID, PWSTR str, DWORD *dwStoreID = NULL); -DWORD ProcessSystemStore(PFILE_KEYBOARD fk, DWORD SystemID, PFILE_STORE sp); -void RecordDeadkeyNames(PFILE_KEYBOARD fk); -DWORD AddCompilerVersionStore(PFILE_KEYBOARD fk); -BOOL CheckStoreUsage(PFILE_KEYBOARD fk, int storeIndex, BOOL fIsStore, BOOL fIsOption, BOOL fIsCall); - -DWORD process_if(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); -DWORD process_reset(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); -DWORD process_set(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); -DWORD process_save(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); -DWORD process_platform(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); // I3430 -DWORD process_baselayout(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); // I3430 -DWORD process_set_synonym(DWORD dwSystemID, PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); // I3437 -DWORD process_expansion(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx, int max); - -BOOL IsValidKeyboardVersion(WCHAR *dpString); // I4140 - -HANDLE UTF16TempFromUTF8(HANDLE hInfile, BOOL hasPreamble); - -const PWCHAR LineTokens[] = { - L"SVNBHBGMNSCCLLCMLB", L"store", L"VERSION ", L"NAME ", - L"BITMAP ", L"HOTKEY ", L"begin", L"group", L"match", L"nomatch", - L"SHIFT FREES CAPS", L"CAPS ON ONLY", L"CAPS ALWAYS OFF", - L"LANGUAGE ", L"LAYOUT ", L"COPYRIGHT ", L"MESSAGE ", L"LANGUAGENAME ", - L"BITMAPS " }; - -#define SSN__PREFIX L"&" - -const PWCHAR StoreTokens[] = { - L"", - SSN__PREFIX L"BITMAP", - SSN__PREFIX L"COPYRIGHT", - SSN__PREFIX L"HOTKEY", - SSN__PREFIX L"LANGUAGE", - SSN__PREFIX L"LAYOUT", - SSN__PREFIX L"MESSAGE", - SSN__PREFIX L"NAME", - SSN__PREFIX L"VERSION", - SSN__PREFIX L"CAPSONONLY", - SSN__PREFIX L"CAPSALWAYSOFF", - SSN__PREFIX L"SHIFTFREESCAPS", - SSN__PREFIX L"LANGUAGENAME", - L"", - L"", - SSN__PREFIX L"ETHNOLOGUECODE", - L"", - SSN__PREFIX L"MNEMONICLAYOUT", - SSN__PREFIX L"INCLUDECODES", - SSN__PREFIX L"OLDCHARPOSMATCHING", - L"", - L"", - L"", - L"", - SSN__PREFIX L"VISUALKEYBOARD", - SSN__PREFIX L"KMW_RTL", - SSN__PREFIX L"KMW_HELPFILE", - SSN__PREFIX L"KMW_HELPTEXT", - SSN__PREFIX L"KMW_EMBEDJS", - SSN__PREFIX L"WINDOWSLANGUAGES", - L"", - SSN__PREFIX L"PLATFORM", // read only // I3430 - SSN__PREFIX L"BASELAYOUT", // read only // I3430 - SSN__PREFIX L"LAYER", // read-write via set? // I3430 - L"", // I3438 - SSN__PREFIX L"LAYOUTFILE", // I3483 - SSN__PREFIX L"KEYBOARDVERSION", // I4140 - SSN__PREFIX L"KMW_EMBEDCSS", - SSN__PREFIX L"TARGETS", // I4504 - SSN__PREFIX L"CASEDKEYS", // #2241 - SSN__PREFIX L"", // TSS_BEGIN_NEWCONTEXT - SSN__PREFIX L"", // TSS_BEGIN_POSTKEYSTROKE - SSN__PREFIX L"NEWLAYER", - SSN__PREFIX L"OLDLAYER", - NULL -}; - -static_assert(_countof(StoreTokens) == TSS__MAX + 2, "StoreTokens should have exactly TSS__MAX+2 elements"); - -HINSTANCE g_hInstance; -CompilerMessageProc msgproc = NULL; -int currentLine = 0, nErrors = 0; -char CompileDir[MAX_PATH]; -int ErrChr; -char ErrExtra[256]; -BOOL FSaveDebug, FCompilerWarningsAsErrors, FWarnDeprecatedCode; // I4865 // I4866 -BOOL FShouldAddCompilerVersion = TRUE; -BOOL FOldCharPosMatching = FALSE, FMnemonicLayout = FALSE; -NamedCodeConstants *CodeConstants = NULL; - -int BeginLine[4]; - -/* Compile target */ - -int CompileTarget; - -#define CKF_KEYMAN 0 -#define CKF_KEYMANWEB 1 - -BOOL WINAPI DllMain(HINSTANCE hinst, DWORD fdwReason, LPVOID lpvReserved) -{ - if (fdwReason == DLL_PROCESS_ATTACH) g_hInstance = hinst; - return TRUE; -} - - -PWSTR strtowstr(PSTR in) -{ - PWSTR result; - size_t len; - - mbstowcs_s(&len, NULL, 0, in, strlen(in)); // I3481 - result = new WCHAR[len + 1]; - mbstowcs_s(&len, result, len + 1, in, strlen(in)); // I3481 // I3641 - result[len] = 0; - return result; -} - -PSTR wstrtostr(PWSTR in) -{ - PSTR result; - size_t len; - - wcstombs_s(&len, NULL, 0, in, wcslen(in)); // I3481 - result = new CHAR[len + 1]; - wcstombs_s(&len, result, len + 1, in, wcslen(in)); // I3481 // I3641 - result[len] = 0; - return result; -} - -BOOL AddCompileString(LPSTR buf) -{ - SetLastError(0); - (*msgproc)(currentLine + 1, CWARN_Info, buf); - return FALSE; -} - -BOOL AddCompileMessage(DWORD msg) -{ - char szText[SZMAX_ERRORTEXT + 1 + 280]; - - SetLastError(0); - if (msg & CERR_FATAL) - { - LoadString(g_hInstance, msg, szText, SZMAX_ERRORTEXT); - (*msgproc)(currentLine + 1, msg, szText); - nErrors++; - return TRUE; - } - - if (msg & CERR_ERROR) nErrors++; - LoadString(g_hInstance, msg, szText, SZMAX_ERRORTEXT); - if (ErrChr > 0) - wsprintf(strchr(szText, 0), " character offset: %d", ErrChr); - if (*ErrExtra) - wsprintf(strchr(szText, 0), " %s", ErrExtra); - - ErrChr = 0; *ErrExtra = 0; - - if (!(*msgproc)(currentLine, msg, szText)) return TRUE; - - return FALSE; -} - -extern "C" BOOL __declspec(dllexport) SetCompilerOptions(PCOMPILER_OPTIONS options) { - if(!options || options->dwSize < sizeof(COMPILER_OPTIONS)) { - return FALSE; - } - - FShouldAddCompilerVersion = options->ShouldAddCompilerVersion; - return TRUE; -} - -extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR pszOutfile, BOOL ASaveDebug, BOOL ACompilerWarningsAsErrors, BOOL AWarnDeprecatedCode, CompilerMessageProc pMsgProc) // I4865 // I4866 -{ - HANDLE hInfile = INVALID_HANDLE_VALUE, hOutfile = INVALID_HANDLE_VALUE; - BOOL err; - DWORD len; - char str[260]; - - FSaveDebug = ASaveDebug; - FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 - FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 - - CompileTarget = CKF_KEYMAN; - - if (!pMsgProc || !pszInfile || !pszOutfile) SetError(CERR_BadCallParams); - - PSTR p; - if (p = strrchr(pszInfile, '\\')) - { - strncpy_s(CompileDir, _countof(CompileDir), pszInfile, (INT_PTR)(p - pszInfile + 1)); // I3481 - CompileDir[(INT_PTR)(p - pszInfile + 1)] = 0; - } - else - CompileDir[0] = 0; - - msgproc = pMsgProc; - currentLine = 0; - nErrors = 0; - - AddCompileString("NOTE: Using legacy compiler"); - - hInfile = CreateFileA(pszInfile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hInfile == INVALID_HANDLE_VALUE) SetError(CERR_InfileNotExist); - - // Transfer the file to a memory stream for processing UTF-8 or ANSI to UTF-16? - // What about really large files? Transfer to a temp file... - - if (!ReadFile(hInfile, str, 3, &len, NULL)) - { - CloseHandle(hInfile); - return CERR_CannotReadInfile; - } - SetFilePointer(hInfile, 0, NULL, FILE_BEGIN); - if (str[0] == UTF8Sig[0] && str[1] == UTF8Sig[1] && str[2] == UTF8Sig[2]) - hInfile = UTF16TempFromUTF8(hInfile, TRUE); - else if (str[0] == UTF16Sig[0] && str[1] == UTF16Sig[1]) - SetFilePointer(hInfile, 2, NULL, FILE_BEGIN); - else - hInfile = UTF16TempFromUTF8(hInfile, FALSE); // Will fall back to ansi for invalid UTF-8 - if (hInfile == INVALID_HANDLE_VALUE) // I3228 // I3510 - { - return CERR_CannotCreateTempfile; - } - - hOutfile = CreateFileA(pszOutfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); - if (hOutfile == INVALID_HANDLE_VALUE) SetError(CERR_CannotCreateOutfile); - - DWORD msg; - FILE_KEYBOARD fk; - CodeConstants = new NamedCodeConstants; - - err = CompileKeyboardHandle(hInfile, &fk); - if (err) - { - if ((msg = WriteCompiledKeyboard(&fk, hOutfile)) != CERR_None) - AddCompileMessage(msg); - } - else - AddCompileMessage(CERR_InvalidValue); - - CloseHandle(hInfile); - CloseHandle(hOutfile); - - delete CodeConstants; - - if (nErrors > 0) - { - DeleteFile(pszOutfile); - return FALSE; - } - - return err; -} - - -extern "C" BOOL __declspec(dllexport) CompileKeyboardFileToBuffer(PSTR pszInfile, PFILE_KEYBOARD pfkBuffer, BOOL ACompilerWarningsAsErrors, BOOL AWarnDeprecatedCode, CompilerMessageProc pMsgProc, int Target) // I4865 // I4866 -{ - HANDLE hInfile = INVALID_HANDLE_VALUE; - BOOL err; - DWORD len; - char str[260]; - - FSaveDebug = TRUE; // I3681 - FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 - FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 - - CompileTarget = Target; - - if (!pMsgProc || !pszInfile || !pfkBuffer) SetError(CERR_BadCallParams); - - PSTR p; - if (p = strrchr(pszInfile, '\\')) - { - strncpy_s(CompileDir, _countof(CompileDir), pszInfile, (INT_PTR)(p - pszInfile + 1)); // I3481 - CompileDir[(INT_PTR)(p - pszInfile + 1)] = 0; - } - else - CompileDir[0] = 0; - - msgproc = pMsgProc; - currentLine = 0; - nErrors = 0; - - AddCompileString("NOTE: Using legacy compiler"); - - hInfile = CreateFileA(pszInfile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hInfile == INVALID_HANDLE_VALUE) SetError(CERR_InfileNotExist); - - // Transfer the file to a memory stream for processing UTF-8 or ANSI to UTF-16? - // What about really large files? Transfer to a temp file... - - if (!ReadFile(hInfile, str, 3, &len, NULL)) - { - CloseHandle(hInfile); - return CERR_CannotReadInfile; - } - SetFilePointer(hInfile, 0, NULL, FILE_BEGIN); - if (str[0] == UTF8Sig[0] && str[1] == UTF8Sig[1] && str[2] == UTF8Sig[2]) - hInfile = UTF16TempFromUTF8(hInfile, TRUE); - else if (str[0] == UTF16Sig[0] && str[1] == UTF16Sig[1]) - SetFilePointer(hInfile, 2, NULL, FILE_BEGIN); - else - hInfile = UTF16TempFromUTF8(hInfile, FALSE); - - CodeConstants = new NamedCodeConstants; - err = CompileKeyboardHandle(hInfile, pfkBuffer); - delete CodeConstants; - CloseHandle(hInfile); - - if (nErrors > 0) - return FALSE; - - return err; -} - -void GetVersionInfo(DWORD *VersionMajor, DWORD *VersionMinor) -{ - HRSRC hres = FindResource(0, MAKEINTRESOURCE(1), RT_VERSION); - if (hres) - { - HGLOBAL hmem = LoadResource(0, hres); - PSTR buf = (PSTR)LockResource(hmem); - *VersionMajor = *((PDWORD)&buf[0x30]); - *VersionMinor = *((PDWORD)&buf[0x34]); - } -} - -BOOL CompileKeyboardHandle(HANDLE hInfile, PFILE_KEYBOARD fk) -{ - PWSTR str, p; - - DWORD msg; - - FMnemonicLayout = FALSE; - - if (!fk) { - SetError(CERR_SomewhereIGotItWrong); - } - - str = new WCHAR[LINESIZE]; - if (!str) { - SetError(CERR_CannotAllocateMemory); - } - - fk->KeyboardID = 0; - fk->version = 0; - fk->dpStoreArray = NULL; - fk->dpGroupArray = NULL; - fk->cxStoreArray = 0; - fk->cxGroupArray = 0; - fk->StartGroup[0] = fk->StartGroup[1] = -1; - fk->szName[0] = 0; - fk->szCopyright[0] = 0; - fk->dwFlags = KF_AUTOMATICVERSION; - fk->currentGroup = 0xFFFFFFFF; - fk->currentStore = 0; - fk->cxDeadKeyArray = 0; - fk->dpDeadKeyArray = NULL; - fk->cxVKDictionary = 0; // I3438 - fk->dpVKDictionary = NULL; // I3438 - -/* fk->szMessage[0] = 0; - fk->szLanguageName[0] = 0;*/ - fk->dwBitmapSize = 0; - fk->dwHotKey = 0; - - BeginLine[BEGIN_ANSI] = -1; - BeginLine[BEGIN_UNICODE] = -1; - BeginLine[BEGIN_NEWCONTEXT] = -1; - BeginLine[BEGIN_POSTKEYSTROKE] = -1; - - /* Add a store for the Keyman 6.0 copyright information string */ - - if(FShouldAddCompilerVersion) { - DWORD vmajor, vminor; - GetVersionInfo(&vmajor, &vminor); - swprintf(str, LINESIZE, L"Created with Keyman Developer version %d.%d.%d.%d", HIWORD(vmajor), - LOWORD(vmajor), HIWORD(vminor), LOWORD(vminor)); // I3481 - - AddStore(fk, TSS_KEYMANCOPYRIGHT, str); - } - - /* Add a system store for the Keyman edition number */ - - swprintf(str, LINESIZE, L"%d", 0); // I3481 - AddStore(fk, TSS_CUSTOMKEYMANEDITION, str); - PWSTR tbuf = strtowstr((char*) "Keyman"); - AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, tbuf); - delete tbuf; - - // must preprocess for group and store names -> this isn't really necessary, but never mind! - while ((msg = ReadLine(hInfile, str, TRUE)) == CERR_None) - { - p = str; - switch (LineTokenType(&p)) - { - case T_VERSION: - *(p + 4) = 0; - if ((msg = AddStore(fk, TSS_VERSION, p)) != CERR_None) SetError(msg); - break; - - case T_GROUP: - if ((msg = ProcessGroupLine(fk, p)) != CERR_None) SetError(msg); - break; - - case T_STORE: - if ((msg = ProcessStoreLine(fk, p)) != CERR_None) SetError(msg); - break; - - default: - break; - } - } - - if (msg != CERR_EndOfFile) SetError(msg); - - SetFilePointer(hInfile, 2, NULL, FILE_BEGIN); - currentLine = 0; - - /* Reindex the list of codeconstants after stores added */ - - CodeConstants->reindex(); - - /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ - while ((msg = ReadLine(hInfile, str, FALSE)) == CERR_None) - { - msg = ParseLine(fk, str); - if (msg != CERR_None) SetError(msg); - } - - if (msg != CERR_EndOfFile) SetError(msg); - - ProcessGroupFinish(fk); - - if (FSaveDebug) RecordDeadkeyNames(fk); - - /* Add the compiler version as a system store */ - if ((msg = AddCompilerVersionStore(fk)) != CERR_None) SetError(msg); - - if ((msg = BuildVKDictionary(fk)) != CERR_None) SetError(msg); // I3438 - - if ((msg = CheckFilenameConsistencyForCalls(fk)) != CERR_None) SetError(msg); - - delete str; - - if (!CheckKeyboardFinalVersion(fk)) { - return FALSE; - } - - /* Warn on inconsistent use of NCAPS */ - if (!FMnemonicLayout) { - CheckNCapsConsistency(fk); - } - - /* Flag presence of deprecated features */ - CheckForDeprecatedFeatures(fk); - - return TRUE; -} - -DWORD ProcessBeginLine(PFILE_KEYBOARD fk, PWSTR p) -{ - WCHAR tstr[128]; - PWSTR q, pp; - int BeginMode; - DWORD msg; - - pp = p; - - q = wcschr(p, '>'); - if (!q) return CERR_NoTokensFound; - - while (iswspace(*p)) p++; - if (_wcsnicmp(p, L"unicode", 7) == 0) BeginMode = BEGIN_UNICODE; - else if (_wcsnicmp(p, L"ansi", 4) == 0) BeginMode = BEGIN_ANSI; - else if (_wcsnicmp(p, L"newContext", 10) == 0) BeginMode = BEGIN_NEWCONTEXT; - else if (_wcsnicmp(p, L"postKeystroke", 13) == 0) BeginMode = BEGIN_POSTKEYSTROKE; - else if (*p != '>') return CERR_InvalidToken; - else BeginMode = BEGIN_ANSI; - - if(BeginLine[BeginMode] != -1) { - return CERR_RepeatedBegin; - } - BeginLine[BeginMode] = currentLine; - - if ((msg = GetRHS(fk, p, tstr, 80, (int)(INT_PTR)(p - pp), FALSE)) != CERR_None) return msg; - - if (tstr[0] != UC_SENTINEL || tstr[1] != CODE_USE) { - return CERR_InvalidBegin; - } - if (tstr[3] != 0) { - return CERR_InvalidToken; - } - - if (BeginMode == BEGIN_ANSI || BeginMode == BEGIN_UNICODE) { - fk->StartGroup[BeginMode] = tstr[2] - 1; - // mcd-03-01-2000: removed the secondary group idea; this was undocumented and - // is not supported under Keyman 5.0: ugly!! - // if(tstr[3] == UC_SENTINEL && tstr[4] == CODE_USE) fk->StartGroup[1] = tstr[5] - 1; - - if (FSaveDebug) { - /* Record a system store for the line number of the begin statement */ - AddDebugStore(fk, BeginMode == BEGIN_UNICODE ? DEBUGSTORE_BEGIN L"Unicode" : DEBUGSTORE_BEGIN L"ANSI"); - } - } else { - PFILE_GROUP gp = &fk->dpGroupArray[tstr[2] - 1]; - if (!gp->fReadOnly) { - return BeginMode == BEGIN_NEWCONTEXT ? - CERR_NewContextGroupMustBeReadonly : - CERR_PostKeystrokeGroupMustBeReadonly; - } - return AddStore(fk, BeginMode == BEGIN_NEWCONTEXT ? TSS_BEGIN_NEWCONTEXT : TSS_BEGIN_POSTKEYSTROKE, tstr, NULL); - } - - return CERR_None; -} - -DWORD ValidateMatchNomatchOutput(PWSTR p) { - while (p && *p) { - if (*p == UC_SENTINEL) { - switch (*(p + 1)) { - case CODE_CONTEXT: - case CODE_CONTEXTEX: - case CODE_INDEX: - return CERR_ContextAndIndexInvalidInMatchNomatch; - } - } - p = incxstr(p); - } - return CERR_None; -} - -DWORD ParseLine(PFILE_KEYBOARD fk, PWSTR str) -{ - PWSTR p, q, pp; - PFILE_GROUP gp; - DWORD msg; - int IsUnicode = TRUE; // For NOW! - - p = str; - pp = str; - - switch (LineTokenType(&p)) - { - case T_BLANK: - case T_COMMENT: - break; // Ignore the line - case T_VERSION: - case T_STORE: - break; // The line has already been processed - - case T_BEGIN: - // after a begin can be "Unicode", "ANSI", "NewContext", "PostKeystroke", or nothing (=ANSI) - if ((msg = ProcessBeginLine(fk, p)) != CERR_None) return msg; - break; - - case T_GROUP: - if (fk->currentGroup == 0xFFFFFFFF) fk->currentGroup = 0; - else - { - if ((msg = ProcessGroupFinish(fk)) != CERR_None) return msg; // finish off previous group first? - fk->currentGroup++; - } - // if( (err = ProcessGroupLine( fk, p )) != CERR_None ) return err; - break; - - case T_NAME: - WarnDeprecatedHeader(); // I4866 - q = GetDelimitedString(&p, L"\"\"", 0); - if (!q) return CERR_InvalidName; - - if ((msg = AddStore(fk, TSS_NAME, q)) != CERR_None) return msg; - break; - - case T_COPYRIGHT: - WarnDeprecatedHeader(); // I4866 - q = GetDelimitedString(&p, L"\"\"", 0); - if (!q) return CERR_InvalidCopyright; - - if ((msg = AddStore(fk, TSS_COPYRIGHT, q)) != CERR_None) return msg; - break; - - case T_MESSAGE: - WarnDeprecatedHeader(); // I4866 - q = GetDelimitedString(&p, L"\"\"", 0); - if (!q) return CERR_InvalidMessage; - - if ((msg = AddStore(fk, TSS_MESSAGE, q)) != CERR_None) return msg; - break; - - case T_LANGUAGENAME: - WarnDeprecatedHeader(); // I4866 - q = GetDelimitedString(&p, L"\"\"", 0); - if (!q) return CERR_InvalidLanguageName; - - if ((msg = AddStore(fk, TSS_LANGUAGENAME, q)) != CERR_None) return msg; - break; - - case T_LANGUAGE: - { - WarnDeprecatedHeader(); // I4866 - wchar_t *tokcontext = NULL; - q = wcstok_s(p, L"\n", &tokcontext); // I3481 - if ((msg = AddStore(fk, TSS_LANGUAGE, q)) != CERR_None) return msg; - break; - } - case T_LAYOUT: - { - WarnDeprecatedHeader(); // I4866 - wchar_t *tokcontext = NULL; - q = wcstok_s(p, L"\n", &tokcontext); // I3481 - if ((msg = AddStore(fk, TSS_LAYOUT, q)) != CERR_None) return msg; - break; - } - case T_CAPSOFF: - WarnDeprecatedHeader(); // I4866 - if ((msg = AddStore(fk, TSS_CAPSALWAYSOFF, L"1")) != CERR_None) return msg; - break; - - case T_CAPSON: - WarnDeprecatedHeader(); // I4866 - if ((msg = AddStore(fk, TSS_CAPSONONLY, L"1")) != CERR_None) return msg; - break; - - case T_SHIFT: - WarnDeprecatedHeader(); // I4866 - if ((msg = AddStore(fk, TSS_SHIFTFREESCAPS, L"1")) != CERR_None) return msg; - break; - - case T_HOTKEY: - { - WarnDeprecatedHeader(); // I4866 - wchar_t *tokcontext = NULL; - if ((q = wcstok_s(p, L"\n", &tokcontext)) == NULL) return CERR_CodeInvalidInThisSection; // I3481 - if ((msg = AddStore(fk, TSS_HOTKEY, q)) != CERR_None) return msg; - break; - } - case T_BITMAP: - { - WarnDeprecatedHeader(); // I4866 - wchar_t *tokcontext = NULL; - if ((q = wcstok_s(p, L"\n", &tokcontext)) == NULL) return CERR_InvalidBitmapLine; // I3481 - - while (iswspace(*q)) q++; - if (*q == '"') { - p = q; - q = GetDelimitedString(&p, L"\"\"", 0); - if (!q) return CERR_InvalidBitmapLine; - } - - if ((msg = AddStore(fk, TSS_BITMAP, q)) != CERR_None) return msg; - break; - } - case T_BITMAPS: - { - WarnDeprecatedHeader(); // I4866 - wchar_t *tokcontext = NULL; - AddWarning(CWARN_BitmapNotUsed); - - if ((q = wcstok_s(p, L"\n", &tokcontext)) == NULL) return CERR_InvalidBitmapLine; // I3481 - if (wcschr(q, ',')) *wcschr(q, ',') = 0; - if ((msg = AddStore(fk, TSS_BITMAP, q)) != CERR_None) return msg; - - break; - } - case T_KEYTOKEY: // A rule - if (fk->currentGroup == 0xFFFFFFFF) return CERR_CodeInvalidInThisSection; - if ((msg = ProcessKeyLine(fk, p, IsUnicode)) != CERR_None) return msg; - break; - - case T_MATCH: - if (fk->currentGroup == 0xFFFFFFFF) return CERR_CodeInvalidInThisSection; - { - PWCHAR buf = new WCHAR[GLOBAL_BUFSIZE]; - if ((msg = GetRHS(fk, p, buf, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(p - pp), IsUnicode)) != CERR_None) - { - delete buf; - return msg; - } - - if ((msg = ValidateMatchNomatchOutput(buf)) != CERR_None) { - delete buf; - return msg; - } - - gp = &fk->dpGroupArray[fk->currentGroup]; - - gp->dpMatch = new WCHAR[wcslen(buf) + 1]; - wcscpy_s(gp->dpMatch, wcslen(buf) + 1, buf); // I3481 - - delete buf; - - if (FSaveDebug) - { - WCHAR tstr[128]; - //char buf[256]; - //swprintf(tstr, "%d", fk->currentGroup); - /* Record a system store for the line number of the begin statement */ - //wcscpy(tstr, DEBUGSTORE_MATCH); - - //wcscat(tstr, pw); - - swprintf(tstr, _countof(tstr), L"%ls%d %ls", DEBUGSTORE_MATCH, (int) fk->currentGroup, gp->szName); // I3481 - AddDebugStore(fk, tstr); - } - } - break; - - case T_NOMATCH: - if (fk->currentGroup == 0xFFFFFFFF) return CERR_CodeInvalidInThisSection; - { - PWCHAR buf = new WCHAR[GLOBAL_BUFSIZE]; - if ((msg = GetRHS(fk, p, buf, GLOBAL_BUFSIZE, (int)(INT_PTR)(p - pp), IsUnicode)) != CERR_None) - { - delete[] buf; - return msg; - } - - if ((msg = ValidateMatchNomatchOutput(buf)) != CERR_None) { - delete[] buf; - return msg; - } - - gp = &fk->dpGroupArray[fk->currentGroup]; - - gp->dpNoMatch = new WCHAR[wcslen(buf) + 1]; - wcscpy_s(gp->dpNoMatch, wcslen(buf) + 1, buf); // I3481 - - delete[] buf; - - if (FSaveDebug) - { - WCHAR tstr[128]; - /* Record a system store for the line number of the begin statement */ - swprintf(tstr, _countof(tstr), L"%ls%d %ls", DEBUGSTORE_NOMATCH, fk->currentGroup, gp->szName); // I3481 - AddDebugStore(fk, tstr); - } - } - break; - - default: - return CERR_InvalidToken; - } - - return CERR_None; -} - -//********************************************************************************************************************** - -DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PWSTR p) -{ - PFILE_GROUP gp; - PWSTR q; - - gp = new FILE_GROUP[fk->cxGroupArray + 1]; - if (!gp) return CERR_CannotAllocateMemory; - - if (fk->dpGroupArray) - { - memcpy(gp, fk->dpGroupArray, sizeof(FILE_GROUP) * fk->cxGroupArray); - delete fk->dpGroupArray; - } - - fk->dpGroupArray = gp; - gp = &fk->dpGroupArray[fk->cxGroupArray]; - fk->cxGroupArray++; - - gp->dpKeyArray = NULL; - gp->dpMatch = NULL; - gp->dpNoMatch = NULL; - gp->cxKeyArray = 0; - - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q) return CERR_InvalidGroupLine; - - gp->fUsingKeys = FALSE; - gp->fReadOnly = IsSameToken(&p, L"readonly"); - if (!gp->fReadOnly) { - if (IsSameToken(&p, L"using") && IsSameToken(&p, L"keys")) - gp->fUsingKeys = TRUE; - } - - safe_wcsncpy(gp->szName, q, SZMAX_GROUPNAME); - - gp->Line = currentLine; - - if (FSaveDebug) - { - WCHAR tstr[128]; - /* Record a system store for the line number of the begin statement */ - swprintf(tstr, _countof(tstr), L"%ls%d %ls", DEBUGSTORE_GROUP, fk->cxGroupArray - 1, gp->szName); // I3481 - AddDebugStore(fk, tstr); - } - - return CheckForDuplicateGroup(fk, gp); -} - -int cmpkeys(const void *key, const void *elem) -{ - PFILE_KEY akey, aelem; - int l1, l2; - WCHAR char_key, char_elem; - akey = (PFILE_KEY)key; - aelem = (PFILE_KEY)elem; - char_key = VKToChar(akey->Key, akey->ShiftFlags); - char_elem = VKToChar(aelem->Key, aelem->ShiftFlags); - if (char_key == char_elem) //akey->Key == aelem->Key) - { - l1 = xstrlen(akey->dpContext); l2 = xstrlen(aelem->dpContext); - if (l1 == l2) - { - if (akey->Line < aelem->Line) return -1; - if (akey->Line > aelem->Line) return 1; - if(akey->Key == aelem->Key) { - if(akey->ShiftFlags == aelem->ShiftFlags) { - return akey->LineStoreIndex - aelem->LineStoreIndex; - } - return akey->ShiftFlags - aelem->ShiftFlags; - } - return akey->Key - aelem->Key; - } - if (l1 < l2) return 1; - if (l1 > l2) return -1; - if(akey->Key == aelem->Key) { - if(akey->ShiftFlags == aelem->ShiftFlags) { - return akey->LineStoreIndex - aelem->LineStoreIndex; - } - return akey->ShiftFlags - aelem->ShiftFlags; - } - return akey->Key - aelem->Key; - } - return(char_key - char_elem); // akey->Key - aelem->Key); -} - -DWORD ProcessGroupFinish(PFILE_KEYBOARD fk) -{ - PFILE_GROUP gp; - DWORD msg; - - if (fk->currentGroup == 0xFFFFFFFF) return CERR_None; - // Just got to first group - so nothing to finish yet - - gp = &fk->dpGroupArray[fk->currentGroup]; - - // Finish off the previous group stuff! - if ((msg = ExpandCapsRulesForGroup(fk, gp)) != CERR_None) return msg; - qsort(gp->dpKeyArray, gp->cxKeyArray, sizeof(FILE_KEY), cmpkeys); - - return VerifyUnreachableRules(gp); -} - -/*************************************** -* Store management -*/ - -DWORD ProcessStoreLine(PFILE_KEYBOARD fk, PWSTR p) -{ - PWSTR q, pp; - PFILE_STORE sp; - //WCHAR temp[GLOBAL_BUFSIZE]; - DWORD msg; - int i = 0; - - pp = p; - - if ((q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL)) == NULL) return CERR_InvalidStoreLine; - - if (*q == *SSN__PREFIX) - { - for (i = 0; StoreTokens[i]; i++) - if (!_wcsicmp(q, StoreTokens[i])) // I3481 - break; - if (!StoreTokens[i]) return CERR_InvalidSystemStore; - } - - sp = new FILE_STORE[fk->cxStoreArray + 1]; - if (!sp) return CERR_CannotAllocateMemory; - - if (fk->dpStoreArray) - { - memcpy(sp, fk->dpStoreArray, sizeof(FILE_STORE) * fk->cxStoreArray); - delete fk->dpStoreArray; - } - - fk->dpStoreArray = sp; - sp = &fk->dpStoreArray[fk->cxStoreArray]; - - sp->line = currentLine; - sp->fIsOption = FALSE; - sp->fIsReserved = FALSE; - sp->fIsStore = FALSE; - sp->fIsDebug = FALSE; - sp->fIsCall = FALSE; - - safe_wcsncpy(sp->szName, q, SZMAX_STORENAME); - { - PWCHAR temp = new WCHAR[GLOBAL_BUFSIZE]; - - if ((msg = GetXString(fk, p, L"c\n", temp, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(p - pp), &p, FALSE, TRUE)) != CERR_None) - { - delete[] temp; - return msg; - } - - sp->dwSystemID = i; - sp->dpString = new WCHAR[wcslen(temp) + 1]; - wcscpy_s(sp->dpString, wcslen(temp) + 1, temp); // I3481 - - delete[] temp; - } - - if (xstrlen(sp->dpString) == 1 && *sp->dpString != UC_SENTINEL && - sp->dwSystemID == 0 && (fk->version >= VERSION_60 || fk->version == 0)) - { - // In this case, we want to change behaviour for older versioned keyboards so that - // we don't mix up named character codes which weren't supported in 5.x - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - // Add a single char store as a defined character constant - if (Uni_IsSurrogate1(*sp->dpString)) - CodeConstants->AddCode(Uni_SurrogateToUTF32(sp->dpString[0], sp->dpString[1]), sp->szName, fk->cxStoreArray); - else - CodeConstants->AddCode(sp->dpString[0], sp->szName, fk->cxStoreArray); - CodeConstants->reindex(); // has to be done after every character add due to possible use in another store. // I4982 - } - - fk->cxStoreArray++; // increment now, because GetXString refers to stores - - if (i > 0) - if ((msg = ProcessSystemStore(fk, i, sp)) != CERR_None) return msg; - - return CheckForDuplicateStore(fk, sp); -} - -DWORD AddStore(PFILE_KEYBOARD fk, DWORD SystemID, PWSTR str, DWORD *dwStoreID) -{ - PFILE_STORE sp; - - sp = new FILE_STORE[fk->cxStoreArray + 1]; - if (!sp) return CERR_CannotAllocateMemory; - - if (fk->dpStoreArray) - { - memcpy(sp, fk->dpStoreArray, sizeof(FILE_STORE) * fk->cxStoreArray); - delete fk->dpStoreArray; - } - - fk->dpStoreArray = sp; - sp = &fk->dpStoreArray[fk->cxStoreArray]; - - sp->line = currentLine; - sp->fIsOption = FALSE; // I3686 - sp->fIsReserved = (SystemID != TSS_NONE); - sp->fIsStore = FALSE; - sp->fIsDebug = FALSE; - sp->fIsCall = FALSE; - - safe_wcsncpy(sp->szName, StoreTokens[SystemID], SZMAX_STORENAME); - - sp->dpString = new WCHAR[wcslen(str) + 1]; - wcscpy_s(sp->dpString, wcslen(str) + 1, str); // I3481 - - sp->dwSystemID = SystemID; - - if (dwStoreID) *dwStoreID = fk->cxStoreArray; - - fk->cxStoreArray++; - - return ProcessSystemStore(fk, SystemID, sp); -} - -DWORD AddDebugStore(PFILE_KEYBOARD fk, PWSTR str) -{ - PFILE_STORE sp; - WCHAR tstr[16]; - - swprintf(tstr, _countof(tstr), L"%d", currentLine); // I3481 - - sp = new FILE_STORE[fk->cxStoreArray + 1]; - if (!sp) return CERR_CannotAllocateMemory; - - if (fk->dpStoreArray) - { - memcpy(sp, fk->dpStoreArray, sizeof(FILE_STORE) * fk->cxStoreArray); - delete[] fk->dpStoreArray; - } - - fk->dpStoreArray = sp; - sp = &fk->dpStoreArray[fk->cxStoreArray]; - - safe_wcsncpy(sp->szName, str, SZMAX_STORENAME); - - sp->dpString = new WCHAR[wcslen(tstr) + 1]; - wcscpy_s(sp->dpString, wcslen(tstr) + 1, tstr); // I3481 - sp->line = 0; - sp->fIsOption = FALSE; - sp->fIsReserved = TRUE; - sp->fIsStore = FALSE; - sp->fIsDebug = TRUE; - sp->fIsCall = FALSE; - sp->dwSystemID = TSS_DEBUG_LINE; - fk->cxStoreArray++; - - return CERR_None; -} - -PWCHAR pssBuf = NULL; - -DWORD ProcessSystemStore(PFILE_KEYBOARD fk, DWORD SystemID, PFILE_STORE sp) -{ - //WCHAR buf[GLOBAL_BUFSIZE]; - int i, j; - DWORD msg; - PWSTR p, q; - char *pp; - - if (!pssBuf) pssBuf = new WCHAR[GLOBAL_BUFSIZE]; - PWCHAR buf = pssBuf; - - switch (SystemID) - { - case TSS_BITMAP: - if ((msg = ImportBitmapFile(fk, sp->dpString, &fk->dwBitmapSize, &fk->lpBitmap)) != CERR_None) - return msg; - break; - - case TSS_CALLDEFINITION: - break; - - case TSS_CALLDEFINITION_LOADFAILED: - break; - - case TSS_CAPSALWAYSOFF: - if (*sp->dpString == L'1') fk->dwFlags |= KF_CAPSALWAYSOFF; - break; - - case TSS_CAPSONONLY: - if (*sp->dpString == L'1') fk->dwFlags |= KF_CAPSONONLY; - break; - - case TSS_COMPILEDVERSION: - break; - - case TSS_COPYRIGHT: - break; - - case TSS_DEBUG_LINE: - break; - - case TSS_ETHNOLOGUECODE: - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_EthnologueCode); - if ((msg = ProcessEthnologueStore(sp->dpString)) != CERR_None) return msg; // I2646 - break; - - case TSS_HOTKEY: - if ((msg = ProcessHotKey(sp->dpString, &fk->dwHotKey)) != CERR_None) return msg; - - swprintf(buf, GLOBAL_BUFSIZE, L"%d", (int)fk->dwHotKey); // I3481 - delete[] sp->dpString; - sp->dpString = new WCHAR[wcslen(buf) + 1]; - wcscpy_s(sp->dpString, wcslen(buf) + 1, buf); // I3481 - break; - - case TSS_INCLUDECODES: - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - pp = wstrtostr(sp->dpString); - if (!CodeConstants->LoadFile(pp)) - { - delete[] pp; - return CERR_CannotLoadIncludeFile; - } - delete[] pp; - CodeConstants->reindex(); // I4982 - break; - - case TSS_LANGUAGE: - { - wchar_t *context = NULL; - q = wcstok_s(sp->dpString, L", ", &context); // I3481 - if (!q) return CERR_InvalidLanguageLine; - - i = xatoi(&q); - q = wcstok_s(NULL, L" c\n", &context); // I3481 - if (!q) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_InvalidLanguageLine); - j = SUBLANGID(i); - i = PRIMARYLANGID(i); - } - else - j = xatoi(&q); - - if (i < 1 || j < 1 || i > 0x3FF || j > 0x3F) return CERR_InvalidLanguageLine; - if (i >= 0x200 || j >= 0x20) AddWarning(CWARN_CustomLanguagesNotSupported); - - fk->KeyboardID = (DWORD)MAKELANGID(i, j); - - swprintf(buf, GLOBAL_BUFSIZE, L"%x %x", i, j); // I3481 - delete[] sp->dpString; - sp->dpString = new WCHAR[wcslen(buf) + 1]; - wcscpy_s(sp->dpString, wcslen(buf) + 1, buf); // I3481 - - break; - } - case TSS_LANGUAGENAME: - break; - - case TSS_LAYOUT: - if (fk->KeyboardID == 0) return CERR_LayoutButNoLanguage; - - q = sp->dpString; - - fk->KeyboardID |= (xatoi(&q) << 16L); - break; - - case TSS_MESSAGE: - break; - - case TSS_MNEMONIC: - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_MnemonicLayout); - FMnemonicLayout = atoiW(sp->dpString) == 1; - if (FMnemonicLayout && FindSystemStore(fk, TSS_CASEDKEYS) != NULL) { - // The &CasedKeys system store is not supported for - // mnemonic layouts - return CERR_CasedKeysNotSupportedWithMnemonicLayout; - } - break; - - case TSS_NAME: - break; - - case TSS_OLDCHARPOSMATCHING: - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_OldCharPosMatching); - FOldCharPosMatching = atoiW(sp->dpString); - break; - - case TSS_SHIFTFREESCAPS: - if (*sp->dpString == L'1') fk->dwFlags |= KF_SHIFTFREESCAPS; - break; - - case TSS_VERSION: - if ((fk->dwFlags & KF_AUTOMATICVERSION) == 0) return CERR_VersionAlreadyIncluded; - p = sp->dpString; - if (wcstof(p, NULL) < 5.0) { - AddWarning(CWARN_OldVersion); - } - - if (wcsncmp(p, L"3.0", 3) == 0) fk->version = VERSION_50; //0x0a0b000n= a.bn - else if (wcsncmp(p, L"3.1", 3) == 0) fk->version = VERSION_50; //all versions < 5.0 - else if (wcsncmp(p, L"3.2", 3) == 0) fk->version = VERSION_50; //we compile as if - else if (wcsncmp(p, L"4.0", 3) == 0) fk->version = VERSION_50; //they are 5.0.100.0 - else if (wcsncmp(p, L"5.01", 4) == 0) fk->version = VERSION_501; - else if (wcsncmp(p, L"5.0", 3) == 0) fk->version = VERSION_50; - else if (wcsncmp(p, L"6.0", 3) == 0) fk->version = VERSION_60; - else if (wcsncmp(p, L"7.0", 3) == 0) fk->version = VERSION_70; - else if (wcsncmp(p, L"8.0", 3) == 0) fk->version = VERSION_80; - else if (wcsncmp(p, L"9.0", 3) == 0) fk->version = VERSION_90; - else if (wcsncmp(p, L"10.0", 4) == 0) fk->version = VERSION_100; - else if (wcsncmp(p, L"14.0", 4) == 0) fk->version = VERSION_140; // Adds support for #917 -- context() with notany() for KeymanWeb - else if (wcsncmp(p, L"15.0", 4) == 0) fk->version = VERSION_150; // Adds support for U_xxxx_yyyy #2858 - else if (wcsncmp(p, L"16.0", 4) == 0) fk->version = VERSION_160; // KMXPlus - else return CERR_InvalidVersion; - - if (fk->version < VERSION_60) FOldCharPosMatching = TRUE; - - fk->dwFlags &= ~KF_AUTOMATICVERSION; - - break; - - case TSS_VISUALKEYBOARD: - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - { - // Strip path from the store, leaving bare filename only - p = sp->dpString; - wchar_t *pp = wcsrchr(p, L'\\'); - if (!pp) { - pp = p; - } else { - pp++; - } - q = new WCHAR[wcslen(pp) + 1]; - wcscpy_s(q, wcslen(pp) + 1, pp); - - // Change compiled reference file extension to .kvk - pp = wcschr(q, 0) - 5; - if (pp > q && _wcsicmp(pp, L".kvks") == 0) { - pp[4] = 0; - } - - delete[] sp->dpString; - sp->dpString = q; - - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } - } - break; - case TSS_KMW_RTL: - case TSS_KMW_HELPTEXT: - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - break; - - case TSS_KMW_HELPFILE: - case TSS_KMW_EMBEDJS: - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } - break; - - case TSS_KMW_EMBEDCSS: - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyEmbedCSS); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } - break; - - case TSS_TARGETS: // I4504 - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyTargets); - break; - - case TSS_WINDOWSLANGUAGES: - { - wchar_t *context = NULL; - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - size_t szQ = wcslen(sp->dpString) * 6 + 1; // I3481 - q = new WCHAR[szQ]; // guaranteed to be enough space for recoding - *q = 0; WCHAR *r = q; - p = wcstok_s(sp->dpString, L" ", &context); // I3481 - while (p) - { - int n = xatoi(&p); - - j = SUBLANGID(n); - i = PRIMARYLANGID(n); - - if (i < 1 || j < 1 || i > 0x3FF || j > 0x3F) { - delete[] q; - return CERR_InvalidLanguageLine; - } - - swprintf(r, szQ - (size_t)(r - q), L"x%04.4x ", n); // I3481 - - p = wcstok_s(NULL, L" ", &context); // I3481 - r = wcschr(q, 0); // I3481 - } - delete[] sp->dpString; - if (*q) *(wcschr(q, 0) - 1) = 0; // delete final space - safe because we control the formatting - ugly? scared? - sp->dpString = q; - break; - } - case TSS_COMPARISON: - VERIFY_KEYBOARD_VERSION(fk, VERSION_80, CERR_80FeatureOnly); - break; - - case TSS_VKDICTIONARY: // I3438 - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyVirtualKeyDictionary); - break; - - case TSS_LAYOUTFILE: // I3483 - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyLayoutFile); // I4140 - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } - // Used by KMW compiler - break; - - case TSS_KEYBOARDVERSION: // I4140 - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyKeyboardVersion); - if (!IsValidKeyboardVersion(sp->dpString)) { - return CERR_KeyboardVersionFormatInvalid; - } - - break; - - case TSS_CASEDKEYS: - if ((msg = VerifyCasedKeys(sp)) != CERR_None) { - return msg; - } - break; - - case TSS_BEGIN_NEWCONTEXT: - case TSS_BEGIN_POSTKEYSTROKE: - break; - - case TSS_NEWLAYER: - case TSS_OLDLAYER: - break; - - default: - return CERR_InvalidSystemStore; - } - return CERR_None; -} - -BOOL IsValidKeyboardVersion(WCHAR *dpString) { // I4140 - /* version format \d+(\.\d+)* e.g. 9.0.3, 1.0, 1.2.3.4, 6.2.1.4.6.4, blank is not allowed */ - - do { - if (!iswdigit(*dpString)) { - return FALSE; - } - while (iswdigit(*dpString)) { - dpString++; - } - if (*dpString == '.') { - dpString++; - if (!iswdigit(*dpString)) { - return FALSE; - } - } - } while (*dpString != 0); - - return TRUE; -} - -BOOL GetFileVersion(char *filename, WORD *d1, WORD *d2, WORD *d3, WORD *d4) -{ - char fnbuf[260]; - DWORD h; - DWORD sz; - PSTR p; - VS_FIXEDFILEINFO *vffi; - UINT len; - - GetModuleFileName(0, fnbuf, 260); - sz = GetFileVersionInfoSize(fnbuf, &h); - if (sz == 0) return FALSE; - p = new char[sz]; - if (!p) return FALSE; - GetFileVersionInfo(fnbuf, h, sz, p); - VerQueryValue(p, "\\", (void **)&vffi, &len); - - *d1 = HIWORD(vffi->dwFileVersionMS); - *d2 = LOWORD(vffi->dwFileVersionMS); - *d3 = HIWORD(vffi->dwFileVersionLS); - *d4 = LOWORD(vffi->dwFileVersionLS); - - delete[] p; - return TRUE; -} - -DWORD AddCompilerVersionStore(PFILE_KEYBOARD fk) -{ - if(!FShouldAddCompilerVersion) { - return CERR_None; - } - - WCHAR verstr[32]; - WORD d1, d2, d3, d4; - DWORD msg; - - GetFileVersion(NULL, &d1, &d2, &d3, &d4); - swprintf(verstr, _countof(verstr), L"%d.%d.%d.%d", d1, d2, d3, d4); // I3481 - - if ((msg = AddStore(fk, TSS_COMPILEDVERSION, verstr)) != CERR_None) return msg; - - return CERR_None; -} - -/**************************** -* Rule lines -*/ - -DWORD CheckStatementOffsets(PFILE_KEYBOARD fk, PFILE_GROUP gp, PWSTR context, PWSTR output, PWSTR key) { - PWSTR p, q; - int i; - for (p = output; *p; p = incxstr(p)) { - if (*p == UC_SENTINEL) { - if (*(p + 1) == CODE_INDEX) { - int indexStore = *(p + 2) - 1; - int contextOffset = *(p + 3); - for (q = context, i = 1; *q && i < contextOffset; q = incxstr(q), i++); - - if (*q == 0) { - if (!gp->fUsingKeys) - // no key in the rule, so offset is past end of context - return CERR_IndexDoesNotPointToAny; - if (i < contextOffset) // I4914 - // offset is beyond the key - return CERR_IndexDoesNotPointToAny; - q = key; - } - - // find the any - if (*q != UC_SENTINEL || *(q + 1) != CODE_ANY) - return CERR_IndexDoesNotPointToAny; - - int anyStore = *(q + 2) - 1; - - if (xstrlen(fk->dpStoreArray[indexStore].dpString) < xstrlen(fk->dpStoreArray[anyStore].dpString)) { - AddWarning(CWARN_IndexStoreShort); //TODO: if this fails, then we return FALSE instead of an error - } - } else if (*(p + 1) == CODE_CONTEXTEX) { - int contextOffset = *(p + 2); - if (contextOffset > xstrlen(context)) - return CERR_ContextExHasInvalidOffset; - - // Due to a limitation in earlier versions of KeymanWeb, the minimum version - // for context() referring to notany() is 14.0. See #917 for details. - if (CompileTarget == CKF_KEYMANWEB) { - for (q = context, i = 1; *q && i < contextOffset; q = incxstr(q), i++); - if (*q == UC_SENTINEL && *(q + 1) == CODE_NOTANY) { - VERIFY_KEYBOARD_VERSION(fk, VERSION_140, CERR_140FeatureOnlyContextAndNotAnyWeb); - } - } - } - } - } - return CERR_None; -} - -/** - * Checks that the order of statements in the context matches the specification - * Rule structure: [context] ['+' key] '>' output - * Context structure: [nul] [if()|baselayout()|platform()]+ [char|any|context()|deadkey()|dk()|index()|notany()|outs()] - * Test that nul is first, then if(), baselayout(), platform() statements are before any other content - */ -BOOL CheckContextStatementPositions(PWSTR context) { - BOOL hadContextChar = FALSE; - for (PWSTR p = context; *p; p = incxstr(p)) { - if (*p == UC_SENTINEL) { - switch (*(p + 1)) { - case CODE_NUL: - if (p > context) { - AddWarning(CWARN_NulNotFirstStatementInContext); - } - break; - case CODE_IFOPT: - case CODE_IFSYSTEMSTORE: - if (hadContextChar) { - AddWarning(CWARN_IfShouldBeAtStartOfContext); - } - break; - default: - hadContextChar = TRUE; - } - } - else { - hadContextChar = TRUE; - } - } - - return TRUE; -} - -/** - * Checks if a use() statement is followed by other content in the output of a rule - */ -DWORD -CheckUseStatementsInOutput(const PFILE_GROUP gp, const PWSTR output) { // I4867 - BOOL hasUse = FALSE; - PWSTR p; - for (p = output; *p; p = incxstr(p)) { - if (*p == UC_SENTINEL && *(p + 1) == CODE_USE) { - hasUse = TRUE; - } else if (hasUse) { - AddWarning(CWARN_UseNotLastStatementInRule); - break; - } - } - return CERR_None; -} - -/** - * Adds implicit `context` to start of output of rules for readonly groups - */ -DWORD -InjectContextToReadonlyOutput(PWSTR pklOut) { - if (pklOut[0] != UC_SENTINEL || pklOut[1] != CODE_CONTEXT) { - if (wcslen(pklOut) > GLOBAL_BUFSIZE - 3) { - return CERR_CannotAllocateMemory; - } - memmove(pklOut + 2, pklOut, (wcslen(pklOut) + 1) * 2); - pklOut[0] = UC_SENTINEL; - pklOut[1] = CODE_CONTEXT; - } - return CERR_None; -} - -/** - * Verifies that a keyboard does not attempt to emit characters or - * other changes to text store when processing a readonly group - */ -DWORD -CheckOutputIsReadonly(const PFILE_KEYBOARD fk, const PWSTR output) { // I4867 - PWSTR p; - for (p = output; *p; p = incxstr(p)) { - if (*p != UC_SENTINEL) { - return CERR_OutputInReadonlyGroup; - } - switch (*(p + 1)) { - case CODE_CALL: - // We cannot be sure that the callee is going to be readonly - // but we have to operate on a trust basis for call() in any - // case, so we'll allow it. - continue; - case CODE_USE: - // We only allow use() of other readonly groups - { - PFILE_GROUP targetGroup = &fk->dpGroupArray[*(p + 2) - 1]; - if (!targetGroup->fReadOnly) { - return CERR_CannotUseReadWriteGroupFromReadonlyGroup; - } - } - continue; - case CODE_SETOPT: - case CODE_RESETOPT: - case CODE_SAVEOPT: - // it is okay to set, reset or save keyboard options - // although it's hard to see good use cases for this - continue; - case CODE_SETSYSTEMSTORE: - // it is okay to set system stores; Engine or Core will - // ignore set(&) that are not permissible in the given context - continue; - case CODE_CONTEXT: - // We allow `context` but only as the very first statement in output - if (p == output) { - continue; - } - return CERR_OutputInReadonlyGroup; - default: - // Note: conceptually, CODE_NUL could be transformed to CODE_CONTEXT - // if the context was also empty, but it is probably safest to avoid this, - // given CODE_CONTEXT does what we need anyway - return CERR_StatementNotPermittedInReadonlyGroup; - } - } - return CERR_None; -} - - DWORD ProcessKeyLine(PFILE_KEYBOARD fk, PWSTR str, BOOL IsUnicode) -{ - PWSTR p, pp; - DWORD msg; - PFILE_GROUP gp; - PFILE_KEY kp; - PWCHAR pklIn, pklKey, pklOut; - - pklIn = new WCHAR[GLOBAL_BUFSIZE]; // I2432 - Allocate buffers each line -- slightly slower but safer than keeping a single buffer - pklKey = new WCHAR[GLOBAL_BUFSIZE]; - pklOut = new WCHAR[GLOBAL_BUFSIZE]; - if (!pklIn || !pklKey || !pklOut) - return CERR_CannotAllocateMemory; // forget about the little leak if pklKey or pklOut fail... - - __try - { - - gp = &fk->dpGroupArray[fk->currentGroup]; - - pp = str; - - if (gp->fUsingKeys) { - if ((msg = GetXString(fk, str, L"+", pklIn, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(str - pp), &p, TRUE, IsUnicode)) != CERR_None) return msg; - - str = p + 1; - if ((msg = GetXString(fk, str, L">", pklKey, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(str - pp), &p, TRUE, IsUnicode)) != CERR_None) return msg; - if (pklKey[0] == 0) return CERR_ZeroLengthString; - if (xstrlen(pklKey) > 1) AddWarning(CWARN_KeyBadLength); - } else { - if ((msg = GetXString(fk, str, L">", pklIn, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(str - pp), &p, TRUE, IsUnicode)) != CERR_None) return msg; - if (pklIn[0] == 0) return CERR_ZeroLengthString; - } - - str = p + 1; - if ((msg = GetXString(fk, str, L"c\n", pklOut, GLOBAL_BUFSIZE - 1, (int)(INT_PTR)(str - pp), &p, TRUE, IsUnicode)) != CERR_None) return msg; - - if (pklOut[0] == 0) return CERR_ZeroLengthString; - - CheckContextStatementPositions(pklIn); - - // Test index and context offsets in context - if ((msg = CheckStatementOffsets(fk, gp, pklIn, pklOut, pklKey)) != CERR_None) return msg; - - // Test that use() statements are not followed by other content - if ((msg = CheckUseStatementsInOutput(gp, pklOut)) != CERR_None) { - return msg; // I4867 - } - - if (gp->fReadOnly) { - // Ensure no output is made from the rule, and that - // use() statements meet required readonly semantics - if ((msg = CheckOutputIsReadonly(fk, pklOut)) != CERR_None) { - return msg; - } - - // Inject `context` to start of output if group is readonly - // to keep the output internally consistent - if ((msg = InjectContextToReadonlyOutput(pklOut)) != CERR_None) { - return msg; - } - } - - kp = new FILE_KEY[gp->cxKeyArray + 1]; - if (!kp) return CERR_CannotAllocateMemory; - if (gp->dpKeyArray) - { - memcpy(kp, gp->dpKeyArray, gp->cxKeyArray * sizeof(FILE_KEY)); - delete gp->dpKeyArray; - } - - gp->dpKeyArray = kp; - kp = &gp->dpKeyArray[gp->cxKeyArray]; - gp->cxKeyArray++; - - kp->dpOutput = new WCHAR[wcslen(pklOut) + 1]; - wcscpy_s(kp->dpOutput, wcslen(pklOut) + 1, pklOut); // I3481 - - kp->dpContext = new WCHAR[wcslen(pklIn) + 1]; - wcscpy_s(kp->dpContext, wcslen(pklIn) + 1, pklIn); // I3481 - - kp->Line = currentLine; - kp->LineStoreIndex = 0; - - // Finished if we are not using keys - - if (!gp->fUsingKeys) - { - kp->Key = 0; - kp->ShiftFlags = 0; - return CERR_None; - } - - // Expand each rule out into multiple rules - much faster processing at the key hit time - - if (*pklKey == 0) return CERR_ZeroLengthString; - - if (*pklKey == UC_SENTINEL) - switch (*(pklKey + 1)) - { - case CODE_ANY: - kp->ShiftFlags = 0; - if ((msg = ExpandKp(fk, kp, *(pklKey + 2) - 1)) != CERR_None) return msg; - break; - - case CODE_EXTENDED: - kp->Key = *(pklKey + 3); - kp->ShiftFlags = *(pklKey + 2); - break; - - default: - return CERR_InvalidCodeInKeyPartOfRule; - } - else - { - kp->ShiftFlags = 0; - kp->Key = *pklKey; - } - - } - __finally - { - delete pklIn; // I2432 - Allocate buffers each line -- slightly slower but safer than keeping a single buffer - delete pklKey; - delete pklOut; - } - - return CERR_None; -} - - - -DWORD ExpandKp_ReplaceIndex(PFILE_KEYBOARD fk, PFILE_KEY k, DWORD keyIndex, int nAnyIndex) -{ - /* Replace each index(xx,keyIndex) in k->dpOutput with appropriate char as based on nAnyIndex */ - PFILE_STORE s; - int i; - PWSTR pIndex, pStore; - - for (pIndex = k->dpOutput; *pIndex; pIndex = incxstr(pIndex)) - { - if (*pIndex == UC_SENTINEL && *(pIndex + 1) == CODE_INDEX && *(pIndex + 3) == keyIndex) - { - s = &fk->dpStoreArray[*(pIndex + 2) - 1]; - for (i = 0, pStore = s->dpString; i < nAnyIndex; i++, pStore = incxstr(pStore)); - PWSTR qStore = incxstr(pStore); - - int w = (int)(INT_PTR)(qStore - pStore); - if (w > 4) - { - *pIndex = UC_SENTINEL; - *(pIndex + 1) = CODE_BEEP; - memmove(pIndex + 2, pIndex + 4, wcslen(pIndex + 3) * 2); - } - else - { - memcpy(pIndex, pStore, w * 2); - if (w < 4) memmove(pIndex + w, pIndex + 4, wcslen(pIndex + 3) * 2); - } - } - } - - return CERR_None; -} - - -DWORD ExpandKp(PFILE_KEYBOARD fk, PFILE_KEY kpp, DWORD storeIndex) -{ - PFILE_KEY k; - PWSTR pn; - DWORD nchrs, n; - int keyIndex; - - PFILE_STORE sp = &fk->dpStoreArray[storeIndex]; - PFILE_GROUP gp = &fk->dpGroupArray[fk->currentGroup]; - - PWSTR dpContext = kpp->dpContext; - PWSTR dpOutput = kpp->dpOutput; - - nchrs = xstrlen(sp->dpString); - pn = sp->dpString; - keyIndex = xstrlen(dpContext) + 1; - - /* - Now we change them to plain characters in the output in multiple rules, - and set the keystroke to the appropriate character in the store. - */ - - k = new FILE_KEY[gp->cxKeyArray + nchrs - 1]; - if (!k) return CERR_CannotAllocateMemory; - memcpy(k, gp->dpKeyArray, gp->cxKeyArray * sizeof(FILE_KEY)); - - kpp = &k[(INT_PTR)(kpp - gp->dpKeyArray)]; - - delete gp->dpKeyArray; - gp->dpKeyArray = k; - gp->cxKeyArray += nchrs - 1; - - for (k = kpp, n = 0, pn = sp->dpString; *pn; pn = incxstr(pn), k++, n++) - { - k->dpContext = new WCHAR[wcslen(dpContext) + 1]; - k->dpOutput = new WCHAR[wcslen(dpOutput) + 1]; - - wcscpy_s(k->dpContext, wcslen(dpContext) + 1, dpContext); // copy the context. // I3481 - wcscpy_s(k->dpOutput, wcslen(dpOutput) + 1, dpOutput); // copy the output. - - if (*pn == UC_SENTINEL) - { - switch (*(pn + 1)) - { - case CODE_EXTENDED: - k->Key = *(pn + 3); // set the key to store offset. - k->ShiftFlags = *(pn + 2); - break; - default: - return CERR_CodeInvalidInKeyStore; - } - } - else - { - k->Key = *pn; // set the key to store offset. - k->ShiftFlags = 0; - } - k->Line = kpp->Line; - k->LineStoreIndex = (WORD)n; - ExpandKp_ReplaceIndex(fk, k, keyIndex, n); - } - - delete dpContext; - delete dpOutput; - - return CERR_None; -} - - -PWSTR GetDelimitedString(PWSTR *p, PWSTR Delimiters, WORD Flags) -{ - PWSTR q, r; - WCHAR dOpen, dClose; - - dOpen = *Delimiters; dClose = *(Delimiters + 1); - - q = *p; - while (iswspace(*q)) q++; //***QUERY - - if (*q != dOpen) return NULL; - - q++; - - r = wcschr(q, dClose); // Find closing delimiter - if (!r) return NULL; - - if (Flags & GDS_CUTLEAD) - while (iswspace(*q)) q++; // cut off leading spaces - - if (Flags & GDS_CUTFOLL) - if (!iswspace(*(r - 1))) *r = 0; - else - { - r--; // Cut off following spaces - while (iswspace(*r) && r > q) r--; - r++; - *r = 0; r = wcschr((r + 1), dClose); - } - else *r = 0; - - r++; while (iswspace(*r)) r++; // Ignore spaces after the close - if (*r == 0) r--; // Safety for terminating strings. - - *p = r; // Update pointer position - - return q; // Return delimited string -} - - -enum LinePrefixType { lptNone, lptKeymanAndKeymanWeb, lptKeymanWebOnly, lptKeymanOnly, lptOther }; - -LinePrefixType GetLinePrefixType(PWSTR *p) -{ - PWSTR s = *p; - - while (iswspace(*s)) s++; - - PWSTR q = s; - - if (*s != '$') return lptNone; - - /* I1569 - fix named constants at the start of the line */ - s++; - while (__iswcsym(*s)) s++; - if (*s != ':') return lptNone; - - if (_wcsnicmp(q, L"$keyman:", 8) == 0) - { - *p += 8; - return lptKeymanAndKeymanWeb; - } - if (_wcsnicmp(q, L"$keymanweb:", 11) == 0) - { - *p += 11; - return lptKeymanWebOnly; - } - if (_wcsnicmp(q, L"$keymanonly:", 12) == 0) - { - *p += 12; - return lptKeymanOnly; - } - - return lptOther; -} - -int LineTokenType(PWSTR *str) -{ - int i; - size_t l; - PWSTR p = *str; - - LinePrefixType lpt = GetLinePrefixType(&p); - if (lpt == lptOther) return T_BLANK; - - /* Test KeymanWeb, Keyman and KeymanOnly prefixes */ - if (CompileTarget == CKF_KEYMAN && lpt == lptKeymanWebOnly) return T_BLANK; - if (CompileTarget == CKF_KEYMANWEB && lpt == lptKeymanOnly) return T_BLANK; - - while (iswspace(*p)) p++; - - if (wcschr(LineTokens[0], towupper(*p))) - for (i = 0; i <= T_W_END - T_W_START; i++) - { - l = wcslen(LineTokens[i + 1]); - if (_wcsnicmp(p, LineTokens[i + 1], l) == 0) - { - p += l; while (iswspace(*p)) p++; *str = p; - return i + T_W_START; - } - } - - switch (towupper(*p)) - { - case 'C': - if (iswspace(*(p + 1))) return T_COMMENT; - break; - case 0: - return T_BLANK; - default: - if (wcschr(L"\"aAbBlLpPnN[OoxXdD0123456789\'+UuiI$", *p)) // I4784 - { - *str = p; - return T_KEYTOKEY; - } - } - return T_UNKNOWN; -} - -const PWSTR DeadKeyChars = -L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; - -BOOL strvalidchrs(PWSTR q, PWSTR chrs) -{ - for (; *q; q++) - if (!wcschr(chrs, *q)) return FALSE; - return TRUE; -} - -DWORD GetXString(PFILE_KEYBOARD fk, PWSTR str, PWSTR token, PWSTR output, int max, int offset, PWSTR *newp, int isVKey, int isUnicode) -{ - DWORD err; - PWSTR p = str, q, r; - int type, mx = 0, n, n1, n2, tokenFound = FALSE, z, sFlag = 0, j; - DWORD i; - BOOL finished = FALSE; - WCHAR c; - - PWCHAR tstr = NULL; - int tstrMax = 0; - - tstr = new WCHAR[max]; // I2432 - Allocate buffers each line -- slightly slower but safer than keeping a single buffer - GetXString is re-entrant with if() - tstrMax = max; - - __try - { - *tstr = 0; - - *output = 0; - - p = str; - do - { - tokenFound = FALSE; - while (iswspace(*p) && !wcschr(token, *p)) p++; - if (!*p) break; - - ErrChr = (int)(INT_PTR)(p - str) + offset + 1; - - /* - char *tokenTypes[] = { - "clearcontext", "deadkey", "context", "return", "switch", - "index", "outs", "beep", "nul", "use", "any", "fix", "dk", "k_", "x", "d", "c", - "[", "]" }; - */ - - switch (towupper(*p)) - { - case 'X': - case 'D': type = 0; break; // xFF, d130: chars, deadkey(n) - case '\"': type = 1; break; // "xxxx": chars - case '\'': type = 2; break; // 'xxxx': chars - case 'A': type = 3; break; // any(s) - case 'B': type = 4; break; // beep, baselayout (synonym for if(&baselayout)) // I3430 - case 'I': type = 5; break; // index(s,n), if - case 'O': type = 6; break; // outs(s) - case 'C': type = 7; break; // context, comments, clearcontext, call(s) - case 'N': type = 8; break; // nul, notany - case 'U': type = 9; break; // use(g) - case 'R': type = 10; break; // return, reset - case '[': type = 11; break; // start of vkey section - //case ']': type = 12; break; // end of vkey section - //case 'K': type = 13; break; // virtual key name or "key" - case 'S': type = 14; break; // switch, set, save - case 'F': type = 15; break; // fix (synonym for clearcontext) - case '$': type = 16; break; // named code constants - case 'P': type = 17; break; // platform (synonym for if(&platform)) // I3430 - case 'L': type = 18; break; // layer (synonym for set(&layer)) // I3437 - case '.': type = 19; break; // .. allows us to specify ranges -- either vkeys or characters - default: - if (iswdigit(*p)) type = 0; // octal number - else type = 99; // error! - } - if (wcschr(token, *p)) tokenFound = TRUE; - - switch (type) - { - case 99: - if (tokenFound) break; - wsprintf(ErrExtra, "token: %c", (int)*p); - return CERR_InvalidToken; - case 0: - if (_wcsnicmp(p, L"deadkey", z = 7) == 0 || - _wcsnicmp(p, L"dk", z = 2) == 0) - { - p += z; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidDeadkey; - - DWORD n = fk->cxDeadKeyArray; - - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_DEADKEY; - if (!strvalidchrs(q, DeadKeyChars)) return CERR_InvalidDeadkey; - tstr[mx++] = GetDeadKey(fk, q); //atoiW(q); 7-5-01: named deadkeys - tstr[mx] = 0; - } - else - { - n = xatoi(&p); - if (*p != '\0' && !iswspace(*p)) return CERR_InvalidValue; - if ((err = UTF32ToUTF16(n, &n1, &n2)) != CERR_None) return err; - tstr[mx++] = n1; - if (n2 >= 0) tstr[mx++] = n2; - tstr[mx] = 0; - } - continue; - case 1: - q = wcschr(p + 1, '\"'); - if (!q) return CERR_UnterminatedString; - if ((INT_PTR)(q - p) - 1 + mx > max) return CERR_UnterminatedString; - if (sFlag) return CERR_StringInVirtualKeySection; - wcsncat_s(tstr, max, p + 1, (INT_PTR)(q - p) - 1); // I3481 - mx += (int)(INT_PTR)(q - p) - 1; - tstr[mx] = 0; - p = q + 1; - continue; - case 2: - q = wcschr(p + 1, '\''); - if (!q) return CERR_UnterminatedString; - if ((INT_PTR)(q - p) - 1 + mx > max) return CERR_UnterminatedString; - if (sFlag) return CERR_StringInVirtualKeySection; - wcsncat_s(tstr, max, p + 1, (INT_PTR)(q - p) - 1); // I3481 - mx += (int)(INT_PTR)(q - p) - 1; - tstr[mx] = 0; - p = q + 1; - continue; - case 3: - if (_wcsnicmp(p, L"any", 3) != 0) return CERR_InvalidToken; - if (sFlag) return CERR_AnyInVirtualKeySection; - p += 3; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidAny; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - - if (!*fk->dpStoreArray[i].dpString) return CERR_ZeroLengthString; - CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); - - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_ANY; - tstr[mx++] = (WCHAR)i + 1; // store to index + 1, avoids End-of-string - tstr[mx] = 0; - continue; - case 4: - if (_wcsnicmp(p, L"beep", 4) == 0) - { - if (sFlag) return CERR_BeepInVirtualKeySection; - p += 4; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_BEEP; - tstr[mx] = 0; - } - else if (_wcsnicmp(p, L"baselayout", 10) == 0) // I3430 - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnly_IfSystemStores); - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 10; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidToken; - err = process_baselayout(fk, q, tstr, &mx); - if (err != CERR_None) return err; - } - else - return CERR_InvalidToken; - - continue; - case 5: - if (_wcsnicmp(p, L"if", 2) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_80, CERR_80FeatureOnly); - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 2; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidIf; - - err = process_if(fk, q, tstr, &mx); - if (err != CERR_None) return err; - } - else - { - if (_wcsnicmp(p, L"index", 5) != 0) return CERR_InvalidToken; - if (sFlag) return CERR_IndexInVirtualKeySection; - p += 5; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - - if (!q || !*q) return CERR_InvalidIndex; - - { - wchar_t *context = NULL; - r = wcstok_s(q, L" ,", &context); // I3481 - if (!r) return CERR_InvalidIndex; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(r, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - - CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); - - r = wcstok_s(NULL, L" ,", &context); // I3481 - if (!r) return CERR_InvalidIndex; - } - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_INDEX; - tstr[mx++] = (WCHAR)i + 1; // avoid EOS for stores - tstr[mx++] = atoiW(r); // character offset of original any. - - tstr[mx] = 0; - } - continue; - case 6: - if (_wcsnicmp(p, L"outs", 4) != 0) return CERR_InvalidToken; - if (sFlag) return CERR_OutsInVirtualKeySection; - p += 4; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidOuts; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - - CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); - - for (q = fk->dpStoreArray[i].dpString; *q; q++) - { - tstr[mx++] = *q; - if (mx >= max - 1) return CERR_BufferOverflow; - } - tstr[mx] = 0; - continue; - case 7: - if (iswspace(*(p + 1))) break; // is a comment -- pre-stripped - so why this test? - if (_wcsnicmp(p, L"context", 7) == 0) - { - if (sFlag) return CERR_ContextInVirtualKeySection; - p += 7; - - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (q && *q) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_Contextn); - int n1; - n1 = atoiW(q); - if (n1 < 1 || n1 >= 0xF000) return CERR_InvalidToken; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_CONTEXTEX; - tstr[mx++] = n1; - tstr[mx] = 0; - } - else - { - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_CONTEXT; - tstr[mx] = 0; - } - } - else if (_wcsnicmp(p, L"clearcontext", 12) == 0) - { - p += 12; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_CLEARCONTEXT; - tstr[mx] = 0; - } - else if (_wcsnicmp(p, L"call", 4) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_501, CERR_501FeatureOnly_Call); - if (sFlag) return CERR_CallInVirtualKeySection; - p += 4; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidCall; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - - if (!IsValidCallStore(&fk->dpStoreArray[i])) return CERR_InvalidCall; - CheckStoreUsage(fk, i, FALSE, FALSE, TRUE); - - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_CALL; - tstr[mx++] = (WCHAR)i + 1; - tstr[mx] = 0; - - fk->dpStoreArray[i].dwSystemID = TSS_CALLDEFINITION; - } - else - return CERR_InvalidToken; - continue; - case 8: - if (_wcsnicmp(p, L"notany", 6) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly) - if (sFlag) return CERR_AnyInVirtualKeySection; - p += 6; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidAny; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_NOTANY; - tstr[mx++] = (WCHAR)i + 1; // store to index + 1, avoids End-of-string - tstr[mx] = 0; - continue; - } - if (_wcsnicmp(p, L"nul", 3) != 0) return CERR_InvalidToken; - - p += 3; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_NUL; - tstr[mx] = 0; - continue; - case 9: - if (_wcsnicmp(p, L"use", 3) != 0) - { - if (*(p + 1) == '+') - { - n = xatoi(&p); - if (*p != '\0' && !iswspace(*p)) return CERR_InvalidValue; - if ((err = UTF32ToUTF16(n, &n1, &n2)) != CERR_None) return err; - tstr[mx++] = n1; - if (n2 >= 0) tstr[mx++] = n2; - tstr[mx] = 0; - if (!isUnicode) AddWarning(CWARN_UnicodeInANSIGroup); - continue; - } - return CERR_InvalidToken; - } - p += 3; - - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidUse; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_USE; - tstr[mx] = GetGroupNum(fk, q); - if (tstr[mx] == 0) return CERR_GroupDoesNotExist; - tstr[++mx] = 0; - continue; - case 10: - if (_wcsnicmp(p, L"reset", 5) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_80, CERR_80FeatureOnly); - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 5; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidReset; - - err = process_reset(fk, q, tstr, &mx); - if (err != CERR_None) return err; - } - else - { - if (_wcsnicmp(p, L"return", 6) != 0) return CERR_InvalidToken; - - p += 6; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_RETURN; - tstr[mx] = 0; - wcsncpy_s(output, max, tstr, max); // I3481 - output[max - 1] = 0; - return 0; - } - continue; - case 11: - p++; sFlag = ISVIRTUALKEY /* 0 */; finished = FALSE; - - //printf("--EXTENDEDSTRING--\n"); - - do - { - while (iswspace(*p)) p++; - - switch (towupper(*p)) - { - case 'N': - if (_wcsnicmp(p, L"NCAPS", 5) == 0) - sFlag |= NOTCAPITALFLAG, p += 5; - else finished = TRUE; - break; - case 'L': - if (_wcsnicmp(p, L"LALT", 4) == 0) - sFlag |= LALTFLAG, p += 4; - else if (_wcsnicmp(p, L"LCTRL", 5) == 0) - sFlag |= LCTRLFLAG, p += 5; - else finished = TRUE; - break; - case 'R': - if (_wcsnicmp(p, L"RALT", 4) == 0) - sFlag |= RALTFLAG, p += 4; - else if (_wcsnicmp(p, L"RCTRL", 5) == 0) - sFlag |= RCTRLFLAG, p += 5; - else finished = TRUE; - break; - case 'A': - if (_wcsnicmp(p, L"ALT", 3) == 0) - sFlag |= K_ALTFLAG, p += 3; - else finished = TRUE; - break; - case 'C': - if (_wcsnicmp(p, L"CTRL", 4) == 0) - sFlag |= K_CTRLFLAG, p += 4; - else if (_wcsnicmp(p, L"CAPS", 4) == 0) - sFlag |= CAPITALFLAG, p += 4; - else finished = TRUE; - break; - case 'S': - if (_wcsnicmp(p, L"SHIFT", 5) == 0) - sFlag |= K_SHIFTFLAG, p += 5; - else finished = TRUE; - break; - default: - finished = TRUE; - break; - } - } while (!finished); - - if ((sFlag & (LCTRLFLAG | LALTFLAG)) && (sFlag & (RCTRLFLAG | RALTFLAG))) { - AddWarning(CWARN_MixingLeftAndRightModifiers); - } - - // If we use chiral modifiers, or we use state keys, and we target web in the keyboard, and we don't manually specify a keyboard version, bump the minimum - // version to 10.0. This makes an assumption that if we are using these features in a keyboard and it has no version specified, that we want to use the features - // in the web target platform, even if there are platform() rules excluding this possibility. In that (rare) situation, the keyboard developer should simply specify - // the &version to be 9.0 or whatever to avoid this behaviour. - if (sFlag & (LCTRLFLAG | LALTFLAG | RCTRLFLAG | RALTFLAG | CAPITALFLAG | NOTCAPITALFLAG | NUMLOCKFLAG | NOTNUMLOCKFLAG | SCROLLFLAG | NOTSCROLLFLAG) && - CompileTarget == CKF_KEYMANWEB && - fk->dwFlags & KF_AUTOMATICVERSION) { - VERIFY_KEYBOARD_VERSION(fk, VERSION_100, 0); - } - //printf("sFlag: %x\n", sFlag); - - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_EXTENDED; - tstr[mx++] = sFlag; - - while (iswspace(*p)) p++; - - q = p; - - if (*q == ']') - { - return CERR_InvalidToken; // I3137 - key portion of VK is missing e.g. "[CTRL ALT]", this generates invalid kmx file that can crash Keyman or compiler later on // I3511 - } - - while (*q != ']') - { - if (*q == '\'' || *q == '"') - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_VirtualCharKey); - if (!FMnemonicLayout) AddWarning(CWARN_VirtualCharKeyWithPositionalLayout); - WCHAR chQuote = *q; - q++; if (*q == chQuote || *q == '\n' || *q == 0) return CERR_InvalidToken; - tstr[mx - 1] |= VIRTUALCHARKEY; - tstr[mx++] = *q; - q++; if (*q != chQuote) return CERR_InvalidToken; - q++; - while (iswspace(*q)) q++; - if (*q != ']') return CERR_InvalidToken; - break; /* out of while loop */ - } - - for (j = 0; !iswspace(*q) && *q != ']' && *q != 0; q++, j++); - - if (*q == 0) return CERR_InvalidToken; - - WCHAR vkname[SZMAX_VKDICTIONARYNAME]; // I3438 - - if (j >= SZMAX_VKDICTIONARYNAME) return CERR_InvalidToken; - - wcsncpy_s(vkname, _countof(vkname), p, j); // I3481 - vkname[j] = 0; - - if (_wcsicmp(vkname, L"K_NPENTER") == 0) - i = 5; // I649 - K_NPENTER hack - else - { - for (i = 0; i <= VK__MAX; i++) - { - if (_wcsicmp(vkname, VKeyNames[i]) == 0 || _wcsicmp(vkname, VKeyISO9995Names[i]) == 0) - break; - } - } - - if (i == VK__MAX + 1) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_InvalidToken); - - i = GetVKCode(fk, vkname); // I3438 - if (i == 0) - return CERR_InvalidToken; - } - - p = q; - - tstr[mx++] = (int)i; - - if (FMnemonicLayout && (i <= VK__MAX) && VKeyMayBeVCKey[i]) AddWarning(CWARN_VirtualKeyWithMnemonicLayout); // I3438 - - while (iswspace(*q)) q++; - } - tstr[mx++] = UC_SENTINEL_EXTENDEDEND; - tstr[mx] = 0; - //printf("--EXTENDEDEND--\n"); - - p = q + 1; - - sFlag = 0; - - continue; - case 14: - if (_wcsnicmp(p, L"set", 3) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_80, CERR_80FeatureOnly); - p += 3; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidSet; - - err = process_set(fk, q, tstr, &mx); - if (err != CERR_None) return err; - } - else if (_wcsnicmp(p, L"save", 4) == 0) - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_80, CERR_80FeatureOnly); - p += 4; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidSave; - - err = process_save(fk, q, tstr, &mx); - if (err != CERR_None) return err; - } - else - { - if (_wcsnicmp(p, L"switch", 6) != 0) return CERR_InvalidToken; - p += 6; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidSwitch; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_SWITCH; - tstr[mx++] = atoiW(q); - tstr[mx] = 0; - } - continue; - case 15: - if (_wcsnicmp(p, L"fix", 3) == 0) - { - p += 3; - tstr[mx++] = UC_SENTINEL; - tstr[mx++] = CODE_CLEARCONTEXT; - tstr[mx] = 0; - } - else - return CERR_InvalidToken; - continue; - case 16: - VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - q = p + 1; - while (*q && !iswspace(*q)) q++; - c = *q; *q = 0; - n = CodeConstants->GetCode(p + 1, &i); - *q = c; - if (n == 0) return CERR_InvalidNamedCode; - if (i < 0xFFFFFFFFL) CheckStoreUsage(fk, i, TRUE, FALSE, FALSE); // I2993 - if (n > 0xFFFF) - { - tstr[mx++] = Uni_UTF32ToSurrogate1(n); - tstr[mx++] = Uni_UTF32ToSurrogate2(n); - } - else - tstr[mx++] = n; - tstr[mx] = 0; - p = q; - continue; - case 17: - if (_wcsnicmp(p, L"platform", 8) != 0) return CERR_InvalidToken; // I3430 - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnly_IfSystemStores); - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 8; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidToken; - err = process_platform(fk, q, tstr, &mx); - if (err != CERR_None) return err; - continue; - case 18: // I3437 - if (_wcsnicmp(p, L"layer", 5) != 0) return CERR_InvalidToken; - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnly_SetSystemStores); - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 5; - q = GetDelimitedString(&p, L"()", GDS_CUTLEAD | GDS_CUTFOLL); - if (!q || !*q) return CERR_InvalidToken; - err = process_set_synonym(TSS_LAYER, fk, q, tstr, &mx); - if (err != CERR_None) return err; - continue; - case 19: // #2241 - if (*(p + 1) != '.') return CERR_InvalidToken; - if (sFlag) return CERR_InvalidInVirtualKeySection; - p += 2; - err = process_expansion(fk, p, tstr, &mx, max); - if (err != CERR_None) return err; - continue; - default: - return CERR_InvalidToken; - } - if (tokenFound) - { - *newp = p; - wcsncpy_s(output, max, tstr, max); // I3481 - output[max - 1] = 0; - ErrChr = 0; - return CERR_None; - } - if (mx >= max) return CERR_BufferOverflow; - } while (*p); - - if (!*token) - { - *newp = p; - wcsncpy_s(output, max, tstr, max); // I3481 - output[max - 1] = 0; - ErrChr = 0; - return CERR_None; - } - - } - __finally - { - delete[] tstr; // I2432 - Allocate buffers each line -- slightly slower but safer than keeping a single buffer - GetXString is re-entrant with if() - } - - return CERR_NoTokensFound; -} - -DWORD process_if_synonym(DWORD dwSystemID, PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx); // I3430 - -DWORD process_baselayout(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) // I3430 -{ - /* baselayout() */ - return process_if_synonym(TSS_BASELAYOUT, fk, q, tstr, mx); -} - -DWORD process_platform(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) // I3430 -{ - /* platform() */ - return process_if_synonym(TSS_PLATFORM, fk, q, tstr, mx); -} - -DWORD process_if_synonym(DWORD dwSystemID, PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) // I3430 -{ - PWCHAR temp = new WCHAR[GLOBAL_BUFSIZE]; - - DWORD msg; - - PWSTR r; - - if ((msg = GetXString(fk, q, L"", temp, GLOBAL_BUFSIZE - 1, 0, &r, FALSE, TRUE)) != CERR_None) - { - delete temp; - return msg; - } - - DWORD dwStoreID; - - if ((msg = AddStore(fk, TSS_COMPARISON, temp, &dwStoreID)) != CERR_None) - { - delete temp; - return msg; - } - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = (WCHAR)CODE_IFSYSTEMSTORE; - tstr[(*mx)++] = (WCHAR)(dwSystemID + 1); // I4785 - tstr[(*mx)++] = 2; - tstr[(*mx)++] = (WCHAR)(dwStoreID + 1); - tstr[(*mx)] = 0; - - delete[] temp; - - return CERR_None; -} - -DWORD process_if(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) // I3431 -{ - /* if( <'='|'!='> ) */ - DWORD i, code, not = FALSE; - LPWSTR r = q, s = q; - while (*s && *s != L' ' && *s != L'!' && *s != L'=') s++; - r = s; - while (*s == L' ') s++; - if (*s == L'!') - { - s++; - not = TRUE; - } - - if (*s != '=') return CERR_InvalidIf; - s++; - while (*s == ' ') s++; - *r = 0; - r = q; - - if (r[0] == '&') - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnly_IfSystemStores); - for (i = 0; StoreTokens[i]; i++) - { - if (_wcsicmp(r, StoreTokens[i]) == 0) break; - } - if (!StoreTokens[i]) return CERR_IfSystemStore_NotFound; - code = CODE_IFSYSTEMSTORE; - } - else - { - code = CODE_IFOPT; - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(r, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - CheckStoreUsage(fk, i, FALSE, TRUE, FALSE); - } - - PWCHAR temp = new WCHAR[GLOBAL_BUFSIZE]; - - DWORD msg; - - if ((msg = GetXString(fk, s, L"", temp, GLOBAL_BUFSIZE - 1, 0, &r, FALSE, TRUE)) != CERR_None) - { - delete[] temp; - return msg; - } - - DWORD dwStoreID; - - if ((msg = AddStore(fk, TSS_COMPARISON, temp, &dwStoreID)) != CERR_None) - { - delete[] temp; - return msg; - } - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = (WCHAR)code; - tstr[(*mx)++] = (WCHAR)(i + 1); - tstr[(*mx)++] = not ? 1 : 2; - tstr[(*mx)++] = (WCHAR)(dwStoreID + 1); - tstr[(*mx)] = 0; - - return CERR_None; -} - -DWORD process_reset(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) -{ - /* reset() */ - DWORD i; - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - CheckStoreUsage(fk, i, FALSE, TRUE, FALSE); - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = CODE_RESETOPT; - tstr[(*mx)++] = (WCHAR)(i + 1); - tstr[(*mx)] = 0; - - return CERR_None; -} - -DWORD process_expansion(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx, int max) { - BOOL isVKey = FALSE; - - WORD BaseKey=0, BaseShiftFlags=0; - DWORD BaseChar=0; - - if (*mx == 0) { - return CERR_ExpansionMustFollowCharacterOrVKey; - } - LPWSTR p = &tstr[*mx]; - p = decxstr(p, tstr); - if (*p == UC_SENTINEL) { - if (*(p + 1) != CODE_EXTENDED) { - return CERR_ExpansionMustFollowCharacterOrVKey; - } - isVKey = TRUE; - BaseKey = *(p + 3); - BaseShiftFlags = *(p + 2); - } - else { - BaseChar = Uni_UTF16ToUTF32(p); - } - - // Look ahead at next element - WCHAR temp[GLOBAL_BUFSIZE]; - PWCHAR r = NULL; - - DWORD msg; - - if ((msg = GetXString(fk, q, L"", temp, _countof(temp) - 1, 0, &r, FALSE, TRUE)) != CERR_None) - { - return msg; - } - - WORD HighKey, HighShiftFlags; - DWORD HighChar; - - switch(temp[0]) { - case 0: - return isVKey ? CERR_VKeyExpansionMustBeFollowedByVKey : CERR_CharacterExpansionMustBeFollowedByCharacter; - case UC_SENTINEL: - // Verify that range is valid virtual key range - if(!isVKey) { - return CERR_CharacterExpansionMustBeFollowedByCharacter; - } - if (temp[1] != CODE_EXTENDED) { - return CERR_VKeyExpansionMustBeFollowedByVKey; - } - HighKey = temp[3], HighShiftFlags = temp[2]; - if (HighShiftFlags != BaseShiftFlags) { - return CERR_VKeyExpansionMustUseConsistentShift; - } - if (HighKey <= BaseKey) { - return CERR_ExpansionMustBePositive; - } - // Verify space in buffer - if (*mx + (HighKey - BaseKey) * 5 + 1 >= max) return CERR_BufferOverflow; - // Inject an expansion. - for (BaseKey++; BaseKey < HighKey; BaseKey++) { - // < HighKey because caller will add HighKey to output - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = CODE_EXTENDED; - tstr[(*mx)++] = BaseShiftFlags; - tstr[(*mx)++] = BaseKey; - tstr[(*mx)++] = UC_SENTINEL_EXTENDEDEND; - } - tstr[*mx] = 0; - break; - default: - // Verify that range is a valid character range - if (isVKey) { - return CERR_VKeyExpansionMustBeFollowedByVKey; - } - - HighChar = Uni_UTF16ToUTF32(temp); - if (HighChar <= BaseChar) { - return CERR_ExpansionMustBePositive; - } - // Inject an expansion. - for (BaseChar++; BaseChar < HighChar; BaseChar++) { - // < HighChar because caller will add HighChar to output - if (Uni_IsSMP(BaseChar)) { - // We'll test on each char to avoid complex calculations crossing SMP boundary - if (*mx + 3 >= max) return CERR_BufferOverflow; - tstr[(*mx)++] = (WCHAR) Uni_UTF32ToSurrogate1(BaseChar); - tstr[(*mx)++] = (WCHAR) Uni_UTF32ToSurrogate2(BaseChar); - } - else { - if (*mx + 2 >= max) return CERR_BufferOverflow; - tstr[(*mx)++] = (WCHAR) BaseChar; - } - } - tstr[*mx] = 0; - } - - return CERR_None; -} - -DWORD process_set_synonym(DWORD dwSystemID, PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) // I3437 -{ - /* set( <'='> ), layer */ - DWORD code = CODE_SETSYSTEMSTORE; - PWCHAR temp = new WCHAR[GLOBAL_BUFSIZE], r; - DWORD msg; - - if ((msg = GetXString(fk, q, L"", temp, GLOBAL_BUFSIZE - 1, 0, &r, FALSE, TRUE)) != CERR_None) - { - delete[] temp; - return msg; - } - - DWORD dwStoreID; - - msg = AddStore(fk, TSS_COMPARISON, temp, &dwStoreID); - delete[] temp; - if (msg != CERR_None) return msg; - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = (WCHAR)CODE_SETSYSTEMSTORE; - tstr[(*mx)++] = (WCHAR)(dwSystemID + 1); - tstr[(*mx)++] = (WCHAR)(dwStoreID + 1); - tstr[(*mx)] = 0; - return CERR_None; -} - -DWORD process_set(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) -{ - /* set( <'='> */ - LPWSTR r = q, s = q; // I3440 - while (*s && *s != L' ' && *s != L'=') s++; - r = s; - while (*s == L' ') s++; - if (*s != '=') return CERR_InvalidSet; - s++; - while (*s == ' ') s++; - *r = 0; - r = q; - - DWORD i, code; - - if (r[0] == '&') - { - VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnly_SetSystemStores); // I3437 - for (i = 0; StoreTokens[i]; i++) - { - if (_wcsicmp(r, StoreTokens[i]) == 0) break; - } - if (!StoreTokens[i]) return CERR_SetSystemStore_NotFound; - code = CODE_SETSYSTEMSTORE; - } - else - { - wchar_t *context = NULL; - LPWSTR r = wcstok_s(q, L" =", &context); // I3481 - - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(r, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - CheckStoreUsage(fk, i, FALSE, TRUE, FALSE); - code = CODE_SETOPT; - } - - PWCHAR temp = new WCHAR[GLOBAL_BUFSIZE]; - - DWORD msg; - - //r = wcstok(NULL, L" ="); - - if ((msg = GetXString(fk, s, L"", temp, GLOBAL_BUFSIZE - 1, 0, &r, FALSE, TRUE)) != CERR_None) - { - delete[] temp; - return msg; - } - - DWORD dwStoreID; - - msg = AddStore(fk, TSS_COMPARISON, temp, &dwStoreID); - delete[] temp; - if (msg != CERR_None) return msg; - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = (WCHAR)code; - tstr[(*mx)++] = (WCHAR)(i + 1); - tstr[(*mx)++] = (WCHAR)(dwStoreID + 1); - tstr[(*mx)] = 0; - return CERR_None; -} - -DWORD process_save(PFILE_KEYBOARD fk, LPWSTR q, LPWSTR tstr, int *mx) -{ - /* save() */ - DWORD i; - for (i = 0; i < fk->cxStoreArray; i++) - { - if (_wcsicmp(q, fk->dpStoreArray[i].szName) == 0) break; - } - if (i == fk->cxStoreArray) return CERR_StoreDoesNotExist; - CheckStoreUsage(fk, i, FALSE, TRUE, FALSE); - - tstr[(*mx)++] = UC_SENTINEL; - tstr[(*mx)++] = CODE_SAVEOPT; - tstr[(*mx)++] = (WCHAR)(i + 1); - tstr[(*mx)] = 0; - return CERR_None; -} - -int xatoi(PWSTR *p) -{ - PWSTR endptr; - int n; - - switch (towupper(**p)) - { - case 'U': - (*p)++; - if (**p != '+') return 0; - (*p)++; - n = (int)wcstol(*p, &endptr, 16); - *p = endptr; - break; - case 'X': - (*p)++; - n = (int)wcstol(*p, &endptr, 16); - *p = endptr; - break; - case 'D': - (*p)++; - n = (int)wcstol(*p, &endptr, 10); - *p = endptr; - break; - default: - n = (int)wcstol(*p, &endptr, 8); - *p = endptr; - break; - } - return n; -} - -int GetGroupNum(PFILE_KEYBOARD fk, PWSTR p) -{ - PFILE_GROUP gp; - DWORD i; - - for (i = 0, gp = fk->dpGroupArray; i < fk->cxGroupArray; gp++, i++) - { - if (_wcsicmp(gp->szName, p) == 0) return i + 1; - } - return 0; -} - - -DWORD ProcessEthnologueStore(PWSTR p) // I2646 -{ - DWORD res = CERR_None; - PWSTR q = NULL; - while (*p) - { - while (wcschr(L" ,;", *p)) - { - if (*p != ' ') res = CWARN_PunctuationInEthnologueCode; - p++; - } - if (q == p) return CERR_InvalidEthnologueCode; - if (*p) - { - for (int i = 0; i < 3; i++) - { - if (!iswalpha(*p)) return CERR_InvalidEthnologueCode; - p++; - } - } - q = p; - } - return res; -} - -#define K_HOTKEYSHIFTFLAGS (K_SHIFTFLAG | K_CTRLFLAG | K_ALTFLAG | ISVIRTUALKEY) - -DWORD ProcessHotKey(PWSTR p, DWORD *hk) -{ - PWSTR q, r; - DWORD sFlag; - int j, i; - - *hk = 0; - - if (*p == UC_SENTINEL && *(p + 1) == CODE_EXTENDED) { - WORD Key = *(p + 3); - WORD ShiftFlags = *(p + 2); - - // Convert virtual key to hotkey (different bitflags) - - if (ShiftFlags & ~K_HOTKEYSHIFTFLAGS) { - AddWarning(CWARN_HotkeyHasInvalidModifier); - } - - if (ShiftFlags & K_SHIFTFLAG) *hk |= HK_SHIFT; - if (ShiftFlags & K_CTRLFLAG) *hk |= HK_CTRL; - if (ShiftFlags & K_ALTFLAG) *hk |= HK_ALT; - - *hk |= Key; - - return CERR_None; - } - - q = wcschr(p, '['); - if (q) - { - q++; - sFlag = 0; - - do - { - while (iswspace(*q)) q++; - - if (_wcsnicmp(q, L"ALT", 3) == 0) sFlag |= HK_ALT, q += 3; - else if (_wcsnicmp(q, L"CTRL", 4) == 0) sFlag |= HK_CTRL, q += 4; - else if (_wcsnicmp(q, L"SHIFT", 5) == 0) sFlag |= HK_SHIFT, q += 5; - else if (towupper(*q) != 'K') return CERR_InvalidToken; - } while (towupper(*q) != 'K'); - - r = wcschr(q, ']'); - if (r) - { - r--; - while (iswspace(*r) && r > q) r--; - r++; - } - else return CERR_NoTokensFound; - - j = (int)(INT_PTR)(r - q); - - for (i = 0; i <= VK__MAX; i++) // I3438 - if (j == (int)wcslen(VKeyNames[i]) && _wcsnicmp(q, VKeyNames[i], j) == 0) break; - - if (i == VK__MAX + 1) return CERR_InvalidToken; // I3438 - - *hk = i | sFlag; - - return CERR_None; - } - - q = GetDelimitedString(&p, L"\"\"", GDS_CUTLEAD | GDS_CUTFOLL); - if (q) - { - if (wcschr(q, '^')) *hk |= HK_CTRL; - if (wcschr(q, '+')) *hk |= HK_SHIFT; - if (wcschr(q, '%')) *hk |= HK_ALT; - q = wcschr(q, 0) - 1; - *hk |= *q; - return CERR_None; - } - - return CERR_CodeInvalidInThisSection; -} - - -void SetChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz) -{ - BuildCRCTable(); - *CheckSum = CalculateBufferCRC(buf, sz); -} - - -BOOL CheckStoreUsage(PFILE_KEYBOARD fk, int storeIndex, BOOL fIsStore, BOOL fIsOption, BOOL fIsCall) -{ - PFILE_STORE sp = &fk->dpStoreArray[storeIndex]; - if (fIsStore && !sp->fIsStore) - { - if (sp->fIsDebug || sp->fIsOption || sp->fIsReserved || sp->fIsCall) - AddWarning(CWARN_StoreAlreadyUsedAsOptionOrCall); - sp->fIsStore = TRUE; - } - else if (fIsOption && !sp->fIsOption) - { - if (sp->fIsDebug || sp->fIsStore || sp->fIsReserved || sp->fIsCall) - AddWarning(CWARN_StoreAlreadyUsedAsStoreOrCall); - sp->fIsOption = TRUE; - } - else if (fIsCall && !sp->fIsCall) - { - if (sp->fIsDebug || sp->fIsStore || sp->fIsReserved || sp->fIsOption) - AddWarning(CWARN_StoreAlreadyUsedAsStoreOrOption); - sp->fIsCall = TRUE; - } - - return TRUE; -} - -DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, HANDLE hOutfile) -{ - PFILE_GROUP fgp; - PFILE_STORE fsp; - PFILE_KEY fkp; - - PCOMP_KEYBOARD ck; - PCOMP_GROUP gp; - PCOMP_STORE sp; - PCOMP_KEY kp; - PBYTE buf; - size_t offset; - size_t size; - DWORD i, j; - - // Calculate how much memory to allocate - - size = sizeof(COMP_KEYBOARD) + - fk->cxGroupArray * sizeof(COMP_GROUP) + - fk->cxStoreArray * sizeof(COMP_STORE) + - /*wcslen(fk->szName)*2 + 2 + - wcslen(fk->szCopyright)*2 + 2 + - wcslen(fk->szLanguageName)*2 + 2 + - wcslen(fk->szMessage)*2 + 2 +*/ - fk->dwBitmapSize; - - for (i = 0, fgp = fk->dpGroupArray; i < fk->cxGroupArray; i++, fgp++) - { - if (FSaveDebug) size += wcslen(fgp->szName) * 2 + 2; - size += fgp->cxKeyArray * sizeof(COMP_KEY); - for (j = 0, fkp = fgp->dpKeyArray; j < fgp->cxKeyArray; j++, fkp++) - { - size += wcslen(fkp->dpOutput) * 2 + 2; - size += wcslen(fkp->dpContext) * 2 + 2; - } - - if (fgp->dpMatch) size += wcslen(fgp->dpMatch) * 2 + 2; - if (fgp->dpNoMatch) size += wcslen(fgp->dpNoMatch) * 2 + 2; - } - - for (i = 0; i < fk->cxStoreArray; i++) - { - size += wcslen(fk->dpStoreArray[i].dpString) * 2 + 2; - if (FSaveDebug || fk->dpStoreArray[i].fIsOption) size += wcslen(fk->dpStoreArray[i].szName) * 2 + 2; - } - - buf = new BYTE[size]; - if (!buf) return CERR_CannotAllocateMemory; - memset(buf, 0, size); - - ck = (PCOMP_KEYBOARD)buf; - - ck->dwIdentifier = FILEID_COMPILED; - ck->dwFileVersion = fk->version; - ck->dwCheckSum = 0; // do checksum afterwards. - ck->KeyboardID = fk->KeyboardID; - ck->IsRegistered = TRUE; // I5135 - ck->cxStoreArray = fk->cxStoreArray; - ck->cxGroupArray = fk->cxGroupArray; - ck->StartGroup[0] = fk->StartGroup[0]; - ck->StartGroup[1] = fk->StartGroup[1]; - ck->dwHotKey = fk->dwHotKey; - - ck->dwFlags = fk->dwFlags; - - offset = sizeof(COMP_KEYBOARD); - - /*ck->dpLanguageName = offset; - wcscpy((PWSTR)(buf + offset), fk->szLanguageName); - offset += wcslen(fk->szLanguageName)*2 + 2; - - ck->dpName = offset; - wcscpy((PWSTR)(buf + offset), fk->szName); - offset += wcslen(fk->szName)*2 + 2; - - ck->dpCopyright = offset; - wcscpy((PWSTR)(buf + offset), fk->szCopyright); - offset += wcslen(fk->szCopyright)*2 + 2; - - ck->dpMessage = offset; - wcscpy((PWSTR)(buf + offset), fk->szMessage); - offset += wcslen(fk->szMessage)*2 + 2;*/ - - ck->dpStoreArray = (DWORD)offset; - sp = (PCOMP_STORE)(buf + offset); - fsp = fk->dpStoreArray; - offset += sizeof(COMP_STORE) * ck->cxStoreArray; - for (i = 0; i < ck->cxStoreArray; i++, sp++, fsp++) - { - sp->dwSystemID = fsp->dwSystemID; - sp->dpString = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fsp->dpString); // I3481 // I3641 - offset += wcslen(fsp->dpString) * 2 + 2; - - if (FSaveDebug || fsp->fIsOption) - { - sp->dpName = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fsp->szName); // I3481 // I3641 - offset += wcslen(fsp->szName) * 2 + 2; - } - else sp->dpName = 0; - } - - ck->dpGroupArray = (DWORD)offset; - gp = (PCOMP_GROUP)(buf + offset); - fgp = fk->dpGroupArray; - - offset += sizeof(COMP_GROUP) * ck->cxGroupArray; - - for (i = 0; i < ck->cxGroupArray; i++, gp++, fgp++) - { - gp->cxKeyArray = fgp->cxKeyArray; - gp->fUsingKeys = fgp->fUsingKeys; - - gp->dpMatch = gp->dpNoMatch = 0; - - if (fgp->dpMatch) - { - gp->dpMatch = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fgp->dpMatch); // I3481 // I3641 - offset += wcslen(fgp->dpMatch) * 2 + 2; - } - if (fgp->dpNoMatch) - { - gp->dpNoMatch = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fgp->dpNoMatch); // I3481 // I3641 - offset += wcslen(fgp->dpNoMatch) * 2 + 2; - } - - if (FSaveDebug) - { - gp->dpName = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fgp->szName); // I3481 // I3641 - offset += wcslen(fgp->szName) * 2 + 2; - } - else gp->dpName = 0; - - gp->dpKeyArray = (DWORD)offset; - kp = (PCOMP_KEY)(buf + offset); - fkp = fgp->dpKeyArray; - offset += gp->cxKeyArray * sizeof(COMP_KEY); - for (j = 0; j < gp->cxKeyArray; j++, kp++, fkp++) - { - kp->_reserved = 0; - kp->Key = fkp->Key; - if (FSaveDebug) kp->Line = fkp->Line; else kp->Line = 0; - kp->ShiftFlags = fkp->ShiftFlags; - kp->dpOutput = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fkp->dpOutput); // I3481 // I3641 - offset += wcslen(fkp->dpOutput) * 2 + 2; - kp->dpContext = (DWORD)offset; - wcscpy_s((PWSTR)(buf + offset), (size - offset) / sizeof(WCHAR), fkp->dpContext); // I3481 // I3641 - offset += wcslen(fkp->dpContext) * 2 + 2; - } - } - - ck->dwBitmapSize = fk->dwBitmapSize; - ck->dpBitmapOffset = (DWORD)offset; - memcpy(buf + offset, fk->lpBitmap, fk->dwBitmapSize); - offset += fk->dwBitmapSize; - - if (offset != size) { - delete[] buf; - return CERR_SomewhereIGotItWrong; - } - - if (ck->dwFileVersion < VERSION_160) { - SetChecksum(buf, &ck->dwCheckSum, (DWORD)size); - } - else { - ck->dwCheckSum = 0; // checksum is deprecated for 16.0+ - } - - DWORD dwBytesWritten = 0; - WriteFile(hOutfile, buf, (DWORD)size, &dwBytesWritten, NULL); - - if (dwBytesWritten != size) { - delete[] buf; - return CERR_UnableToWriteFully; - } - - delete[] buf; - - return CERR_None; -} - -DWORD ReadLine(HANDLE hInfile, PWSTR wstr, BOOL PreProcess) -{ - DWORD len; - PWSTR p; - BOOL LineCarry = FALSE, InComment = FALSE; - DWORD n; - WCHAR currentQuotes = 0; - WCHAR str[LINESIZE + 3]; - - if (!ReadFile(hInfile, str, LINESIZE * 2, &len, NULL)) return CERR_CannotReadInfile; - len /= 2; - str[len] = 0; - - if (SetFilePointer(hInfile, 0, NULL, FILE_CURRENT) == GetFileSize(hInfile, NULL)) - // Always a "\r\n" to the EOF, avoids funny bugs - wcscat_s(str, _countof(str), L"\r\n"); // I3481 - - if (len == 0) return CERR_EndOfFile; - - for (p = str, n = 0; n < len; n++, p++) - { - if (currentQuotes != 0) - { - if (*p == L'\n') - { - *p = 0; // I2525 - wcscpy_s(wstr, LINESIZE, str); // I3481 - return (PreProcess ? CERR_None : CERR_UnterminatedString); - } - if (*p == currentQuotes) currentQuotes = 0; - continue; - } - if (InComment) { - if (*p == L'\n') break; - *p = L' '; - continue; - } - if (*p == L'\\') { - LineCarry = TRUE; - *p = L' '; - continue; - } - if (LineCarry) - { - switch (*p) - { - case L' ': - case L'\t': - case L'\r': - *p = L' '; - continue; - case L'\n': - currentLine++; - LineCarry = FALSE; - *p = L' '; - continue; - } - *p = 0; // I2525 - wcscpy_s(wstr, LINESIZE, str); // I3481 - return (PreProcess ? CERR_None : CERR_InvalidLineContinuation); - } - - if (*p == L'\n') break; - switch (*p) - { - case L'c': - case L'C': - if ((p == str || iswspace(*(p - 1))) && iswspace(*(p + 1))) { - InComment = TRUE; - *p = L' '; - } - continue; - case L'\r': - case L'\t': - *p = L' '; - continue; - case L'\'': - case L'\"': - currentQuotes = *p; - continue; - } - } - - if (n == len) - { - str[LINESIZE - 1] = 0; // I2525 - wcscpy_s(wstr, LINESIZE, str); // I3481 - if (len == LINESIZE) - return (PreProcess ? CERR_None : CERR_LineTooLong); - } - - if (*p == L'\n') currentLine++; - - SetFilePointer(hInfile, -(int)(len * 2 - (INT_PTR)(p - str) * 2 - 2), NULL, FILE_CURRENT); - - p--; - while (p >= str && iswspace(*p)) p--; - p++; - *p++ = L'\n'; - *p = 0; - // trim spaces now, why not? - wcscpy_s(wstr, LINESIZE, str); // I3481 - - return CERR_None; -} - -DWORD GetRHS(PFILE_KEYBOARD fk, PWSTR p, PWSTR buf, int bufsize, int offset, int IsUnicode) -{ - PWSTR q; - - p = wcschr(p, '>'); - - if (!p) return CERR_NoTokensFound; - - p++; - - return GetXString(fk, p, L"c\n", buf, bufsize, offset, &q, TRUE, IsUnicode); -} - -void safe_wcsncpy(PWSTR out, PWSTR in, int cbMax) -{ - wcsncpy_s(out, cbMax, in, cbMax - 1); // I3481 - out[cbMax - 1] = 0; -} - -BOOL IsSameToken(PWSTR *p, PWSTR token) -{ - PWSTR q; - q = *p; - while (iswspace(*q)) q++; - if (_wcsnicmp(q, token, wcslen(token)) == 0) - { - q += wcslen(token); - while (iswspace(*q)) q++; - *p = q; - return TRUE; - } - return FALSE; -} - - -DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PWSTR szName, PDWORD FileSize, PBYTE *Buf) -{ - HANDLE hFile; - char szNewName[260], *p; - - p = wstrtostr(szName); - - if (IsRelativePath(p)) - { - strcpy_s(szNewName, _countof(szNewName), CompileDir); // I3481 - strcat_s(szNewName, _countof(szNewName), p); // I3481 - } - else - strcpy_s(szNewName, _countof(szNewName), p); // I3481 - - hFile = CreateFileA(szNewName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hFile == INVALID_HANDLE_VALUE) - { - strcat_s(szNewName, _countof(szNewName), ".bmp"); // I3481 - hFile = CreateFileA(szNewName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hFile == INVALID_HANDLE_VALUE) return CERR_CannotReadBitmapFile; - } - - DWORD msg; - - if ((msg = CheckFilenameConsistency(szNewName, FALSE)) != CERR_None) { - return msg; - } - - delete[] p; - - *FileSize = GetFileSize(hFile, NULL); - - if (*FileSize < 2) return CERR_CannotReadBitmapFile; - - *Buf = new BYTE[*FileSize]; - - if (!ReadFile(hFile, *Buf, *FileSize, FileSize, NULL)) { - delete[] * Buf; - *Buf = NULL; - return CERR_CannotReadBitmapFile; - } - - CloseHandle(hFile); - - /* Test for version 7.0 icon support */ - if (*((PCHAR)*Buf) != 'B' && *(((PCHAR)*Buf) + 1) != 'M') { - VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - } - - return CERR_None; -} - -int atoiW(PWSTR p) -{ - PSTR q = wstrtostr(p); - int i = atoi(q); - delete[] q; - return i; -} - -int CheckUTF16(int n) -{ - const int res[] = { - 0xFDD0, 0xFDD1, 0xFDD2, 0xFDD3, 0xFDD4, 0xFDD5, 0xFDD6, 0xFDD7, - 0xFDD8, 0xFDD9, 0xFDDA, 0xFDDB, 0xFDDC, 0xFDDD, 0xFDDE, 0xFDDF, - 0xFDE0, 0xFDE1, 0xFDE2, 0xFDE3, 0xFDE4, 0xFDE5, 0xFDE6, 0xFDE7, - 0xFDE8, 0xFDE9, 0xFDEA, 0xFDEB, 0xFDEC, 0xFDED, 0xFDEE, 0xFDEF, - 0xFFFF, 0xFFFE, 0 }; - - if (n == 0) return CERR_ReservedCharacter; - for (int i = 0; res[i] > 0; i++) - if (n == res[i]) - { - AddWarning(CWARN_ReservedCharacter); - break; - } - return CERR_None; -} - -int UTF32ToUTF16(int n, int *n1, int *n2) -{ - *n2 = -1; - if (n <= 0xFFFF) - { - *n1 = n; - if (n >= 0xD800 && n <= 0xDFFF) AddWarning(CWARN_UnicodeSurrogateUsed); - return CheckUTF16(*n1); - } - - if ((n & 0xFFFF) == 0xFFFF || (n & 0xFFFF) == 0xFFFE) AddWarning(CWARN_ReservedCharacter); - if (n < 0 || n > 0x10FFFF) return CERR_InvalidCharacter; - n = n - 0x10000; - *n1 = (n / 0x400) + 0xD800; - *n2 = (n % 0x400) + 0xDC00; - if ((n = CheckUTF16(*n1)) != CERR_None) return n; - return CheckUTF16(*n2); -} - -DWORD BuildVKDictionary(PFILE_KEYBOARD fk) // I3438 -{ - DWORD i; - size_t len = 0; - if (fk->cxVKDictionary == 0) return CERR_None; - for (i = 0; i < fk->cxVKDictionary; i++) - { - len += wcslen(fk->dpVKDictionary[i].szName) + 1; - } - PWSTR storeval = new WCHAR[len], p = storeval; - for (i = 0; i < fk->cxVKDictionary; i++) - { - wcscpy_s(p, len - (size_t)(p - storeval), fk->dpVKDictionary[i].szName); // I3481 - p = wcschr(p, 0); - *p = ' '; - p++; - } - - p--; - *p = 0; - - DWORD dwStoreID; - DWORD msg = AddStore(fk, TSS_VKDICTIONARY, storeval, &dwStoreID); - delete[] storeval; - return msg; -} - -int GetVKCode(PFILE_KEYBOARD fk, PWSTR p) // I3438 // TODO: Consolidate GetDeadKey and GetVKCode? -{ - DWORD i; - - for (i = 0; i < fk->cxVKDictionary; i++) - if (_wcsicmp(fk->dpVKDictionary[i].szName, p) == 0) - return i + VK__MAX + 1; // 256 - - if (fk->cxVKDictionary % 10 == 0) - { - PFILE_VKDICTIONARY pvk = new FILE_VKDICTIONARY[fk->cxVKDictionary + 10]; - memcpy(pvk, fk->dpVKDictionary, fk->cxVKDictionary * sizeof(FILE_VKDICTIONARY)); - delete fk->dpVKDictionary; - fk->dpVKDictionary = pvk; - } - wcsncpy_s(fk->dpVKDictionary[fk->cxVKDictionary].szName, _countof(fk->dpVKDictionary[fk->cxVKDictionary].szName), p, SZMAX_VKDICTIONARYNAME - 1); // I3481 - fk->dpVKDictionary[fk->cxVKDictionary].szName[SZMAX_VKDICTIONARYNAME - 1] = 0; - - fk->cxVKDictionary++; - return fk->cxVKDictionary + VK__MAX; // 256-1 -} - -int GetDeadKey(PFILE_KEYBOARD fk, PWSTR p) -{ - DWORD i; - - for (i = 0; i < fk->cxDeadKeyArray; i++) - if (_wcsicmp(fk->dpDeadKeyArray[i].szName, p) == 0) - return i + 1; - - if (fk->cxDeadKeyArray % 10 == 0) - { - PFILE_DEADKEY dk = new FILE_DEADKEY[fk->cxDeadKeyArray + 10]; - memcpy(dk, fk->dpDeadKeyArray, fk->cxDeadKeyArray * sizeof(FILE_DEADKEY)); - delete[] fk->dpDeadKeyArray; - fk->dpDeadKeyArray = dk; - } - wcsncpy_s(fk->dpDeadKeyArray[fk->cxDeadKeyArray].szName, _countof(fk->dpDeadKeyArray[fk->cxDeadKeyArray].szName), p, SZMAX_DEADKEYNAME); // I3481 - fk->dpDeadKeyArray[fk->cxDeadKeyArray].szName[SZMAX_DEADKEYNAME - 1] = 0; - - fk->cxDeadKeyArray++; - return fk->cxDeadKeyArray; -} - -void RecordDeadkeyNames(PFILE_KEYBOARD fk) -{ - WCHAR buf[SZMAX_DEADKEYNAME + 16]; - DWORD i; - for (i = 0; i < fk->cxDeadKeyArray; i++) - { - swprintf(buf, _countof(buf), L"%ls%d %ls", DEBUGSTORE_DEADKEY, (int)i, fk->dpDeadKeyArray[i].szName); // I3481 - AddDebugStore(fk, buf); - } -} - -BOOL IsValidCallStore(PFILE_STORE fs) -{ - int i; - PWSTR p; - for (i = 0, p = fs->dpString; *p; p++) - if (*p == ':') i++; - else if (!((*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z') || - (*p >= '0' && *p <= '9') || - *p == '.' || - *p == '_')) - return FALSE; - - return i == 1; -} - -HANDLE CreateTempFile() -{ - char szTempPathBuffer[MAX_PATH], szTempFileName[MAX_PATH]; // I3228 // I3510 - if (!GetTempPath(MAX_PATH, szTempPathBuffer)) return INVALID_HANDLE_VALUE; - if (!GetTempFileName(szTempPathBuffer, "kmx", 0, szTempFileName)) return INVALID_HANDLE_VALUE; // I3228 // I3510 - return CreateFile(szTempFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); -} - -/////////////////// -HANDLE UTF16TempFromUTF8(HANDLE hInfile, BOOL hasPreamble) -{ - HANDLE hOutfile = CreateTempFile(); - if (hOutfile == INVALID_HANDLE_VALUE) // I3228 // I3510 - { - CloseHandle(hInfile); - return INVALID_HANDLE_VALUE; - } - - PBYTE buf, p; - PWSTR outbuf, poutbuf; - DWORD len, len2; - WCHAR prolog = 0xFEFF; - WriteFile(hOutfile, &prolog, 2, &len2, NULL); - - len = GetFileSize(hInfile, NULL); - if (hasPreamble) { - SetFilePointer(hInfile, 3, NULL, FILE_BEGIN); // Cut off UTF-8 marker - len -= 3; - } - - buf = new BYTE[len + 1]; // null terminated - outbuf = new WCHAR[len + 1]; - if (ReadFile(hInfile, buf, len, &len2, NULL)) { - buf[len2] = 0; - p = buf; - poutbuf = outbuf; - if (hasPreamble) { - // We have a preamble, so we attempt to read as UTF-8 and allow conversion errors to be filtered. This is not great for a - // compiler but matches existing behaviour -- in future versions we may not do lenient conversion. - ConversionResult cr = ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], lenientConversion); - WriteFile(hOutfile, outbuf, (DWORD)(INT_PTR)(poutbuf - outbuf) * 2, &len2, NULL); - } - else { - // No preamble, so we attempt to read as strict UTF-8 and fall back to ANSI if that fails - ConversionResult cr = ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], strictConversion); - if (cr == sourceIllegal) { - // Not a valid UTF-8 file, so fall back to ANSI - AddCompileMessage(CHINT_NonUnicodeFile); - poutbuf = strtowstr((PSTR)buf); - WriteFile(hOutfile, poutbuf, (DWORD)wcslen(poutbuf) * 2, &len2, NULL); - delete[] poutbuf; - } - else { - WriteFile(hOutfile, outbuf, (DWORD)(INT_PTR)(poutbuf - outbuf) * 2, &len2, NULL); - } - } - } - - CloseHandle(hInfile); - delete[] buf; - delete[] outbuf; - SetFilePointer(hOutfile, 2, NULL, FILE_BEGIN); - return hOutfile; -} - -extern "C" void __declspec(dllexport) Keyman_Diagnostic(int mode) { - if (mode == 0) { - RaiseException(0x0EA0BEEF, EXCEPTION_NONCONTINUABLE, 0, NULL); - } -} - -PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, DWORD dwSystemID) { - assert(fk != NULL); - assert(dwSystemID != 0); - - PFILE_STORE sp = fk->dpStoreArray; - for (DWORD i = 0; i < fk->cxStoreArray; i++, sp++) { - if (sp->dwSystemID == dwSystemID) { - return sp; - } - } - return NULL; -} diff --git a/developer/src/kmcmpdll/Compiler.rc b/developer/src/kmcmpdll/Compiler.rc deleted file mode 100644 index 4c58996cb19..00000000000 --- a/developer/src/kmcmpdll/Compiler.rc +++ /dev/null @@ -1,252 +0,0 @@ -#include -#include "kmn_compiler_errors.h" - -#include "version.rc" - -///////////////////////////////////////////////////////////////////////////// -// English (Australia) resources - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS - -///////////////////////////////////////////////////////////////////////////// -// -// String Table -// - -STRINGTABLE -BEGIN - CERR_InvalidLayoutLine "Invalid 'layout' command" - CERR_NoVersionLine "No version line found for file" - CERR_InvalidGroupLine "Invalid 'group' command" - CERR_InvalidStoreLine "Invalid 'store' command" - CERR_InvalidCodeInKeyPartOfRule - "Invalid command or code found in key part of rule" - CERR_InvalidDeadkey "Invalid 'deadkey' or 'dk' command" - CERR_InvalidValue "Invalid value in extended string" - CERR_ZeroLengthString "A string of zero characters was found" - CERR_TooManyIndexToKeyRefs - "Too many index commands refering to key string" - CERR_UnterminatedString "Unterminated string in line" - CERR_StringInVirtualKeySection - "extended string illegal in virtual key section" - CERR_AnyInVirtualKeySection - "'any' command is illegal in virtual key section" - CERR_InvalidAny "Invalid 'any' command" - CERR_StoreDoesNotExist "Store referenced does not exist" - CERR_BeepInVirtualKeySection - "'beep' command is illegal in virtual key section" - CERR_IndexInVirtualKeySection - "'index' command is illegal in virtual key section" -END - -STRINGTABLE -BEGIN - CERR_BadCallParams "CompileKeyboardFile was called with bad parameters" - CERR_InfileNotExist "Cannot find the input file" - CERR_CannotCreateOutfile "Cannot open output file for writing" - CERR_UnableToWriteFully "Unable to write the file completely" - CERR_CannotReadInfile "Cannot read the input file - possible disk error" - CERR_SomewhereIGotItWrong "Internal error: contact Tavultesoft" -END - -STRINGTABLE -BEGIN - CERR_BufferOverflow "The compiler memory buffer overflowed" - CERR_Break "Compiler interrupted by user" -END - -STRINGTABLE -BEGIN - CERR_CannotAllocateMemory "Out of memory" -END - -STRINGTABLE -BEGIN - CERR_InvalidBitmapLine "Invalid 'bitmaps' command" - CERR_CannotReadBitmapFile "Cannot open the bitmap file for reading" - CERR_IndexDoesNotPointToAny - "An index() in the output does not have a corresponding any() statement" - CERR_ReservedCharacter "A reserved character was found" - CERR_InvalidCharacter "A character was found that is outside the valid Unicode range (U+0000 - U+10FFFF)" - CERR_InvalidCall "The 'call' command is invalid" - CERR_CallInVirtualKeySection - "'call' command is illegal in virtual key section" - CERR_CodeInvalidInKeyStore - "The command is invalid inside a store that is used in a key part of the rule" - CERR_CannotLoadIncludeFile - "Cannot load the included file: it is either invalid or does not exist" - CERR_60FeatureOnly_EthnologueCode - "EthnologueCode system store requires VERSION 6.0" - CERR_60FeatureOnly_MnemonicLayout - "MnemonicLayout functionality requires VERSION 6.0" - CERR_60FeatureOnly_OldCharPosMatching - "OldCharPosMatching system store requires VERSION 6.0" - CERR_60FeatureOnly_NamedCodes - "Named character constants requires VERSION 6.0" - CERR_60FeatureOnly_Contextn "Context(n) requires VERSION 6.0" - CERR_501FeatureOnly_Call "Call() requires VERSION 5.01" - CERR_InvalidNamedCode "Invalid named code constant" -END - -STRINGTABLE -BEGIN - CERR_InvalidSystemStore "Invalid system store name found" - CERR_60FeatureOnly_VirtualCharKey - "Virtual character keys require VERSION 6.0" - CERR_VersionAlreadyIncluded - "Only one VERSION or store(&version) line allowed in a source file." - CERR_70FeatureOnly "This feature requires store(&version) '7.0'" - CERR_80FeatureOnly "This feature requires store(&version) '8.0'" - CERR_InvalidInVirtualKeySection - "This statement is not valid in a virtual key section" - CERR_InvalidIf "The if() statement is not valid" - CERR_InvalidReset "The reset() statement is not valid" - CERR_InvalidSet "The set() statement is not valid" - CERR_InvalidSave "The save() statement is not valid" - CERR_InvalidEthnologueCode "Invalid ðnologuecode format" - CERR_90FeatureOnly_IfSystemStores - "if(&store) requires store(&version) '9.0'" - CERR_IfSystemStore_NotFound "System store in if() not found" - CERR_90FeatureOnly_SetSystemStores "set(&store) requires store(&version) '9.0'" - CERR_SetSystemStore_NotFound "System store in set() not found" - CERR_90FeatureOnlyVirtualKeyDictionary "Custom virtual key names require store(&version) '9.0'" -END - -STRINGTABLE -BEGIN - CERR_InvalidIndex "Invalid 'index' command" - CERR_OutsInVirtualKeySection - "'outs' command is illegal in virtual key section" - CERR_InvalidOuts "Invalid 'outs' command" - CERR_ContextInVirtualKeySection - "'context' command is illegal in virtual key section" - CERR_InvalidUse "Invalid 'use' command" - CERR_GroupDoesNotExist "Group does not exist" - CERR_VirtualKeyNotAllowedHere "Virtual key is not allowed here" - CERR_InvalidSwitch "Invalid 'switch' command" - CERR_NoTokensFound "No tokens found in line" - CERR_InvalidLineContinuation "Invalid line continuation" - CERR_LineTooLong "Line too long" - CERR_InvalidCopyright "Invalid 'copyright' command" - CERR_CodeInvalidInThisSection - "This line is invalid in this section of the file" - CERR_InvalidMessage "Invalid 'message' command" - CERR_InvalidLanguageName "Invalid 'languagename' command" -END - -STRINGTABLE -BEGIN - CWARN_TooManyWarnings "Too many warnings or errors" - CWARN_OldVersion "The keyboard file is an old version" - CWARN_BitmapNotUsed "The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'." - CWARN_CustomLanguagesNotSupported - "Languages over 0x1FF, 0x1F are not supported correctly by Windows. You should use no LANGUAGE line instead." - CWARN_KeyBadLength "There are too many characters in the keystroke part of the rule." - CWARN_IndexStoreShort "The store referenced in index() is shorter than the store referenced in any()" - CWARN_UnicodeInANSIGroup "A Unicode character was found in an ANSI group" - CWARN_ANSIInUnicodeGroup "An ANSI character was found in a Unicode group" - CWARN_UnicodeSurrogateUsed - "A Unicode surrogate character was found. You should use Unicode scalar values to represent values > U+FFFF" - CWARN_ReservedCharacter "A Unicode character was found that should not be used" - CWARN_Info "Information" - CWARN_VirtualKeyWithMnemonicLayout - "Virtual key used instead of virtual character key with a mnemonic layout" - CWARN_VirtualCharKeyWithPositionalLayout - "Virtual character key used with a positional layout instead of mnemonic layout" - CWARN_StoreAlreadyUsedAsOptionOrCall - "Store already used as an option or in a call statement and should not be used as a normal store" - CWARN_StoreAlreadyUsedAsStoreOrCall - "Store already used as a normal store or in a call statement and should not be used as an option" - CWARN_StoreAlreadyUsedAsStoreOrOption - "Store already used as a normal store or as an option and should not be used in a call statement" -END - -STRINGTABLE -BEGIN - CERR_None "(no error)" - CERR_EndOfFile "(no error - reserved code)" -END - -STRINGTABLE -BEGIN - CERR_InvalidToken "Invalid token found" - CERR_InvalidBegin "Invalid 'begin' command" - CERR_InvalidName "Invalid 'name' command" - CERR_InvalidVersion "Invalid 'version' command" - CERR_InvalidLanguageLine "Invalid 'language' command" - CERR_LayoutButNoLanguage "Layout command found but no language command" -END - -STRINGTABLE -BEGIN - CWARN_PunctuationInEthnologueCode - "Punctuation should not be used to separate Ethnologue codes; instead use spaces" -END - -STRINGTABLE -BEGIN - CERR_CannotCreateTempfile "Cannot create temp file" -END - -STRINGTABLE -BEGIN - CERR_90FeatureOnlyLayoutFile "Touch layout file reference requires store(&version) '9.0'" - CERR_90FeatureOnlyKeyboardVersion "KeyboardVersion system store requires store(&version) '9.0'" - CERR_KeyboardVersionFormatInvalid "KeyboardVersion format is invalid, expecting dot-separated integers" - CERR_ContextExHasInvalidOffset "context() statement has offset out of range" - CERR_90FeatureOnlyEmbedCSS "Embedding CSS requires store(&version) '9.0'" - CERR_90FeatureOnlyTargets "&TARGETS system store requires store(&version) '9.0'" - CERR_ContextAndIndexInvalidInMatchNomatch "context and index statements cannot be used in a match or nomatch statement" - CERR_140FeatureOnlyContextAndNotAnyWeb "For web and touch platforms, context() statement referring to notany() requires store(&version) '14.0'" - CWARN_PlatformNotInTargets "The specified platform is not a target platform" -END - -STRINGTABLE -BEGIN - CWARN_HeaderStatementIsDeprecated "Header statements are deprecated; use instead the equivalent system store" - CWARN_UseNotLastStatementInRule "A rule with use() statements in the output should not have other content following the use() statements" - CWARN_KVKFileIsInSourceFormat ".kvk file should be binary but is an XML file" -END - -STRINGTABLE -BEGIN - CWARN_DontMixChiralAndNonChiralModifiers "Don't mix the use of left/right modifiers with non-left/right modifiers in the same platform" - CWARN_MixingLeftAndRightModifiers "Left and right modifiers should not both be used in the same rule" -END - -STRINGTABLE -BEGIN - CWARN_LanguageHeadersDeprecatedInKeyman10 "This language header has been deprecated in Keyman 10. Instead, add language metadata in the package file" - CWARN_HotkeyHasInvalidModifier "Hotkey has modifiers that are not supported. Use only SHIFT, CTRL and ALT" - CHINT_NonUnicodeFile "Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8" - CWARN_NulNotFirstStatementInContext "nul must be the first statement in the context" - CWARN_IfShouldBeAtStartOfContext "if, platform and baselayout should be at start of context (after nul, if present)" - CWARN_KeyShouldIncludeNCaps "Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches" - CHINT_UnreachableRule "This rule will never be matched as another rule takes precedence" - CHINT_FilenameHasDifferingCase "Casing differences may fail on some platforms:" - CWARN_MissingFile "The referenced file could not be found" -END - -STRINGTABLE -BEGIN - CERR_ExpansionMustFollowCharacterOrVKey "An expansion must follow a character or a virtual key" - CERR_VKeyExpansionMustBeFollowedByVKey "A virtual key expansion must be terminated by a virtual key" - CERR_CharacterExpansionMustBeFollowedByCharacter "A character expansion must be terminated by a character key" - CERR_VKeyExpansionMustUseConsistentShift "A virtual key expansion must use the same shift state for both terminators" - CERR_ExpansionMustBePositive "An expansion must have positive difference (i.e. A-Z, not Z-A)" -END - -STRINGTABLE -BEGIN - CERR_CasedKeysMustContainOnlyVirtualKeys "The &CasedKeys system store must contain only virtual keys or characters found on a US English keyboard" - CERR_CasedKeysMustNotIncludeShiftStates "The &CasedKeys system store must not include shift states" - CERR_CasedKeysNotSupportedWithMnemonicLayout "The &CasedKeys system store is not supported with mnemonic layouts" - CERR_CannotUseReadWriteGroupFromReadonlyGroup "Group used from a readonly group must also be readonly" - CERR_StatementNotPermittedInReadonlyGroup "Statement is not permitted in output of readonly group" - CERR_OutputInReadonlyGroup "Output is not permitted in a readonly group" - CERR_NewContextGroupMustBeReadonly "Group used in begin newContext must be readonly" - CERR_PostKeystrokeGroupMustBeReadonly "Group used in begin postKeystroke must be readonly" - CERR_DuplicateGroup "A group with this name has already been defined." - CERR_DuplicateStore "A store with this name has already been defined." - CERR_RepeatedBegin "This begin statement has already been defined." -END diff --git a/developer/src/kmcmpdll/DeprecationChecks.cpp b/developer/src/kmcmpdll/DeprecationChecks.cpp deleted file mode 100644 index d64f2279e06..00000000000 --- a/developer/src/kmcmpdll/DeprecationChecks.cpp +++ /dev/null @@ -1,52 +0,0 @@ - -#include "pch.h" - -#include -#include -#include - -BOOL WarnDeprecatedHeader() { // I4866 - if (FWarnDeprecatedCode) { - AddWarning(CWARN_HeaderStatementIsDeprecated); - } - return TRUE; -} - -/* Flag presence of deprecated features */ -BOOL CheckForDeprecatedFeatures(PFILE_KEYBOARD fk) { - /* - For Keyman 10, we deprecated: - // < Keyman 7 - #define TSS_LANGUAGE 4 - #define TSS_LAYOUT 5 - #define TSS_LANGUAGENAME 12 - #define TSS_ETHNOLOGUECODE 15 - - // Keyman 7 - #define TSS_WINDOWSLANGUAGES 29 - */ - int oldCurrentLine = currentLine; - DWORD i; - PFILE_STORE sp; - - if (!FWarnDeprecatedCode) { - return TRUE; - } - - if (fk->version >= VERSION_100) { - for (i = 0, sp = fk->dpStoreArray; i < fk->cxStoreArray; i++, sp++) { - if (sp->dwSystemID == TSS_LANGUAGE || - sp->dwSystemID == TSS_LAYOUT || - sp->dwSystemID == TSS_LANGUAGENAME || - sp->dwSystemID == TSS_ETHNOLOGUECODE || - sp->dwSystemID == TSS_WINDOWSLANGUAGES) { - currentLine = sp->line; - AddWarning(CWARN_LanguageHeadersDeprecatedInKeyman10); - } - } - } - - currentLine = oldCurrentLine; - - return TRUE; -} diff --git a/developer/src/kmcmpdll/DeprecationChecks.h b/developer/src/kmcmpdll/DeprecationChecks.h deleted file mode 100644 index 1397289b421..00000000000 --- a/developer/src/kmcmpdll/DeprecationChecks.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include -#include - -BOOL WarnDeprecatedHeader(); -BOOL CheckForDeprecatedFeatures(PFILE_KEYBOARD fk); \ No newline at end of file diff --git a/developer/src/kmcmpdll/Edition.cpp b/developer/src/kmcmpdll/Edition.cpp deleted file mode 100644 index 3253804c6cf..00000000000 --- a/developer/src/kmcmpdll/Edition.cpp +++ /dev/null @@ -1,8 +0,0 @@ - -#include "pch.h" -#include "edition.h" - -int GetEdition() -{ - return ED_STANDARD; -} diff --git a/developer/src/kmcmpdll/Edition.h b/developer/src/kmcmpdll/Edition.h deleted file mode 100644 index 26eb44c6160..00000000000 --- a/developer/src/kmcmpdll/Edition.h +++ /dev/null @@ -1,12 +0,0 @@ - -#ifndef _EDITION_H -#define _EDITION_H - -int GetEdition(); - -#define ED_DEMO 0 -#define ED_STANDARD 1 -#define ED_PRO 2 -#define ED_OEM 3 - -#endif diff --git a/developer/src/kmcmpdll/Makefile b/developer/src/kmcmpdll/Makefile deleted file mode 100644 index 1769adac773..00000000000 --- a/developer/src/kmcmpdll/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -# -# KMCmpDll Makefile -# - -!include ..\Defines.mak - - -build: version.res dirs - $(MSBUILD) kmcmpdll.vcxproj $(MSBUILD_BUILD) "/p:Platform=Win32" - $(COPY) $(WIN32_TARGET_PATH)\kmcmpdll.dll $(DEVELOPER_PROGRAM) - $(COPY) $(WIN32_TARGET_PATH)\kmcmpdll.lib $(DEVELOPER_OUTLIB) - $(COPY) $(WIN32_TARGET_PATH)\kmcmpdll.pdb $(DEVELOPER_DEBUGPATH) -# for debugging purposes, it's useful to have a copy -# of kmcmpdll.dll in the TIKE project folder; it is -# .gitignored - -mkdir ..\kmcomp\$(WIN32_TARGET_PATH) - -mkdir ..\tike\$(WIN32_TARGET_PATH) - $(COPY) $(WIN32_TARGET_PATH)\kmcmpdll.dll ..\tike\$(WIN32_TARGET_PATH)\kmcmpdll.dll - $(COPY) $(WIN32_TARGET_PATH)\kmcmpdll.dll ..\kmcomp\$(WIN32_TARGET_PATH)\kmcmpdll.dll - - $(MSBUILD) kmcmpdll.vcxproj $(MSBUILD_BUILD) "/p:Platform=x64" - $(COPY) $(X64_TARGET_PATH)\kmcmpdll.x64.dll $(DEVELOPER_PROGRAM)\kmcmpdll.x64.dll - $(COPY) $(X64_TARGET_PATH)\kmcmpdll.x64.lib $(DEVELOPER_OUTLIB)\kmcmpdll.x64.lib - $(COPY) $(X64_TARGET_PATH)\kmcmpdll.x64.pdb $(DEVELOPER_DEBUGPATH)\kmcmpdll.x64.pdb - -mkdir ..\kmcomp\$(X64_TARGET_PATH) - $(COPY) $(X64_TARGET_PATH)\kmcmpdll.x64.dll ..\kmcomp\$(X64_TARGET_PATH)\kmcmpdll.x64.dll - - -clean: def-clean - $(MSBUILD) kmcmpdll.vcxproj $(MSBUILD_CLEAN) - -signcode: - $(SIGNCODE) /d "Keyman Developer Compiler" $(DEVELOPER_PROGRAM)\kmcmpdll.dll - $(SIGNCODE) /d "Keyman Developer Compiler" $(DEVELOPER_PROGRAM)\kmcmpdll.x64.dll - -wrap-symbols: - $(SYMSTORE) $(DEVELOPER_PROGRAM)\kmcmpdll.dll /t keyman-developer - $(SYMSTORE) $(DEVELOPER_PROGRAM)\kmcmpdll.x64.dll /t keyman-developer - $(SYMSTORE) $(DEVELOPER_DEBUGPATH)\kmcmpdll.pdb /t keyman-developer - $(SYMSTORE) $(DEVELOPER_DEBUGPATH)\kmcmpdll.x64.pdb /t keyman-developer - -test-manifest: - @rem This target needed as dependency for TIKE and KMCMPDLL - -install: - $(COPY) $(DEVELOPER_PROGRAM)\kmcmpdll.dll "$(INSTALLPATH_KEYMANDEVELOPER)\kmcmpdll.dll" - $(COPY) $(DEVELOPER_PROGRAM)\kmcmpdll.x64.dll "$(INSTALLPATH_KEYMANDEVELOPER)\kmcmpdll.x64.dll" - -!include ..\Target.mak diff --git a/developer/src/kmcmpdll/NamedCodeConstants.cpp b/developer/src/kmcmpdll/NamedCodeConstants.cpp deleted file mode 100644 index 14f523b35ec..00000000000 --- a/developer/src/kmcmpdll/NamedCodeConstants.cpp +++ /dev/null @@ -1,316 +0,0 @@ -/* - Name: NamedCodeConstants - Copyright: Copyright (C) 2003-2017 SIL International. - Documentation: - Description: - Create Date: 19 Jul 2011 - - Modified Date: 13 Dec 2012 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 19 Jul 2011 - mcdurdin - I2993 - Named code constants cause a warning 0x208D to appear - 24 Oct 2012 - mcdurdin - I3481 - V9.0 - Eliminate unsafe calls in C++ - 06 Feb 2012 - mcdurdin - I3056 - If file was not found in first search folder, named code constants failed to compile - 06 Feb 2012 - mcdurdin - I3056 - UTF-8 support for named code constants file - 03 Nov 2012 - mcdurdin - I3512 - V9.0 - Merge of I3056 - If file was not found in first search folder, named code constants failed to compile - 13 Dec 2012 - mcdurdin - I3641 - V9.0 - compiler dll buffer overrun bugs -*/ - -#include "pch.h" -#include -#include "NamedCodeConstants.h" -#include "CheckFilenameConsistency.h" -#include "kmcmpdll.h" - -extern char CompileDir[]; - - -int IsHangulSyllable(const wchar_t *codename, int *code); - - -NamedCodeConstants::NamedCodeConstants() -{ - nEntries = 0; - entries = NULL; - nEntries_file = 0; - entries_file = NULL; - reindex(); -} - -NamedCodeConstants::~NamedCodeConstants() -{ - if(entries) delete entries; - if(entries_file) delete entries_file; -} - -void NamedCodeConstants::AddCode(int n, const wchar_t *p, DWORD storeIndex) -{ - if((nEntries_file % ALLOC_SIZE) == 0) - { - NCCENTRY *bn = new NCCENTRY[nEntries_file + ALLOC_SIZE]; - if(nEntries_file > 0) - { - memcpy(bn, entries_file, sizeof(NCCENTRY) * nEntries_file); - delete entries_file; - } - entries_file = bn; - } - - entries_file[nEntries_file].code = n; - wcsncpy_s(entries_file[nEntries_file].name, _countof(entries_file[nEntries_file].name), p, MAX_ENAME); // I3481 - entries_file[nEntries_file].name[MAX_ENAME] = 0; - - for (wchar_t *r = entries_file[nEntries_file].name; *r; r++) - if (iswblank(*r) && *r != '-') *r = '_'; - - entries_file[nEntries_file].storeIndex = storeIndex; - nEntries_file++; -} - -void NamedCodeConstants::AddCode_IncludedCodes(int n, const wchar_t *p) -{ - if((nEntries % ALLOC_SIZE) == 0) - { - NCCENTRY *bn = new NCCENTRY[nEntries + ALLOC_SIZE]; - if(nEntries > 0) - { - memcpy(bn, entries, sizeof(NCCENTRY) * nEntries); - delete entries; - } - entries = bn; - } - - entries[nEntries].code = n; - wcsncpy_s(entries[nEntries].name, _countof(entries[nEntries].name), p, MAX_ENAME); // I3481 - entries[nEntries].name[MAX_ENAME] = 0; - for (wchar_t *r = entries[nEntries].name; *r; r++) - if (iswblank(*r)) *r = '_'; - - entries[nEntries].storeIndex = 0xFFFFFFFFL; - nEntries++; -} - -int __cdecl sort_entries(const void *elem1, const void *elem2) -{ - return _wcsicmp( - ((NCCENTRY *)elem1)->name, - ((NCCENTRY *)elem2)->name); -} - -BOOL NamedCodeConstants::IntLoadFile(const char *filename) -{ - FILE *fp = NULL; - - if (CheckFilenameConsistency(filename, FALSE) != CERR_None) { - return FALSE; - } - - if(fopen_s(&fp, filename, "rt") != 0) return FALSE; // I3481 - - char str[256], *p, *q, *context = NULL; - BOOL neol, first = TRUE; - - while(fgets(str, 256, fp)) - { - neol = *(strchr(str, 0) - 1) == '\n'; - p = strtok_s(str, ";", &context); // I3481 - q = strtok_s(NULL, ";\n", &context); - if(p && q) - { - if(first && *p == (char)0xEF && *(p+1) == (char)0xBB && *(p+2) == (char)0xBF) p += 3; // I3056 UTF-8 // I3512 - first = FALSE; - _strupr_s(q, strlen(q)+1); // I3481 // I3641 - int n = strtol(p, NULL, 16); - if (*q != '<') { - PWSTR q0 = strtowstr(q); - AddCode_IncludedCodes(n, q0); - delete[] q0; - } - } - if(!neol) - { - while(fgets(str, 256, fp)) if(*(strchr(str, 0)-1) == '\n') break; - } - } - - fclose(fp); - - return TRUE; -} - -BOOL NamedCodeConstants::LoadFile(const char *filename) -{ - char buf[260]; - // Look in current directory first - strncpy_s(buf, _countof(buf), filename, 259); buf[259] = 0; // I3481 - if(FileExists(buf)) - return IntLoadFile(buf); - // Then look in keyboard file directory (CompileDir) - strncpy_s(buf, _countof(buf), CompileDir, 259); buf[259] = 0; // I3481 - strncat_s(buf, _countof(buf), filename, 259-strlen(CompileDir)); buf[259] = 0; - if(FileExists(buf)) - return IntLoadFile(buf); - // Finally look in kmcmpdll.dll directory - GetModuleFileName(0, buf, 260); - char *p = strrchr(buf, '\\'); if(p) p++; else p = buf; - *p = 0; - strncat_s(buf, _countof(buf), filename, 259-strlen(buf)); buf[259] = 0; // I3481 // I3641 - if(FileExists(buf)) - return IntLoadFile(buf); - - reindex(); - - return FALSE; -} - -void NamedCodeConstants::reindex() -{ - if (entries != NULL) { - qsort(entries, nEntries, sizeof(NCCENTRY), sort_entries); - } - - wchar_t c = L'.', d; - int i; - - for(i = 0; i < 128; i++) chrindexes[i] = -1; - - if (entries != NULL) { - for (i = 0; i < nEntries; i++) - { - d = towupper(entries[i].name[0]); - if (d != c && d >= 32 && d <= 127) - chrindexes[c = d] = i; - } - } -} - -int NamedCodeConstants::GetCode(const wchar_t *codename, DWORD *storeIndex) -{ - *storeIndex = 0xFFFFFFFFL; // I2993 - int code = GetCode_IncludedCodes(codename); - if(code) return code; - for(int i = 0; i < nEntries_file; i++) - if(!_wcsicmp(entries_file[i].name, codename)) - { - *storeIndex = entries_file[i].storeIndex; - return entries_file[i].code; - } - return 0; -} - -int NamedCodeConstants::GetCode_IncludedCodes(const wchar_t *codename) -{ - wchar_t c = towupper(*codename); - int code; - - if(IsHangulSyllable(codename, &code)) return code; - - if(c < 32 || c > 127 || chrindexes[c] < 0) return 0; - for(int n = chrindexes[c]; n < nEntries && towupper(entries[n].name[0]) == c; n++) - { - int cmp = _wcsicmp(codename, entries[n].name); - if(cmp == 0) return entries[n].code; - if(cmp < 0) break; - } - return 0; -} - -/* - - Hangul Syllables - -*/ - -const int - HangulSBase = 0xAC00, - HangulLBase = 0x1100, - HangulVBase = 0x1161, - HangulTBase = 0x11A7, - HangulLCount = 19, - HangulVCount = 21, - HangulTCount = 28, - HangulNCount = HangulVCount * HangulTCount, // 588 - HangulSCount = HangulLCount * HangulNCount; // 11172 - -const wchar_t * - Hangul_JAMO_L_TABLE[] = { - L"G", L"GG", L"N", L"D", L"DD", L"R", L"M", L"B", L"BB", - L"S", L"SS", L"", L"J", L"JJ", L"C", L"K", L"T", L"P", L"H" }; - -const wchar_t * - Hangul_JAMO_V_TABLE[] = { - L"A", L"AE", L"YA", L"YAE", L"EO", L"E", L"YEO", L"YE", L"O", - L"WA", L"WAE", L"OE", L"YO", L"U", L"WEO", L"WE", L"WI", - L"YU", L"EU", L"YI", L"I" }; - -const wchar_t * - Hangul_JAMO_T_TABLE[] = { - L"", L"G", L"GG", L"GS", L"N", L"NJ", L"NH", L"D", L"L", L"LG", L"LM", - L"LB", L"LS", L"LT", L"LP", L"LH", L"M", L"B", L"BS", - L"S", L"SS", L"NG", L"J", L"C", L"K", L"T", L"P", L"H" }; - -int IsHangulSyllable(const wchar_t *codename, int *code) -{ - if(_wcsnicmp(codename, L"HANGUL_SYLLABLE_", 16)) return 0; - codename += 16; - if(!*codename) return 0; - - int i, LIndex, VIndex, TIndex; - - /* Find initial */ - - int ch = towupper(*codename); - if(strchr("GNDRMBSJCKTPH", ch)) - { - /* Has an initial syllable */ - int fdouble = towupper(*(codename+1)) == ch; - - LIndex = -1; - for(i = 0; i < HangulLCount; i++) - if(Hangul_JAMO_L_TABLE[i][0] == ch && - (!fdouble || (Hangul_JAMO_L_TABLE[i][1] == ch && fdouble))) - { - LIndex = i; - break; - } - if(LIndex == -1) return 0; - codename++; - if(fdouble) codename++; - } - else LIndex = 11; /* no initial */ - - /* Find vowel */ - - wchar_t V[4] = L""; - V[0] = *codename; - if(V[0] && strchr("AEIOUWY", towupper(*(codename+1)))) V[1] = *(codename+1); - if(V[1] && strchr("AEIOUWY", towupper(*(codename+2)))) V[2] = *(codename+2); - - VIndex = -1; - for(i = 0; i < HangulVCount; i++) - if(!_wcsicmp(Hangul_JAMO_V_TABLE[i], V)) { VIndex = i; break; } - - if(VIndex == -1) return 0; - - codename += wcslen(V); - - /* Find final */ - - TIndex = -1; - - for(i = 0; i < HangulTCount; i++) - if(!_wcsicmp(Hangul_JAMO_T_TABLE[i], codename)) { TIndex = i; break; } - - if(TIndex == -1) return 0; - - /* Composition */ - - *code = (HangulSBase + (LIndex * HangulVCount + VIndex) * HangulTCount) + TIndex; - - return 1; -} diff --git a/developer/src/kmcmpdll/NamedCodeConstants.h b/developer/src/kmcmpdll/NamedCodeConstants.h deleted file mode 100644 index 3efaf4ed1df..00000000000 --- a/developer/src/kmcmpdll/NamedCodeConstants.h +++ /dev/null @@ -1,36 +0,0 @@ - -#ifndef _NAMEDCODECONSTANTS_H -#define _NAMEDCODECONSTANTS_H - -#define MAX_ENAME 128 -#define ALLOC_SIZE 256 - -struct NCCENTRY -{ - wchar_t name[MAX_ENAME+1]; - int code; - DWORD storeIndex; -}; - -class NamedCodeConstants -{ -private: - NCCENTRY *entries; // entries from &includecodes - NCCENTRY *entries_file; // entries from store(myconst) x - int nEntries, nEntries_file; - int chrindexes[128]; // A-Z, 0-9, -, _; simple index - - int GetCode_IncludedCodes(const wchar_t *codename); - void AddCode_IncludedCodes(int n, const wchar_t *p); - BOOL IntLoadFile(const char *filename); -public: - NamedCodeConstants(); - ~NamedCodeConstants(); - - void reindex(); - void AddCode(int n, const wchar_t *p, DWORD storeIndex); - BOOL LoadFile(const char *filename); - int GetCode(const wchar_t *codename, DWORD *storeIndex); -}; - -#endif //_NAMEDCODECONSTANTS_H diff --git a/developer/src/kmcmpdll/UnreachableRules.cpp b/developer/src/kmcmpdll/UnreachableRules.cpp deleted file mode 100644 index 268360904d6..00000000000 --- a/developer/src/kmcmpdll/UnreachableRules.cpp +++ /dev/null @@ -1,52 +0,0 @@ - -#include "pch.h" - -#include -#include -#include "../../../common/windows/cpp/include/vkeys.h" -#include - -#include -#include -#include -#include - -#include "UnreachableRules.h" - -std::wstring MakeHashKeyFromFileKey(PFILE_KEY kp) { - std::wstringstream key; - key << kp->Key << "," << kp->ShiftFlags << ","; - if (kp->dpContext) - key << kp->dpContext; - return key.str(); -} - -DWORD VerifyUnreachableRules(PFILE_GROUP gp) { - PFILE_KEY kp = gp->dpKeyArray; - DWORD i; - - int oldCurrentLine = currentLine; - - std::unordered_map map; - std::unordered_set reportedLines; - - for (i = 0; i < gp->cxKeyArray; i++, kp++) { - std::wstring key = MakeHashKeyFromFileKey(kp); - if (map.count(key) > 0) { - FILE_KEY const & k1 = map.at(key); - if (kp->Line != k1.Line && reportedLines.count(kp->Line) == 0) { - reportedLines.insert(kp->Line); - currentLine = kp->Line; - wsprintf(ErrExtra, "Overridden by rule on line %d", k1.Line); - AddWarning(CHINT_UnreachableRule); - } - } - else { - map.insert({ key, *kp }); - } - } - - currentLine = oldCurrentLine; - - return CERR_None; -} diff --git a/developer/src/kmcmpdll/UnreachableRules.h b/developer/src/kmcmpdll/UnreachableRules.h deleted file mode 100644 index 94815af1c0c..00000000000 --- a/developer/src/kmcmpdll/UnreachableRules.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -DWORD VerifyUnreachableRules(PFILE_GROUP gp); diff --git a/developer/src/kmcmpdll/compfile.h b/developer/src/kmcmpdll/compfile.h deleted file mode 100644 index 9e65c36600e..00000000000 --- a/developer/src/kmcmpdll/compfile.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - Name: Compfile - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 25 Jan 2007 - - Modified Date: 25 May 2010 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 25 Jan 2007 - mcdurdin - Add GLOBAL_BUFSIZE, enlarge LINESIZE - 25 May 2010 - mcdurdin - I1632 - Keyboard Options -*/ - -#ifndef _COMPFILE_H -#define _COMPFILE_H - -#include "../../../common/windows/cpp/include/legacy_kmx_file.h" - - -// This file is deprecated; see kmcmplib/src/compfile.h. However, as long as -// this file continues to live, we need to keep structures in it in exact sync -// with kmcmplib/src/compfile.h, as FILE_KEYBOARD is passed between kmcmplib and -// kmcmpdll. KMX_WCHAR on Windows is identical to wchar_t, so it is safe to map -// accordingly. - -#define LINESIZE 8192 -#define GLOBAL_BUFSIZE 4096 - -#define SZMAX_STORENAME 80 -#define SZMAX_GROUPNAME 80 -#define SZMAX_DEADKEYNAME 80 -#define SZMAX_ERRORTEXT 512 -#define SZMAX_VKDICTIONARYNAME 80 - -#define MAX_WARNINGS 100 - -#define T_COMMENT 1 // A comment line -#define T_KEYTOKEY 2 // A rule line -#define T_BLANK 3 // A blank line - -#define T_W_START 4 // Start of easily matched line types -#define T_STORE 4 // A store line -#define T_VERSION 5 // A 'VERSION 3.2/3.1/3.0' line -#define T_NAME 6 // A 'NAME "xxxx"' line -#define T_BITMAP 7 // A 'BITMAP bmpfile' line -#define T_HOTKEY 8 // A 'HOTKEY "^%+C"' line -#define T_BEGIN 9 // A 'begin > use(xxxx)' line -#define T_GROUP 10 // A 'group(xxxx)' line -#define T_MATCH 11 // A 'match > "XXXX"' line -#define T_NOMATCH 12 // A 'nomatch > "XXXX"' line -#define T_SHIFT 13 // A 'SHIFT FREES CAPS' line -#define T_CAPSON 14 // A 'CAPS ON ONLY' line -#define T_CAPSOFF 15 // A 'CAPS ALWAYS OFF' line -#define T_LANGUAGE 16 // A 'LANGUAGE xxxh, xxh' line -#define T_LAYOUT 17 // A 'LAYOUT xxxxh' line -#define T_COPYRIGHT 18 // A 'COPYRIGHT "so and so"' line -#define T_MESSAGE 19 // A 'MESSAGE "so and so"' line -#define T_LANGUAGENAME 20 // A 'LANGUAGENAME "xxxx"' line -#define T_BITMAPS 21 // An unused 'BITMAPS' line (version 3.x) -#define T_W_END 21 // End of easily matched rule types - -#define T_UNKNOWN 99 // Unrecognized line type (illegal line) - -#define GDS_CUTLEAD 0x01 // GetDelimitedString: cut leading spaces -#define GDS_CUTFOLL 0x02 // GetDelimitedString: cut following spaces - -#define GXS_LINE 0x00 // GetExtendedString error: in a "line" -#define GXS_RULE 0x01 // GetExtendedString error: in a "rule" -#define GXS_STORE 0x02 // GetExtendedString error: in a "store" - -enum FileStoreType { FST_STORE, FST_OPTION, FST_RESERVED }; - -struct FILE_STORE { - DWORD dwSystemID; - WCHAR szName[SZMAX_STORENAME]; // the name of the store - PWSTR dpString; // from start of store structure - //FileStoreType fstType; - BOOL fIsStore; - BOOL fIsReserved; - BOOL fIsOption; - BOOL fIsDebug; - BOOL fIsCall; - int line; // TODO: int vs dword, line vs Line (see FILE_KEY, FILE_GROUP) -}; - -typedef FILE_STORE *PFILE_STORE; - -struct FILE_KEY { - WCHAR Key; // WCHAR for consistency; only a byte used however - WORD LineStoreIndex; - DWORD Line; - DWORD ShiftFlags; - PWSTR dpOutput; // from start of key structure - PWSTR dpContext; // from start of key structure -}; -typedef FILE_KEY *PFILE_KEY; - -struct FILE_GROUP { - WCHAR szName[SZMAX_GROUPNAME]; - PFILE_KEY dpKeyArray; // address of first item in key array, from start of group structure - PWSTR dpMatch; // from start of group structure - PWSTR dpNoMatch; // from start of group structure - DWORD cxKeyArray; // in array items - BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not - BOOL fReadOnly; // group(xx) [readonly] <-- specified or not - DWORD Line; -}; - -typedef FILE_GROUP *PFILE_GROUP; - -struct FILE_DEADKEY { - WCHAR szName[SZMAX_DEADKEYNAME]; -}; - -typedef FILE_DEADKEY *PFILE_DEADKEY; - -struct FILE_VKDICTIONARY { - WCHAR szName[SZMAX_VKDICTIONARYNAME]; -}; -typedef FILE_VKDICTIONARY *PFILE_VKDICTIONARY; - -struct FILE_KEYBOARD { - DWORD KeyboardID; // deprecated, unused - - DWORD version; // keyboard file version with VERSION keyword - - PFILE_STORE dpStoreArray; // address of first item in store array, from start of store structure - PFILE_GROUP dpGroupArray; // address of first item in group array, from start of group structure - - DWORD cxStoreArray; // in number of items - DWORD cxGroupArray; // in number of items - DWORD StartGroup[2]; // index of starting groups [ANSI=0, Unicode=1] - - DWORD dwHotKey; // standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) - - WCHAR szName[SZMAX_KEYBOARDNAME]; // Keyboard layout name - WCHAR szLanguageName[SZMAX_LANGUAGENAME]; // Language name - WCHAR szCopyright[SZMAX_COPYRIGHT]; // Copyright information - WCHAR szMessage[SZMAX_MESSAGE]; // General information about the keyboard - PBYTE lpBitmap; - DWORD dwBitmapSize; - DWORD dwFlags; // Flags for the keyboard file - - DWORD currentGroup; // temp - current processing group - DWORD currentStore; // temp - current processing store - DWORD cxDeadKeyArray; - PFILE_DEADKEY dpDeadKeyArray; // temp - dead key array - DWORD cxVKDictionary; - PFILE_VKDICTIONARY dpVKDictionary; // temp - virtual key dictionary - - void* extra; // used by kmcmplib and its consumers; unused in kmcmpdll -}; - -typedef FILE_KEYBOARD *PFILE_KEYBOARD; - -/* - These size values are used in unit tests to ensure - that the structure sizes correspond precisely across - compilers (pas and c++). -*/ - -const DWORD sz_FILE_STORE = sizeof(FILE_STORE); -const DWORD sz_FILE_KEY = sizeof(FILE_KEY); -const DWORD sz_FILE_GROUP = sizeof(FILE_GROUP); -const DWORD sz_FILE_DEADKEY = sizeof(FILE_DEADKEY); -const DWORD sz_FILE_VKDICTIONARY = sizeof(FILE_VKDICTIONARY); -const DWORD sz_FILE_KEYBOARD = sizeof(FILE_KEYBOARD); - -struct COMPMSG { - char szText[SZMAX_ERRORTEXT]; - DWORD Line; - DWORD dwMsgCode; -}; - -typedef COMPMSG *PCOMPMSG; - -struct COMPILEMESSAGES { - int nMessages; - int nErrors; - - PCOMPMSG cm; - - DWORD fatalCode; - char szFatalText[SZMAX_ERRORTEXT]; - - DWORD currentLine; -}; - -typedef COMPILEMESSAGES *PCOMPILEMESSAGES; - -/* -struct TVersion -{ - //int MinVersion; // 0x0500 usually - //int CompilerVersion[4]; - //int MinCompilerVersion[4]; - int KeyboardVersion; // 0x0500 usually -}; - -extern TVersion FVersionInfo; -*/ - -/* -#define bstrcpy(c,d) (LPBYTE)strcpy((LPSTR)(c),(LPSTR)(d)) -#define bstrlen(c) strlen((LPSTR)(c)) -#define bstrcmp(c,d) strcmp((LPSTR)(c),(LPSTR)(d)) -#define bstrncmp(c,d,n) strncmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrnicmp(c,d,n) strnicmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstricmp(c,d) stricmp((LPSTR)(c),(LPSTR)(d)) -#define bstrchr(c,ch) (LPBYTE)strchr((LPSTR)(c),(char)ch) -#define bstrncpy(c,d,n) (LPBYTE)strncpy((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrtok(c,d) (LPBYTE)strtok((LPSTR)(c),(LPSTR)(d)) -#define bstrcat(c,d) (LPBYTE)strcat((LPSTR)(c),(LPSTR)(d)) -#define bstrncat(c,d,n) (LPBYTE)strncat((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrrev(c) (LPBYTE)strrev((LPSTR)(c)) -#define batoi(c) atoi((LPSTR)(c)) -#define bstrtol(c,d,n) strtol((LPSTR)(c),(LPSTR *)(d),(n)) -*/ - -#endif // _COMPFILE_H diff --git a/developer/src/kmcmpdll/debugstore.h b/developer/src/kmcmpdll/debugstore.h deleted file mode 100644 index 5d5e8c2e3b3..00000000000 --- a/developer/src/kmcmpdll/debugstore.h +++ /dev/null @@ -1,20 +0,0 @@ - -#ifndef DEBUGSTORE_H -#define DEBUGSTORE_H - -#define DEBUGSTORE_BEGIN L"B" -#define DEBUGSTORE_BEGIN_C L'B' - -#define DEBUGSTORE_MATCH L"M" -#define DEBUGSTORE_MATCH_C L'M' - -#define DEBUGSTORE_NOMATCH L"N" -#define DEBUGSTORE_NOMATCH_C L'N' - -#define DEBUGSTORE_GROUP L"G" -#define DEBUGSTORE_GROUP_C L'G' - -#define DEBUGSTORE_DEADKEY L"D" -#define DEBUGSTORE_DEADKEY_C L'D' - -#endif /* DEBUGSTORE_H */ diff --git a/developer/src/kmcmpdll/json-validation.cpp b/developer/src/kmcmpdll/json-validation.cpp deleted file mode 100644 index c3c1af04843..00000000000 --- a/developer/src/kmcmpdll/json-validation.cpp +++ /dev/null @@ -1,83 +0,0 @@ - -#include -#include - -#include - -typedef bool (*kmcmp_ValidateJsonMessageProc)(int64_t offset, const char* szText, void* context); - -using nlohmann::json; -using nlohmann::json_uri; -using nlohmann::json_schema_draft4::json_validator; - -typedef BOOL (CALLBACK *ValidateJsonMessageProc)(INT64 offset, const char* szText); - -static void loader(const json_uri &uri, json &schema) -{ - std::fstream lf("." + uri.path()); - if (!lf.good()) - throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path()); - - try { - lf >> schema; - } - catch (std::exception &e) { - throw e; - } -} - -bool kmcmpMessageProc(int64_t offset, const char* szText, void* context) { - return ((ValidateJsonMessageProc)context)(offset, szText); -} - -extern "C" BOOL __declspec(dllexport) ValidateJsonFile(PWSTR pwszSchemaFile, PWSTR pwszJsonFile, ValidateJsonMessageProc MessageProc) { - - std::fstream f(pwszSchemaFile); - if (!f.good()) { - MessageProc(-1, "Schema file could not be loaded."); - return FALSE; - } - - // 1) Read the schema for the document you want to validate - json schema; - try { - f >> schema; - } - catch (std::exception &e) { - MessageProc(f.tellp(), e.what()); - return FALSE; - } - - // 2) create the validator and - json_validator validator(loader, [](const std::string &, const std::string &) {}); - - try { - // insert this schema as the root to the validator - // this resolves remote-schemas, sub-schemas and references via the given loader-function - validator.set_root_schema(schema); - } - catch (std::exception &e) { - MessageProc(-2, e.what()); - return FALSE; - } - - // 3) do the actual validation of the document - json document; - - std::fstream fd(pwszJsonFile); - if (!fd.good()) { - MessageProc(-3, "Json file could not be loaded."); - return FALSE; - } - - try { - fd >> document; - validator.validate(document); - } - catch (std::exception &e) { - MessageProc(fd.tellp(), e.what()); - return FALSE; - } - - return TRUE; -} \ No newline at end of file diff --git a/developer/src/kmcmpdll/kcframe/kcframe.cpp b/developer/src/kmcmpdll/kcframe/kcframe.cpp deleted file mode 100644 index 22402e52300..00000000000 --- a/developer/src/kmcmpdll/kcframe/kcframe.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - Name: kcframe - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 24 Aug 2015 - - Modified Date: 24 Aug 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - - 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - -*/ - -#include -#include - -#include - -extern "C" BOOL CompileKeyboardFile(PSTR pszInfile, PSTR pszOutfile, BOOL FSaveDebug, BOOL ACompilerWarningsAsErrors, BOOL AWarnDeprecatedCode, CompilerMessageProc pMsgProc); // I4865 // I4866 - -int WINAPI msgproc(int line, DWORD dwMsgCode, LPSTR szText) -{ - printf("line %d error %x %s\n", line, (unsigned int) dwMsgCode, szText); - return 1; -} - -int main(int argc, char *argv[]) -{ - if (argc == 2 && strcmp(argv[1], "--sizeof") == 0) { - printf("FILE_KEYBOARD_SIZE = %zu\n", sizeof(FILE_KEYBOARD)); - printf("FILE_GROUP_SIZE = %zu\n", sizeof(FILE_GROUP)); - printf("FILE_STORE_SIZE = %zu\n", sizeof(FILE_STORE)); - printf("FILE_KEY_SIZE = %zu\n", sizeof(FILE_KEY)); - printf("FILE_DEADKEY_SIZE = %zu\n", sizeof(FILE_DEADKEY)); - return 0; - } - if(argc < 3) - { - puts("Usage: kcframe infile.kmn outfile.kmx"); - return 1; - } - - return CompileKeyboardFile(argv[1], argv[2], TRUE, FALSE, TRUE, msgproc) ? 0 : 1; -} diff --git a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj b/developer/src/kmcmpdll/kcframe/kcframe.vcxproj deleted file mode 100644 index 24cb578d733..00000000000 --- a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj +++ /dev/null @@ -1,267 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {E92CC897-8228-4728-8110-028088D7A99C} - kcframe - 10.0.17763.0 - - - - Application - false - MultiByte - v141 - - - Application - false - MultiByte - v141 - - - Application - false - MultiByte - v141 - - - Application - false - MultiByte - v141 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - false - false - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - - - $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - - - obj\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ - - - obj\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)..\bin\$(Platform)\$(Configuration)\ - - - - .\Release/kcframe.tlb - - - - - MaxSpeed - OnlyExplicitInline - .;..;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreaded - true - .\Release/kcframe.pch - .\Release/ - Level3 - true - true - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - Console - MachineX86 - true - - - true - .\Release/kcframe.bsc - - - - - .\Release/kcframe.tlb - - - - - MaxSpeed - OnlyExplicitInline - .;..;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreaded - true - .\Release/kcframe.pch - .\Release/ - Level3 - true - true - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - Console - true - - - true - .\Release/kcframe.bsc - - - - - .\Debug/kcframe.tlb - - - - - Disabled - .;..;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - EnableFastChecks - MultiThreadedDebug - .\Debug/kcframe.pch - .\Debug/ - Level3 - true - EditAndContinue - true - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - true - Console - MachineX86 - true - - - true - .\Debug/kcframe.bsc - - - - - .\Debug/kcframe.tlb - - - - - Disabled - .;..;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - .\Debug/kcframe.pch - .\Debug/ - Level3 - true - ProgramDatabase - true - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - true - Console - true - - - true - .\Debug/kcframe.bsc - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - {7e26fe08-721e-424b-9da0-4a0dca88a86e} - - - - - - \ No newline at end of file diff --git a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj.filters b/developer/src/kmcmpdll/kcframe/kcframe.vcxproj.filters deleted file mode 100644 index 0cd42a560b1..00000000000 --- a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {52df63e2-6932-4c95-8a20-bc312924cb32} - cpp;c;cxx;rc;def;r;odl;idl;hpj;bat - - - {251970ed-fef3-4dfa-a930-a6a4baf00219} - h;hpp;hxx;hm;inl - - - {91a9b75f-0ab8-4840-92cc-cc9e963b5c67} - ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe - - - - - Source Files - - - \ No newline at end of file diff --git a/developer/src/kmcmpdll/kmcmpdll.h b/developer/src/kmcmpdll/kmcmpdll.h deleted file mode 100644 index d0523391253..00000000000 --- a/developer/src/kmcmpdll/kmcmpdll.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include "compfile.h" - -BOOL AddCompileString(LPSTR buf); -BOOL AddCompileMessage(DWORD msg); - -// TODO: These macros can return FALSE in functions that expect a DWORD CERR_x -// return value type. This is just plain wrong! -#define SetError(err) { if(AddCompileMessage(err) || (err & CERR_FATAL)) return FALSE; } -#define AddWarning(warn) { if(AddCompileMessage(warn)) return FALSE; } - -extern BOOL FWarnDeprecatedCode; -extern int currentLine; -extern char ErrExtra[]; - - -PWSTR strtowstr(PSTR in); -PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, DWORD dwSystemID); diff --git a/developer/src/kmcmpdll/kmcmpdll.sln b/developer/src/kmcmpdll/kmcmpdll.sln deleted file mode 100644 index 2da430e2494..00000000000 --- a/developer/src/kmcmpdll/kmcmpdll.sln +++ /dev/null @@ -1,41 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.705 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kcframe", "kcframe.vcxproj", "{E92CC897-8228-4728-8110-028088D7A99C}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmcmpdll", "kmcmpdll.vcxproj", "{7E26FE08-721E-424B-9DA0-4A0DCA88A86E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|Win32.ActiveCfg = Debug|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|Win32.Build.0 = Debug|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.ActiveCfg = Debug|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Debug|x64.Build.0 = Debug|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|Win32.ActiveCfg = Release|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|Win32.Build.0 = Release|Win32 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.ActiveCfg = Release|x64 - {E92CC897-8228-4728-8110-028088D7A99C}.Release|x64.Build.0 = Release|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|Win32.ActiveCfg = Debug|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|Win32.Build.0 = Debug|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x64.ActiveCfg = Debug|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Debug|x64.Build.0 = Debug|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|Win32.ActiveCfg = Release|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|Win32.Build.0 = Release|Win32 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x64.ActiveCfg = Release|x64 - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {167A867F-8CD6-4890-90E5-96DF59FFBB8C} - EndGlobalSection -EndGlobal diff --git a/developer/src/kmcmpdll/kmcmpdll.vcxproj b/developer/src/kmcmpdll/kmcmpdll.vcxproj deleted file mode 100644 index 2bcc038bd00..00000000000 --- a/developer/src/kmcmpdll/kmcmpdll.vcxproj +++ /dev/null @@ -1,450 +0,0 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {7E26FE08-721E-424B-9DA0-4A0DCA88A86E} - kmcmpdll - 10.0 - - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - false - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - - - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) - - - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - - - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) - - - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmcmpdll.tlb - - - - - MaxSpeed - OnlyExplicitInline - .;$(KEYMAN_ROOT)\windows\src\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions);JSON_SCHEMA_VALIDATOR_EXPORTS - true - Sync - MultiThreaded - true - All - $(IntDir) - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - $(KEYMAN_ROOT)\windows\src\global\inc - - - version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - true - - - Windows - 0x3C100000 - MachineX86 - true - true - - - true - ./kmcmpdll.bsc - - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - ./kmcmpdll.tlb - - - - - MaxSpeed - OnlyExplicitInline - .;$(KEYMAN_ROOT)\windows\src\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions);JSON_SCHEMA_VALIDATOR_EXPORTS - true - Sync - MultiThreaded - true - All - $(IntDir) - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - $(KEYMAN_ROOT)\windows\src\global\inc - - - version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - true - Windows - true - true - - - true - ./kmcmpdll.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmcmpdll.tlb - - - - - Disabled - .;$(KEYMAN_ROOT)\windows\src\global\inc;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions);JSON_SCHEMA_VALIDATOR_EXPORTS - Sync - MultiThreadedDebug - true - Level3 - true - EditAndContinue - true - Use - pch.h - $(IntDir) - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - $(KEYMAN_ROOT)\windows\src\global\inc - - - version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - true - true - Windows - 0x3C100000 - MachineX86 - true - - - true - ./kmcmpdll.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - ./kmcmpdll.tlb - - - - - Disabled - .;$(KEYMAN_ROOT)\windows\src\global\inc;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions);JSON_SCHEMA_VALIDATOR_EXPORTS - Sync - MultiThreadedDebug - $(IntDir) - true - Level3 - true - ProgramDatabase - true - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - $(KEYMAN_ROOT)\windows\src\global\inc - - - version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - true - true - Windows - true - - - true - ./kmcmpdll.bsc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - NotUsing - NotUsing - NotUsing - NotUsing - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - NotUsing - NotUsing - NotUsing - NotUsing - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - Create - Create - Create - Create - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - - - - - \ No newline at end of file diff --git a/developer/src/kmcmpdll/kmcmpdll.vcxproj.filters b/developer/src/kmcmpdll/kmcmpdll.vcxproj.filters deleted file mode 100644 index 4131bedac41..00000000000 --- a/developer/src/kmcmpdll/kmcmpdll.vcxproj.filters +++ /dev/null @@ -1,119 +0,0 @@ - - - - - {dd851de3-1698-40e6-9560-0ef4523ddb79} - h - - - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - Header files - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/developer/src/kmcmpdll/pch.cpp b/developer/src/kmcmpdll/pch.cpp deleted file mode 100644 index 17305716aac..00000000000 --- a/developer/src/kmcmpdll/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" \ No newline at end of file diff --git a/developer/src/kmcmpdll/pch.h b/developer/src/kmcmpdll/pch.h deleted file mode 100644 index dcba976ea74..00000000000 --- a/developer/src/kmcmpdll/pch.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0600 -#endif - -#ifndef STRICT -#define STRICT -#endif - -#include -#include - -#include "../../../common/windows/cpp/include/legacy_kmx_file.h" -#include "../../../common/windows/cpp/include/xstring.h" - -#include "../../../common/windows/cpp/include/registry.h" -#include "../../../common/windows/cpp/include/unicode.h" -#include "../../../common/windows/cpp/include/crc32.h" - -#include -#include - -#include diff --git a/developer/src/kmcmpdll/version.rc b/developer/src/kmcmpdll/version.rc deleted file mode 100644 index b963c10ddb1..00000000000 --- a/developer/src/kmcmpdll/version.rc +++ /dev/null @@ -1,32 +0,0 @@ -#include "../../../common/windows/cpp/include/keymanversion.h" - -1 VERSIONINFO - FILEVERSION KV_FILEVERSION - PRODUCTVERSION KV_PRODUCTVERSION - FILEFLAGSMASK 0x3fL - FILEFLAGS 0x0L - FILEOS 0x4L - FILETYPE 0x2L - FILESUBTYPE 0x0L - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "0C0904E4" - BEGIN - VALUE "CompanyName", KV_COMPANY_NAME - VALUE "FileDescription", "Keyman compiler library\0" - VALUE "FileVersion", KV_VERSION_STRING - VALUE "InternalName", "KMCOMP\0" - VALUE "LegalCopyright", KV_LEGAL_COPYRIGHT - VALUE "LegalTrademarks", KV_LEGAL_TRADEMARKS - VALUE "OriginalFilename", "KMCOMP.DLL\0" - VALUE "ProductName", "Keyman Developer\0" - VALUE "ProductVersion", KV_VERSION_STRING - VALUE "Comments", "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0xc09, 1252 - END - END diff --git a/developer/src/kmcmpdll/versioning.cpp b/developer/src/kmcmpdll/versioning.cpp deleted file mode 100644 index 1601e955632..00000000000 --- a/developer/src/kmcmpdll/versioning.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "pch.h" -#include -#include -#include - -BOOL CheckKeyboardFinalVersion(PFILE_KEYBOARD fk) { - char buf[128]; - - if (fk->dwFlags & KF_AUTOMATICVERSION) { - if (fk->version <= 0) { - fk->version = VERSION_60; // minimum version that we can be safe with - } - - wsprintf(buf, "The compiler has assigned a minimum engine version of %d.%d based on features used in this keyboard", (int)((fk->version & 0xFF00) >> 8), (int)(fk->version & 0xFF)); - AddCompileString(buf); - } - - return TRUE; -} - -BOOL VerifyKeyboardVersion(PFILE_KEYBOARD fk, DWORD ver) { - if (fk->dwFlags & KF_AUTOMATICVERSION) { - fk->version = max(fk->version, ver); - return TRUE; - } - - return fk->version >= ver; -} diff --git a/developer/src/kmcmpdll/versioning.h b/developer/src/kmcmpdll/versioning.h deleted file mode 100644 index 6e4c0c986f2..00000000000 --- a/developer/src/kmcmpdll/versioning.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include - -#define VERIFY_KEYBOARD_VERSION(fk, ver, err) { \ - if(!VerifyKeyboardVersion((fk), (ver))) \ - return (err); \ -} - -BOOL CheckKeyboardFinalVersion(PFILE_KEYBOARD fk); -BOOL VerifyKeyboardVersion(PFILE_KEYBOARD fk, DWORD ver); \ No newline at end of file diff --git a/developer/src/kmcmpdll/virtualcharkeys.cpp b/developer/src/kmcmpdll/virtualcharkeys.cpp deleted file mode 100644 index 3b50ab957e8..00000000000 --- a/developer/src/kmcmpdll/virtualcharkeys.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "pch.h" -#include "virtualcharkeys.h" - -BOOL VKeyMayBeVCKey[256] = { - FALSE, // L"K_?00", // &H0 - FALSE, // L"K_LBUTTON", // &H1 - FALSE, // L"K_RBUTTON", // &H2 - FALSE, // L"K_CANCEL", // &H3 - FALSE, // L"K_MBUTTON", // &H4 - FALSE, // L"K_?05", // &H5 - FALSE, // L"K_?06", // &H6 - FALSE, // L"K_?07", // &H7 - FALSE, // L"K_BKSP", // &H8 - FALSE, // L"K_TAB", // &H9 - FALSE, // L"K_?0A", // &HA - FALSE, // L"K_?0B", // &HB - FALSE, // L"K_KP5", // &HC - FALSE, // L"K_ENTER", // &HD - FALSE, // L"K_?0E", // &HE - FALSE, // L"K_?0F", // &HF - FALSE, // L"K_SHIFT", // &H10 - FALSE, // L"K_CONTROL", // &H11 - FALSE, // L"K_ALT", // &H12 - FALSE, // L"K_PAUSE", // &H13 - FALSE, // L"K_CAPS", // &H14 - FALSE, // L"K_KANJI?15", // &H15 - FALSE, // L"K_KANJI?16", // &H16 - FALSE, // L"K_KANJI?17", // &H17 - FALSE, // L"K_KANJI?18", // &H18 - FALSE, // L"K_KANJI?19", // &H19 - FALSE, // L"K_?1A", // &H1A - FALSE, // L"K_ESC", // &H1B - FALSE, // L"K_KANJI?1C", // &H1C - FALSE, // L"K_KANJI?1D", // &H1D - FALSE, // L"K_KANJI?1E", // &H1E - FALSE, // L"K_KANJI?1F", // &H1F - TRUE, // L"K_SPACE", // &H20 - FALSE, // L"K_PGUP", // &H21 - FALSE, // L"K_PGDN", // &H22 - FALSE, // L"K_END", // &H23 - FALSE, // L"K_HOME", // &H24 - FALSE, // L"K_LEFT", // &H25 - FALSE, // L"K_UP", // &H26 - FALSE, // L"K_RIGHT", // &H27 - FALSE, // L"K_DOWN", // &H28 - FALSE, // L"K_SEL", // &H29 - FALSE, // L"K_PRINT", // &H2A - FALSE, // L"K_EXEC", // &H2B - FALSE, // L"K_PRTSCN", // &H2C - FALSE, // L"K_INS", // &H2D - FALSE, // L"K_DEL", // &H2E - FALSE, // L"K_HELP", // &H2F - TRUE, // L"K_0", // &H30 - TRUE, // L"K_1", // &H31 - TRUE, // L"K_2", // &H32 - TRUE, // L"K_3", // &H33 - TRUE, // L"K_4", // &H34 - TRUE, // L"K_5", // &H35 - TRUE, // L"K_6", // &H36 - TRUE, // L"K_7", // &H37 - TRUE, // L"K_8", // &H38 - TRUE, // L"K_9", // &H39 - FALSE, // L"K_?3A", // &H3A - FALSE, // L"K_?3B", // &H3B - FALSE, // L"K_?3C", // &H3C - FALSE, // L"K_?3D", // &H3D - FALSE, // L"K_?3E", // &H3E - FALSE, // L"K_?3F", // &H3F - FALSE, // L"K_?40", // &H40 - - TRUE, // L"K_A", // &H41 - TRUE, // L"K_B", // &H42 - TRUE, // L"K_C", // &H43 - TRUE, // L"K_D", // &H44 - TRUE, // L"K_E", // &H45 - TRUE, // L"K_F", // &H46 - TRUE, // L"K_G", // &H47 - TRUE, // L"K_H", // &H48 - TRUE, // L"K_I", // &H49 - TRUE, // L"K_J", // &H4A - TRUE, // L"K_K", // &H4B - TRUE, // L"K_L", // &H4C - TRUE, // L"K_M", // &H4D - TRUE, // L"K_N", // &H4E - TRUE, // L"K_O", // &H4F - TRUE, // L"K_P", // &H50 - TRUE, // L"K_Q", // &H51 - TRUE, // L"K_R", // &H52 - TRUE, // L"K_S", // &H53 - TRUE, // L"K_T", // &H54 - TRUE, // L"K_U", // &H55 - TRUE, // L"K_V", // &H56 - TRUE, // L"K_W", // &H57 - TRUE, // L"K_X", // &H58 - TRUE, // L"K_Y", // &H59 - TRUE, // L"K_Z", // &H5A - FALSE, // L"K_?5B", // &H5B - FALSE, // L"K_?5C", // &H5C - FALSE, // L"K_?5D", // &H5D - FALSE, // L"K_?5E", // &H5E - FALSE, // L"K_?5F", // &H5F - FALSE, // L"K_NP0", // &H60 - FALSE, // L"K_NP1", // &H61 - FALSE, // L"K_NP2", // &H62 - FALSE, // L"K_NP3", // &H63 - FALSE, // L"K_NP4", // &H64 - FALSE, // L"K_NP5", // &H65 - FALSE, // L"K_NP6", // &H66 - FALSE, // L"K_NP7", // &H67 - FALSE, // L"K_NP8", // &H68 - FALSE, // L"K_NP9", // &H69 - FALSE, // L"K_NPSTAR", // &H6A - FALSE, // L"K_NPPLUS", // &H6B - FALSE, // L"K_SEPARATOR", // &H6C - FALSE, // L"K_NPMINUS", // &H6D - FALSE, // L"K_NPDOT", // &H6E - FALSE, // L"K_NPSLASH", // &H6F - FALSE, // L"K_F1", // &H70 - FALSE, // L"K_F2", // &H71 - FALSE, // L"K_F3", // &H72 - FALSE, // L"K_F4", // &H73 - FALSE, // L"K_F5", // &H74 - FALSE, // L"K_F6", // &H75 - FALSE, // L"K_F7", // &H76 - FALSE, // L"K_F8", // &H77 - FALSE, // L"K_F9", // &H78 - FALSE, // L"K_F10", // &H79 - FALSE, // L"K_F11", // &H7A - FALSE, // L"K_F12", // &H7B - FALSE, // L"K_F13", // &H7C - FALSE, // L"K_F14", // &H7D - FALSE, // L"K_F15", // &H7E - FALSE, // L"K_F16", // &H7F - FALSE, // L"K_F17", // &H80 - FALSE, // L"K_F18", // &H81 - FALSE, // L"K_F19", // &H82 - FALSE, // L"K_F20", // &H83 - FALSE, // L"K_F21", // &H84 - FALSE, // L"K_F22", // &H85 - FALSE, // L"K_F23", // &H86 - FALSE, // L"K_F24", // &H87 - - FALSE, // L"K_?88", // &H88 - FALSE, // L"K_?89", // &H89 - FALSE, // L"K_?8A", // &H8A - FALSE, // L"K_?8B", // &H8B - FALSE, // L"K_?8C", // &H8C - FALSE, // L"K_?8D", // &H8D - FALSE, // L"K_?8E", // &H8E - FALSE, // L"K_?8F", // &H8F - - FALSE, // L"K_NUMLOCK", // &H90 - FALSE, // L"K_SCROLL", // &H91 - - FALSE, // L"K_?92", // &H92 - FALSE, // L"K_?93", // &H93 - FALSE, // L"K_?94", // &H94 - FALSE, // L"K_?95", // &H95 - FALSE, // L"K_?96", // &H96 - FALSE, // L"K_?97", // &H97 - FALSE, // L"K_?98", // &H98 - FALSE, // L"K_?99", // &H99 - FALSE, // L"K_?9A", // &H9A - FALSE, // L"K_?9B", // &H9B - FALSE, // L"K_?9C", // &H9C - FALSE, // L"K_?9D", // &H9D - FALSE, // L"K_?9E", // &H9E - FALSE, // L"K_?9F", // &H9F - FALSE, // L"K_?A0", // &HA0 - FALSE, // L"K_?A1", // &HA1 - FALSE, // L"K_?A2", // &HA2 - FALSE, // L"K_?A3", // &HA3 - FALSE, // L"K_?A4", // &HA4 - FALSE, // L"K_?A5", // &HA5 - FALSE, // L"K_?A6", // &HA6 - FALSE, // L"K_?A7", // &HA7 - FALSE, // L"K_?A8", // &HA8 - FALSE, // L"K_?A9", // &HA9 - FALSE, // L"K_?AA", // &HAA - FALSE, // L"K_?AB", // &HAB - FALSE, // L"K_?AC", // &HAC - FALSE, // L"K_?AD", // &HAD - FALSE, // L"K_?AE", // &HAE - FALSE, // L"K_?AF", // &HAF - FALSE, // L"K_?B0", // &HB0 - FALSE, // L"K_?B1", // &HB1 - FALSE, // L"K_?B2", // &HB2 - FALSE, // L"K_?B3", // &HB3 - FALSE, // L"K_?B4", // &HB4 - FALSE, // L"K_?B5", // &HB5 - FALSE, // L"K_?B6", // &HB6 - FALSE, // L"K_?B7", // &HB7 - FALSE, // L"K_?B8", // &HB8 - FALSE, // L"K_?B9", // &HB9 - - TRUE, // L"K_COLON", // &HBA - TRUE, // L"K_EQUAL", // &HBB - TRUE, // L"K_COMMA", // &HBC - TRUE, // L"K_HYPHEN", // &HBD - TRUE, // L"K_PERIOD", // &HBE - TRUE, // L"K_SLASH", // &HBF - TRUE, // L"K_BKQUOTE", // &HC0 - - TRUE, // L"K_?C1", // &HC1 -- 103rd key on Brazilian - /? - FALSE, // L"K_?C2", // &HC2 -- 104th key on Brazilian - numpad . - FALSE, // L"K_?C3", // &HC3 - FALSE, // L"K_?C4", // &HC4 - FALSE, // L"K_?C5", // &HC5 - FALSE, // L"K_?C6", // &HC6 - FALSE, // L"K_?C7", // &HC7 - FALSE, // L"K_?C8", // &HC8 - FALSE, // L"K_?C9", // &HC9 - FALSE, // L"K_?CA", // &HCA - FALSE, // L"K_?CB", // &HCB - FALSE, // L"K_?CC", // &HCC - FALSE, // L"K_?CD", // &HCD - FALSE, // L"K_?CE", // &HCE - FALSE, // L"K_?CF", // &HCF - FALSE, // L"K_?D0", // &HD0 - FALSE, // L"K_?D1", // &HD1 - FALSE, // L"K_?D2", // &HD2 - FALSE, // L"K_?D3", // &HD3 - FALSE, // L"K_?D4", // &HD4 - FALSE, // L"K_?D5", // &HD5 - FALSE, // L"K_?D6", // &HD6 - FALSE, // L"K_?D7", // &HD7 - FALSE, // L"K_?D8", // &HD8 - FALSE, // L"K_?D9", // &HD9 - FALSE, // L"K_?DA", // &HDA - - TRUE, // L"K_LBRKT", // &HDB - TRUE, // L"K_BKSLASH", // &HDC - TRUE, // L"K_RBRKT", // &HDD - TRUE, // L"K_QUOTE", // &HDE - TRUE, // L"K_oDF", // &HDF - TRUE, // L"K_oE0", // &HE0 - TRUE, // L"K_oE1", // &HE1 - TRUE, // L"K_oE2", // &HE2 - TRUE, // L"K_oE3", // &HE3 - TRUE, // L"K_oE4", // &HE4 - - FALSE, // L"K_?E5", // &HE5 - - TRUE, // L"K_oE6", // &HE6 - - FALSE, // L"K_?E7", // &HE7 - FALSE, // L"K_?E8", // &HE8 - - TRUE, // L"K_oE9", // &HE9 - TRUE, // L"K_oEA", // &HEA - TRUE, // L"K_oEB", // &HEB - TRUE, // L"K_oEC", // &HEC - TRUE, // L"K_oED", // &HED - TRUE, // L"K_oEE", // &HEE - TRUE, // L"K_oEF", // &HEF - TRUE, // L"K_oF0", // &HF0 - TRUE, // L"K_oF1", // &HF1 - TRUE, // L"K_oF2", // &HF2 - TRUE, // L"K_oF3", // &HF3 - TRUE, // L"K_oF4", // &HF4 - TRUE, // L"K_oF5", // &HF5 - - FALSE, // L"K_?F6", // &HF6 - FALSE, // L"K_?F7", // &HF7 - FALSE, // L"K_?F8", // &HF8 - FALSE, // L"K_?F9", // &HF9 - FALSE, // L"K_?FA", // &HFA - FALSE, // L"K_?FB", // &HFB - FALSE, // L"K_?FC", // &HFC - FALSE, // L"K_?FD", // &HFD - FALSE, // L"K_?FE", // &HFE - FALSE, // L"K_?FF" // &HFF - }; - diff --git a/developer/src/kmcmpdll/virtualcharkeys.h b/developer/src/kmcmpdll/virtualcharkeys.h deleted file mode 100644 index fbb724781ff..00000000000 --- a/developer/src/kmcmpdll/virtualcharkeys.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _VIRTUALCHARKEYS_H -#define _VIRTUALCHARKEYS_H - -#include - -extern BOOL VKeyMayBeVCKey[256]; - -#endif - diff --git a/developer/src/kmcmpdll/xstring.cpp b/developer/src/kmcmpdll/xstring.cpp deleted file mode 100644 index 471878d6275..00000000000 --- a/developer/src/kmcmpdll/xstring.cpp +++ /dev/null @@ -1,48 +0,0 @@ - -class XString -{ -friend class XStringPointer; -private: - int RefCount; /* If RefCount > 0 when ~XString called, log an error */ - WCHAR *buf; /* Buffer for XString */ - int nbuf; /* Length of buf in bytes */ -public: - XString(); /* New, empty XString */ - XString(PWSTR FInitWStr); /* New, copy from FInitWStr */ - XString(XString *FInitXStr); /* New, copy from FInitXStr */ - ~XString(); - int Length(); /* Length in characters */ - int WordLength() { return nbuf; } /* Length in WORDs */ - - void Append(XString *FAppendXStr); - void Append(WCHAR *FAppendWStr); - void Append(WCHAR FAppendChar); - void Delete(int Position); /* Delete the character at Position */ - void Delete(int Position, int Length); /* Delete the character at Position to Pos+Len-1 */ -}; - -class XStringPointer -{ -private: - XString str; - WCHAR *p; - -public: - XStringPointer(XString FParent); - ~XStringPointer(); - - operator++(); - operator--(); - WCHAR operator[](int n); - WCHAR *cp() { return p; } - BOOL IsSurrogate(); - BOOL IsFunction(); - BOOL IsFunction(int FunctionID); - int Position(); /* Returns the current character position */ - int WordPosition(); /* Returns the current word position */ - void Insert(XString *FInsertXStr); /* Insert a character before pointer position */ - void Insert(WCHAR *FInsertWStr); - void Insert(WCHAR FInsertChar); - void Delete(); /* Delete the current character */ - void Delete(int n); /* Delete the current character + n-1 chrs */ -}; diff --git a/developer/src/kmcmplib/Makefile b/developer/src/kmcmplib/Makefile index 4acc7d3cfbf..26aed7fdc2c 100644 --- a/developer/src/kmcmplib/Makefile +++ b/developer/src/kmcmplib/Makefile @@ -28,7 +28,7 @@ wrap-symbols: @rem Not required test-manifest: - @rem This target needed as dependency for TIKE and KMCMPDLL + @rem This target needed as dependency for TIKE test: $(CALLER) ./build.sh test $(BUILD_DEBUG) diff --git a/developer/src/kmcomp/kmcomp.dpr b/developer/src/kmcomp/kmcomp.dpr index dd7fff9c2e1..24da9a4a259 100644 --- a/developer/src/kmcomp/kmcomp.dpr +++ b/developer/src/kmcomp/kmcomp.dpr @@ -34,7 +34,6 @@ uses UfrmTike in '..\tike\main\UfrmTike.pas' {TikeForm: TTntForm}, CompilePackageInstaller in '..\common\delphi\compiler\CompilePackageInstaller.pas', UTikeDebugMode in '..\tike\main\UTikeDebugMode.pas', - CompileKeymanWeb in '..\tike\compile\CompileKeymanWeb.pas', VisualKeyboard in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', KeymanWebKeyCodes in '..\tike\compile\KeymanWebKeyCodes.pas', ExtShiftState in '..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', @@ -82,7 +81,6 @@ uses VisualKeyboardSaverBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', VisualKeyboardSaverXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', kccompilekvk in 'kccompilekvk.pas', - ValidateKeyboardInfo in '..\tike\compile\ValidateKeyboardInfo.pas', MergeKeyboardInfo in '..\tike\compile\MergeKeyboardInfo.pas', JsonExtractKeyboardInfo in '..\tike\compile\JsonExtractKeyboardInfo.pas', Keyman.System.PackageInfoRefreshKeyboards in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', @@ -133,11 +131,6 @@ begin TKeymanSentryClient.Start(TSentryClientConsole, kscpDeveloper, LOGGER_DEVELOPER_TOOLS_KMCOMP); try try - if (ParamStr(1) = '-sentry-client-test-exception') and (ParamStr(2) = 'dll') then - begin - compile.Compiler_Diagnostic_Console(0); - Exit; - end; TKeymanSentryClient.Validate; Run; except diff --git a/developer/src/kmcomp/kmcomp.dproj b/developer/src/kmcomp/kmcomp.dproj index bb802bc5f7c..0feab699b43 100644 --- a/developer/src/kmcomp/kmcomp.dproj +++ b/developer/src/kmcomp/kmcomp.dproj @@ -183,7 +183,6 @@ - @@ -231,7 +230,6 @@ - diff --git a/developer/src/kmcomp/main.pas b/developer/src/kmcomp/main.pas index 4aa29cf6c1a..a0b5f9b865a 100644 --- a/developer/src/kmcomp/main.pas +++ b/developer/src/kmcomp/main.pas @@ -58,14 +58,11 @@ implementation KCCompileProject, KCCompileKVK, KeymanVersion, - CompileKeymanWeb, JsonExtractKeyboardInfo, - ValidateKeyboardInfo, MergeKeyboardInfo, UKeymanTargets; function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; forward; // I4706 -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; forward; //function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; forward; procedure FixupPathSlashes(var path: string); forward; @@ -291,14 +288,12 @@ procedure Run; TProjectLogConsole.Create(FSilent, FFullySilent, hOutfile, FColorMode); - KCSetCompilerOptions(FParamInfile, FShouldAddCompilerVersion); - if FValidateRepoChanges then FError := not TValidateRepoChanges.Execute(FParamInfile, FParamOutfile) else if FMerging then FError := not TMergeKeyboardInfo.Execute(FParamSourcePath, FParamInfile, FParamInfile2, FParamOutfile, FParamHelpLink, FMergingValidateIds, FSilent, TProjectLogConsole.Instance.Log) else if FValidating then - FError := not TValidateKeyboardInfo.Execute(FParamInfile, FJsonSchemaPath, FParamDistribution, FSilent, TProjectLogConsole.Instance.Log) + FError := True else if FJsonExtract then FError := not TJsonExtractKeyboardInfo.Execute(FParamInfile, FParamJsonFields, FSilent, TProjectLogConsole.Instance.Log) else if LowerCase(ExtractFileExt(FParamInfile)) = '.kpj' then // I4699 @@ -317,85 +312,9 @@ procedure Run; ExitCode := 1; end; -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; -var - opt: TCompilerOptions; -begin - TProjectLogConsole.Instance.Filename := FInFile; - - opt.dwSize := sizeof(TCompilerOptions); - opt.ShouldAddCompilerVersion := FShouldAddCompilerVersion; - - Result := SetCompilerOptions(@opt, @CompilerMessageW); - - if not Result then - begin - TProjectLogConsole.Instance.Log(plsError, FInFile, 'Could not set compiler options', 0, 0); - end; -end; - function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; // I4706 -var - FIsJS, FIsKMX: Boolean; - kp: TKeyboardParser; - FTargets: TKeymanTargets; begin - if ExtractFileExt(FOutFile) = '.*' then - begin - // Load the input .kmn and determine if it targets .js and .kmx - kp := TKeyboardParser.Create; - try - kp.LoadFromFile(FInFile); - - // Compile targets - copied from kmnProjectFile - FTargets := StringToKeymanTargets(kp.GetSystemStoreValue(ssTargets)); - if ktAny in FTargets then FTargets := AllKeymanTargets; - if FTargets = [] then FTargets := [ktWindows]; - - FIsJS := FTargets * KMWKeymanTargets <> []; - FIsKMX := FTargets * KMXKeymanTargets <> []; - finally - kp.Free; - end; - end - else - begin - FIsJS := SameText(ExtractFileExt(FOutFile), '.js'); - FIsKMX := not FIsJS; - end; - - Result := True; - - if FIsJS then - begin - if FOutFile = '' then FOutFile := FInFile; - FOutFile := ChangeFileExt(FOutFile, '.js'); - - with TCompileKeymanWeb.Create do - try - Result := Result and Compile(nil, FInFile, FOutFile, FDebug, @CompilerMessageW); // I3681 // I4865 // I4866 - finally - Free; - end; - - if TProjectLogConsole.Instance.HasWarning and FWarnAsError then Result := False; // I4706 - if Result - then TProjectLogConsole.Instance.Log(plsSuccess, FInFile, 'Keyboard '+FInFile+' compiled, output saved as '+FOutFile+'.', 0, 0) - else TProjectLogConsole.Instance.Log(plsFailure, FInFile, 'Keyboard '+FInFile+' could not be compiled.', 0, 0); - end; - - if Result and FIsKMX then - begin - if FOutFile = '' then FOutFile := FInFile; - FOutFile := ChangeFileExt(FOutFile, '.kmx'); - Result := Result and (CompileKeyboardFile(PChar(FInFile), PChar(FOutFile), FDebug, FWarnAsError, True, @CompilerMessage) <> 0); // I4865 // I4866 - Result := Result and CompileVisualKeyboardFromKMX(FInFile, FOutFile); - - if TProjectLogConsole.Instance.HasWarning and FWarnAsError then Result := False; // I4706 - if Result - then TProjectLogConsole.Instance.Log(plsSuccess, FInFile, 'Keyboard '+FInFile+' compiled, output saved as '+FOutFile+'.', 0, 0) - else TProjectLogConsole.Instance.Log(plsFailure, FInFile, 'Keyboard '+FInFile+' could not be compiled.', 0, 0); - end; + Result := False; end; procedure FixupPathSlashes(var path: string); diff --git a/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas b/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas index fae07d5eed8..8fd0c993f79 100644 --- a/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas +++ b/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas @@ -66,14 +66,6 @@ procedure TCompilePackageVersioningTest.Setup; FRoot := ExtractFileDir(ExtractFileDir(ExtractFileDir(ExtractFileDir(ParamStr(0))))); - // - // Force load development version of kmcmpdll - // Assumes it has already been built, of course... - // - FUnitTestKMCmpDllPath := FRoot + '\..\..\..\..\bin\'; - Assert.IsTrue(FileExists(FUnitTestKMCmpDllPath + 'kmcmpdll.dll'), - 'kmcmpdll.dll does not exist at ' + FUnitTestKMCmpDllPath + 'kmcmpdll.dll'); - p := TProjectConsole.Create(ptUnknown, FRoot+'\test-1.0\test-1.0.kpj', False); try for i := 0 to p.Files.Count - 1 do diff --git a/developer/src/test/auto/kmcomp-x64-structures/Makefile b/developer/src/test/auto/kmcomp-x64-structures/Makefile index c1523c9538f..2514106f442 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/Makefile +++ b/developer/src/test/auto/kmcomp-x64-structures/Makefile @@ -1,5 +1,5 @@ # -# Test that struct sizes in kmcomp and kmcmpdll match +# Test that struct sizes in kmcomp and kmcmplib match # for each of x86 and x64 # diff --git a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.cpp b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.cpp index 630c88d80e4..2241323f60c 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.cpp +++ b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.cpp @@ -2,7 +2,7 @@ // #include "pch.h" -#include "../../../../kmcmpdll/compfile.h" +#include "../../../../kmcmplib/src/compfile.h" #include int main() diff --git a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj index 92715db8184..a54b7f38c60 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj +++ b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj @@ -159,7 +159,7 @@ - + diff --git a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters index 03e55d50fd6..7a0d5716b37 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters +++ b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters @@ -18,7 +18,7 @@ Header Files - + Header Files diff --git a/developer/src/tike/child/UfrmEditor.pas b/developer/src/tike/child/UfrmEditor.pas index 2610a2df9e7..9cf0ca5277d 100644 --- a/developer/src/tike/child/UfrmEditor.pas +++ b/developer/src/tike/child/UfrmEditor.pas @@ -118,7 +118,6 @@ implementation CharacterDragObject, CharMapDropTool, ClipBrd, - CompileKeymanWeb, dmActionsMain, dmActionsTextEditor, keymanstrings, diff --git a/developer/src/tike/child/UfrmKeymanWizard.pas b/developer/src/tike/child/UfrmKeymanWizard.pas index 9b68894b229..1e244164c45 100644 --- a/developer/src/tike/child/UfrmKeymanWizard.pas +++ b/developer/src/tike/child/UfrmKeymanWizard.pas @@ -572,7 +572,6 @@ implementation CharMapDropTool, Clipbrd, compile, - CompileKeymanWeb, dmActionsMain, KeymanDeveloperOptions, KeymanVersion, @@ -582,6 +581,7 @@ implementation Keyman.Developer.System.Project.ProjectLog, Keyman.Developer.System.Project.kmnProjectFileAction, Keyman.Developer.System.ServerAPI, + Keyman.Developer.System.KmcWrapper, Keyman.Developer.UI.Project.ProjectFileUI, Keyman.Developer.UI.UfrmMessageDlgWithSave, ErrorControlledRegistry, @@ -2950,6 +2950,7 @@ procedure TfrmKeymanWizard.OSKImportKMX(Sender: TObject; var KMXFileName: TTempF kbdparser: TKeyboardParser; FEncoding: TEncoding; FIncludeCodes: string; + w: TKmcWrapper; begin KMXFileName := TTempFileManager.Get('.kmx'); // I4181 KMNFileName := TTempFileManager.Get('.kmn'); // I4181 @@ -2997,11 +2998,16 @@ procedure TfrmKeymanWizard.OSKImportKMX(Sender: TObject; var KMXFileName: TTempF TProject.CompilerMessageFile := ProjectFile; frmMessages.Clear; - if CompileKeyboardFile(PChar(KMNFileName.Name), PChar(KMXFileName2), False, False, False, ProjectCompilerMessage) <= 0 then // I4181 // I4865 // I4866 - begin - frmMessages.DoShowForm; - ShowMessage('There were errors compiling the keyboard to convert to the On Screen Keyboard.'); - FreeAndNil(KMXFileName); // I4181 + w := TKmcWrapper.Create; + try + if not w.Compile(ProjectFile, KMNFileName.Name, KMXFileName2, False) then + begin + frmMessages.DoShowForm; + ShowMessage('There were errors compiling the keyboard to convert to the On Screen Keyboard.'); + FreeAndNil(KMXFileName); // I4181 + end; + finally + w.Free; end; FreeAndNil(KMNFileName); // I4181 TProject.CompilerMessageFile := nil; diff --git a/developer/src/tike/compile/CompileKeymanWeb.pas b/developer/src/tike/compile/CompileKeymanWeb.pas deleted file mode 100644 index 13d5219f706..00000000000 --- a/developer/src/tike/compile/CompileKeymanWeb.pas +++ /dev/null @@ -1,2844 +0,0 @@ -(* - Name: CompileKeymanWeb - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 26 Apr 2006 - - Modified Date: 24 Aug 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 26 Apr 2006 - mcdurdin - Add support for ContextEx and Context, and lots of polish - 21 Jun 2006 - mcdurdin - Add embedded javascript output (for IMX support) - 21 Jun 2006 - mcdurdin - Add semicolons as necessary to javascript - 21 Jun 2006 - mcdurdin - Add right-to-left support - 06 Oct 2006 - mcdurdin - Merge forward v6.2 keymanweb compiler code (v7.0 code was old) - 04 Dec 2006 - mcdurdin - Add ContextEx, LHS ContextEx - 19 Mar 2007 - mcdurdin - I714 - Compiler not using correct system stores - 19 Mar 2007 - mcdurdin - I716 - Update to KMW build 83 - 19 Mar 2007 - mcdurdin - I732 - Added partial support for mnemonic layouts (compiler code only, KMW code under development) - 30 Apr 2007 - mcdurdin - I783 - Fix index() statement for KMW compiler - 05 Jun 2007 - mcdurdin - I853 - Avoid crash when compiling ANSI keyboards - 27 Mar 2008 - mcdurdin - I1370 - Rename keyboards to avoid invalid characters during compile - 27 Mar 2008 - mcdurdin - Refactor incxstr into kmxfileutils - 14 Jun 2008 - mcdurdin - I1474 - Strip UTF8 prolog from help text - 28 Jul 2008 - mcdurdin - Compile target support - 28 Aug 2008 - mcdurdin - I1585 - spacebar not being accepted in positional KMW keyboards - 28 Aug 2008 - mcdurdin - I783 - Problems with index/any in KMW keyboards - 16 Jan 2009 - mcdurdin - I1592 - Fix crash compiling KMW keyboard when file is missing - 25 May 2009 - mcdurdin - I1971 - Don't succeed when error compiling - 25 May 2009 - mcdurdin - I1520 - Don't allow specific unsupported functions in stores for 'any' - 25 May 2009 - mcdurdin - I1959 - Fix bugs with multiple groups - 25 May 2009 - mcdurdin - I1964 - Fix crash when compiling keyboard with empty group - 22 Mar 2010 - mcdurdin - I2224 - Fix nomatch not working in KMW Compiler - 22 Mar 2010 - mcdurdin - I2242 - Fixup CR, LF not compiling in correctly in KMW - 22 Mar 2010 - mcdurdin - I2243 - Add support for nul in context of rules - 19 Apr 2010 - mcdurdin - I2308 - KeymanWeb call functions could be out of sync due to sorted list in compiler - 26 Jul 2010 - mcdurdin - I2468 - Eliminate KeymanWeb Pack - 18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support - 18 May 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes - 08 Jun 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes - 08 Jun 2012 - mcdurdin - I3337 - V9.0 - Review of input/output for Unicode - 17 Aug 2012 - mcdurdin - I3429 - V9.0 - Add support for if, set, reset, save to KeymanWeb compiler - 17 Aug 2012 - mcdurdin - I3430 - V9.0 - Add support for if(&platform) and if(&baselayout) to compilers - 17 Aug 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes - 27 Aug 2012 - mcdurdin - I3437 - V9.0 - Add support for set(&layer) and layer() - 27 Aug 2012 - mcdurdin - I3438 - V9.0 - Add support for custom virtual keys - 19 Oct 2012 - mcdurdin - I3474 - V9.0 - KeymanWeb Compiler should no longer emit UTF-8 prologue - 24 Oct 2012 - mcdurdin - I3482 - V9.0 - If compiler dll is missing, TIKE crashes after showing error message - 24 Oct 2012 - mcdurdin - I3483 - V9.0 - Add support for compiling in the layout file - 13 Dec 2012 - mcdurdin - I3659 - V9.0 - KSAVE has logical design issue with how stores are reloaded from cookie - 13 Dec 2012 - mcdurdin - I3681 - V9.0 - KeymanWeb compiler should output formatted js when debug=1 - 13 Dec 2012 - mcdurdin - I3317 - Add KS to KeymanWeb keyboard compiler for keyboards with SMP chars - 13 Dec 2012 - mcdurdin - I3683 - V9.0 - If embedjs file is missing the kmw compiler will crash - 13 Dec 2012 - mcdurdin - I3684 - V9.0 - KeymanWeb compiler needs to emit only non-system stores and a couple of system stores - 01 Jan 2013 - mcdurdin - I3690 - V9.0 - KSAVE wrongly includes initial _ in cookie name - 11 Aug 2013 - mcdurdin - I3886 - V9.0 - KMW 2.0 compiler needs to publish DisplayUnderling for KVK as Keyboard.KDU - 11 Aug 2013 - mcdurdin - I3643 - V9.0 - KMW compiler does not validate format of &layoutfile - 15 Oct 2013 - mcdurdin - I3910 - KeymanWeb compiler calculates indexes incorrectly when deadkeys are on LHS - 07 Nov 2013 - mcdurdin - I3946 - V9.0 - KDU flag needs to be added to keyboard object, not VK object in kmw compiler - 07 Nov 2013 - mcdurdin - I3947 - V9.0 - If KVK file is missing then KMW compiler will crash - 08 Nov 2013 - mcdurdin - I3956 - V9.0 - I3946 KDU insertion fails due to syntax error in compiled file - 29 Nov 2013 - mcdurdin - I3980 - V9.0 - KeymanWeb compiler does not support context() on LHS - 29 Nov 2013 - mcdurdin - I3981 - V9.0 - Keyman Engine for Web Compiler does not support notany() - 21 Feb 2014 - mcdurdin - I4060 - V9.0 - KeymanWeb compiler should validate the layout file - 21 Feb 2014 - mcdurdin - I4061 - V9.0 - KeymanWeb compiler needs defined codes for some errors - 06 Mar 2014 - mcdurdin - I4118 - V9.0 - KMW compiler should warn when extended shift flags are used - 06 Mar 2014 - mcdurdin - I4119 - V9.0 - KMW compiler should only warn unassociated keys that are not special keys - 19 Mar 2014 - mcdurdin - I4139 - V9.0 - Compress layout file when compiling to KMW - 19 Mar 2014 - mcdurdin - I4140 - V9.0 - Add keyboard version information to keyboards - 19 Mar 2014 - mcdurdin - I4141 - V9.0 - Warn when unusable key ids are used - 19 Mar 2014 - mcdurdin - I4142 - V9.0 - Validate key ids are in an acceptable format - 21 Mar 2014 - mcdurdin - I4155 - V9.0 - Keyboard version is not compiled into kmw js - 21 Mar 2014 - mcdurdin - I4154 - V9.0 - keyboardversion is not read by KeymanWeb compiler and applied to filename. - 01 May 2014 - mcdurdin - I4198 - V9.0 - Block U_0000-U_001F and U_0080-U_009F in layout files - 10 Jun 2014 - mcdurdin - I4259 - V9.0 - Generate a .json file when compiling keyboard for web - 12 Jun 2014 - mcdurdin - I4263 - V9.0 - Compile to web and test fails when keyboard version is not 1.0 - 12 Aug 2014 - mcdurdin - I4368 - V9.0 - Add custom stylesheet to kmw compile - 12 Aug 2014 - mcdurdin - I4373 - V9.0 - Add line number comments to js when compiling with debug - 28 Aug 2014 - mcdurdin - I4384 - V9.0 - KMW compiler adds line number comment in non-debug mode in some cases - 13 Oct 2014 - mcdurdin - I4447 - V9.0 - The dismiss keyboard and tab buttons should not be required by Keyman Developer now - 04 Nov 2014 - mcdurdin - I4505 - V9.0 - Add JSON metadata editor to keyboard wizard - 07 Mar 2015 - mcdurdin - I4611 - V9.0 - context statement in output could fail in some situations in KeymanWeb compiler - 04 May 2015 - mcdurdin - I4688 - V9.0 - Add build path to project settings - 27 May 2015 - mcdurdin - I4724 - Compiler generates warnings for JSON files if output path is source path - 24 Aug 2015 - mcdurdin - I4872 - OSK font and Touch Layout font should be the same in Developer - 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - - 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - 28 Feb 2018 - jahorton - GH 281 - Changed the compilation targets for KMW 10 to better support deadkeys. - -*) -unit CompileKeymanWeb; // I3306 // I3310 - -interface - -uses - Winapi.Windows, - System.Character, - System.Classes, - System.Generics.Collections, - System.UITypes, - - compile, - kmxfile, - kmxfileconsts, - Keyman.Developer.System.Project.ProjectFile; - -type - TSentinelRecordAny = record - StoreIndex: Integer; - Store: PFILE_STORE; - end; - - TSentinelRecordIndex = record - StoreIndex: Integer; - Store: PFILE_STORE; - Index: Integer; - end; - - TSentinelRecordContextEx = record - Index: Integer; - end; - - TSentinelRecordDeadkey = record - DeadKey: Integer; - end; - - TSentinelRecordUse = record - GroupIndex: Integer; - Group: PFILE_GROUP; - end; - - TSentinelRecordCall = record - StoreIndex: Integer; - Store: PFILE_STORE; - end; - - TSentinelRecordIfOpt = record // I3429 - StoreIndex1: Integer; - Store1: PFILE_STORE; - StoreIndex2: Integer; - Store2: PFILE_STORE; - IsNot: Integer; - end; - - TSentinelRecordSetOpt = record // I3429 - StoreIndex1: Integer; - Store1: PFILE_STORE; - StoreIndex2: Integer; - Store2: PFILE_STORE; - end; - - TSentinelRecordResetOpt = record // I3429 - StoreIndex: Integer; - Store: PFILE_STORE; - end; - - TSentinelRecordSaveOpt = record // I3429 - StoreIndex: Integer; - Store: PFILE_STORE; - end; - - TSentinelRecordIfSystemStore = record // I3430 - dwSystemID: DWORD; - SystemStore: PFILE_STORE; - StoreIndex: Integer; - Store: PFILE_STORE; - IsNot: Integer; - end; - - TSentinelRecordSetSystemStore = record // I3437 - dwSystemID: DWORD; - SystemStore: PFILE_STORE; - StoreIndex: Integer; - Store: PFILE_STORE; - end; - - TSentinelRecord = record - IsSentinel: Boolean; - Code: Integer; - Any: TSentinelRecordAny; - Index: TSentinelRecordIndex; - Deadkey: TSentinelRecordDeadkey; - Use: TSentinelRecordUse; - Call: TSentinelRecordCall; - ContextEx: TSentinelRecordContextEx; - IfOpt: TSentinelRecordIfOpt; // I3429 - IfSystemStore: TSentinelRecordIfSystemStore; // I3430 - SetOpt: TSentinelRecordSetOpt; // I3429 - SetSystemStore: TSentinelRecordSetSystemStore; // I3437 - ResetOpt: TSentinelRecordResetOpt; // I3429 - SaveOpt: TSentinelRecordSaveOpt; // I3429 - //Use: Integer; - ChrVal: DWord; - end; - - TCompileKeymanWeb = class - private - FError: Boolean; // I1971 - FCallback: TCompilerCallbackW; - FCallFunctions: TStringList; - FOutFile, FInFile: string; - FKeyboardVersion: string; - fk: FILE_KEYBOARD; - nl: string; // I3681 - FDebug: Boolean; // I3681 - FTabStop: string; // I3681 - fMnemonic: Boolean; - FCompilerWarningsAsErrors: Boolean; - FTouchLayoutFont: string; - FFix183_LadderLength: Integer; - FCloseBrace: Boolean; // I4872 - FUnreachableKeys: TList; - - function JavaScript_String(ch: DWord): string; // I2242 - - function IsKeyboardVersion10OrLater: Boolean; - function IsKeyboardVersion14OrLater: Boolean; - - procedure ReportError(line: Integer; msgcode: LongWord; const text: string); // I1971 - function ExpandSentinel(pwsz: PWideChar): TSentinelRecord; - function CallFunctionName(s: WideString): WideString; - function JavaScript_Name(i: Integer; pwszName: PWideChar; KeepNameForPersistentStorage: Boolean = False): string; // I3659 - function JavaScript_Store(line: Integer; pwsz: PWideChar): string; - function JavaScript_Shift(fkp: PFILE_KEY; FMnemonic: Boolean): Integer; - function JavaScript_ShiftAsString(fkp: PFILE_KEY; FMnemonic: Boolean): string; // I4872 - function JavaScript_Key(fkp: PFILE_KEY; FMnemonic: Boolean): Integer; - function JavaScript_KeyAsString(fkp: PFILE_KEY; FMnemonic: Boolean): string; - function JavaScript_ContextLength(Context: PWideChar): Integer; - function JavaScript_OutputString(FTabstops: string; fkp: PFILE_KEY; pwszOutput: PWideChar; fgp: PFILE_GROUP): string; - function JavaScript_ContextMatch(fkp: PFILE_KEY; context: PWideChar): string; - function JavaScript_Rule(FTabStops, FElse: string; fgp: PFILE_GROUP; fkp: PFILE_KEY): string; - function JavaScript_Rules(fgp: PFILE_GROUP): string; - function JavaScript_CompositeContextValue(fkp: PFILE_KEY; pwsz: PWideChar): string; - function JavaScript_FullContextValue(fkp: PFILE_KEY; pwsz: PWideChar): string; - function RuleIsExcludedByPlatform(fkp: PFILE_KEY): Boolean; - function RequotedString(s: WideString; RequoteSingleQuotes: Boolean = False): string; - function VisualKeyboardFromFile( - const FVisualKeyboardFileName: string; var fDisplayUnderlying: Boolean): WideString; - function WriteCompiledKeyboard: string; - procedure CheckStoreForInvalidFunctions(key: PFILE_KEY; store: PFILE_STORE); // I1971 - function GetCodeName(code: Integer): string; // I3438 - function HasSupplementaryPlaneChars: Boolean; // I3317 - function ValidateLayoutFile(var sLayoutFile: string; const sVKDictionary: string): Boolean; // I4139 - function GetKeyboardModifierBitmask: string; - function FormatModifierAsBitflags(FBitMask: Cardinal): string; - function FormatKeyAsString(key: Integer): string; - function JavaScript_SetupDebug: string; - function JavaScript_SetupEpilog: string; - function JavaScript_SetupProlog: string; - function IsKeyboardVersion15OrLater: Boolean; - function WriteBeginStatement(const name: string; - groupIndex: Integer): string; - function FormatKeyForErrorMessage(fkp: PFILE_KEY; - FMnemonic: Boolean): string; - public - function Compile(AOwnerProject: TProject; const InFile: string; const OutFile: string; Debug: Boolean; Callback: TCompilerCallbackW): Boolean; // I3681 // I4140 // I4688 // I4866 - constructor Create; - destructor Destroy; override; - end; - -implementation - -uses - Vcl.Graphics, - System.JSON, - System.Math, - System.StrUtils, - System.SysUtils, - System.TypInfo, - - CompileErrorCodes, - JsonUtil, - KeymanDeveloperOptions, - Keyman.System.KeyboardUtils, - KeymanWebKeyCodes, - kmxfileutils, - TouchLayout, - Unicode, - utilstr, - VisualKeyboard, - VKeys; - -const - SValidIdentifierCharSet = ['A'..'Z','a'..'z','0'..'9','_']; - -var - GCallbackW: TCompilerCallbackW = nil; - -function WebCompilerMessageA(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; // I3310 // I4694 -begin - if Assigned(GCallbackW) - then Result := GCallbackW(line, msgcode, PWideChar(WideString(AnsiString(text)))) - else Result := 1; -end; - -function TCompileKeymanWeb.Compile(AOwnerProject: TProject; const InFile: string; const OutFile: string; Debug: Boolean; Callback: TCompilerCallbackW): Boolean; // I3681 // I4140 // I4688 // I4866 // I4865 -var - WarnDeprecatedCode: Boolean; - Data: string; -begin - FUnreachableKeys.Clear; - - FCallback := Callback; - FInFile := InFile; - FOutFile := OutFile; // I4140 // I4155 // I4154 - FDebug := Debug; // I3681 - FError := False; // I1971 - - if FileExists(OutFile) then - DeleteFile(OutFile); - - if FDebug then // I3681 - begin - nl := #13#10; - FTabStop := ' '; - end - else - begin - nl := ''; - FTabStop := ''; - end; - - if Assigned(AOwnerProject) then // I4865 // I4866 - begin - FCompilerWarningsAsErrors := AOwnerProject.Options.CompilerWarningsAsErrors; - WarnDeprecatedCode := AOwnerProject.Options.WarnDeprecatedCode; - end - else - begin - FCompilerWarningsAsErrors := False; - WarnDeprecatedCode := True; - end; - - GCallbackW := Callback; - FCallFunctions := TStringList.Create; - try - if CompileKeyboardFileToBuffer(PChar(InFile), @fk, - FCompilerWarningsAsErrors, WarnDeprecatedCode, - WebCompilerMessageA, CKF_KEYMANWEB) > 0 then // I3482 // I4866 // I4865 - // TODO: Free fk - begin - if Assigned(AOwnerProject) and - Assigned(AOwnerProject.CompilerMessageFile) and - AOwnerProject.CompilerMessageFile.HasCompileWarning and - FCompilerWarningsAsErrors then - FError := True; - - if not FError then - begin - Data := WriteCompiledKeyboard; - - if not FError then - with TStringStream.Create(Data, TEncoding.UTF8) do - try - SaveToFile(OutFile); - finally - Free; - end; - end; - - Result := not FError; // I1971 - end - else - begin - Result := False; - end; - finally - FreeAndNil(FCallFunctions); - GCallbackW := nil; - end; -end; - -constructor TCompileKeymanWeb.Create; -begin - FUnreachableKeys := TList.Create; - FillChar(fk, sizeof(fk), 0); - FFix183_LadderLength := FKeymanDeveloperOptions.Fix183_LadderLength; // How frequently to break ladders -end; - -destructor TCompileKeymanWeb.Destroy; -begin - // TODO: Free FK values - FUnreachableKeys.Free; - inherited; -end; - -function TCompileKeymanWeb.JavaScript_ContextLength(Context: PWideChar): Integer; -begin - Result := xstrlen_printing(Context); -end; - -// Used when targeting versions prior to 10.0, before the introduction of FullContextMatch/KFCM. -function TCompileKeymanWeb.JavaScript_CompositeContextValue(fkp: PFILE_KEY; pwsz: PWideChar): string; - // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - function IsRegExpSpecialChar(ch: WideChar): Boolean; - begin - Result := Pos('"\^$*+?{}.()|[]/', ch) > 0; - end; - -var - StartQuotes, Len, Cur: Integer; - InQuotes: Boolean; - rec: TSentinelRecord; -begin - Result := ''; - - InQuotes := False; - Len := JavaScript_ContextLength(pwsz); - StartQuotes := -1; - Cur := 0; - - while pwsz^ <> #0 do - begin - rec := ExpandSentinel(pwsz); - if rec.IsSentinel then - begin - if InQuotes then - begin - Result := Result + Format('",%d)', [Cur - StartQuotes]); - InQuotes := False; - end; - if Result <> '' then Result := Result + '&&'; - - case rec.Code of - CODE_ANY: - begin - CheckStoreForInvalidFunctions(fkp, rec.Any.Store); // I1520 - Result := Result + Format('k.KA(%d,k.KC(%d,1,t),this.s%s)', [Cur, Len - Cur, JavaScript_Name(rec.Any.StoreIndex, rec.Any.Store.szName)]); - end; - CODE_DEADKEY: - begin - Result := Result + Format('k.KDM(%d,t,%d)', [Len-Cur, rec.Deadkey.Deadkey]); - Dec(Cur); // don't increment on deadkeys -- correlates with AdjustIndex function // I3910 - end; - CODE_NUL: // I2243 - begin - Result := Result + Format('k.KN(%d,t)', [Len-Cur]); - Dec(Cur); // don't increment on nul -- correlates with AdjustIndex function // I3910 - end; - CODE_IFOPT: // I3429 - begin - Result := Result + Format('this.s%s%sthis.s%s', - [JavaScript_Name(rec.IfOpt.StoreIndex1, rec.IfOpt.Store1.szName), - IfThen(rec.IfOpt.IsNot = 0, '!==', '==='), - JavaScript_Name(rec.IfOpt.StoreIndex2,rec.IfOpt.Store2.szName)]); // I3429 // I3659 // I3681 - Dec(Cur); // don't increment on ifopt -- correlates with AdjustIndex function // I3910 - end; - CODE_IFSYSTEMSTORE: // I3430 - begin - Result := Result + Format('%sk.KIFS(%d,this.s%s,t)', - [IfThen(rec.IfSystemStore.IsNot = 0, '!', ''), - rec.IfSystemStore.dwSystemID, - JavaScript_Name(rec.IfSystemStore.StoreIndex,rec.IfSystemStore.Store.szName)]); // I3430 // I3659 // I3681 - Dec(Cur); // don't increment on ifsystemstore -- correlates with AdjustIndex function // I3910 - end; - CODE_CONTEXTEX: // I3980 - begin - Result := Result + Format('k.KCCM(%d,%d,t)', [Len-Cur,Len-rec.ContextEx.Index+1]); - end; - CODE_NOTANY: // I3981 - begin - CheckStoreForInvalidFunctions(fkp, rec.Any.Store); // I1520 - Result := Result + Format('k.KC(%d,1,t)!=""&&!k.KA(%d,k.KC(%d,1,t),this.s%s)', [Len - Cur, Cur, Len - Cur, JavaScript_Name(rec.Any.StoreIndex, rec.Any.Store.szName)]); - end; - else - - begin - ReportError(fkp.Line, CERR_NotSupportedInKeymanWebContext, Format('Statement %s is not currently supported in context', [GetCodeName(rec.Code)])); // I1971 // I4061 - //CODE_NUL: ; // todo: check if context is longer than that... - Result := Result + '/*.*/ 0 '; - end; - end; - - -(* - switch(*(Context+1)) - { - case CODE_NUL: /* todo: check if context is longer than that... */ - break; - case CODE_INDEX: - /*todo: check index - s = &lpActiveKeyboard->Keyboard->dpStoreArray[(*(p+2))-1]; - *indexp = n = IndexStack[(*(p+3))-1]; - - for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); - if(n != 0) return FALSE; - if(GetSuppChar(temp) != GetSuppChar(q)) return FALSE; - - */ - break; - case CODE_CONTEXT: - - /* if(GetSuppChar(q) != GetSuppChar(qbuf)) return FALSE; */ - break; - case CODE_CONTEXTEX: - - // only the nth character - /* for(n = *(p+2) - 1, temp = qbuf; temp < q && n > 0; n--, temp = incxstr(temp)); - if(n == 0) - if(GetSuppChar(temp) != GetSuppChar(q)) return FALSE; */ - break; - default: - return ""; - } -*) - end - else - begin - if not InQuotes then - begin - if Result <> '' then Result := Result + '&&'; - Result := Result + Format('k.KCM(%d,t,"', [Len - Cur]); - StartQuotes := Cur; - InQuotes := True; - end; - if rec.ChrVal in [Ord('"'), Ord('\')] then Result := Result + '\'; - Result := Result + Javascript_String(rec.ChrVal); // I2242 - end; - - Inc(Cur); - pwsz := incxstr(pwsz); - end; - - if InQuotes then - Result := Result + Format('",%d)', [Cur - StartQuotes]); -end; - -// Used when targeting versions >= 10.0, after the introduction of FullContextMatch/KFCM. -function TCompileKeymanWeb.JavaScript_FullContextValue(fkp: PFILE_KEY; pwsz: PWideChar): string; - // Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - function IsRegExpSpecialChar(ch: WideChar): Boolean; - begin - Result := Pos('"\^$*+?{}.()|[]/', ch) > 0; - end; - -var - Len: Integer; - rec: TSentinelRecord; - FullContext, Suffix: string; -begin - Result := ''; - FullContext := ''; - Suffix := ''; - Len := xstrlen(pwsz); - - while pwsz^ <> #0 do - begin - if FullContext <> '' then - FullContext := FullContext + ','; - - rec := ExpandSentinel(pwsz); - if rec.IsSentinel then - begin - case rec.Code of - CODE_ANY: - begin - CheckStoreForInvalidFunctions(fkp, rec.Any.Store); // I1520 - FullContext := FullContext + Format('{t:''a'',a:this.s%s}', [JavaScript_Name(rec.Any.StoreIndex, rec.Any.Store.szName)]); - end; - CODE_DEADKEY: - begin - FullContext := FullContext + Format('{t:''d'',d:%d}', [rec.Deadkey.Deadkey]); - end; - CODE_NUL: // I2243 - begin - FullContext := FullContext + '{t:''n''}'; - end; - CODE_IFOPT: // I3429 - begin - Dec(Len); - if Suffix <> '' then Suffix := Suffix + '&&'; - if FullContext = ',' then FullContext := ''; - Suffix := Suffix + Format('this.s%s%sthis.s%s', - [JavaScript_Name(rec.IfOpt.StoreIndex1, rec.IfOpt.Store1.szName), - IfThen(rec.IfOpt.IsNot = 0, '!==', '==='), - JavaScript_Name(rec.IfOpt.StoreIndex2,rec.IfOpt.Store2.szName)]); // I3429 // I3659 // I3681 - end; - CODE_IFSYSTEMSTORE: // I3430 - begin - Dec(Len); - if Suffix <> '' then Suffix := Suffix + '&&'; - if FullContext = ',' then FullContext := ''; - Suffix := Suffix + Format('%sk.KIFS(%d,this.s%s,t)', - [IfThen(rec.IfSystemStore.IsNot = 0, '!', ''), - rec.IfSystemStore.dwSystemID, - JavaScript_Name(rec.IfSystemStore.StoreIndex,rec.IfSystemStore.Store.szName)]); // I3430 // I3659 // I3681 - end; - CODE_NOTANY: // I3981 - begin - CheckStoreForInvalidFunctions(fkp, rec.Any.Store); // I1520 - FullContext := FullContext + Format('{t:''a'',a:this.s%s,n:1}', [JavaScript_Name(rec.Any.StoreIndex, rec.Any.Store.szName)]); - end; - CODE_CONTEXTEX: - begin - FullContext := FullContext + Format('{t:''c'',c:%d}', [rec.ContextEx.Index]); // I4611 - end; - CODE_INDEX: - begin - FullContext := FullContext + Format('{t:''i'',i:this.s%s,o:%d}', - [JavaScript_Name(rec.Index.StoreIndex, rec.Index.Store.szName), rec.Index.Index]); // I4611 - end; - else - - begin - ReportError(fkp.Line, CERR_NotSupportedInKeymanWebContext, Format('Statement %s is not currently supported in context', [GetCodeName(rec.Code)])); // I1971 // I4061 - //CODE_NUL: ; // todo: check if context is longer than that... - Result := Result + '/*.*/ 0 '; - end; - end; - end - else - begin // Simple context character. - FullContext := FullContext + ''''; - if rec.ChrVal in [Ord('"'), Ord('\'), Ord('''')] then - FullContext := FullContext + '\'; - FullContext := FullContext + Javascript_String(rec.ChrVal) + ''''; // I2242 - end; - - pwsz := incxstr(pwsz); - end; - - if FullContext <> '' then - Result := Format('k.KFCM(%d,t,[%s])', [Len, FullContext]); - - if (Result <> '') and (Suffix <> '') - then Result := Result + '&&' + Suffix - else if Suffix <> '' then - Result := Suffix; - -end; - -function TCompileKeymanWeb.JavaScript_ContextMatch(fkp: PFILE_KEY; context: PWideChar): string; -begin - if IsKeyboardVersion10OrLater - then Result := JavaScript_FullContextValue(fkp, context) - else Result := JavaScript_CompositeContextValue(fkp, context); -end; - -function TCompileKeymanWeb.JavaScript_Rule(FTabStops, FElse: string; fgp: PFILE_GROUP; fkp: PFILE_KEY): string; -var - predicate, linecomment: string; - FIndent: string; -begin - Result := ''; - - if (fkp.Line > 0) and FDebug // I4384 - then linecomment := Format(' // Line %d', [fkp.Line]) // I4373 - else linecomment := ''; - - if xstrlen(fkp.dpContext) > 0 - then predicate := JavaScript_ContextMatch(fkp, fkp.dpContext) - else predicate := '1'; // Always pass - - FIndent := FTabStops+FTabStop; - FCloseBrace := True; - Result := Result + Format('%s%sif(%s){%s', [ - FTabStops, - FElse, - predicate, - nl - ]); - - if(fgp.fUsingKeys) // I1959 - then Result := Result + Format('%sr=m=1;%s%s', [FIndent,linecomment,JavaScript_OutputString(FIndent, fkp, fkp.dpOutput, fgp)]) // I1959 // I3681 - else Result := Result + Format('%sm=1;%s%s', [FIndent,linecomment,JavaScript_OutputString(FIndent, fkp, fkp.dpOutput, fgp)]); // I1959 // I3681 - if not FCloseBrace - then Result := Result + nl - else Result := Result + Format('%s%s}%s', [nl, FTabStops, nl]); // I3681 -end; - -function TCompileKeymanWeb.JavaScript_Rules(fgp: PFILE_GROUP): string; - function IsEqualKey(k1, k2: PFILE_KEY): Boolean; - begin - Result := - (JavaScript_Key(k1, FMnemonic) = JavaScript_Key(k2, FMnemonic)) and - (JavaScript_Shift(k1, FMnemonic) = JavaScript_Shift(k2, FMnemonic)); - end; - -var - j, j2: Integer; - HasRules: Boolean; - fkp2, fkp: PFILE_KEY; - processed_rule: array of Boolean; - LocalHasRules: Boolean; - LocalCounter: Integer; - Counter: Integer; -begin - Result := ''; - fkp := fgp.dpKeyArray; - HasRules := False; - - SetLength(processed_rule, fgp.cxKeyArray); - for j := 0 to Integer(fgp.cxKeyArray) - 1 do - processed_rule[j] := False; - - j := 0; - Counter := 0; - while j < Integer(fgp.cxKeyArray) do // I1964 - begin - if not processed_rule[j] and not RuleIsExcludedByPlatform(fkp) then - begin - // Break down by key code - // We know the rules are sorted by context length and then key code. - // First pass, break the grouping down by key code. - - if fgp.fUsingKeys then - begin - Result := Result + Format('%s%sif(k.KKM(e,%s,%s)) {%s', - [ - FTabStop+FTabStop, - IfThen(HasRules, 'else ', ''), - JavaScript_ShiftAsString(fkp, fMnemonic), - JavaScript_KeyAsString(fkp, fMnemonic), - nl - ]); - - HasRules := True; - Inc(Counter); - - LocalHasRules := False; - fkp2 := fkp; - j2 := j; - LocalCounter := 0; - while (j < Integer(fgp.cxKeyArray)) do - begin - if not processed_rule[j] and not RuleIsExcludedByPlatform(fkp) and IsEqualKey(fkp, fkp2) then - begin - processed_rule[j] := True; - Result := Result + JavaScript_Rule(FTabStop + FTabStop + FTabStop, IfThen(LocalHasRules, 'else ', ''), fgp, fkp); - Inc(LocalCounter); - - if (FFix183_LadderLength <> 0) and ((LocalCounter mod FFix183_LadderLength) = 0) then - begin - // Break if/else ladders - Result := Result + Format('%sif(m) {}%s', [FTabStop + FTabStop + FTabStop, nl]); - end; - LocalHasRules := True; - end; - - Inc(fkp); - Inc(j); - end; - - Result := Result + FTabStop + FTabStop + '}' + nl; - fkp := fkp2; - j := j2 + 1; - Inc(fkp); - // Inc(j); - end - else - begin - // TODO: context character level switches instead of full context comparisons - Result := Result + JavaScript_Rule(FTabStop + FTabStop + FTabStop, IfThen(HasRules, 'else ', ''), fgp, fkp); - HasRules := True; - Inc(Counter); - Inc(fkp); - Inc(j); - end; - - if (FFix183_LadderLength <> 0) and ((Counter mod FFix183_LadderLength) = 0) then - begin - // Break if/else ladders - // We need to only match if no previous line is matched (i.e. m is false) - Result := Result + Format('%sif(m) {}%s', [FTabStop + FTabStop + FTabStop, nl]); - end; - end - else - begin - Inc(fkp); - Inc(j); - end; - end; -end; - -const // I1585 - add space to conversion - USEnglishUnshift: WideString = ' `' + '1234567890' + '-' + '=' + 'qwertyuiop' + '[' + ']' + '\' + 'asdfghjkl' + ';' + '''' + 'zxcvbnm' + ',' + '.' + '/'; - USEnglishShift: WideString = #$FF'~' + '!@#$%^&*()' + '_' + '+' + 'QWERTYUIOP' + '{' + '}' + '|' + 'ASDFGHJKL' + ':' + '"' + 'ZXCVBNM' + '<' + '>' + '?'; - USEnglishValues: WideString = #$20#$c0 + '1234567890' + #$bd + #$bb + 'QWERTYUIOP' + #$db + #$dd + #$dc + 'ASDFGHJKL' + #$ba + #$de + 'ZXCVBNM' + #$bc + #$be + #$bf; - UnreachableKeyCodes: array[0..19] of Integer = // I4141 - ($00, // &H0 - VK_LBUTTON, // &H1 - VK_RBUTTON, // &H2 - VK_CANCEL, // &H3 - VK_MBUTTON, // &H4 - $05, // &H5 - $06, // &H6 - $07, // &H7 - $0A, // &HA - $0B, // &HB - $0E, // &HE - $0F, // &HF - VK_SHIFT, // &H10 - VK_CONTROL, // &H11 - VK_MENU, // &H12 - VK_PAUSE, // &H13 - VK_CAPITAL, // &H14 - VK_ESCAPE, // &H1B - - VK_NUMLOCK, // &H90 - VK_SCROLL); // &H91 - -function TCompileKeymanWeb.FormatKeyForErrorMessage(fkp: PFILE_KEY; FMnemonic: Boolean): string; - function FormatShift(ShiftFlags: DWord): string; - const - mask: array[0..13] of string = ( - 'LCTRL', // 0X0001 - 'RCTRL', // 0X0002 - 'LALT', // 0X0004 - 'RALT', // 0X0008 - - 'SHIFT', // 0X0010 - 'CTRL', // 0X0020 - 'ALT', // 0X0040 - - '???', // Reserved - - 'CAPS', // 0X0100 - 'NCAPS', // 0X0200 - - 'NUMLOCK', // 0X0400 - 'NNUMLOCK', // 0X0800 - - 'SCROLLLOCK', // 0X1000 - 'NSCROLLLOCK' // 0X2000 - ); - var - i: Integer; - begin - Result := ''; - for i := 0 to High(mask) do - begin - if ShiftFlags and (1 shl i) <> 0 then - begin - Result := Result + mask[i] + ' '; - end; - end; - end; -begin - if not FMnemonic then - begin - if (fkp.ShiftFlags and KMX_ISVIRTUALKEY) = KMX_ISVIRTUALKEY then - begin - if Ord(fkp.Key) < 256 - then Result := Format('[%s%s]', [FormatShift(fkp.ShiftFlags), VKeyNames[Ord(fkp.Key)]]) - else Result := Format('[%sK_%x]', [FormatShift(fkp.ShiftFlags), Ord(fkp.Key)]); - end - else - begin - Result := Format('''%s''', [fkp.Key]); - end; - end - else - begin - if (fkp.ShiftFlags and KMX_VIRTUALCHARKEY) = KMX_VIRTUALCHARKEY - then Result := Format('[%s''%s'']', [FormatShift(fkp.ShiftFlags), fkp.Key]) - else Result := Format('''%s''', [fkp.Key]); - end; -end; - -function TCompileKeymanWeb.JavaScript_Key(fkp: PFILE_KEY; FMnemonic: Boolean): Integer; -var - n: Integer; - i: Integer; -begin - if not FMnemonic then - begin - if (fkp.ShiftFlags and KMX_ISVIRTUALKEY) = KMX_ISVIRTUALKEY then - begin - Result := Ord(fkp.Key); - end - else - begin - // Convert the character to a virtual key - n := Pos(fkp.Key, USEnglishShift); - if n = 0 then n := Pos(fkp.Key, USEnglishUnshift); - if n = 0 - then Result := 0 - else Result := Ord(USEnglishValues[n]); - end; - end - else - begin - Result := Ord(fkp.Key); - end; - - // Check that key is not unreachable (e.g. K_SHIFT, touch-specific special keys 50,000+) - - for i := 0 to High(UnreachableKeyCodes) do // I4141 - if Result = UnreachableKeyCodes[i] then - Result := 0; - - if (Result = 0) or (Result >= Ord(Low(TKeymanWebTouchStandardKey))) then // I4141 - begin - if not FUnreachableKeys.Contains(fkp) then - begin - ReportError(fkp.Line, CHINT_UnreachableKeyCode, - 'The rule will never be matched for key '+ - FormatKeyForErrorMessage(fkp,FMnemonic)+' because its key code is never fired.'); - FUnreachableKeys.Add(fkp); - end; - end; -end; - -/// -/// Returns a Javascript representation of a key value, either as a constant (debug mode) -/// or as an integer. -/// -/// @param fkp Pointer to key record -/// @param FMnemonic True if the keyboard is a mnemonic layout -/// -/// @return string representation of the key value, e.g. 'keyCodes.K_A /* 0x41 */' or '65' -/// -function TCompileKeymanWeb.JavaScript_KeyAsString(fkp: PFILE_KEY; FMnemonic: Boolean): string; -begin - if FDebug - then Result := ' '+FormatKeyAsString(JavaScript_Key(fkp, FMnemonic)) - else Result := IntToStr(JavaScript_Key(fkp, FMnemonic)); -end; - -function TCompileKeymanWeb.JavaScript_Name(i: Integer; pwszName: PWideChar; KeepNameForPersistentStorage: Boolean): string; // I3659 -var - FChanged: Boolean; - p: PWideChar; -begin - FChanged := False; - p := pwszName; - if not Assigned(pwszName) or (pwszName^ = #0) or (not Self.FDebug and not KeepNameForPersistentStorage) then // I3659 // I3681 - Result := IntToStr(i) // for uniqueness - else - begin - if KeepNameForPersistentStorage // I3659 - then Result := '' // Potential for overlap in theory but in practice we only use this for named option stores so can never overlap - else Result := '_'; // Ensures we cannot overlap numbered instances - while pwszName^ <> #0 do - begin - if CharInSet(PChar(pwszName)^, SValidIdentifierCharSet) then // I3681 - begin - Result := Result + PChar(pwszName)^ - end - else - begin - Result := Result + '_'; - FChanged := True; - end; - Inc(pwszName); - end; - if not KeepNameForPersistentStorage then - begin - // Ensure each transformed name is still unique - Result := Result + '_' + IntToStr(i); - if FChanged then - Result := Result + '/*'+string(p).Replace('*/', '*-/')+'*/' - end - else if FChanged then - begin - // For named option stores, we are only supporting the valid identifier - // character set, which is a breaking change in 14.0. - ReportError(0, CWARN_OptionStoreNameInvalid, Format('The option store %s should be named with characters in the range A-Z, a-z, 0-9 and _ only.', - [string(p)])); - end; - end; -end; - -function TCompileKeymanWeb.JavaScript_OutputString(FTabstops: string; fkp: PFILE_KEY; pwszOutput: PWideChar; fgp: PFILE_GROUP): string; -var - i, n, len: Integer; - InQuotes: Boolean; - rec: TSentinelRecord; - pwszcontext,pwsz: PWideChar; - Index: Integer; // I3910 - nlt: string; - - function AdjustIndex(pwszContext: PWideChar; Index: Integer): Integer; // I3910 - var - recContext: TSentinelRecord; - I: Integer; - begin - Result := Index; - for I := 1 to Index-1 do - begin - recContext := ExpandSentinel(pwszContext); - - if IsKeyboardVersion10OrLater then - begin - if recContext.IsSentinel and (recContext.Code in [CODE_NUL, CODE_IFOPT, CODE_IFSYSTEMSTORE]) then - Dec(Result); - end - else - begin - if recContext.IsSentinel and (recContext.Code in [CODE_DEADKEY, CODE_NUL, CODE_IFOPT, CODE_IFSYSTEMSTORE]) then - Dec(Result); - end; - pwszContext := incxstr(pwszContext); - end; - end; - - function ContextChar(ContextIndex: Integer; pwszContext: PWideChar): string; // I4611 - var - Index: Integer; - recContext: TSentinelRecord; - begin - Result := ''; - recContext := ExpandSentinel(pwszContext); - if recContext.IsSentinel then - begin - if InQuotes then // I4611 - begin - Result := Result + '");'; - InQuotes := False; - end; - - case recContext.Code of - CODE_ANY: - begin - Index := AdjustIndex(fkp.dpContext, ContextIndex); // I3910 // I4611 - Result := Result + nlt + Format('k.KIO(%d,this.s%s,%d,t);', [len, JavaScript_Name(recContext.Any.StoreIndex, recContext.Any.Store.szName), Index]); // I4611 - end; - CODE_DEADKEY: - Result := Result + nlt + Format('k.KDO(%d,t,%d);', [len, recContext.Deadkey.Deadkey]); // I4611 - CODE_NOTANY: - begin - // #917: Minimum version required is 14.0: the KCXO function was only added for 14.0 - // Note that this is checked in compiler.cpp as well, so this error can probably never occur - if not IsKeyboardVersion14OrLater then - ReportError(fkp.Line, CERR_NotSupportedInKeymanWebContext, Format('Statement notany in context() match requires version 14.0+ of KeymanWeb', [GetCodeName(recContext.Code)])); // I1971 // I4061 - Result := Result + nlt + Format('k.KCXO(%d,t,%d,%d);', [len, AdjustIndex(fkp.dpContext, xstrlen(fkp.dpContext)), AdjustIndex(fkp.dpContext, ContextIndex)]); - end; - CODE_IFOPT, - CODE_IFSYSTEMSTORE, - CODE_NUL: - // These have no output for a context emit - ; - else - begin - ReportError(fkp.Line, CERR_NotSupportedInKeymanWebContext, Format('Statement %s is not currently supported in context() match', [GetCodeName(recContext.Code)])); // I1971 // I4061 - //CODE_NUL: ; // todo: check if context is longer than that... - Result := Result + nlt + '/*.*/ '; // I4611 - end; - end; - end - else - begin - if not InQuotes then - begin - Result := Result + nlt + Format('k.KO(%d,t,"', [len]); // I4611 - InQuotes := True; - end; - - if recContext.ChrVal in [Ord('"'), Ord('\')] then Result := Result + '\'; - Result := Result + Javascript_String(recContext.ChrVal); // I2242 - end; - end; - -begin - nlt := nl + FTabStops; // I3681 - Result := ''; - InQuotes := False; - - pwsz := pwszOutput; - - if Assigned(fkp) then - begin - if IsKeyboardVersion10OrLater - // KMW >=10.0 use the full, sentinel-based length for context deletions. - then - begin - len := xstrlen(fkp.dpContext); - n := len; - pwszContext := fkp.dpContext; - - for i := 1 to n do - begin - rec := ExpandSentinel(pwszContext); - if rec.IsSentinel and (rec.Code in [CODE_NUL, CODE_IFOPT, CODE_IFSYSTEMSTORE]) then - Dec(len); - pwszContext := incxstr(pwszContext); - end; - - end - // KMW < 10.0 exclude all sentinel-based characters, including deadkeys, from direct context deletion. - // Deadkeys have alternative special handling. - else len := xstrlen_printing(fkp.dpContext); - end - else - len := -1; - - if IsKeyboardVersion10OrLater() and (pwsz^ <> #0) then - begin - if not fgp.fReadOnly then - begin - Result := Result + nlt+Format('k.KDC(%d,t);', [ len ] ); // I3681 - end; - len := -1; - end; - - while pwsz^ <> #0 do - begin - rec := ExpandSentinel(pwsz); - if rec.IsSentinel then - begin - if InQuotes then - begin - if not fgp.fReadOnly then - Result := Result + '");'; - InQuotes := False; - end; - - case rec.Code of - CODE_CONTEXT: - begin - if (pwsz <> pwszOutput) or (len = -1) then - begin - pwszContext := fkp.dpContext; - n := 1; - while pwszContext^ <> '' do // I4611 - begin - if not fgp.fReadOnly then - begin - Result := Result + ContextChar(n, pwszContext); - end; - Inc(n); - pwszContext := incxstr(pwszContext); - end; - - //Result := Result + Format('k.KO(%d,t,k.KC(%d,%d,t));', [len, xstrlen_printing(fkp.dpContext), xstrlen_printing(fkp.dpContext)]); - end; - { else, we don't need to output anything - just don't delete the context } - len := -1; - end; - CODE_CONTEXTEX: - begin - pwszContext := fkp.dpContext; - for i := 1 to rec.ContextEx.Index - 1 do - begin - pwszContext := incxstr(pwszContext); - end; - - if not fgp.fReadOnly then - begin - Result := Result + ContextChar(rec.ContextEx.Index, pwszContext); // I4611 - end; - len := -1; - end; - CODE_BEEP: - begin - if not fgp.fReadOnly then - begin - if len > 0 then Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - Result := Result + nlt+'k.KB(t);'; // I3681 - end; - len := -1; - end; - CODE_NUL: - begin - if not fgp.fReadOnly then - begin - if len > 0 then Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - len := -1; - end; - CODE_INDEX: - begin - CheckStoreForInvalidFunctions(fkp, rec.Index.Store); // I1520 - - // This code was wrong. We need to ignore CODE_NUL, CODE_DEADKEY in LHS context index counter. - // This is why the compiler goes wrong -- and why the previous fix was inconsistent. - // The I783 test did not test either of these cases. It seems some of the keyboards were - // compiled in-between the original fix and I783 re-fix, and then happened to work due to - // their simplicity. - - Index := AdjustIndex(fkp.dpContext, rec.Index.Index); // I3910 - - if not fgp.fReadOnly then - begin - Result := Result + nlt+Format('k.KIO(%d,this.s%s,%d,t);', [len, JavaScript_Name(rec.Index.StoreIndex, rec.Index.Store.szName), - // I783 - was: rec.Index.Index [2007-06-04] - // I783 again. Returned to rec.Index.Index. Was previously: [2008-08-15] - // xstrlen(fkp.dpContext) + 1 - rec.Index.Index]); - // this was wrong. Can't find any reason why this change was made - // which suggests it was in response to another bug and poorly traced (bad Marc) - // and not properly tested (bad, bad Marc). Anyway, now tested with test_i783 - Index]); // I3681 // I3910 - end; - len := -1; - end; - CODE_DEADKEY: - begin - if not fgp.fReadOnly then - begin - Result := Result + nlt+Format('k.KDO(%d,t,%d);', [len, rec.Deadkey.Deadkey]); // I3681 - end; - len := -1; - end; - CODE_USE: - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - Result := Result + nlt+Format('r=this.g%s(t,e);', [JavaScript_Name(rec.Use.GroupIndex, rec.Use.Group.szName)]); // I1959 // I3681 - Result := Result + nlt+'m=2;'; // #5440 - match desktop behavior - len := -1; - end; - CODE_CALL: - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - n := FCallFunctions.IndexOf(CallFunctionName(rec.Call.Store.dpString)); - if n = -1 then - n := FCallFunctions.Add(CallFunctionName(rec.Call.Store.dpString)); - Result := Result + nlt+Format('r=this.c%d(t,e);', [n]); // I1959 // I3681 - Result := Result + nlt+'m=2;'; // #5440 - match desktop behavior - len := -1; - end; - CODE_SETOPT: // I3429 - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - Result := Result + nlt+Format('this.s%s=this.s%s;', - [JavaScript_Name(rec.SetOpt.StoreIndex1,rec.SetOpt.Store1.szName), - JavaScript_Name(rec.SetOpt.StoreIndex2,rec.SetOpt.Store2.szName)]); // I3429 // I3681 - len := -1; - end; - CODE_RESETOPT: // I3429 - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - - Result := Result + nlt+Format('this.s%s=k.KLOAD(this.KI,"%s",%s);', - [JavaScript_Name(rec.ResetOpt.StoreIndex,rec.ResetOpt.Store.szName), - JavaScript_Name(rec.ResetOpt.StoreIndex,rec.ResetOpt.Store.szName,True), - JavaScript_Store(fkp.Line, rec.ResetOpt.Store.dpString)]); // I3429 // I3681 // I3659 - len := -1; - end; - CODE_SAVEOPT: // I3429 - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); - end; - Result := Result + nlt+Format('k.KSAVE("%s",this.s%s);', - [JavaScript_Name(rec.SaveOpt.StoreIndex,rec.SaveOpt.Store.szName,True), // I3690 - JavaScript_Name(rec.SaveOpt.StoreIndex,rec.SaveOpt.Store.szName)]); // I3429 // I3659 // I3681 - len := -1; - end; - CODE_SETSYSTEMSTORE: // I3437 - begin - if not fgp.fReadOnly then - begin - if len > 0 then - Result := Result + nlt+Format('k.KO(%d,t,"");', [len]); // I3681 - end; - Result := Result + nlt+Format('k.KSETS(%d,this.s%s,t);', // I3681 - [rec.SetSystemStore.dwSystemID, - JavaScript_Name(rec.SetSystemStore.StoreIndex, rec.SetSystemStore.Store.szName)]); - len := -1; - end; - else - begin - if Assigned(fkp) - then ReportError(fkp.Line, CERR_NotSupportedInKeymanWebOutput, Format('Statement %s is not currently supported in output', [GetCodeName(rec.Code)])) // I1971 // I4061 - else ReportError(0, CERR_NotSupportedInKeymanWebOutput, Format('Statement %s is not currently supported in output', [GetCodeName(rec.Code)])); // I1971 // I4061 - Result := Result + ''; - end; - end; - end - else - begin - if not InQuotes then - begin - if not fgp.fReadOnly then - begin - Result := Result + nlt+Format('k.KO(%d,t,"', [len]); // I3681 - end; - InQuotes := True; len := -1; - end; - - if not fgp.fReadOnly then - begin - if rec.ChrVal in [Ord('"'), Ord('\')] then Result := Result + '\'; - Result := Result + Javascript_String(rec.ChrVal); // I2242 - end; - end; - - pwsz := incxstr(pwsz); - end; - - if InQuotes then - begin - if not fgp.fReadOnly then - begin - Result := Result + '");'; - end; - end; -end; - -function TCompileKeymanWeb.JavaScript_Shift(fkp: PFILE_KEY; FMnemonic: Boolean): Integer; -begin - if FMnemonic then - begin - if (fkp.ShiftFlags and KMX_VIRTUALCHARKEY) = KMX_VIRTUALCHARKEY then - begin - ReportError(fkp.Line, CERR_VirtualCharacterKeysNotSupportedInKeymanWeb, 'Virtual character keys not currently supported in KeymanWeb'); // I1971 // I4061 - Exit(0); - end; - - if ((fkp.ShiftFlags and KMX_ISVIRTUALKEY) = KMX_ISVIRTUALKEY) and (Ord(fkp.Key) <= 255) then - begin - // We prohibit K_ keys for mnemonic layouts. We don't block T_ and U_ keys. - // TODO: this doesn't resolve the issue of, e.g. SHIFT+K_SPACE - // https://github.com/keymanapp/keyman/issues/265 - ReportError(fkp.Line, CERR_VirtualKeysNotValidForMnemonicLayouts, 'Virtual keys are not valid for mnemonic layouts'); // I1971 // I4061 - Exit(0); - end; - end; - - if (fkp.ShiftFlags and KMX_ISVIRTUALKEY) = KMX_ISVIRTUALKEY then - begin - if IsKeyboardVersion10OrLater then - begin - // Full chiral modifier and state key support starts with KeymanWeb 10.0 - Result := fkp.ShiftFlags; - end - else - begin - // Non-chiral support only and no support for state keys - if (fkp.ShiftFlags and ( - KMX_LCTRLFLAG or KMX_RCTRLFLAG or KMX_LALTFLAG or KMX_RALTFLAG)) <> 0 then // I4118 - begin - ReportError(fkp.Line, CWARN_ExtendedShiftFlagsNotSupportedInKeymanWeb, 'Extended shift flags LALT, RALT, LCTRL, RCTRL are not supported in KeymanWeb'); - end; - - if (fkp.ShiftFlags and ( - KMX_CAPITALFLAG or KMX_NOTCAPITALFLAG or KMX_NUMLOCKFLAG or KMX_NOTNUMLOCKFLAG or KMX_SCROLLFLAG or KMX_NOTSCROLLFLAG)) <> 0 then // I4118 - begin - ReportError(fkp.Line, CWARN_ExtendedShiftFlagsNotSupportedInKeymanWeb, 'Extended shift flags CAPS and NCAPS are not supported in KeymanWeb'); - end; - - Result := KMX_ISVIRTUALKEY or (Integer(fkp.ShiftFlags) and (KMX_SHIFTFLAG or KMX_CTRLFLAG or KMX_ALTFLAG)); - end; - end - else - begin - Result := KMX_ISVIRTUALKEY; - if Pos(fkp.Key, USEnglishShift) > 0 then - Result := Result or KMX_SHIFTFLAG; - end; -end; - -/// -/// Returns a Javascript representation of a key modifier state, either as a constant (debug mode) -/// or as an integer. -/// -/// @param fkp Pointer to key record -/// @param FMnemonic True if the keyboard is a mnemonic layout -/// -/// @return string representation of the key modifier state, e.g. -/// 'modCodes.SHIFT | modCodes.CAPS | modCodes.VIRTUAL_KEY /* 0x4110 */' or -/// '16656' -/// -function TCompileKeymanWeb.JavaScript_ShiftAsString(fkp: PFILE_KEY; FMnemonic: Boolean): string; -begin - if not FDebug - then Result := IntToStr(JavaScript_Shift(fkp, FMnemonic)) - else Result := ' '+FormatModifierAsBitflags(JavaScript_Shift(fkp, FMnemonic)); -end; - -function TCompileKeymanWeb.JavaScript_Store(line: Integer; pwsz: PWideChar): string; -var - ch: DWord; - n: Integer; - rec: TSentinelRecord; -const - wcsentinel: WideString = #$FFFF; -begin - n := Pos(wcsentinel, pwsz); - - // Start: plain text store. Always use for < 10.0, conditionally for >= 10.0. - if (n = 0) or not IsKeyboardVersion10OrLater then - begin - Result := '"'; - while pwsz^ <> #0 do - begin - if PWord(pwsz)^ = UC_SENTINEL then - begin - Result := Result + '.'; // UC_SENTINEL values are not supported in stores for KMW < 10.0. - end - else - begin - ch := GetSuppChar(pwsz); - if ch in [Ord('"'), Ord('\')] then Result := Result + '\'; - Result := Result + Javascript_String(ch); // I2242 - end; - - pwsz := incxstr(pwsz); - end; - Result := Result + '"'; - end - else - begin - Result := '['; - while pwsz^ <> #0 do - begin - if Result <> '[' then Result := Result + ','; - - rec := ExpandSentinel(pwsz); - if rec.IsSentinel then - begin - if rec.Code = CODE_DEADKEY then - begin - Result := Result + Format('{t:''d'',d:%d}', [rec.Deadkey.DeadKey]); - end - else if rec.Code = CODE_BEEP then - begin - Result := Result + '{t:''b''}' - end - else //if rec.Code = CODE_EXTENDED then - begin - // At some point, we may wish to filter which codes are safe to stub out like this - // versus which ones should be an error. The commented-out-code shows the way to - // handle such cases. - Result := Result + ''''''; - end -// else -// begin -// //ReportError(line, CERR_SomewhereIGotItWrong, 'Internal Error: unexpected sentinel character in store definition'); -// end; - end - else - begin - ch := GetSuppChar(pwsz); - Result := Result + '"'; - // TODO: Refactor the section below into JavaScript_String, as it's - // quite common in our code base. - if ch in [Ord('"'), Ord('\')] then Result := Result + '\'; - Result := Result + Javascript_String(ch) + '"'; // I2242 - end; - - pwsz := incxstr(pwsz); - end; - Result := Result + ']'; - end; -end; - -function TCompileKeymanWeb.JavaScript_String(ch: DWord): string; // I2242 -begin - if ch < 32 then - begin - case ch of - 9: Result := '\t'; - 10: Result := '\n'; - 13: Result := '\r'; - else Result := '\x'+IntToHex(ch,2); - end; - end - else - try - Result := Char.ConvertFromUtf32(ch); // I3310 - except - on EArgumentOutOfRangeException do - begin - // #6480 - // This happens when there is an unpaired surrogate. Not technically supported - // by KeymanWeb, warning is issued by kmx compiler, so we won't re-warn here. - Result := Char(ch); - end; - end; -end; - -procedure TCompileKeymanWeb.ReportError(line: Integer; msgcode: LongWord; const text: string); // I1971 -var - flag: LongWord; -begin - flag := CERR_FLAG or CFATAL_FLAG; - if FCompilerWarningsAsErrors then - flag := flag or CWARN_FLAG; - if (msgcode and flag) <> 0 then FError := True; - FCallback(line, msgcode, PWideChar(text)); // I3310 -end; - -function TCompileKeymanWeb.RequotedString(s: WideString; RequoteSingleQuotes: Boolean = False): string; -var - i: Integer; -begin - i := 1; - while i <= Length(s) do - begin - if (s[i] = '"') or (s[i] = '\') then begin s := Copy(s, 1, i-1)+'\'+Copy(s, i, Length(s)); Inc(i); end // I4368 - else if (s[i] = '''') and RequoteSingleQuotes then begin s := Copy(s, 1, i-1)+'\'+Copy(s, i, Length(s)); Inc(i); end - else if (s[i] = #13) then // I4368 - begin - s := Copy(s, 1, i-1) + '\n' + Copy(s,i+1, Length(s)); - Inc(i); - end - else if (s[i] = #10) then // I4368 - s[i] := ' '; - Inc(i); - end; - Result := s; -end; - -{** - Determine if a rule should be ignored by the KeymanWeb compiler because it is - targeted at a .kmx-based or KMFL platform with the platform() or if(&platform) - statement. - - Parameters: fkp Pointer to the rule - - Return Value: True if the rule should be excluded by the compiler -*} -function TCompileKeymanWeb.RuleIsExcludedByPlatform(fkp: PFILE_KEY): Boolean; -var - rec: TSentinelRecord; - pwsz: PChar; -begin - pwsz := fkp.dpContext; - if not Assigned(pwsz) then - Exit(False); - - while pwsz^ <> #0 do - begin - rec := ExpandSentinel(pwsz); - if rec.IsSentinel and - (rec.Code = CODE_IFSYSTEMSTORE) and - (rec.IfSystemStore.dwSystemID = TSS_PLATFORM) and - (Pos('native', rec.IfSystemStore.Store.dpString) > 0) then - begin - if (Pos('windows', rec.IfSystemStore.Store.dpString) > 0) or - (Pos('desktop', rec.IfSystemStore.Store.dpString) > 0) or - (Pos('macosx', rec.IfSystemStore.Store.dpString) > 0) or - (Pos('linux', rec.IfSystemStore.Store.dpString) > 0) then - Exit(True); - end; - pwsz := incxstr(pwsz); - end; - - Result := False; -end; - -function TCompileKeymanWeb.VisualKeyboardFromFile(const FVisualKeyboardFileName: string; var fDisplayUnderlying: Boolean): WideString; - - function WideQuote(s: WideString): WideString; - var - i: Integer; - begin - Result := ''; - for i := 1 to Length(s) do - if (s[i] = '"') or (s[i] = '\') then Result := Result + '\'+s[i] else Result := Result + s[i]; - end; - - function VKShiftToLayerName(Shift: Integer): string; - const - masks: array[0..6] of string = ( - 'leftctrl', - 'rightctrl', - 'leftalt', - 'rightalt', - 'shift', - 'ctrl', - 'alt' - ); - var - i: Integer; - begin - shift := VkShiftStateToKmxShiftState(shift); - if shift = 0 then - Result := 'default' - else - begin - Result := ''; - for i := 0 to 6 do - if shift and (1 shl i) <> 0 then - Result := Result + masks[i] + '-'; - Delete(Result, Length(Result), 1); - end; - end; - - function VisualKeyboardToKLS(FVK: TVisualKeyboard): string; - type - TLayer = record - Shift: Integer; - Name: string; - Keys: array[0..64] of string; - end; - var - i: Integer; - layers: array of TLayer; // TDictionary may be faster but not worth the extra dev cost - n, j: Integer; - Found: Boolean; - begin - // Discover the layers used in the visual keyboard - - for i := 0 to FVK.Keys.Count - 1 do - begin - if kvkkUnicode in FVK.Keys[i].Flags then - begin - // Find the index of the key in KMW VK arrays - n := CKeymanWebKeyCodes[FVK.Keys[i].VKey]; - if n = $FF then Continue; - - Found := False; - for j := 0 to High(layers) do - if layers[j].Shift = FVK.Keys[i].Shift then - begin - Found := True; - layers[j].Keys[n] := FVK.Keys[i].Text; - Break; - end; - - if not Found then - begin - SetLength(layers, Length(layers)+1); - layers[High(Layers)].Shift := FVK.Keys[i].Shift; - layers[High(Layers)].Keys[n] := FVK.Keys[i].Text; - end; - end; - end; - - // Build the layer array - - Result := nl+FTabStop+'this.KV.KLS={'+nl; - - for i := 0 to High(layers) do - begin - Result := Result + Format('%s%s"%s": [', [FTabStop,FTabStop,VKShiftToLayerName(layers[i].Shift)]); - for j := 0 to High(layers[i].Keys)-1 do - begin - Result := Result + '"'+WideQuote(layers[i].Keys[j])+'",'; - end; - Result := Result + '"'+WideQuote(layers[i].Keys[High(layers[i].Keys)])+'"]'; - if i < High(Layers) then - Result := Result + ',' + nl; - end; - Result := Result + nl+FTabStop+'}'; - end; - - function BuildBKFromKLS: string; - const func = - 'function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,"")'+ - ',r=[],v,i,m=[''default'',''shift'',''ctrl'',''shift-ctrl'',''alt'',''shift-alt'','+ - '''ctrl-alt'',''shift-ctrl-alt''];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)'+ - 'r=(v?v:e).slice().concat(r);return r}'; - func_debug = - 'function(x){'#13#10+ - ' var'#13#10+ - ' empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""),'#13#10+ - ' result=[], v, i,'#13#10+ - ' modifiers=[''default'',''shift'',''ctrl'',''shift-ctrl'',''alt'',''shift-alt'',''ctrl-alt'',''shift-ctrl-alt''];'#13#10+ - ' for(i=modifiers.length-1;i>=0;i--) {'#13#10+ - ' v = x[modifiers[i]];'#13#10+ - ' if(v || result.length > 0) {'#13#10+ - ' result=(v ? v : empty).slice().concat(result);'#13#10+ - ' }'#13#10+ - ' }'#13#10+ - ' return result;'#13#10+ - ' }'; - begin - Result := nl+FTabStop+'this.KV.BK=('+IfThen(FDebug,func_debug,func)+')(this.KV.KLS)'; - end; - -var - FVK: TVisualKeyboard; - f102, fbold, fitalic: string; -begin - Result := ''; - FVK := TVisualKeyboard.Create; - try - FVK.LoadFromFile(FVisualKeyboardFileName); - if fsBold in FVK.Header.UnicodeFont.Style then fbold := 'bold ' else fbold := ''; - if fsItalic in FVK.Header.UnicodeFont.Style then fitalic := 'italic ' else fitalic := ''; - if kvkh102 in FVK.Header.Flags then f102 := '1' else f102 := '0'; - fDisplayUnderlying := kvkhDisplayUnderlying in FVK.Header.Flags; - - Result := Format('{F:''%s%s 1em "%s"'',K102:%s}', [fitalic, fbold, RequotedString(FVK.Header.UnicodeFont.Name, True), f102]); // I3886 // I3956 - Result := Result + ';'+VisualKeyboardToKLS(FVK); - Result := Result + ';'+BuildBKFromKLS; - finally - FVK.Free; - end; -end; - -type - TRequiredKey = (K_LOPT, K_BKSP, K_ENTER); // I4447 - TRequiredKeys = set of TRequiredKey; - -function RequiredKeysToString(keys: TRequiredKeys): string; // I4060 -var - k: TRequiredKey; -begin - Result := ''; - for k in keys do - Result := Result + ', ' + GetEnumName(TypeInfo(TRequiredKey), Ord(k)); - Delete(Result, 1, 2); -end; - -function TCompileKeymanWeb.ValidateLayoutFile(var sLayoutFile: string; const sVKDictionary: string): Boolean; // I4060 // I4139 -type - TKeyIdType = (Key_Invalid, Key_Constant, Key_Touch, Key_Unicode, Key_Unicode_Multi); // I4142 -const - CRequiredKeys: TRequiredKeys = [K_LOPT, K_BKSP, K_ENTER]; // I4447 - - // See also builder.js: specialCharacters; web/source/osk/oskKey.ts: specialCharacters - CSpecialText10: string = - '*Shift*'#0'*Enter*'#0'*Tab*'#0'*BkSp*'#0'*Menu*'#0'*Hide*'#0'*Alt*'#0'*Ctrl*'#0'*Caps*'#0+ - '*ABC*'#0'*abc*'#0'*123*'#0'*Symbol*'#0'*Currency*'#0'*Shifted*'#0'*AltGr*'#0'*TabLeft*'; - - // these names were added in Keyman 14 - CSpecialText14: string = - '*LTREnter*'#0'*LTRBkSp*'#0'*RTLEnter*'#0'*RTLBkSp*'#0'*ShiftLock*'#0'*ShiftedLock*'#0'*ZWNJ*'#0'*ZWNJiOS*'#0'*ZWNJAndroid*'; - CSpecialText14ZWNJ: string = - '*ZWNJ*'#0'*ZWNJiOS*'#0'*ZWNJAndroid*'; - - CSpecialText14Map: array[0..8,0..1] of string = ( - ('*LTREnter*', '*Enter*'), - ('*LTRBkSp*', '*BkSp*'), - ('*RTLEnter*', '*Enter*'), - ('*RTLBkSp*', '*BkSp*'), - ('*ShiftLock*', '*Shift*'), - ('*ShiftedLock*', '*Shifted*'), - ('*ZWNJ*', '<|>'), - ('*ZWNJiOS*', '<|>'), - ('*ZWNJAndroid*', '<|>') - ); - -var - FPlatform: TTouchLayoutPlatform; - FLayer: TTouchLayoutLayer; - FRow: TTouchLayoutRow; - FKey: TTouchLayoutKey; - FSubKey: TTouchLayoutSubKey; - FRequiredKeys: set of TRequiredKey; - FDictionary: TStringList; - FDirection: TTouchLayoutFlickDirection; - - function IsValidUnicodeValue(ch: Integer): Boolean; // I4198 - begin - Result := - ((ch >= $0020) and (ch <= $7F)) or - ((ch >= $00A0) and (ch <= $10FFFF)); - end; - - function GetKeyIdUnicodeType(const value: string): TKeyIdType; - var - v: string; - values: TArray; - begin - values := value.Split(['_']); - for v in values do - if not IsValidUnicodeValue(StrToIntDef('$'+v, 0)) then - Exit(Key_Invalid); - if Length(values) > 1 then - Exit(Key_Unicode_Multi); - Result := Key_Unicode; - end; - - function KeyIdType(const FId: string): TKeyIdType; // I4142 - begin - Result := Key_Invalid; - case UpCase(FId[1]) of - 'T': Result := Key_Touch; - 'U': if (Copy(FId, 1, 2) = 'U_') then Result := GetKeyIdUnicodeType(FId.Substring(2)); - else if FindVKeyName(FId) <> $FFFF then Result := Key_Constant; - end; - end; - - procedure CheckKey(const FId, FText, FNextLayer: string; FKeyType: TTouchKeyType); // I4119 - var - FValid: TKeyIdType; - v: Integer; - begin - // - // Check that each touch layer has K_LOPT, K_ROPT, K_BKSP, K_ENTER - // - - v := GetEnumValue(TypeInfo(TRequiredKey), FId); - if v >= 0 then Include(FRequiredKeys, TRequiredKey(v)); - - // - // Check that each layer referenced exists - // - - if FNextLayer <> '' then - begin - if FPlatform.Layers.IndexOfId(FNextLayer) < 0 then - ReportError(0, CWARN_TouchLayoutMissingLayer, 'Key "'+FId+'" on platform "'+FPlatform.Name+'", layer "'+FLayer.Id+'", platform "'+FPlatform.Name+'", references a missing layer "'+FNextLayer+'".'); - end; - - // - // Check that the key has a valid id // I4142 - // - - if Trim(FId) = '' then - begin - if not (FKeyType in [tktBlank, tktSpacer]) and (FNextLayer = '') then - ReportError(0, CWARN_TouchLayoutUnidentifiedKey, 'A key on layer "'+FLayer.Id+'" has no identifier.'); - Exit; - end; - - FValid := KeyIdType(FId); - - if FValid = Key_Invalid then - begin - ReportError(0, CERR_TouchLayoutInvalidIdentifier, 'Key "'+FId+'" on "'+FPlatform.Name+'", layer "'+FLayer.Id+'" has an invalid identifier.'); - end - else if (FValid = Key_Unicode_Multi) and not IsKeyboardVersion15OrLater then - begin - ReportError(0, CERR_TouchLayoutInvalidIdentifier, 'Key "'+FId+'" on "'+FPlatform.Name+'", layer "'+FLayer.Id+'" has a multi-part identifier which requires version 15.0 or newer.'); - end; - // - // Check that each custom key code has at least *a* rule associated with it - // - - if (FValid = Key_Touch) and (FNextLayer = '') and (FKeyType in [tktNormal, tktDeadKey]) then // I4119 - begin - // Search for the key in the key dictionary - ignore K_LOPT, K_ROPT... - if FDictionary.IndexOf(FId) < 0 then - ReportError(0, CWARN_TouchLayoutCustomKeyNotDefined, 'Key "'+FId+'" on layer "'+FLayer.Id+'", platform "'+FPlatform.Name+'", is a custom key but has no corresponding rule in the source.'); - end; - - // - // Check that if the key has a *special* label, it is available in the target version - // - if FText.StartsWith('*') and FText.EndsWith('*') and (FText.Length > 2) then - begin - // Keyman versions before 14 do not support '*special*' labels on non-special keys. - // ZWNJ use, however, is safe because it will be transformed in function - // TransformSpecialKeys14 to '<|>', which does not require the custom OSK font. - if (CSpecialText10.Contains(FText) or CSpecialText14.Contains(FText)) and - not CSpecialText14ZWNJ.Contains(FText) and - not IsKeyboardVersion14OrLater and - not (FKeyType in [tktSpecial, tktSpecialActive]) then - begin - ReportError(0, CWARN_TouchLayoutSpecialLabelOnNormalKey, - Format('Key "%s" on layout "%s", platform "%s" does not have the key type "Special" or "Special (active)" but has the label "%s". This feature is only supported in Keyman 14 or later', [ - FId, FLayer.Id, FPlatform.Name, FText - ])); - end; - end; - end; - - procedure CheckDictionaryKeyValidity; // I4142 - var - gp, kp: Cardinal; - i: Integer; - fgp: PFILE_GROUP; - fkp: PFILE_KEY; - begin - for i := 0 to FDictionary.Count - 1 do - begin - if FDictionary[i] = '' then - Continue; - if KeyIdType(FDictionary[i]) in [Key_Invalid, Key_Constant] then - begin - gp := 0; - fgp := fk.dpGroupArray; - while gp < fk.cxGroupArray do - begin - if fgp.fUsingKeys then - begin - kp := 0; - fkp := fgp.dpKeyArray; - while kp < fgp.cxKeyArray do - begin - if JavaScript_Key(fkp, fMnemonic) = i+256 then - begin - ReportError(fkp.Line, CERR_InvalidKeyCode, 'Invalid key identifier "'+FDictionary[i]+'"'); - end; - Inc(kp); - Inc(fkp); - end; - end; - Inc(gp); - Inc(fgp); - end; - end; - end; - end; - - procedure TransformSpecialKeys14(var sLayoutFile: string); - var - i: Integer; - begin - // Rewrite Special key labels that are only supported in Keyman 14+ - // This code is a little ugly but effective. - if not IsKeyboardVersion14OrLater then - begin - for i := 0 to High(CSpecialText14Map) do - begin - // Assumes the JSON output format will not change - if FDebug - then sLayoutFile := ReplaceStr(sLayoutFile, '"text": "'+CSpecialText14Map[i][0]+'"', '"text": this._v>13 ? "'+CSpecialText14Map[i][0]+'" : "'+CSpecialText14Map[i][1]+'"') - else sLayoutFile := ReplaceStr(sLayoutFile, '"text":"'+CSpecialText14Map[i][0]+'"', '"text":this._v>13?"'+CSpecialText14Map[i][0]+'":"'+CSpecialText14Map[i][1]+'"'); - end; - end; - end; -begin - FDictionary := TStringList.Create; - try - FDictionary.Delimiter := ' '; - FDictionary.DelimitedText := sVKDictionary; - - CheckDictionaryKeyValidity; // I4142 - - with TTouchLayout.Create do - try - OnMessage := - procedure(Sender: TObject; const sMsg:string) - begin - ReportError(0, CERR_InvalidTouchLayoutFile, sMsg); - end; - - if not Load(sLayoutFile) then - Exit(False); - - FTouchLayoutFont := ''; // I4872 - - for FPlatform in Data.Platforms do - begin - // Test that the font matches on all platforms // I4872 - - if FTouchLayoutFont = '' then - FTouchLayoutFont := FPlatform.Font - else if not SameText(FPlatform.Font, FTouchLayoutFont) then - begin - ReportError(0, CWARN_TouchLayoutFontShouldBeSameForAllPlatforms, 'The touch layout font should be the same for all platforms.'); - end; - - // Test that all required keys are present - for FLayer in FPlatform.Layers do - begin - FRequiredKeys := []; - for FRow in FLayer.Rows do - for FKey in FRow.Keys do - begin - CheckKey(FKey.Id, FKey.Text, FKey.NextLayer, FKey.SpT); // I4119 - for FSubKey in FKey.Sk do - CheckKey(FSubKey.Id, FKey.Text, FSubKey.NextLayer, FSubKey.SpT); // I4119 - for FDirection in FKey.Flick.Keys do - CheckKey(FKey.Flick[FDirection].Id, FKey.Flick[FDirection].Text, - FKey.Flick[FDirection].NextLayer, FKey.Flick[FDirection].SpT); - for FSubKey in FKey.MultiTap do - CheckKey(FSubKey.Id, FKey.Text, FSubKey.NextLayer, FSubKey.SpT); - end; - - if FRequiredKeys <> CRequiredKeys then - ReportError(0, CWARN_TouchLayoutMissingRequiredKeys, 'Layer "'+FLayer.Id+'" on platform "'+FPlatform.Name+'" is missing the required key(s) '+RequiredKeysToString(CRequiredKeys-FRequiredKeys)+'.'); - end; - end; - - // If not debugging, then this strips out formatting for a big saving in file size - // This also normalises any values such as Pad or Width which should be strings - sLayoutFile := Write(FDebug); - - TransformSpecialKeys14(sLayoutFile); - finally - Free; - end; - finally - FDictionary.Free; - end; - - Result := True; -end; - -function TCompileKeymanWeb.WriteBeginStatement(const name: string; groupIndex: Integer): string; -var - fgp: PFILE_GROUP; -begin - fgp := fk.dpGroupArray; Inc(fgp, groupIndex); - Result := Format( - '%sthis.%s=function(t,e) {%s'+ - '%sreturn this.g%s(t,e);%s'+ - '%s};%s', - [FTabStop, name, nl, - FTabStop+FTabStop, JavaScript_Name(groupIndex, fgp.szName), nl, - FTabStop, nl]); // I3681 -end; - -//{$WARNINGS OFF} // bug in Delphi compiler returning W1035 return value undefined?!? -function TCompileKeymanWeb.WriteCompiledKeyboard: string; {UTF8} - function Requote(const S: string): string; - var - I: Integer; - begin - Result := S; - for I := Length(Result) downto 1 do - if CharInSet(Result[I], ['''', '\']) then Insert('\', Result, I); - Result := '''' + Result + ''''; - end; -var - fgp: PFILE_GROUP; - fsp: PFILE_STORE; - fkp: PFILE_KEY; - - i, j, n: Integer; // I1964 - crash with empty group - - vMnemonic: Integer; - s, sRTL, sHelp, sHelpFile, sName, sEmbedJS, sEmbedCSS: string; - sVisualKeyboard, sFullName: WideString; - sBegin_NewContext, sBegin_PostKeystroke: string; - sLayoutFile, sVKDictionary: string; - linecomment: string; // I3438 - HasRules: Boolean; - sModifierBitmask: string; - fDisplayUnderlying: Boolean; - FOptionStores: string; - rec: TSentinelRecord; -begin - Result := '';//UTF16SignatureW; // + '// compiled by Keyman Developer'+nl; // I3474 - { Locate the name of the keyboard } - fsp := fk.dpStoreArray; - sHelp := ''''''; - sFullName := ''; - sHelpFile := ''; - sEmbedJS := ''; - sEmbedCSS := ''; // I4368 - sVisualKeyboard := ''; - sLayoutFile := ''; // I3483 - FKeyboardVersion := '1.0'; // I4155 - sRTL := ''; - vMnemonic := 0; - for i := 0 to fk.cxStoreArray - 1 do - begin - if fsp.dwSystemID = TSS_NAME then - sFullName := fsp.dpString - else if fsp.dwSystemID = TSS_KEYBOARDVERSION then // I4155 - FKeyboardVersion := fsp.dpString // I4155 - else if (fsp.szName = 'HelpFile') or (fsp.dwSystemID = TSS_KMW_HELPFILE) then - sHelpFile := fsp.dpString - else if (fsp.szName = 'Help') or (fsp.dwSystemID = TSS_KMW_HELPTEXT) then - sHelp := '"'+RequotedString(fsp.dpString)+'"' - else if (fsp.szName = 'VisualKeyboard') or (fsp.dwSystemID = TSS_VISUALKEYBOARD) then - sVisualKeyboard := fsp.dpString - else if (fsp.szName = 'EmbedJS') or (fsp.dwSystemID = TSS_KMW_EMBEDJS) then - sEmbedJS := fsp.dpString - else if (fsp.szName = 'EmbedCSS') or (fsp.dwSystemID = TSS_KMW_EMBEDCSS) then // I4368 - sEmbedCSS := fsp.dpString - else if (fsp.szName = 'RTL') or (fsp.dwSystemID = TSS_KMW_RTL) then - if AnsiCompareText(fsp.dpString, '1') = 0 then sRTL := FTabStop+'this.KRTL=1;'+nl else sRTL := '' // I3681 - else if fsp.dwSystemID = TSS_MNEMONIC then - if AnsiCompareText(fsp.dpString, '1') = 0 then vMnemonic := 1 else vMnemonic := 0 - else if fsp.dwSystemID = TSS_VKDICTIONARY then // I3438 - sVKDictionary := fsp.dpString - else if fsp.dwSystemID = TSS_LAYOUTFILE then // I3483 - sLayoutFile := fsp.dpString - else if fsp.dwSystemID = TSS_BEGIN_NEWCONTEXT then - sBegin_NewContext := fsp.dpString - else if fsp.dwSystemID = TSS_BEGIN_POSTKEYSTROKE then - sBegin_PostKeystroke := fsp.dpString; - Inc(fsp); - end; - - sName := 'Keyboard_'+TKeyboardUtils.GetKeymanWebCompiledNameFromFileName(FInFile); - - if sHelpFile <> '' then - begin - sHelp := ''; - with TStringList.Create do - try - try - LoadFromFile(ExtractFilePath(FInFile) + sHelpFile, TEncoding.UTF8); // I3337 - for n := 0 to Count - 1 do - sHelp := sHelp + Strings[n] + ' '; - except - on E:EFOpenError do - begin - ReportError(0, CWARN_HelpFileMissing, E.Message); // I1971 // I4061 - sHelp := ''; - end; - end; - finally - Free; - end; - - sHelp := Requote(sHelp); - end; - - if sEmbedJS <> '' then - begin - try - with TStringList.Create do - try - LoadFromFile(ExtractFilePath(FInFile) + sEmbedJS, TEncoding.UTF8); // I3337 - sEmbedJS := Text; - finally - Free; - end; - except - on E:EFOpenError do // I3683 - begin - ReportError(0, CWARN_EmbedJsFileMissing, E.Message); // I4061 - sEmbedJS := ''; - end; - end; - end; - - if sEmbedCSS <> '' then // I4368 - begin - try - with TStringList.Create do - try - LoadFromFile(ExtractFilePath(FInFile) + sEmbedCSS, TEncoding.UTF8); // I3337 - sEmbedCSS := Text; - finally - Free; - end; - except - on E:EFOpenError do // I3683 - begin - ReportError(0, CWARN_EmbedJsFileMissing, E.Message); // I4061 - sEmbedCSS := ''; - end; - end; - end; - - if sLayoutFile <> '' then // I3483 - begin - try - with TStringList.Create do - try - LoadFromFile(ExtractFilePath(FInFile) + sLayoutFile, TEncoding.UTF8); - sLayoutFile := Text; - if not ValidateLayoutFile(sLayoutFile, sVKDictionary) then // I4060 - begin - sLayoutFile := ''; - end; - finally - Free; - end; - except - on E:EFOpenError do // I3683 - begin - ReportError(0, CWARN_TouchLayoutFileMissing, E.Message); // I4061 - sLayoutFile := ''; - end; - end; - end; - - // Default to hide underlying layout characters. This is overridden by touch - // layout platform.displayUnderlying property or, if that is not present for - // the given platform, by the OSK property. - fDisplayUnderlying := False; - - if sVisualKeyboard <> '' then - begin - try - // The Keyman .kmx compiler will change the value of this store from a - // .kvks to a .kvk during the build. Earlier in the build, the visual keyboard - // would have been compiled, so we need to account for that and use that file. - - sVisualKeyboard := VisualKeyboardFromFile(ExtractFilePath(FOutFile) + sVisualKeyboard, fDisplayUnderlying); - except - on E:EFOpenError do // I3947 - begin - ReportError(0, CWARN_VisualKeyboardFileMissing, E.Message); // I4061 - sVisualKeyboard := 'null'; - end; - end; - - { viskbd = array(...); viskbdfont = ... - sVisualKeyboard := 'null'; } - end - else - sVisualKeyboard := 'null'; - - sModifierBitmask := GetKeyboardModifierBitmask; - - fMnemonic := vMnemonic = 1; - - Result := Result + Format( - '%s%s'+ - 'KeymanWeb.KR(new %s());%s'+ - '%s%s'+ - 'function %s()%s'+ - '{%s'+ - '%s%s%s'+ - // Following line caches the Keyman major version - '%sthis._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;%s'+ - '%sthis.KI="%s";%s'+ - '%sthis.KN="%s";%s'+ - '%sthis.KMINVER="%d.%d";%s'+ - '%sthis.KV=%s;%s'+ - '%sthis.KDU=%s;%s'+ - '%sthis.KH=%s;%s'+ - '%sthis.KM=%d;%s'+ - '%sthis.KBVER="%s";%s'+ // I4155 - '%sthis.KMBM=%s;%s'+ - '%s', - [ - JavaScript_SetupProlog, nl, - sName, nl, - JavaScript_SetupEpilog, nl, - sName, nl, - nl, - FTabStop, JavaScript_SetupDebug, nl, - FTabStop, nl, - FTabStop, sName, nl, - FTabStop, RequotedString(sFullName), nl, - FTabStop, (fk.version and VERSION_MASK_MAJOR) shr 8, fk.version and VERSION_MASK_MINOR, nl, - FTabStop, sVisualKeyboard, nl, - FTabStop, IfThen(fDisplayUnderlying, '1', '0'), nl, - FTabStop, sHelp, nl, - FTabStop, vMnemonic, nl, - FTabStop, FKeyboardVersion, nl, // I4155 - FTabStop, sModifierBitmask, nl, - sRTL]); // I3681 - - if HasSupplementaryPlaneChars then - Result := Result + Format('%sthis.KS=1;%s', [FTabStop, nl]); // I3317 - - if sVKDictionary <> '' then // I3438 - Result := Result + Format('%sthis.KVKD="%s";%s', [FTabStop, RequotedString(sVKDictionary), nl]); // I3681 - - if sLayoutFile <> '' then // I3483 - begin - Result := Result + Format('%sthis.KVKL=%s;%s', [FTabStop, sLayoutFile, nl]); // I3681 - end; - - if sEmbedCSS <> '' then // I4368 - Result := Result + Format('%sthis.KCSS="%s";%s', [FTabStop, RequotedString(sEmbedCSS), nl]); - - { Write the stores out } - FOptionStores := ''; - fsp := fk.dpStoreArray; - for i := 0 to fk.cxStoreArray - 1 do - begin - // I3438 - Save all system stores to the keyboard, for now // I3684 - - if not fsp.fIsDebug then // and not (fsp.dwSystemID in [TSS_BITMAP, TSS_NAME, TSS_VERSION, TSS_CUSTOMKEYMANEDITION, TSS_CUSTOMKEYMANEDITIONNAME, TSS_KEYMANCOPYRIGHT]) then - begin - if fsp.dwSystemID = TSS_COMPARISON then - Result := Result + Format('%sthis.s%s=%s;%s', [FTabStop, JavaScript_Name(i, fsp.szName), JavaScript_Store(fsp.line, fsp.dpString), nl]) - else if fsp.dwSystemID = TSS_COMPILEDVERSION then - Result := Result + Format('%sthis.KVER=%s;%s', [FTabStop, JavaScript_Store(fsp.line, fsp.dpString), nl]) - //else if fsp.dwSystemID = TSS_VKDICTIONARY then // I3438, required for vkdictionary - // Result := Result + Format('%sthis.s%s=%s;%s', [FTabStop, JavaScript_Name(i, fsp.szName), JavaScript_Store(fsp.line, fsp.dpString), nl]) - else if fsp.fIsOption and not fsp.fIsReserved then - begin - Result := Result + Format('%sthis.s%s=KeymanWeb.KLOAD(this.KI,"%s",%s);%s', - [FTabstop, - JavaScript_Name(i,fsp.szName), - JavaScript_Name(i,fsp.szName,True), - JavaScript_Store(fsp.line, fsp.dpString), - nl]); // I3429 - - if FOptionStores <> '' then - FOptionStores := FOptionStores + ','; - FOptionStores := FOptionStores + Format('''s%s''', [JavaScript_Name(i,fsp.szName)]); - end - else if fsp.dwSystemID = TSS_NONE {aka not fsp.fIsReserved} then - Result := Result + Format('%sthis.s%s=%s;%s', [FTabStop, JavaScript_Name(i, fsp.szName), JavaScript_Store(fsp.line, fsp.dpString), nl]); // I3681 - end; - Inc(fsp); - end; - - Result := Result + Format('%sthis.KVS=[%s];%s', [FTabStop, FOptionStores, nl]); - - { Write the groups out } - - // I853 - begin unicode missing causes crash - if fk.StartGroup[BEGIN_UNICODE] = $FFFFFFFF then - begin - ReportError(0, CERR_InvalidBegin, 'A "begin unicode" statement is required to compile a KeymanWeb keyboard'); - Exit; - end; - - Result := Result + WriteBeginStatement('gs', fk.StartGroup[BEGIN_UNICODE]); - rec := ExpandSentinel(PChar(sBegin_NewContext)); - if rec.Code = CODE_USE then - Result := Result + WriteBeginStatement('gn', rec.Use.GroupIndex); - rec := ExpandSentinel(PChar(sBegin_PostKeystroke)); - if rec.Code = CODE_USE then - Result := Result + WriteBeginStatement('gpk', rec.Use.GroupIndex); - - fgp := fk.dpGroupArray; Inc(fgp, fk.StartGroup[BEGIN_UNICODE]); - Result := Result + Format( - '%sthis.gs=function(t,e) {%s'+ - '%sreturn this.g%s(t,e);%s'+ - '%s};%s', - [FTabStop, nl, - FTabStop+FTabStop, JavaScript_Name(fk.StartGroup[BEGIN_UNICODE], fgp.szName), nl, - FTabStop, nl]); // I3681 - - fgp := fk.dpGroupArray; - for i := 0 to Integer(fk.cxGroupArray)-1 do // I1964 - begin - { - Note on `r` and `m` variables in a group function: - - `m` can have one of three values: - 0: no rule from this group was matched - 1: a rule from this group was matched and did not include a `use` - statement - 2: a rule from this group matched and did include a `use` statement - (#5440) - - `m` is only used within a rule group to control the firing of the - `match` and `nomatch` rules. - - `r` can have one of two values: - 0: no rule from the final group matched (even if a rule from an - higher-level group did) - 1: a rule from the final group did match; - - `r` serves as the rule group's return value and is forwarded - recursively, best serving as a flag for whether or not default - output for a key should be emitted (0 means yes, emit the - default character output for that key). - } - - Result := Result + Format( - '%sthis.g%s=function(t,e) {%s'+ - '%svar k=KeymanWeb,r=%d,m=0;%s', //I1959 - [FTabstop, JavaScript_Name(i, fgp.szName), nl, - FTabstop+FTabstop, IfThen(fgp.fUsingKeys,0,1), nl]); // I3681 - - fkp := fgp.dpKeyArray; - HasRules := False; - - if FFix183_LadderLength <> 0 then - begin - Result := Result + JavaScript_Rules(fgp); - end - else - begin - for j := 0 to Integer(fgp.cxKeyArray) - 1 do // I1964 - begin - if not RuleIsExcludedByPlatform(fkp) then - begin - Result := Result + FTabstop+FTabstop; // I3681 - if HasRules then Result := Result + 'else '; - HasRules := TRue; - if fgp.fUsingKeys then - begin - Result := Result + Format('if(k.KKM(e,%s,%s)', - [JavaScript_ShiftAsString(fkp, fMnemonic), - JavaScript_KeyAsString(fkp, fMnemonic)]); - end; - - if xstrlen(fkp.dpContext) > 0 then - begin - if not fgp.fUsingKeys - then Result := Result + 'if(' - else Result := Result + '&&'; - - Result := Result + JavaScript_ContextMatch(fkp, fkp.dpContext); - end - else if not fgp.fUsingKeys then - Result := Result + 'if(1'; - - if (fkp.Line > 0) and FDebug // I4384 - then linecomment := Format(' // Line %d', [fkp.Line]) // I4373 - else linecomment := ''; - - Result := Result + Format( - ') {%s%s'+ - '%s', - [linecomment, nl, - FTabstop+FTabstop+FTabstop]); // I3681 - if(fgp.fUsingKeys) // I1959 - then Result := Result + Format('r=m=1;%s', [JavaScript_OutputString(FTabStop + FTabStop + FTabStop, fkp, fkp.dpOutput, fgp)]) // I1959 // I3681 - else Result := Result + Format('m=1;%s', [JavaScript_OutputString(FTabStop + FTabStop + FTabStop, fkp, fkp.dpOutput, fgp)]); // I1959 // I3681 - Result := Result + Format('%s%s}%s', [nl, FTabstop+FTabstop, nl]); // I3681 - end; - Inc(fkp); - end; - end; - - if Assigned(fgp.dpMatch) then - Result := Result + Format( - '%sif(m==1) {%s'+ - '%s%s%s'+ - '%s}%s', - [FTabstop+FTabstop, nl, - FTabstop+Ftabstop, JavaScript_OutputString(FTabStop + FTabStop + FTabStop, nil, fgp.dpMatch, fgp), nl, - FTabstop+FTabstop, nl]); // I3681 - if Assigned(fgp.dpNoMatch) then - if fgp.fUsingKeys then // I1382 - fixup m=1 to m=g() - Result := Result + Format( - '%sif(!m&&k.KIK(e)) {%s'+ - '%sr=1;%s%s'+ - '%s}%s', - [FTabstop+FTabstop, nl, - FTabstop+FTabstop+FTabstop, JavaScript_OutputString(FTabStop + FTabStop + FTabStop, nil, fgp.dpNoMatch, fgp), nl, - FTabstop+FTabstop, nl]) // I1959. part 2, I2224 // I3681 - else - Result := Result + Format( - '%sif(!m) {%s'+ - '%s%s%s'+ - '%s}%s', - [FTabstop+FTabstop, nl, - FTabstop+FTabstop, JavaScript_OutputString(FTabStop + FTabStop + FTabStop, nil, fgp.dpNoMatch, fgp), nl, - FTabstop+FTabstop, nl]); // I1959 // I3681 - - Result := Result + Format('%sreturn r;%s'+ - '%s};%s', - [FTabstop+FTabstop, nl, - FTabstop, nl]); // I1959 // I3681 - Inc(fgp); - end; - - for n := 0 to FCallFunctions.Count - 1 do - begin - s := ExtractFilePath(FInFile) + FCallFunctions[n] + '.call_js'; - if FileExists(s) then - with TStringList.Create do - try - LoadFromFile(s, TEncoding.UTF8); // I3337 - Result := Result + Format('%sthis.c%d=function(t,e){%s};%s', [FTabstop, n, Trim(Text), nl]); // I3681 - finally - Free; - end - else - Result := Result + Format('%sthis.c%d=function(t,e){alert("call(%s) not defined");};%s', [FTabstop, n, FCallFunctions[n], nl]); // I3681 - end; - - Result := Result + sEmbedJS + '}' + nl; // I3681 -end; - -function TCompileKeymanWeb.GetCodeName(code: Integer): string; // I1971 -begin - if (code >= Low(KMXCodeNames)) and (code <= High(KMXCodeNames)) - then Result := KMXCodeNames[code] - else Result := IntToStr(code); -end; - -function TCompileKeymanWeb.HasSupplementaryPlaneChars: Boolean; // I3317 - function StringHasSuppChars(p: PWideChar): Boolean; - begin - if not Assigned(p) then - Exit(False); - - while p^ <> #0 do - begin - if Char.IsSurrogate(p, 0) then - Exit(True); - p := incxstr(p); - end; - - Result := False; - end; - -var - I: Integer; - fsp: PFILE_STORE; - fgp: PFILE_GROUP; - j: Integer; - fkp: PFILE_KEY; -begin - fsp := fk.dpStoreArray; - for i := 0 to Integer(fk.cxStoreArray) - 1 do - begin - if StringHasSuppChars(fsp.dpString) then - Exit(True); - Inc(fsp); - end; - - fgp := fk.dpGroupArray; - for i := 0 to Integer(fk.cxGroupArray) - 1 do - begin - fkp := fgp.dpKeyArray; - for j := 0 to Integer(fgp.cxKeyArray) - 1 do - begin - if StringHasSuppChars(fkp.dpContext) or - StringHasSuppChars(fkp.dpOutput) then - Exit(True); - Inc(fkp); - end; - - if StringHasSuppChars(fgp.dpMatch) or - StringHasSuppChars(fgp.dpNoMatch) then - Exit(True); - - Inc(fgp); - end; - - Result := False; -end; - -function TCompileKeymanWeb.IsKeyboardVersion10OrLater: Boolean; -begin - Result := fk.version >= VERSION_100; -end; - -function TCompileKeymanWeb.IsKeyboardVersion14OrLater: Boolean; -begin - Result := fk.version >= VERSION_140; -end; - -function TCompileKeymanWeb.IsKeyboardVersion15OrLater: Boolean; -begin - Result := fk.version >= VERSION_150; -end; - -procedure TCompileKeymanWeb.CheckStoreForInvalidFunctions(key: PFILE_KEY; store: PFILE_STORE); // I1520 -var - n: Integer; - pwsz: PWideChar; - rec: TSentinelRecord; -const - wcsentinel: WideString = #$FFFF; -begin - n := Pos(wcsentinel, store.dpString); - // Disable the check with versions >= 10.0, since we now support deadkeys in stores. - if (n > 0) and not IsKeyboardVersion10OrLater then - begin - pwsz := PWideChar(store.dpString); - Inc(pwsz, n-1); - rec := ExpandSentinel(pwsz); - ReportError(key.Line, CERR_NotSupportedInKeymanWebStore, Format('%s is not currently supported in store ''%s'' when used by any or index', [GetCodeName(rec.Code), store.szName])); // I4061 - end; -end; - -//{$WARNINGS ON} // bug in Delphi compiler returning W1035 return value undefined?!? - - -function TCompileKeymanWeb.ExpandSentinel( - pwsz: PWideChar): TSentinelRecord; -var - i: Integer; - Found: Boolean; -begin - FillChar(Result, SizeOf(Result), 0); - if Ord(pwsz^) = UC_SENTINEL then - begin - Result.IsSentinel := True; - Inc(pwsz); - Result.Code := Ord(pwsz^); - Inc(pwsz); - case Result.Code of - CODE_ANY, CODE_NOTANY: // I3981 - begin - Result.Any.StoreIndex := Ord(pwsz^) - 1; - Result.Any.Store := fk.dpStoreArray; - Inc(Result.Any.Store, Result.Any.StoreIndex); - end; - CODE_INDEX: - begin - Result.Index.StoreIndex := Ord(pwsz^) - 1; - Result.Index.Store := fk.dpStoreArray; - Inc(Result.Index.Store, Result.Index.StoreIndex); - Inc(pwsz); - Result.Index.Index := Ord(pwsz^); - end; - CODE_DEADKEY: - Result.DeadKey.DeadKey := Ord(pwsz^) - 1; - CODE_USE: - begin - Result.Use.GroupIndex := Ord(pwsz^) - 1; - Result.Use.Group := fk.dpGroupArray; - Inc(Result.Use.Group, Result.Use.GroupIndex); - end; - CODE_CALL: - begin - Result.Call.StoreIndex := Ord(pwsz^) - 1; - Result.Call.Store := fk.dpStoreArray; - Inc(Result.Call.Store, Result.Call.StoreIndex); - end; - CODE_CONTEXTEX: - Result.ContextEx.Index := Ord(pwsz^); - CODE_SETOPT: // I3429 - begin - Result.SetOpt.StoreIndex1 := Ord(pwsz^) - 1; - Result.SetOpt.Store1 := fk.dpStoreArray; - Inc(Result.SetOpt.Store1, Result.SetOpt.StoreIndex1); - Inc(pwsz); - Result.SetOpt.StoreIndex2 := Ord(pwsz^) - 1; - Result.SetOpt.Store2 := fk.dpStoreArray; - Inc(Result.SetOpt.Store2, Result.SetOpt.StoreIndex2); - end; - CODE_SETSYSTEMSTORE: // I3437 - begin - Result.SetSystemStore.dwSystemID := Ord(pwsz^) - 1; - Result.SetSystemStore.SystemStore := fk.dpStoreArray; - Found := False; - - for i := 0 to fk.cxStoreArray - 1 do - begin - if Result.SetSystemStore.SystemStore.dwSystemID = Result.SetSystemStore.dwSystemID then - begin - Found := True; - Break; - end; - Inc(Result.SetSystemStore.SystemStore); - end; - - if not Found then Result.SetSystemStore.SystemStore := nil; - - Inc(pwsz); - Result.SetSystemStore.StoreIndex := Ord(pwsz^) - 1; - Result.SetSystemStore.Store := fk.dpStoreArray; - Inc(Result.SetSystemStore.Store, Result.SetSystemStore.StoreIndex); - end; - CODE_RESETOPT: // I3429 - begin - Result.ResetOpt.StoreIndex := Ord(pwsz^) - 1; - Result.ResetOpt.Store := fk.dpStoreArray; - Inc(Result.ResetOpt.Store, Result.ResetOpt.StoreIndex); - end; - CODE_SAVEOPT: // I3429 - begin - Result.SaveOpt.StoreIndex := Ord(pwsz^) - 1; - Result.SaveOpt.Store := fk.dpStoreArray; - Inc(Result.SaveOpt.Store, Result.SaveOpt.StoreIndex); - end; - CODE_IFOPT: // I3429 - begin - Result.IfOpt.StoreIndex1 := Ord(pwsz^) - 1; - Result.IfOpt.Store1 := fk.dpStoreArray; - Inc(Result.IfOpt.Store1, Result.IfOpt.StoreIndex1); - Inc(pwsz); - Result.IfOpt.IsNot := Ord(pwsz^) - 1; // I3429 - Inc(pwsz); - Result.IfOpt.StoreIndex2 := Ord(pwsz^) - 1; - Result.IfOpt.Store2 := fk.dpStoreArray; - Inc(Result.IfOpt.Store2, Result.IfOpt.StoreIndex2); - end; - CODE_IFSYSTEMSTORE: // I3430 - begin - Result.IfSystemStore.dwSystemID := Ord(pwsz^) - 1; - Result.IfSystemStore.SystemStore := fk.dpStoreArray; - - Found := False; - - for i := 0 to fk.cxStoreArray - 1 do - begin - if Result.IfSystemStore.SystemStore.dwSystemID = Result.IfSystemStore.dwSystemID then - begin - Found := True; - Break; - end; - Inc(Result.IfSystemStore.SystemStore); - end; - - if not Found then Result.IfSystemStore.SystemStore := nil; - Inc(pwsz); - Result.IfSystemStore.IsNot := Ord(pwsz^) - 1; // I3430 - Inc(pwsz); - Result.IfSystemStore.StoreIndex := Ord(pwsz^) - 1; - Result.IfSystemStore.Store := fk.dpStoreArray; - Inc(Result.IfSystemStore.Store, Result.IfSystemStore.StoreIndex); - end; - end; - end - else - Result.ChrVal := GetSuppChar(pwsz); -end; - -function TCompileKeymanWeb.CallFunctionName(s: WideString): WideString; -var - n: Integer; -begin - n := Pos(':', s); - if n = 0 then Result := s - else Result := Copy(s,n+1,Length(s)); -end; - -/// -/// If debug mode, then returns Javascript code necessary for -/// accessing constants in the compiled keyboard -/// -/// @return string of JavaScript code -/// -function TCompileKeymanWeb.JavaScript_SetupDebug: string; -begin - if IsKeyboardVersion10OrLater then - begin - if FDebug then - Result := 'var modCodes = keyman.osk.modifierCodes;'+nl+ - FTabStop+'var keyCodes = keyman.osk.keyCodes;'+nl - else - Result := ''; - end - else - Result := ''; -end; - -function TCompileKeymanWeb.JavaScript_SetupProlog: string; -begin - if IsKeyboardVersion10OrLater then - begin - Result := 'if(typeof keyman === ''undefined'') {'+nl+ - FTabStop+'console.log(''Keyboard requires KeymanWeb 10.0 or later'');'+nl+ - FTabStop+'if(typeof tavultesoft !== ''undefined'') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");'+nl+ - '} else {'; - end - else - Result := ''; -end; - -function TCompileKeymanWeb.JavaScript_SetupEpilog: string; -begin - if IsKeyboardVersion10OrLater then - Result := '}' - else - Result := ''; -end; -/// -/// Converts a modifier bit mask integer into its component bit flags -/// -/// @param FBitMask A KMX modifier bitmask value -/// -/// @return string of JavaScript code, e.g. 'modCodes.SHIFT | modCodes.CTRL /* 0x0030 */' -/// -function TCompileKeymanWeb.FormatModifierAsBitflags(FBitMask: Cardinal): string; -const - mask: array[0..14] of string = ( - 'LCTRL', // 0X0001 - 'RCTRL', // 0X0002 - 'LALT', // 0X0004 - 'RALT', // 0X0008 - - 'SHIFT', // 0X0010 - 'CTRL', // 0X0020 - 'ALT', // 0X0040 - - '???', // Reserved - - 'CAPS', // 0X0100 - 'NO_CAPS', // 0X0200 - - 'NUM_LOCK', // 0X0400 - 'NO_NUM_LOCK', // 0X0800 - - 'SCROLL_LOCK', // 0X1000 - 'NO_SCROLL_LOCK', // 0X2000 - - 'VIRTUAL_KEY' // 0X4000 - ); -var - i: Integer; -begin - //TODO: We need to think about mnemonic layouts which are incompletely supported at present - //tavultesoft.keymanweb.osk. - - if IsKeyboardVersion10OrLater then - begin - // This depends on flags defined in KeymanWeb 10.0 - Result := ''; - - for i := 0 to High(mask) do - begin - if FBitMask and (1 shl i) <> 0 then - begin - if Result <> '' then Result := Result + ' | '; - Result := Result + 'modCodes.'+mask[i]; - end; - end; - - if Result = '' then - Result := '0'; - - Result := Result + ' /* 0x' + IntToHex(FBitMask, 4) + ' */' - end - else - Result := '0x'+IntToHex(FBitMask, 4); -end; - -/// -/// Converts a key value into a constant -/// -/// @param key A virtual key code -/// -/// @return string of JavaScript code, e.g. 'keyCodes.K_A /* 0x41 */' -/// -function TCompileKeymanWeb.FormatKeyAsString(key: Integer): string; -begin - if IsKeyboardVersion10OrLater then - begin - // Depends on flags defined in KeymanWeb 10.0 - if (key <= 255) and (KMWVKeyNames[key] <> '') - then Result := 'keyCodes.'+KMWVKeyNames[key]+ ' /* 0x' + IntToHex(key, 2) + ' */' - else Result := '0x' + IntToHex(key, 2); - end - else - Result := '0x' + IntToHex(key, 2); -end; - -/// -/// Determine the modifiers used in the target keyboard and return a bitmask -/// representing them, or an integer value when not in debug mode -/// -/// @return string of JavaScript code, e.g. 'modCodes.SHIFT | modCodes.CTRL /* 0x0030 */' -/// -function TCompileKeymanWeb.GetKeyboardModifierBitmask: string; -var - i: Integer; - gp: PFILE_GROUP; - j: Integer; - kp: PFILE_KEY; - FBitMask: Integer; -begin - FBitMask := 0; - gp := fk.dpGroupArray; - if fk.cxGroupArray > 0 then - begin - for i := 0 to fk.cxGroupArray-1 do - begin - if gp.fUsingKeys then - begin - kp := gp.dpKeyArray; - if gp.cxKeyArray > 0 then - begin - for j := 0 to gp.cxKeyArray-1 do - begin - if not RuleIsExcludedByPlatform(kp) then - FBitMask := FBitMask or JavaScript_Shift(kp, fMnemonic); - Inc(kp); - end; - end; - end; - Inc(gp); - end; - end; - - if ((FBitMask and KMX_MASK_MODIFIER_CHIRAL) <> 0) and - ((FBitMask and KMX_MASK_MODIFIER_NONCHIRAL) <> 0) then - begin - ReportError(0, CWARN_DontMixChiralAndNonChiralModifiers, 'This keyboard contains Ctrl,Alt and LCtrl,LAlt,RCtrl,RAlt sets of modifiers. Use only one or the other set for web target.'); - end; - - if FDebug - then Result := FormatModifierAsBitflags(FBitMask and KMX_MASK_KEYS) // Exclude KMX_ISVIRTUALKEY, KMX_VIRTUALCHARKEY - else Result := '0x'+IntToHex(FBitMask and KMX_MASK_KEYS, 4); -end; - -end. diff --git a/developer/src/tike/compile/ValidateKeyboardInfo.pas b/developer/src/tike/compile/ValidateKeyboardInfo.pas deleted file mode 100644 index 5dc3a6a41ad..00000000000 --- a/developer/src/tike/compile/ValidateKeyboardInfo.pas +++ /dev/null @@ -1,189 +0,0 @@ -unit ValidateKeyboardInfo; - -// TODO: this unit is deprecated - -interface - -uses - System.JSON, - Keyman.Developer.System.Project.ProjectLog; - -type - TValidateKeyboardInfo = class - private - FJsonFile: string; - FSilent: Boolean; - json: TJSONObject; - function DoFieldValidation: Boolean; - function LoadJsonFile: Boolean; - constructor Create(AJsonFile: string; ASilent: Boolean); - function Failed(message: string): Boolean; - procedure Warning(message: string); - procedure Info(message: string); - procedure Hint(message: string); - public - class function Execute(JsonFile, JsonSchemaPath: string; FDistribution, FSilent: Boolean; FCallback: TProjectLogObjectEvent): Boolean; - end; - -implementation - -uses - System.Classes, - System.Generics.Collections, - System.SysUtils, - Winapi.Windows, - - BCP47Tag, - compile, -// Keyman.Developer.System.Project.ProjectLogConsole, - Keyman.System.CanonicalLanguageCodeUtils, - Keyman.System.KeyboardInfoFile, - Keyman.System.KMXFileLanguages; - -{ TValidateKeyboardInfo } - -const - SKeyboardInfoSourceSchemaJson = 'keyboard_info.source.json'; - SKeyboardInfoDistSchemaJson = 'keyboard_info.distribution.json'; - -var - GCallback: TProjectLogObjectEvent = nil; - GFilename: string; - -type TValidateJsonMessageProc = function (offset: Int64; message: PAnsiChar): BOOL; stdcall; - -// TODO: dynamically load this -function ValidateJsonFile(pwszSchemaFile, pwszJsonFile: PWideChar; MessageProc: TValidateJsonMessageProc): BOOL; - stdcall; external kmcmpdll_lib; - -function ValidateMessageProc(offset: Int64; message: PAnsiChar): BOOL; stdcall; -begin - GCallback(plsInfo, GFilename, string(AnsiString(message)), 0, 0); - Result := TRUE; -end; - -constructor TValidateKeyboardInfo.Create(AJsonFile: string; ASilent: Boolean); -begin - inherited Create; - FJsonFile := AJsonFile; - FSilent := ASilent; -end; - -function TValidateKeyboardInfo.DoFieldValidation: Boolean; -var - alangs: TJSONArray; - langs: TJSONValue; - i: Integer; - olangs: TJSONObject; - msg: string; -begin - if not LoadJsonFile then - Exit(False); - - // Test that language ids are valid and canonical - langs := json.Values[TKeyboardInfoFile.SLanguages]; - if not Assigned(langs) then - Exit(True); - - Result := True; - - if langs is TJSONArray then - begin - alangs := langs as TJSONArray; - for i := 0 to alangs.Count - 1 do - with TBCP47Tag.Create(alangs.Items[i].Value) do - try - if not IsValid(False, msg) then - Result := Failed(msg); - - if not TCanonicalLanguageCodeUtils.IsCanonical(Tag, msg, False, False) then - Warning(msg); - finally - Free; - end; - end - else - begin - olangs := langs as TJSONObject; - for i := 0 to olangs.Count - 1 do - with TBCP47Tag.Create(olangs.Pairs[i].JsonString.Value) do - try - if not IsValid(False, msg) then - Result := Failed(msg); - - if not TCanonicalLanguageCodeUtils.IsCanonical(Tag, msg, False, False) then - Warning(msg); - finally - Free; - end; - end; -end; - -function TValidateKeyboardInfo.Failed(message: string): Boolean; -begin - GCallback(plsError, FJsonFile, Message, 0, 0); - Result := False; -end; - -procedure TValidateKeyboardInfo.Hint(message: string); -begin - GCallback(plsHint, FJsonFile, Message, 0, 0); -end; - -procedure TValidateKeyboardInfo.Info(message: string); -begin - GCallback(plsInfo, FJsonFile, Message, 0, 0); -end; - -function TValidateKeyboardInfo.LoadJsonFile: Boolean; -begin - try - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(FJsonFile); - json := TJSONObject.ParseJsonValue(DataString) as TJSONObject; - finally - Free; - end; - except - on E:Exception do - Exit(Failed(E.Message)); - end; - - Result := Assigned(json); -end; - -procedure TValidateKeyboardInfo.Warning(message: string); -begin - GCallback(plsWarning, FJsonFile, Message, 0, 0); -end; - -class function TValidateKeyboardInfo.Execute(JsonFile, JsonSchemaPath: string; FDistribution, FSilent: Boolean; FCallback: TProjectLogObjectEvent): Boolean; -var - SchemaFile: string; - t: TValidateKeyboardInfo; -begin - GCallback := FCallback; - GFilename := JsonFile; - - if FDistribution - then SchemaFile := JsonSchemaPath + SKeyboardInfoDistSchemaJson - else SchemaFile := JsonSchemaPath + SKeyboardInfoSourceSchemaJson; - Result := ValidateJsonFile(PWideChar(SchemaFile), PWideChar(JsonFile), ValidateMessageProc); - - if Result then - begin - t := TValidateKeyboardInfo.Create(JsonFile, FSilent); - try - Result := t.DoFieldValidation; - finally - t.Free; - end; - end; - - if Result - then GCallback(plsSuccess, JsonFile, 'File '+JsonFile+' validated successfully.', 0, 0) - else GCallback(plsFailure, JsonFile, 'File '+JsonFile+' has errors.', 0, 0); -end; - -end. diff --git a/developer/src/tike/main/RedistFiles.pas b/developer/src/tike/main/RedistFiles.pas index b02c956679e..1e22bb06ef8 100644 --- a/developer/src/tike/main/RedistFiles.pas +++ b/developer/src/tike/main/RedistFiles.pas @@ -63,7 +63,6 @@ function GetRedistSetupPath: string; function GetRedistProjectTemplatePath: string; function GetWixPath: string; function GetStockKCTPath: string; -function GetDebugKMCmpDllPath: string; function GetUnicodeDataSourcePath(DefaultPath: string = ''): string; // I3463 function GetXMLTemplatePath: string; function GetDeveloperRootPath: string; @@ -135,24 +134,6 @@ function GetRedistProjectTemplatePath: string; Result := GetDebugPath('Debug_RedistTemplatePath', Result); end; -function GetDebugKMCmpDllPath: string; -var - root: string; -const - DevPlatform={$IFDEF WIN64}'x64'{$ELSE}'Win32'{$ENDIF}; - DevConfig={$IFDEF DEBUG}'Debug'{$ELSE}'Release'{$ENDIF}; - DevSrcCompilerPath = 'developer\src\kmcmpdll\bin\'+DevPlatform+'\'+DevConfig; - DevCompilerPath = 'developer\bin'; -begin - if TKeymanPaths.RunningFromSource(root) and FileExists(root + DevSrcCompilerPath + '\kmcmpdll.dll') then - Result := root + DevSrcCompilerPath - else if TKeymanPaths.RunningFromSource(root) and FileExists(root + DevCompilerPath + '\kmcmpdll.dll') then - Result := root + DevCompilerPath - else - Result := ExtractFilePath(ParamStr(0)); - Result := GetDebugPath('Debug_KMCMPDLLPath', Result); -end; - function GetXMLTemplatePath: string; var root: string; diff --git a/developer/src/tike/main/UfrmMain.pas b/developer/src/tike/main/UfrmMain.pas index deff7780a74..4b8a7c235fa 100644 --- a/developer/src/tike/main/UfrmMain.pas +++ b/developer/src/tike/main/UfrmMain.pas @@ -272,7 +272,6 @@ TfrmKeymanDeveloper = class(TTikeForm, IUnicodeDataUIManager, IDragDrop) CompileModel1: TMenuItem; N2: TMenuItem; estLexicalModel1: TMenuItem; - mnuToolsDebugTestsCompilerExceptionTest: TMenuItem; mnuToolsDebugTestsShowDebuggerEventsPanel: TMenuItem; N3: TMenuItem; N4: TMenuItem; @@ -302,7 +301,6 @@ TfrmKeymanDeveloper = class(TTikeForm, IUnicodeDataUIManager, IDragDrop) procedure pagesChange(Sender: TObject); procedure pagesCloseTab(Sender: TObject; Index: Integer); procedure mnuToolsClick(Sender: TObject); - procedure mnuToolsDebugTestsCompilerExceptionTestClick(Sender: TObject); procedure mnuToolsDebugTestsShowDebuggerEventsPanelClick(Sender: TObject); procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); @@ -1437,12 +1435,6 @@ procedure TfrmKeymanDeveloper.cbTextFileFormatItemClick(Sender: TObject); modActionsMain.actToolsFileFormat.Execute; end; -procedure TfrmKeymanDeveloper.mnuToolsDebugTestsCompilerExceptionTestClick( - Sender: TObject); -begin - Compiler_Diagnostic(0); -end; - procedure TfrmKeymanDeveloper.mnuToolsDebugTestsExceptionTestClick(Sender: TObject); begin TKeymanSentryClient.Validate(True); diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.modelTsProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.modelTsProjectFileAction.pas index 7534a6526f8..399b127aa8c 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.modelTsProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.modelTsProjectFileAction.pas @@ -29,7 +29,6 @@ implementation System.Variants, Winapi.Windows, - CompileKeymanWeb, compile, Keyman.System.LexicalModelUtils, Keyman.Developer.System.LexicalModelCompile, diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index 3fae5dadf1d..c3c417a8170 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -89,7 +89,6 @@ uses utilfiletypes in '..\..\..\common\windows\delphi\general\utilfiletypes.pas', UfrmNewFileDetails in 'dialogs\UfrmNewFileDetails.pas' {frmNewFileDetails}, StockFileNames in '..\..\..\common\windows\delphi\general\StockFileNames.pas', - CompileKeymanWeb in 'compile\CompileKeymanWeb.pas', KeymanWebKeyCodes in 'compile\KeymanWebKeyCodes.pas', utildir in '..\..\..\common\windows\delphi\general\utildir.pas', ADOX_TLB in '..\..\..\common\windows\delphi\tlb\ADOX_TLB.pas', diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index c7ac6c36953..f1af6898566 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -236,7 +236,6 @@
frmNewFileDetails
- diff --git a/developer/src/tools/sentry-upload-difs.sh b/developer/src/tools/sentry-upload-difs.sh index 6c1f489d2de..5dd22cc886a 100755 --- a/developer/src/tools/sentry-upload-difs.sh +++ b/developer/src/tools/sentry-upload-difs.sh @@ -30,10 +30,6 @@ cd "$KEYMAN_ROOT/developer/src" # [ -f ./samples/imsample/IMSample.pdb ] && rm -f ./samples/imsample/IMSample.pdb -[ -f ./kmcmpdll/Win32/Release/kcframe.pdb ] && rm -f ./kmcmpdll/Win32/Release/kcframe.pdb -[ -f ./kmcmpdll/Win32/Debug/kcframe.pdb ] && rm -f ./kmcmpdll/Win32/Debug/kcframe.pdb -[ -f ./kmcmpdll/x64/Release/kcframe.x64.pdb ] && rm -f ./kmcmpdll/x64/Release/kcframe.x64.pdb -[ -f ./kmcmpdll/x64/Debug/kcframe.x64.pdb ] && rm -f ./kmcmpdll/x64/Debug/kcframe.x64.pdb # # Upload the files diff --git a/web/src/test/manual/web/regression-tests/README.md b/web/src/test/manual/web/regression-tests/README.md index eebf3274f8b..b818b2d8896 100644 --- a/web/src/test/manual/web/regression-tests/README.md +++ b/web/src/test/manual/web/regression-tests/README.md @@ -34,7 +34,7 @@ the keyman repository, for example: - testing - regression-tests -Keyman Engine for Web, Keyman Developer (kmcomp, kmcmpdll, kmanalyze) must be built +Keyman Engine for Web, Keyman Developer (kmcomp, kmanalyze) must be built prior to running the tests. (If not testing source versions, only kmanalyze is required). See the build documentation for each project for details; summary below: diff --git a/web/src/test/manual/web/regression-tests/test.js b/web/src/test/manual/web/regression-tests/test.js index 227583f9b0a..1205bf7b0e6 100644 --- a/web/src/test/manual/web/regression-tests/test.js +++ b/web/src/test/manual/web/regression-tests/test.js @@ -58,11 +58,10 @@ const bash = process.platform == 'win32' const compilerDestPath = path.join(config.KEYBOARDS_ROOT, 'tools'); const kmcompDestPath = path.join(compilerDestPath, 'kmcomp', 'kmcomp.exe'); -const kmcmpdllDestPath = path.join(compilerDestPath, 'kmcomp', 'kmcmpdll.dll'); let testedCompilerVersions = [], testedEngineVersions = [], firstCompile = true; -// Build keyboards -- get kmcomp.exe, kmcmpdll.dll from appropriate location +// Build keyboards -- get kmcomp.exe from appropriate location process.on('SIGINT', () => { console.log('Received Ctrl+C, aborting'); @@ -119,16 +118,12 @@ if(!program.skipAnalysis) { cleanKeyboards.then(() => { fs.renameSync(kmcompDestPath, kmcompDestPath + '.bak'); - fs.renameSync(kmcmpdllDestPath, kmcmpdllDestPath + '.bak'); process.on('exit', () => { console.log('Restoring original compiler files'); if(fs.existsSync(kmcompDestPath + '.bak')) { fs.renameSync(kmcompDestPath + '.bak', kmcompDestPath); } - if(fs.existsSync(kmcmpdllDestPath + '.bak')) { - fs.renameSync(kmcmpdllDestPath + '.bak', kmcmpdllDestPath); - } }); }).then(() => forEachPromise(program.compilerVersions, version => { // @@ -146,7 +141,6 @@ cleanKeyboards.then(() => { reject(); } fs.copyFileSync(path.join(config.KEYMAN_REPO_BASE_RELATIVE_PATH, 'developer', 'bin', 'kmcomp.exe'), kmcompDestPath); - fs.copyFileSync(path.join(config.KEYMAN_REPO_BASE_RELATIVE_PATH, 'developer', 'bin', 'kmcmpdll.dll'), kmcmpdllDestPath); resolve(); }); break; @@ -162,7 +156,6 @@ cleanKeyboards.then(() => { console.log('Unzipping compiler'); let zip = new AdmZip(response.body); zip.extractEntryTo('kmcomp.exe', compilerDestPath, false, true); - zip.extractEntryTo('kmcmpdll.dll', compilerDestPath, false, true); }); break; default: @@ -173,7 +166,6 @@ cleanKeyboards.then(() => { console.log('Unzipping compiler'); let zip = new AdmZip(response.body); zip.extractEntryTo('kmcomp.exe', compilerDestPath, false, true); - zip.extractEntryTo('kmcmpdll.dll', compilerDestPath, false, true); }); } diff --git a/windows/src/global/delphi/general/Keyman.System.Settings.pas b/windows/src/global/delphi/general/Keyman.System.Settings.pas index b22e47056f0..b021e56cddf 100644 --- a/windows/src/global/delphi/general/Keyman.System.Settings.pas +++ b/windows/src/global/delphi/general/Keyman.System.Settings.pas @@ -95,7 +95,7 @@ TKeymanSettings = class(TObjectList) ValueType: kstInteger ); - BaseKeymanSettings: array[0..34] of TKeymanSettingBase = ( + BaseKeymanSettings: array[0..33] of TKeymanSettingBase = ( // TIKE:UTikeDebugMode.TikeDebugMode ( @@ -231,15 +231,6 @@ TKeymanSettings = class(TObjectList) Description: 'Source files for Unicode data' ), - // TIKE:RedistFiles.GetDebugKMCmpDllPath - ( - ID: 'development.paths.developer.kmcmpdll'; - Name: 'Debug_KMCMPDLLPath'; - RootKey: HKCU; - Key: SRegKey_KeymanDebug_CU; - Description: 'Path to debug version of kmcmpdll.dll' - ), - // TIKE:RedistFiles.GetRedistAddinsPath ( ID: 'development.paths.developer.redistributable_addins'; diff --git a/windows/src/support/kdebug/Makefile b/windows/src/support/kdebug/Makefile index 2655466a54f..1a13a18ebe4 100644 --- a/windows/src/support/kdebug/Makefile +++ b/windows/src/support/kdebug/Makefile @@ -1,5 +1,5 @@ # -# KMCmpDll Makefile +# KDebug Makefile # !include ..\..\Defines.mak diff --git a/windows/src/test/manual-tests/test_httpuploader/test_httpuploader.dproj b/windows/src/test/manual-tests/test_httpuploader/test_httpuploader.dproj index d66af4279da..594c25faaf4 100644 --- a/windows/src/test/manual-tests/test_httpuploader/test_httpuploader.dproj +++ b/windows/src/test/manual-tests/test_httpuploader/test_httpuploader.dproj @@ -186,9 +186,6 @@ 'C:\Program Files\Microsoft Visual Studio 8\VC' 'C:\Program Files\Microsoft Visual Studio 8' - - 'C:\keyman\7.0\src\addins;C:\keyman\7.0\src\buildtools;C:\keyman\7.0\src\desktop;C:\keyman\7.0\src\developer;C:\keyman\7.0\src\engine;C:\keyman\7.0\src\global;C:\keyman\7.0\src\online;C:\keyman\7.0\src\support;C:\keyman\7.0\src\test;C:\keyman\7.0\src\addins\inst;C:\keyman\7.0\src\addins\kmtip;C:\keyman\7.0\src\addins\richedit;C:\keyman\7.0\src\addins\wordlink;C:\keyman\7.0\src\addins\inst\addininst;C:\keyman\7.0\src\addins\wordlink\dot;C:\keyman\7.0\src\addins\wordlink\wordlink_wll;C:\keyman\7.0\src\buildtools\buildunidata;C:\keyman\7.0\src\buildtools\cvscommit;C:\keyman\7.0\src\buildtools\delphiprojectmanager;C:\keyman\7.0\src\buildtools\devreg;C:\keyman\7.0\src\buildtools\genkmnp;C:\keyman\7.0\src\buildtools\getfilelocks;C:\keyman\7.0\src\buildtools\help;C:\keyman\7.0\src\buildtools\InitProductStatus;C:\keyman\7.0\src\buildtools\inst;C:\keyman\7.0\src\buildtools\isadmin;C:\keyman\7.0\src\buildtools\kmdebug;C:\keyman\7.0\src\buildtools\makecert;C:\keyman\7.0\src\buildtools\makeeval;C:\keyman\7.0\src\buildtools\makestockkct;C:\keyman\7.0\src\buildtools\mkver;C:\keyman\7.0\src\buildtools\msimsp;C:\keyman\7.0\src\buildtools\productstatus;C:\keyman\7.0\src\buildtools\stockeditor;C:\keyman\7.0\src\buildtools\test-checkcert;C:\keyman\7.0\src\buildtools\versionhistory;C:\keyman\7.0\src\buildtools\xslt;C:\keyman\7.0\src\buildtools\help\idx;C:\keyman\7.0\src\buildtools\help\img;C:\keyman\7.0\src\buildtools\help\inc;C:\keyman\7.0\src\buildtools\help\main;C:\keyman\7.0\src\desktop\branding;C:\keyman\7.0\src\desktop\help;C:\keyman\7.0\src\desktop\inst;C:\keyman\7.0\src\desktop\kmshell;C:\keyman\7.0\src\desktop\help\context;C:\keyman\7.0\src\desktop\help\idx;C:\keyman\7.0\src\desktop\help\img;C:\keyman\7.0\src\desktop\help\inc;C:\keyman\7.0\src\desktop\help\main;C:\keyman\7.0\src\desktop\help\rtf;C:\keyman\7.0\src\desktop\inst\Bitmaps;C:\keyman\7.0\src\desktop\kmshell\install;C:\keyman\7.0\src\desktop\kmshell\main;C:\keyman\7.0\src\desktop\kmshell\product;C:\keyman\7.0\src\desktop\kmshell\rel;C:\keyman\7.0\src\desktop\kmshell\render;C:\keyman\7.0\src\desktop\kmshell\startup;C:\keyman\7.0\src\desktop\kmshell\tsysinfo;C:\keyman\7.0\src\desktop\kmshell\uninstall;C:\keyman\7.0\src\desktop\kmshell\util;C:\keyman\7.0\src\desktop\kmshell\view;C:\keyman\7.0\src\desktop\kmshell\xml;C:\keyman\7.0\src\developer\help;C:\keyman\7.0\src\developer\inst;C:\keyman\7.0\src\developer\kmcmpdll;C:\keyman\7.0\src\developer\kmcomp;C:\keyman\7.0\src\developer\samples;C:\keyman\7.0\src\developer\stock;C:\keyman\7.0\src\developer\TIKE;C:\keyman\7.0\src\developer\uitemplates;C:\keyman\7.0\src\developer\help\context;C:\keyman\7.0\src\developer\help\guide;C:\keyman\7.0\src\developer\help\idx;C:\keyman\7.0\src\developer\help\img;C:\keyman\7.0\src\developer\help\imx;C:\keyman\7.0\src\developer\help\inc;C:\keyman\7.0\src\developer\help\main;C:\keyman\7.0\src\developer\help\reference;C:\keyman\7.0\src\developer\help\rtf;C:\keyman\7.0\src\developer\help\tutorial;C:\keyman\7.0\src\developer\inst\Bitmaps;C:\keyman\7.0\src\developer\inst\data;C:\keyman\7.0\src\developer\inst\devinsthelper;C:\keyman\7.0\src\developer\inst\fonts;C:\keyman\7.0\src\developer\inst\old;C:\keyman\7.0\src\developer\inst\prodinsthelp;C:\keyman\7.0\src\developer\inst\version_upgrade;C:\keyman\7.0\src\developer\inst\wix;C:\keyman\7.0\src\developer\inst\version_upgrade\old_cpp;C:\keyman\7.0\src\developer\inst\wix\Bitmaps;C:\keyman\7.0\src\developer\inst\wix\doc;C:\keyman\7.0\src\developer\inst\wix\examples;C:\keyman\7.0\src\developer\inst\wix\inc;C:\keyman\7.0\src\developer\inst\wix\lib;C:\keyman\7.0\src\developer\inst\wix\examples\first;C:\keyman\7.0\src\developer\inst\wix\examples\simple;C:\keyman\7.0\src\developer\inst\wix\examples\simple\bin;C:\keyman\7.0\src\developer\kmcmpdll\debug;C:\keyman\7.0\src\developer\kmcmpdll\Release;C:\keyman\7.0\src\developer\samples\Examples;C:\keyman\7.0\src\developer\samples\imsample;C:\keyman\7.0\src\developer\samples\Products;C:\keyman\7.0\src\developer\samples\Tests;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\bin;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\My Project;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\obj;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\bin\Debug;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\bin\Release;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\obj\Debug;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\obj\Release;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\obj\Debug\TempPE;C:\keyman\7.0\src\developer\samples\Products\LaoUnicode\LaoUnicode\obj\Release\TempPE;C:\keyman\7.0\src\developer\TIKE\actions;C:\keyman\7.0\src\developer\TIKE\activation;C:\keyman\7.0\src\developer\TIKE\child;C:\keyman\7.0\src\developer\TIKE\compile;C:\keyman\7.0\src\developer\TIKE\crm;C:\keyman\7.0\src\developer\TIKE\debug;C:\keyman\7.0\src\developer\TIKE\dialogs;C:\keyman\7.0\src\developer\TIKE\dockforms;C:\keyman\7.0\src\developer\TIKE\help;C:\keyman\7.0\src\developer\TIKE\kct;C:\keyman\7.0\src\developer\TIKE\main;C:\keyman\7.0\src\developer\TIKE\project;C:\keyman\7.0\src\developer\TIKE\rel;C:\keyman\7.0\src\developer\TIKE\tests;C:\keyman\7.0\src\developer\TIKE\xml;C:\keyman\7.0\src\developer\TIKE\kct\Editors;C:\keyman\7.0\src\developer\TIKE\kct\icons;C:\keyman\7.0\src\developer\TIKE\xml\help;C:\keyman\7.0\src\developer\TIKE\xml\product;C:\keyman\7.0\src\developer\TIKE\xml\project;C:\keyman\7.0\src\engine\inst;C:\keyman\7.0\src\engine\keyman;C:\keyman\7.0\src\engine\keyman32;C:\keyman\7.0\src\engine\kmcomapi;C:\keyman\7.0\src\engine\inst\insthelper;C:\keyman\7.0\src\engine\keyman\viskbd;C:\keyman\7.0\src\engine\keyman32\appint;C:\keyman\7.0\src\engine\kmcomapi\com;C:\keyman\7.0\src\engine\kmcomapi\idl;C:\keyman\7.0\src\engine\kmcomapi\processes;C:\keyman\7.0\src\engine\kmcomapi\util;C:\keyman\7.0\src\engine\kmcomapi\com\addins;C:\keyman\7.0\src\engine\kmcomapi\com\customisation;C:\keyman\7.0\src\engine\kmcomapi\com\errors;C:\keyman\7.0\src\engine\kmcomapi\com\hotkeys;C:\keyman\7.0\src\engine\kmcomapi\com\keyboards;C:\keyman\7.0\src\engine\kmcomapi\com\languages;C:\keyman\7.0\src\engine\kmcomapi\com\licences;C:\keyman\7.0\src\engine\kmcomapi\com\options;C:\keyman\7.0\src\engine\kmcomapi\com\packages;C:\keyman\7.0\src\engine\kmcomapi\com\products;C:\keyman\7.0\src\engine\kmcomapi\com\system;C:\keyman\7.0\src\engine\kmcomapi\processes\addin;C:\keyman\7.0\src\engine\kmcomapi\processes\font;C:\keyman\7.0\src\engine\kmcomapi\processes\keyboard;C:\keyman\7.0\src\engine\kmcomapi\processes\package;C:\keyman\7.0\src\engine\kmcomapi\processes\product;C:\keyman\7.0\src\engine\kmcomapi\processes\visualkeyboard;C:\keyman\7.0\src\global\delphi;C:\keyman\7.0\src\global\help;C:\keyman\7.0\src\global\inc;C:\keyman\7.0\src\global\inst;C:\keyman\7.0\src\global\res;C:\keyman\7.0\src\global\vc;C:\keyman\7.0\src\global\wix;C:\keyman\7.0\src\global\delphi\charmap;C:\keyman\7.0\src\global\delphi\comp;C:\keyman\7.0\src\global\delphi\crypt;C:\keyman\7.0\src\global\delphi\cust;C:\keyman\7.0\src\global\delphi\debug;C:\keyman\7.0\src\global\delphi\dragdrop;C:\keyman\7.0\src\global\delphi\excmagic;C:\keyman\7.0\src\global\delphi\folderdlg;C:\keyman\7.0\src\global\delphi\general;C:\keyman\7.0\src\global\delphi\htmlhelp;C:\keyman\7.0\src\global\delphi\hwkey;C:\keyman\7.0\src\global\delphi\indy;C:\keyman\7.0\src\global\delphi\jedi;C:\keyman\7.0\src\global\delphi\online;C:\keyman\7.0\src\global\delphi\plusmemou;C:\keyman\7.0\src\global\delphi\png;C:\keyman\7.0\src\global\delphi\productactivation;C:\keyman\7.0\src\global\delphi\tlb;C:\keyman\7.0\src\global\delphi\vcl;C:\keyman\7.0\src\global\delphi\vclzip;C:\keyman\7.0\src\global\delphi\visualkeyboard;C:\keyman\7.0\src\global\delphi\xdom;C:\keyman\7.0\src\global\delphi\xstringgrid;C:\keyman\7.0\src\global\delphi\comp\bitmapeditor;C:\keyman\7.0\src\global\delphi\crypt\Ciphers;C:\keyman\7.0\src\global\delphi\crypt\Docs;C:\keyman\7.0\src\global\delphi\crypt\Hashes;C:\keyman\7.0\src\global\delphi\excmagic\unit;C:\keyman\7.0\src\global\delphi\excmagic\unit\RC;C:\keyman\7.0\src\global\delphi\indy\Core;C:\keyman\7.0\src\global\delphi\indy\Protocols;C:\keyman\7.0\src\global\delphi\indy\SuperCore;C:\keyman\7.0\src\global\delphi\indy\System;C:\keyman\7.0\src\global\inst\data;C:\keyman\7.0\src\global\inst\data\Unihan;C:\keyman\7.0\src\global\res\70;C:\keyman\7.0\src\global\res\bmp;C:\keyman\7.0\src\global\res\cimu;C:\keyman\7.0\src\global\res\geezword;C:\keyman\7.0\src\global\res\keyman;C:\keyman\7.0\src\global\res\lswin;C:\keyman\7.0\src\global\res\paul;C:\keyman\7.0\src\global\res\70\desktop;C:\keyman\7.0\src\global\res\70\developer;C:\keyman\7.0\src\global\res\70\filetypes;C:\keyman\7.0\src\global\res\bmp\splash;C:\keyman\7.0\src\global\res\geezword\bmp;C:\keyman\7.0\src\global\res\geezword\ico;C:\keyman\7.0\src\global\res\geezword\other;C:\keyman\7.0\src\global\res\geezword\ico\16x16;C:\keyman\7.0\src\global\res\geezword\ico\32x32;C:\keyman\7.0\src\global\res\geezword\ico\unused;C:\keyman\7.0\src\global\res\keyman\bmp;C:\keyman\7.0\src\global\res\keyman\ico;C:\keyman\7.0\src\global\res\keyman\other;C:\keyman\7.0\src\global\res\keyman\bmp\startup;C:\keyman\7.0\src\global\res\keyman\bmp\tip;C:\keyman\7.0\src\global\res\keyman\ico\16x16;C:\keyman\7.0\src\global\res\keyman\ico\32x32;C:\keyman\7.0\src\global\res\keyman\ico\unused;C:\keyman\7.0\src\global\res\lswin\bmp;C:\keyman\7.0\src\global\res\lswin\ico;C:\keyman\7.0\src\global\res\lswin\other;C:\keyman\7.0\src\global\res\lswin\ico\16x16;C:\keyman\7.0\src\global\res\lswin\ico\32x32;C:\keyman\7.0\src\global\res\lswin\ico\unused;C:\keyman\7.0\src\global\res\paul\dev-images;C:\keyman\7.0\src\global\res\paul\dev-images\6.0-icons;C:\keyman\7.0\src\global\res\paul\dev-images\filetypes;C:\keyman\7.0\src\global\res\paul\dev-images\icons;C:\keyman\7.0\src\global\res\paul\dev-images\install;C:\keyman\7.0\src\global\res\paul\dev-images\keyman;C:\keyman\7.0\src\global\res\paul\dev-images\tike;C:\keyman\7.0\src\global\res\paul\dev-images\6.0-icons\16x16;C:\keyman\7.0\src\global\res\paul\dev-images\6.0-icons\32x32;C:\keyman\7.0\src\global\res\paul\dev-images\6.0-icons\unused;C:\keyman\7.0\src\global\res\paul\dev-images\keyman\icons;C:\keyman\7.0\src\global\res\paul\dev-images\keyman\xml;C:\keyman\7.0\src\global\res\paul\dev-images\keyman\xml\img;C:\keyman\7.0\src\global\res\paul\dev-images\keyman\xml\kbimg;C:\keyman\7.0\src\global\res\paul\dev-images\tike\xml;C:\keyman\7.0\src\global\res\paul\dev-images\tike\xml\kmg;C:\keyman\7.0\src\global\wix\wixui;C:\keyman\7.0\src\global\wix\wixui\Bitmaps;C:\keyman\7.0\src\global\wix\wixui\featuretree;C:\keyman\7.0\src\global\wix\wixui\installdir;C:\keyman\7.0\src\global\wix\wixui\minimal;C:\keyman\7.0\src\global\wix\wixui\mondo;C:\keyman\7.0\src\online\certificate;C:\keyman\7.0\src\online\k61licence;C:\keyman\7.0\src\online\kencrypt;C:\keyman\7.0\src\online\klicence;C:\keyman\7.0\src\support\braziliantest;C:\keyman\7.0\src\support\imtest;C:\keyman\7.0\src\support\kdebug;C:\keyman\7.0\src\support\keycodetester;C:\keyman\7.0\src\support\kmdecomp;C:\keyman\7.0\src\support\richedit_examine;C:\keyman\7.0\src\support\testcryptdepend;C:\keyman\7.0\src\support\windowinfo;C:\keyman\7.0\src\support\xstringtest;C:\keyman\7.0\src\test\regressiontest;C:\keyman\7.0\src\test\test-comapi;C:\keyman\7.0\src\test\test_alphanumcode;C:\keyman\7.0\src\test\test_uuidgen;C:\keyman\7.0\src\test\regressiontest\tests;C:\keyman\7.0\src\test\test_uuidgen\del_uuidgen;C:\keyman\7.0\src\test\test_uuidgen\vc_uuidgen;C:\keyman\7.0\src\test\test_uuidgen\vc_uuidgen\vc_uuidgen' - $00000C09 From 3a2540b0772ebd9bbfd1821a1a2428fc569aaf1d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 9 Sep 2023 17:47:15 +0200 Subject: [PATCH 046/207] feat(developer): remove kmcomp Removes kmcomp.dpr, related source, compilekeymanweb.pas, and related unit tests. Cleans up a couple of bits and pieces around kmc in order to help tests to pass, and updates some tests that had legacy code in them (particularly missing &TARGETS stores). --- .github/labeler.yml | 2 +- .github/multi-labeler.yml | 12 - .gitignore | 6 - common/test/keyboards/build.sh | 4 +- core/doc/BUILDING.md | 7 - developer/src/.gitignore | 1 - developer/src/Makefile | 13 +- developer/src/README.md | 5 - developer/src/developer.groupproj | 18 +- developer/src/inst/download.in.mak | 13 +- developer/src/inst/kmdev.wxs | 8 - .../commands/buildClasses/BuildKmnKeyboard.ts | 15 +- .../src/messages/infrastructureMessages.ts | 4 + .../src/kmc/src/util/getDeveloperBinPath.ts | 10 +- developer/src/kmcmplib/src/version.rc | 4 +- .../valid-keyboards/compile_legacy.bat | 1 + developer/src/kmcmplib/version.rc | 4 +- ...loper.System.Project.ProjectLogConsole.pas | 193 --- ...n.Developer.System.ValidateRepoChanges.pas | 463 ------- developer/src/kmcomp/Makefile | 57 - developer/src/kmcomp/icons.rc | 1 - developer/src/kmcomp/kccompilekvk.pas | 97 -- developer/src/kmcomp/kccompilepackage.pas | 111 -- developer/src/kmcomp/kccompileproject.pas | 138 -- developer/src/kmcomp/kmcomp.dpr | 147 --- developer/src/kmcomp/kmcomp.dproj | 1172 ----------------- developer/src/kmcomp/kmcomp.res | Bin 940 -> 0 bytes developer/src/kmcomp/kmcomp_Icon.ico | Bin 766 -> 0 bytes developer/src/kmcomp/main.pas | 327 ----- developer/src/kmcomp/manifest.in | 22 - developer/src/kmcomp/manifest.rc | 1 - developer/src/kmcomp/version.rc | 32 - ...n.Developer.System.KMConvertParameters.pas | 2 +- developer/src/test/auto/Makefile | 11 - .../compile-supplementary-support/Makefile | 43 +- .../i3317_nosupp.kmn | 4 +- .../i3317_withsupp.kmn | 6 +- .../i3317_withsupp_incontext.kmn | 6 +- .../i3317_withsupp_inmatch.kmn | 6 +- .../i3317_withsupp_innomatch.kmn | 8 +- .../i3317_withsupp_instore.kmn | 10 +- .../src/test/auto/keyboard-info/Makefile | 18 - .../test-invalid-language-code.keyboard_info | 17 - ...-non-canonical-language-code.keyboard_info | 17 - .../keyboard-info/test-valid.keyboard_info | 21 - .../src/test/auto/keyboard-info/test.bat | 61 - .../KeyboardPackageVersionsTestSuite.dpr | 1 - .../KeyboardPackageVersionsTestSuite.dproj | 1 - .../emptystorename.kmn | 8 - .../test/auto/kmcomp-x64-structures/Makefile | 2 +- .../cppstructsize/cppstructsize.vcxproj | 14 +- .../cppstructsize.vcxproj.filters | 2 +- developer/src/test/auto/kmcomp/Makefile | 6 +- developer/src/test/auto/kmcomp/test.bat | 12 +- .../auto/kmcomp/test_194_Filename_case.kvk | Bin 2878 -> 0 bytes .../kmcomp/test_194_filename_case.out.txt | 22 +- .../src/tike/compile/MergeKeyboardInfo.pas | 1134 ---------------- developer/src/tike/tests/Makefile | 4 +- docs/build/macos.md | 4 +- docs/build/old/core-desktop-notes.md | 47 +- resources/devbox/macos/macos.sh | 16 +- .../manual/web/regression-tests/README.md | 5 +- windows/src/desktop/inst/Makefile | 2 +- .../regressiontest/tests/Makefile | 4 +- 64 files changed, 141 insertions(+), 4261 deletions(-) delete mode 100644 developer/src/kmcomp/Keyman.Developer.System.Project.ProjectLogConsole.pas delete mode 100644 developer/src/kmcomp/Keyman.Developer.System.ValidateRepoChanges.pas delete mode 100644 developer/src/kmcomp/Makefile delete mode 100644 developer/src/kmcomp/icons.rc delete mode 100644 developer/src/kmcomp/kccompilekvk.pas delete mode 100644 developer/src/kmcomp/kccompilepackage.pas delete mode 100644 developer/src/kmcomp/kccompileproject.pas delete mode 100644 developer/src/kmcomp/kmcomp.dpr delete mode 100644 developer/src/kmcomp/kmcomp.dproj delete mode 100644 developer/src/kmcomp/kmcomp.res delete mode 100644 developer/src/kmcomp/kmcomp_Icon.ico delete mode 100644 developer/src/kmcomp/main.pas delete mode 100644 developer/src/kmcomp/manifest.in delete mode 100644 developer/src/kmcomp/manifest.rc delete mode 100644 developer/src/kmcomp/version.rc delete mode 100644 developer/src/test/auto/keyboard-info/Makefile delete mode 100644 developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info delete mode 100644 developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info delete mode 100644 developer/src/test/auto/keyboard-info/test-valid.keyboard_info delete mode 100644 developer/src/test/auto/keyboard-info/test.bat delete mode 100644 developer/src/test/auto/kmcomp-empty-store-names/emptystorename.kmn delete mode 100644 developer/src/test/auto/kmcomp/test_194_Filename_case.kvk delete mode 100644 developer/src/tike/compile/MergeKeyboardInfo.pas diff --git a/.github/labeler.yml b/.github/labeler.yml index c7406d0f6fb..5250e321935 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -35,8 +35,8 @@ developer/: - developer/** developer/compilers/: - - developer/src/kmcomp/** - developer/src/kmc/** + - developer/src/kmcmplib/** - developer/src/kmc-*/** developer/ide/: diff --git a/.github/multi-labeler.yml b/.github/multi-labeler.yml index b2e84bec107..88bb0e73ca6 100644 --- a/.github/multi-labeler.yml +++ b/.github/multi-labeler.yml @@ -87,15 +87,3 @@ labels: - label: 'windows/' matcher: title: '\(.*windows.*\):' - - # - # epics -- we will add/remove these as we work on new epics each release - # - - - label: 'epic-ldml' - matcher: - branch: '.*epic-ldml.*' # anywhere in the branch name, e.g. feat/epic-ldml/developer/... or feat/developer/foo-epic-ldml - - - label: 'epic-kmcompx' - matcher: - branch: '.*epic-kmcompx.*' diff --git a/.gitignore b/.gitignore index 7efdd873b1d..9f24ff68a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -185,9 +185,3 @@ lcov.info /keyman*.changes /keyman*.tar.?z -#Sabine: -# /common/test/keyboards/invalid/source/*.kmx -# /developer/src/test/auto/kmcomp/*.kmn -# /developer/src/test/auto/kmcomp/*.kvk -# /developer/src/test/auto/kmcomp/*.kvk* -# /developer/src/test/auto/kmcomp/*.txt diff --git a/common/test/keyboards/build.sh b/common/test/keyboards/build.sh index 43562ffb13a..2b0a782b94d 100755 --- a/common/test/keyboards/build.sh +++ b/common/test/keyboards/build.sh @@ -19,8 +19,8 @@ display_usage() { echo " --debug, -d Debug build" echo " --silent, -s Suppress information messages" echo " --keyboard, -k Build only keyboards (not packages)" - echo " --kmcomp path Specify path to kmcomp.exe, defaults" - echo " to windows/bin/developer/kmcomp.exe" + echo " --kmc path Specify path to kmc, defaults" + echo " to developer/src/kmc/build/" echo " --zip-source Create zip file for source of each" echo " keyboard for artifact tests" echo " --index Build index.html for artifact tests" diff --git a/core/doc/BUILDING.md b/core/doc/BUILDING.md index ae066550465..5dfd46f9d61 100644 --- a/core/doc/BUILDING.md +++ b/core/doc/BUILDING.md @@ -8,10 +8,3 @@ See [build configuration](../../docs/build/index.md) for details on how to confi On all platforms, use `build.sh`. * See `./build.sh --help` for more details - -## Note on kmcomp - -kmcomp is the command-line compiler from Keyman Developer, available from - or in this repo in -`/windows/src/developer/kmcomp`. The compiler is currently available as a -Windows PE executable only, but it does run under WINE. diff --git a/developer/src/.gitignore b/developer/src/.gitignore index 0a4cbf81ca8..31b79db664b 100644 --- a/developer/src/.gitignore +++ b/developer/src/.gitignore @@ -72,7 +72,6 @@ tike/xml/kmw/resource # These files are generated during build tike/icons.res -kmcomp/icons.res tds_file.txt # These are built from corresponding manifest.in and version.in diff --git a/developer/src/Makefile b/developer/src/Makefile index cfecca71b8f..b2b2408c76a 100644 --- a/developer/src/Makefile +++ b/developer/src/Makefile @@ -3,13 +3,16 @@ # !ifdef NODELPHI -TARGETS=build-tools kmcmplib kmanalyze kmdecomp server kmc +DELPHI_TARGETS= !else -TARGETS=build-tools kmcmplib kmcomp kmanalyze kmconvert tike samples setup inst kmdecomp server kmc +# note: inst is not really a delphi target but depends on delphi +DELPHI_TARGETS=kmconvert tike setup inst !endif +TARGETS=build-tools kmcmplib kmanalyze $(DELPHI_TARGETS) kmdecomp samples server kmc + EXCLUDEPATHDEFINES=1 -MANIFESTS=kmcomp tike setup +MANIFESTS=tike setup CLEANS=clean-developer EXCLUDEPATHDEFINES=1 @@ -37,10 +40,6 @@ kmdecomp: .virtual cd $(DEVELOPER_ROOT)\src\kmdecomp $(MAKE) $(TARGET) -kmcomp: - cd $(DEVELOPER_ROOT)\src\kmcomp - $(MAKE) $(TARGET) - kmanalyze: .virtual cd $(DEVELOPER_ROOT)\src\kmanalyze $(MAKE) $(TARGET) diff --git a/developer/src/README.md b/developer/src/README.md index 7745c3a0abc..05afec59633 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -37,11 +37,6 @@ a full node.js install. A keyboard source analysis and automated regression test tool. Testing for logical errors of a keyboard. -## src/kmcomp - kmcomp.exe & kmcomp.x64.exe - -The main command line compiler for a keyboard. It compiles keyboards (.kmn), -packages (.kps), and projects (.kpj). - ## src/kmconvert - kmconvert.exe & kmconvert.x64.exe KMConvert converts keyboard layouts between different formats. KMConvert will diff --git a/developer/src/developer.groupproj b/developer/src/developer.groupproj index 9427b18ff45..906c009209c 100644 --- a/developer/src/developer.groupproj +++ b/developer/src/developer.groupproj @@ -12,9 +12,6 @@ - - - Default.Personality.12 @@ -50,23 +47,14 @@ - - - - - - - - - - + - + - + diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index e70621a81f0..353622113b7 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -22,7 +22,7 @@ KEYMAN_WIX_TEMP_SERVER=$(TEMP)\keyman_wix_build\Server KEYMAN_WIX_KMDEV_SERVER=$(DEVELOPER_ROOT)\bin\server KEYMAN_DEVELOPER_TEMPLATES_ROOT=$(DEVELOPER_ROOT)\src\kmconvert\data -copykmdev: makeinstaller make-kmcomp-install-zip +copykmdev: makeinstaller make-kmc-install-zip -mkdir $(DEVELOPER_ROOT)\release\$Version copy /Y $(DEVELOPER_ROOT)\src\inst\keymandeveloper.msi $(DEVELOPER_ROOT)\release\$Version\keymandeveloper.msi copy /Y $(DEVELOPER_ROOT)\src\inst\keymandeveloper-$Version.exe $(DEVELOPER_ROOT)\release\$Version\keymandeveloper-$Version.exe @@ -105,17 +105,16 @@ makeinstaller: $(SIGNCODE) /d "Keyman Developer" keymandeveloper-$Version.exe # -# Zip the files we distribute as part of the standalone kmcomp distro into release\$Version\kmcomp-$Version.zip +# Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip # -KMCOMP_ZIP=$(DEVELOPER_ROOT)\release\$Version\kmcomp-$Version.zip +# TODO: rename this to keyman-developer-cli-$Version.zip +KMC_ZIP=$(DEVELOPER_ROOT)\release\$Version\kmcomp-$Version.zip -make-kmcomp-install-zip: copy-schemas +make-kmc-install-zip: copy-schemas cd $(DEVELOPER_ROOT)\bin - $(WZZIP) -bd -bb0 $(KMCOMP_ZIP) \ - kmcomp.exe \ - kmcomp.x64.exe \ + $(WZZIP) -bd -bb0 $(KMC_ZIP) \ kmconvert.exe \ sentry.dll sentry.x64.dll \ kmdecomp.exe \ diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index 0766b5603ac..86f62cc82c5 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -159,18 +159,10 @@ - - - - - - - - diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts index c2a74b256b6..61dd2f97a69 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKmnKeyboard.ts @@ -4,6 +4,7 @@ import { KmnCompiler } from '@keymanapp/kmc-kmn'; import { CompilerOptions, CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types'; import { BuildActivity } from './BuildActivity.js'; import * as fs from 'fs'; +import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; export class BuildKmnKeyboard extends BuildActivity { public get name(): string { return 'Keyman keyboard'; } @@ -19,15 +20,15 @@ export class BuildKmnKeyboard extends BuildActivity { // We need to resolve paths to absolute paths before calling kmc-kmn if(options.outFile) { options.outFile = getPosixAbsolutePath(options.outFile); + const folderName = path.dirname(options.outFile); + try { + fs.mkdirSync(folderName, {recursive: true}); + } catch(e) { + callbacks.reportMessage(InfrastructureMessages.Error_CannotCreateFolder({folderName, e})); + return false; + } } - infile = getPosixAbsolutePath(infile); - try { - fs.mkdirSync(path.dirname(options.outFile), {recursive: true}); - } catch(e) { - // TODO: error - return false; - } return compiler.run(infile, options); } } diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index 49b85404dd4..be73c8f4d59 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -71,5 +71,9 @@ export class InfrastructureMessages { static Error_NotAProjectFile = (o:{filename:string}) => m(this.ERROR_NotAProjectFile, `File ${o.filename} must have a .kpj extension to be treated as a project.`); static ERROR_NotAProjectFile = SevError | 0x000F; + + static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => m(this.ERROR_CannotCreateFolder, null, + `Unable to create folder ${o.folderName}: ${o.e ?? 'unknown error'}`); + static ERROR_CannotCreateFolder = SevError | 0x0010; } diff --git a/developer/src/kmc/src/util/getDeveloperBinPath.ts b/developer/src/kmc/src/util/getDeveloperBinPath.ts index e59e6ac92f3..4bbe4cedc06 100644 --- a/developer/src/kmc/src/util/getDeveloperBinPath.ts +++ b/developer/src/kmc/src/util/getDeveloperBinPath.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; /** * Locates the Keyman Developer bin folder, checking first if this is running * from the source repository (using the presence of the KEYMAN_ROOT environment - * variable), and if not, checking for the presence of kmcomp.exe in each + * variable), and if not, checking for the presence of kmconvert.exe in each * parent folder recursively until we reach the root of the filesystem. * @returns string | null */ @@ -21,18 +21,18 @@ export function getDeveloperBinPath(): string { } } - // Otherwise, we will look in parent folders until we find kmcomp.exe - // TODO: once we eliminate kmcomp.exe, we'll need to do this some other way? + // Otherwise, we will look in parent folders until we find kmconvert.exe + // TODO: if we eliminate kmconvert.exe, we'll need to do this some other way? let p = fileURLToPath(import.meta.url); const root = path.parse(p).root.toLowerCase(); // lower-case for drive letter on Windows p = path.dirname(p); do { - if (fs.existsSync(path.join(p, 'kmcomp.exe'))) { + if (fs.existsSync(path.join(p, 'kmconvert.exe'))) { return p; } p = path.dirname(p); } while (p.toLowerCase() != root); - // kmcomp.exe was not found on the path + // kmconvert.exe was not found on the path return null; } diff --git a/developer/src/kmcmplib/src/version.rc b/developer/src/kmcmplib/src/version.rc index 685b3ff8e4b..2ceffd05acd 100644 --- a/developer/src/kmcmplib/src/version.rc +++ b/developer/src/kmcmplib/src/version.rc @@ -16,10 +16,10 @@ VALUE "CompanyName", KV_COMPANY_NAME VALUE "FileDescription", "Keyman compiler library\0" VALUE "FileVersion", KV_VERSION_STRING - VALUE "InternalName", "KMCOMP\0" + VALUE "InternalName", "KMCMPLIB\0" VALUE "LegalCopyright", KV_LEGAL_COPYRIGHT VALUE "LegalTrademarks", KV_LEGAL_TRADEMARKS - VALUE "OriginalFilename", "KMCOMP.DLL\0" + VALUE "OriginalFilename", "KMCMPLIB.DLL\0" VALUE "ProductName", "Keyman Developer\0" VALUE "ProductVersion", KV_VERSION_STRING VALUE "Comments", "\0" diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat index 5e8b891ea6c..d1a8b9dd115 100644 --- a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -1,4 +1,5 @@ @echo off echo Compiles the keyboards using the legacy kmcomp.exe echo to use as baseline comparisons for kmcmplib +rem TODO: we'll need a binary download for kmcomp.exe for %%d in (*.kmn) do ..\..\..\..\..\bin\kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/kmcmplib/version.rc b/developer/src/kmcmplib/version.rc index b963c10ddb1..04811532c4f 100644 --- a/developer/src/kmcmplib/version.rc +++ b/developer/src/kmcmplib/version.rc @@ -16,10 +16,10 @@ VALUE "CompanyName", KV_COMPANY_NAME VALUE "FileDescription", "Keyman compiler library\0" VALUE "FileVersion", KV_VERSION_STRING - VALUE "InternalName", "KMCOMP\0" + VALUE "InternalName", "KMCMPLIB\0" VALUE "LegalCopyright", KV_LEGAL_COPYRIGHT VALUE "LegalTrademarks", KV_LEGAL_TRADEMARKS - VALUE "OriginalFilename", "KMCOMP.DLL\0" + VALUE "OriginalFilename", "KMCMPLIB.DLL\0" VALUE "ProductName", "Keyman Developer\0" VALUE "ProductVersion", KV_VERSION_STRING VALUE "Comments", "\0" diff --git a/developer/src/kmcomp/Keyman.Developer.System.Project.ProjectLogConsole.pas b/developer/src/kmcomp/Keyman.Developer.System.Project.ProjectLogConsole.pas deleted file mode 100644 index 9961aa25ba7..00000000000 --- a/developer/src/kmcomp/Keyman.Developer.System.Project.ProjectLogConsole.pas +++ /dev/null @@ -1,193 +0,0 @@ -unit Keyman.Developer.System.Project.ProjectLogConsole; - -interface - -uses - Keyman.Developer.System.Project.ProjectLog; - -type - TProjectLogConsole = class - strict private - class var FInstance: TProjectLogConsole; - private - hConsole: THandle; - hOutFile: THandle; - FFullySilent: Boolean; - FSilent: Boolean; - FFilename: string; - FHasWarning: Boolean; - FMessageCount: Integer; - FColor: Boolean; - procedure DetectColorMode; - public - type TColorMode = (cmDefault, cmForceColor, cmForceNoColor); - public - constructor Create(ASilent, AFullySilent: Boolean; AhOutFile: THandle; AColorMode: TColorMode); - procedure Log(AState: TProjectLogState; Filename: string; Msg: string; MsgCode, Line: Integer); overload; - procedure Log(AState: TProjectLogState; Msg: string; MsgCode, Line: Integer); overload; - class property Instance: TProjectLogConsole read FInstance; - property Filename: string read FFilename write FFilename; - property HasWarning: Boolean read FHasWarning; - end; - -function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; // I3310 -function CompilerMessageW( line: Integer; msgcode: LongWord; const text: string): Integer; -implementation - -uses - System.SysUtils, - Winapi.Windows, - - compile; - -const - MAX_MESSAGES = 100; - -{ TProjectLogConsole } - -procedure TProjectLogConsole.Log(AState: TProjectLogState; Filename, - Msg: string; MsgCode, Line: Integer); -var - dw: DWord; - str: string; - astr: RawByteString; -const - nlstr: array[0..2] of ansichar = (#$D, #$A, #$0); // I3310 - -const - ESC=#$1b; - ESC_BRIGHT_YELLOW=ESC+'[38;2;255;255;0m'; - ESC_RED=ESC+'[38;2;255;0;0m'; - ESC_GREEN=ESC+'[38;2;0;255;0m'; - ESC_DEFAULT=ESC+'[0m'; -begin - // TODO: Colour - if (AState = plsInfo) and FSilent then Exit; - - if AState = plsWarning then - FHasWarning := True; - - if (AState in [plsWarning, plsError, plsSuccess, plsFailure]) and FFullySilent then Exit; - - str := ''; - - if AState in [plsWarning, plsError] then - begin - Inc(FMessageCount); - if FMessageCount > MAX_MESSAGES then - Exit; - - if FMessageCount = MAX_MESSAGES then - str := Format('More than %d warnings or errors received; suppressing further messages', [MAX_MESSAGES]); - end; - - if str = '' then - - str := TProjectLog.FormatMessage(AState, Filename, msg, msgcode, line); - - if hOutfile <> 0 then - begin - astr := UTF8Encode(str); - WriteFile(hOutfile, astr, Length(astr), dw, nil); - WriteFile(hOutfile, nlstr, 2, dw, nil); - end - else - begin - if FColor then - begin - case AState of - plsInfo: write(ESC_DEFAULT); - plsWarning: write(ESC_BRIGHT_YELLOW); - plsSuccess: write(ESC_GREEN); - plsFailure, - plsFatal, - plsError: write(ESC_RED); - else write(ESC_DEFAULT); - end; - end; - writeln(str); - if FColor then - write(ESC_DEFAULT); - end; -end; - -procedure TProjectLogConsole.Log(AState: TProjectLogState; Msg: string; MsgCode, Line: Integer); -begin - Log(AState, FFilename, Msg, MsgCode, Line); -end; - -{ TLogger } - -constructor TProjectLogConsole.Create(ASilent, AFullySilent: Boolean; AhOutFile: THandle; AColorMode: TColorMode); -begin - Assert(FInstance = nil); - FInstance := Self; - inherited Create; - FSilent := ASilent; - FFullySilent := AFullySilent; - hOutFile := AhOutFile; - - case AColorMode of - cmDefault: DetectColorMode; - cmForceColor: FColor := True; - cmForceNoColor: FColor := False; - end; -end; - -procedure TProjectLogConsole.DetectColorMode; -var - mode: DWORD; -const - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; -begin - mode := 0; - hConsole := GetStdHandle(STD_OUTPUT_HANDLE); - if hConsole = INVALID_HANDLE_VALUE then - begin - writeln(Format('GetStdHandle failed with %d %s', [GetLastError, SysErrorMessage(GetLastError)])); - Exit; - end; - - if GetEnvironmentVariable('MSYSTEM') = 'MINGW64' then - begin - // MinGW64 test - // Use colour mode only with a non-redirected console. This test fails with pipes. - // For pipe use, explicitly use -no-color parameter - FColor := GetFileType(hConsole) = 3; - end - else - begin - // Win32 console color mode test - if not GetConsoleMode(hConsole, mode) then - Exit; - - mode := mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if not SetConsoleMode(hConsole, mode) then - Exit; - - FColor := True; - end; -end; - -function CompilerMessageW( line: Integer; msgcode: LongWord; const text: string): Integer; -var - state: TProjectLogState; -begin - if (msgcode = CWARN_Info) then state := plsInfo - else if (msgcode and CERR_ERROR) <> 0 then state := plsError - else if (msgcode and CERR_WARNING) <> 0 then begin state := plsWarning; end // I4706 - else if (msgcode and CERR_FATAL) <> 0 then state := plsFatal - else if (msgcode and CERR_HINT) <> 0 then state := plsHint - else state := plsFatal; - - TProjectLogConsole.Instance.Log(state, text, msgcode, line); - - Result := 1; -end; - -function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; // I3310 -begin - Result := CompilerMessageW(line, msgcode, string(AnsiString(text))); -end; - -end. diff --git a/developer/src/kmcomp/Keyman.Developer.System.ValidateRepoChanges.pas b/developer/src/kmcomp/Keyman.Developer.System.ValidateRepoChanges.pas deleted file mode 100644 index 8a5e8443828..00000000000 --- a/developer/src/kmcomp/Keyman.Developer.System.ValidateRepoChanges.pas +++ /dev/null @@ -1,463 +0,0 @@ -unit Keyman.Developer.System.ValidateRepoChanges; - -interface - -uses - System.Classes, - System.JSON, - - kpsfile; - -type - TValidateRepoChanges = class - private - class var err_old, err_new: TStringList; - class var root: string; - class function Search(path, operation: string): Boolean; static; - class function CheckKeyboardInfo(name: string): Boolean; static; - class function CheckPackage(name: string): Boolean; static; - class function LoadKeyboardInfoFile(filename: string): TJSONObject; static; - class function GetLanguageCodesFromJson(root: TJSONObject; - langs: TStringList): Boolean; static; - class function GetLanguageCodesFromKps(kps: TKPSFile; - langs: TStringList): Boolean; static; - class function CompareKeyboardInfoScripts(name: string): Boolean; static; - public - class function Execute(path, operation: string): Boolean; - end; - -implementation - -uses - System.Character, - System.Generics.Collections, - System.SysUtils, - - BCP47Tag, - Keyman.System.KeyboardInfoFile, - Keyman.System.CanonicalLanguageCodeUtils, - Keyman.System.Standards.LangTagsRegistry, - TempFileManager, - Unicode, - utilexecute; - -{ TValidateRepoChanges } - -class function TValidateRepoChanges.Execute(path, operation: string): Boolean; -begin - if path = '' then path := 'c:\projects\keyman\keyboards'; - - path := IncludeTrailingPathDelimiter(path); - - root := path; - -// Result := CheckKeyboardInfo(path + 'release\k\kayan\kayan.keyboard_info'); - - err_old := TStringList.Create; - err_new := TStringList.Create; - try - Result := Search(path, operation); - err_old.SaveToFile('repo-check.old.txt'); - err_new.SaveToFile('repo-check.new.txt'); - finally - err_old.Free; - err_new.Free; - end; -end; - -class function TValidateRepoChanges.Search(path, operation: string): Boolean; -var - f: TSearchRec; -begin - if FindFirst(path + '*.keyboard_info', 0, f) = 0 then - begin - repeat - if operation = 'compare-bcp47-revisions' then CheckKeyboardInfo(path + f.Name) - else if operation = 'compare-bcp47-scripts' then CompareKeyboardInfoScripts(path + f.Name); - until FindNext(f) <> 0; - FindClose(f); - end; - if FindFirst(path + '*.kps', 0, f) = 0 then - begin - repeat - if operation = 'compare-bcp47-revisions' then CheckPackage(path + f.Name); - until FindNext(f) <> 0; - FindClose(f); - end; - if FindFirst(path + '*', faDirectory, f) = 0 then - begin - repeat - if ((f.Attr and faDirectory) = faDirectory) and (f.Name <> '.') and (f.Name <> '..') and not SameText(f.Name, 'build') then - Search(path + f.Name + '\', operation); - until FindNext(f) <> 0; - FindClose(f); - end; - - Result := True; -end; - -class function TValidateRepoChanges.LoadKeyboardInfoFile(filename: string): TJSONObject; -begin - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(filename); - Result := TJSONObject.ParseJsonValue(DataString) as TJSONObject; - finally - Free; - end; -end; - -class function TValidateRepoChanges.CheckKeyboardInfo(name: string): Boolean; -var - i: Integer; - json_new, json_old: TJSONObject; -// t: TTempFile; - relpath: string; - content: string; - ec: Integer; - lang_new: TStringList; - lang_old: TStringList; - langc_old: TArray; - langc_new: TArray; - found_error: Boolean; - j: Integer; - langc_old_prev: string; -begin - found_error := False; - relpath := name.Substring(root.Length).Replace('\', '/'); - - json_new := LoadKeyboardInfoFile(name); - if not Assigned(json_new) then - raise Exception.Create('Unable to load file '+name); - // Get previous revision from git - -// t := TTempFileManager.Get('.keyboard_info'); - - if not TUtilExecute.Console('git show "HEAD:'+relpath+'"', root, content, ec) then - RaiseLastOSError; - - if ec <> 0 then - raise Exception.Create('Unable to execute git show for '+name); - - if Copy(content, 1, 3) = string(UTF8Signature) then - Delete(content, 1, 3); - - json_old := TJSONObject.ParseJsonValue(content) as TJSONObject; - if not Assigned(json_old) then - raise Exception.Create('Unable to load old file '+name); - - lang_new := TStringList.Create; - lang_old := TStringList.Create; - try - if not GetLanguageCodesFromJson(json_new, lang_new) then - raise Exception.Create('Unable to get Language codes'); - if not GetLanguageCodesFromJson(json_old, lang_old) then - raise Exception.Create('Unable to get Language codes'); - - j := 0; - for i := 0 to lang_new.Count - 1 do - begin - repeat - if j >= lang_old.Count then - begin - if not found_error then - begin - writeln('Checking '+relpath); - found_error := True; - end; - writeln(' Mismatch in number of language codes'); - break; - end; - - langc_new := lang_new[i].Split([',']); - langc_old := lang_old[j].Split([',']); - Inc(j); - until langc_old[0] <> langc_old_prev; - langc_old_prev := langc_old[0]; - if j >= lang_old.Count then Break; - - if langc_new[0] <> langc_old[0] then - begin - if not found_error then - begin - writeln('Checking '+relpath); - found_error := True; - end; - writeln(Format(' New code %s [%s] does not match old code %s [%s]', [langc_new[1], langc_new[0], langc_old[1], langc_old[0]])); - end; - end; - - if found_error then - begin - err_old.Add('Checking '+relpath); - for i := 0 to lang_old.Count - 1 do - begin - langc_old := lang_old[i].Split([',']); - err_old.Add(Format(' %s [%s]', [langc_old[1], langc_old[0]])); - end; - err_old.Add(''); - - err_new.Add('Checking '+relpath); - for i := 0 to lang_new.Count - 1 do - begin - langc_new := lang_new[i].Split([',']); - err_new.Add(Format(' %s [%s]', [langc_new[1], langc_new[0]])); - end; - err_new.Add(''); - end; - finally - lang_new.Free; - lang_old.Free; - end; - - Result := True; -end; - -class function TValidateRepoChanges.GetLanguageCodesFromJson(root: TJSONObject; langs: TStringList): Boolean; -var - i: Integer; - a: TJSONArray; - - procedure AddLang(lang: string); - begin - langs.Add(TCanonicalLanguageCodeUtils.FindBestTag(lang, False, False)+','+lang); - end; - -begin - if root.Values[TKeyboardInfoFile.SLanguages] = nil then - Exit(False); - - if root.Values[TKeyboardInfoFile.SLanguages] is TJSONArray then - begin - a := root.Values[TKeyboardInfoFile.SLanguages] as TJSONArray; - for i := 0 to a.Count - 1 do - AddLang(a.Items[i].AsType); - end - else - begin - root := root.Values[TKeyboardInfoFile.SLanguages] as TJSONObject; - for i := 0 to root.Count - 1 do - AddLang(root.Pairs[i].JsonString.Value); - end; - - langs.Sort; - - Result := True; -end; - -class function TValidateRepoChanges.GetLanguageCodesFromKps(kps: TKPSFile; langs: TStringList): Boolean; -var - i: Integer; - j: Integer; - - procedure AddLang(lang: string); - begin - langs.Add(TCanonicalLanguageCodeUtils.FindBestTag(lang, False, False)+','+lang); - end; - -begin - for i := 0 to kps.Keyboards.Count - 1 do - for j := 0 to kps.Keyboards[i].Languages.Count - 1 do - AddLang(kps.Keyboards[i].Languages[j].ID); - - langs.Sort; - - Result := True; -end; - -class function TValidateRepoChanges.CheckPackage(name: string): Boolean; -var - i: Integer; - kps_new, kps_old: TKPSFile; -// t: TTempFile; - relpath: string; - content: string; - ec: Integer; - lang_new: TStringList; - lang_old: TStringList; - langc_old: TArray; - langc_new: TArray; - found_error: Boolean; - j: Integer; - langc_old_prev: string; -begin - found_error := False; - relpath := name.Substring(root.Length).Replace('\', '/'); - - kps_new := TKPSFile.Create; - kps_new.FileName := name; - kps_new.LoadXML; - - if not TUtilExecute.Console('git show "HEAD:'+relpath+'"', root, content, ec) then - RaiseLastOSError; - - if ec <> 0 then - raise Exception.Create('Unable to execute git show for '+name); - - if Copy(content, 1, 3) = string(UTF8Signature) then - Delete(content, 1, 3); - - kps_old := TKPSFile.Create; - kps_old.LoadXMLFromText(content); - - lang_new := TStringList.Create; - lang_old := TStringList.Create; - try - if not GetLanguageCodesFromKps(kps_new, lang_new) then - raise Exception.Create('Unable to get Language codes'); - if not GetLanguageCodesFromKps(kps_old, lang_old) then - raise Exception.Create('Unable to get Language codes'); - - j := 0; - for i := 0 to lang_new.Count - 1 do - begin - repeat - if j >= lang_old.Count then - begin - if not found_error then - begin - writeln('Checking '+relpath); - found_error := True; - end; - writeln(' Mismatch in number of language codes'); - break; - end; - - langc_new := lang_new[i].Split([',']); - langc_old := lang_old[j].Split([',']); - Inc(j); - until langc_old[0] <> langc_old_prev; - langc_old_prev := langc_old[0]; - if j >= lang_old.Count then Break; - - if langc_new[0] <> langc_old[0] then - begin - if not found_error then - begin - writeln('Checking '+relpath); - found_error := True; - end; - writeln(Format(' New code %s [%s] does not match old code %s [%s]', [langc_new[1], langc_new[0], langc_old[1], langc_old[0]])); - end; - end; - - if found_error then - begin - err_old.Add('Checking '+relpath); - for i := 0 to lang_old.Count - 1 do - begin - langc_old := lang_old[i].Split([',']); - err_old.Add(Format(' %s [%s]', [langc_old[1], langc_old[0]])); - end; - err_old.Add(''); - - err_new.Add('Checking '+relpath); - for i := 0 to lang_new.Count - 1 do - begin - langc_new := lang_new[i].Split([',']); - err_new.Add(Format(' %s [%s]', [langc_new[1], langc_new[0]])); - end; - err_new.Add(''); - end; - finally - lang_new.Free; - lang_old.Free; - end; - kps_old.Free; - kps_new.Free; - - Result := True; -end; - -class function TValidateRepoChanges.CompareKeyboardInfoScripts(name: string): Boolean; -var - i: Integer; - json: TJSONObject; - relpath: string; - script, base_script: string; - lang: TStringList; - base_lang_item, lang_item: TArray; - found_error: Boolean; - - procedure WriteHeader; - begin - if not found_error then - begin - writeln; - writeln('Checking '+relpath); - end; - found_error := True; - end; - - function GetScript(lang: string): string; - var - v: string; - LangTag: TLangTag; - BCP47: TBCP47Tag; - begin - if TLangTagsMap.AllTags.TryGetValue(lang, v) then - lang := v; - - if not TLangTagsMap.LangTags.TryGetValue(lang, LangTag) then - begin - BCP47 := TBCP47Tag.Create(lang); - try - Exit(BCP47.Script); - finally - BCP47.Free; - end; - end; - - Result := LangTag.script; - end; - -begin - found_error := False; - relpath := name.Substring(root.Length).Replace('\', '/'); - - json := LoadKeyboardInfoFile(name); - if not Assigned(json) then - raise Exception.Create('Unable to load file '+name); - - lang := TStringList.Create; - try - if not GetLanguageCodesFromJson(json, lang) then - raise Exception.Create('Unable to get Language codes'); - - if lang.Count = 0 then - begin - WriteHeader; - writeln(' Warning: no languages found'); - Exit(True); - end; - - base_lang_item := lang[0].Split([',']); - - // Lookup the tag first, canonicalize to the base tag for known tags - base_script := GetScript(base_lang_item[0]); - if base_script = '' then - begin - WriteHeader; - writeln(' Warning: could not identify tag '+base_lang_item[0]); - Exit(True); - end; - - for i := 1 to lang.Count - 1 do - begin - lang_item := lang[i].Split([',']); - script := GetScript(lang_item[0]); - if script <> base_script then - begin - WriteHeader; - writeln(Format(' Tag %s [%s] has script <%s>, which differs from base tag %s [%s], script <%s>', - [lang_item[1], lang_item[0], script, base_lang_item[1], base_lang_item[0], base_script])); - end; - end; - finally - lang.Free; - end; - - Result := True; -end; - -end. diff --git a/developer/src/kmcomp/Makefile b/developer/src/kmcomp/Makefile deleted file mode 100644 index f559f32afa6..00000000000 --- a/developer/src/kmcomp/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -# -# Kmcomp Makefile -# - -!include ..\Defines.mak - -TARGET_BASE=kmcomp -TARGET_EXT=exe -TARGET_GROUP=developer - -build: version.res manifest.res dirs icons - $(DELPHI_MSBUILD) kmcomp.dproj "/p:Platform=Win32" - - $(SENTRYTOOL_DELPHIPREP) $(WIN32_TARGET_PATH)\kmcomp.exe -dpr kmcomp.dpr - $(TDS2DBG) $(WIN32_TARGET_PATH)\kmcomp.exe - $(COPY) $(WIN32_TARGET_PATH)\kmcomp.exe $(DEVELOPER_PROGRAM) - if exist $(WIN32_TARGET_PATH)\kmcomp.dbg $(COPY) $(WIN32_TARGET_PATH)\kmcomp.dbg $(DEVELOPER_DEBUGPATH) - - $(DELPHI_MSBUILD) kmcomp.dproj "/p:Platform=Win64" - - if exist $(WIN64_TARGET_PATH)\kmcomp.x64.exe del $(WIN64_TARGET_PATH)\kmcomp.x64.exe -# Delphi does not allow us to build to a different target filename so we rename after build - ren $(WIN64_TARGET_PATH)\kmcomp.exe kmcomp.x64.exe - - if exist $(WIN64_TARGET_PATH)\kmcomp.x64.map del $(WIN64_TARGET_PATH)\kmcomp.x64.map - ren $(WIN64_TARGET_PATH)\kmcomp.map kmcomp.x64.map - -# $(SENTRYTOOL_DELPHIPREP) $(WIN64_TARGET_PATH)\kmcomp.x64.exe -dpr kmcomp.dpr - $(COPY) $(WIN64_TARGET_PATH)\kmcomp.x64.exe $(DEVELOPER_PROGRAM)\kmcomp.x64.exe - if exist $(WIN64_TARGET_PATH)\kmcomp.dbg $(COPY) $(WIN64_TARGET_PATH)\kmcomp.dbg $(DEVELOPER_DEBUGPATH)\kmcomp.x64.dbg - -icons: - rc icons.rc - -clean: def-clean - if exist icons.res del icons.res - -signcode: - $(SIGNCODE) /d "Keyman Developer Command-Line Compiler" $(DEVELOPER_PROGRAM)\kmcomp.exe - $(SIGNCODE) /d "Keyman Developer Command-Line Compiler" $(DEVELOPER_PROGRAM)\kmcomp.x64.exe - -wrap-symbols: - $(SYMSTORE) $(DEVELOPER_PROGRAM)\kmcomp.exe /t keyman-developer - $(SYMSTORE) $(DEVELOPER_PROGRAM)\kmcomp.x64.exe /t keyman-developer - $(SYMSTORE) $(DEVELOPER_DEBUGPATH)\kmcomp.dbg /t keyman-developer -#TODO: $(SYMSTORE) $(DEVELOPER_DEBUGPATH)\kmcomp.x64.dbg /t keyman-developer - -test-manifest: -# test that (a) linked manifest exists and correct - $(MT) -nologo -inputresource:$(DEVELOPER_PROGRAM)\kmcomp.exe -validate_manifest - $(MT) -nologo -inputresource:$(DEVELOPER_PROGRAM)\kmcomp.x64.exe -validate_manifest - -install: - $(COPY) $(DEVELOPER_PROGRAM)\kmcomp.exe "$(INSTALLPATH_KEYMANDEVELOPER)\kmcomp.exe" - $(COPY) $(DEVELOPER_PROGRAM)\kmcomp.exe "$(INSTALLPATH_KEYMANDEVELOPER)\kmcomp.x64.exe" - -!include ..\Target.mak diff --git a/developer/src/kmcomp/icons.rc b/developer/src/kmcomp/icons.rc deleted file mode 100644 index aa14bfef90d..00000000000 --- a/developer/src/kmcomp/icons.rc +++ /dev/null @@ -1 +0,0 @@ -1 ICON DISCARDABLE "..\\images\\KeymanDeveloper90.ico" diff --git a/developer/src/kmcomp/kccompilekvk.pas b/developer/src/kmcomp/kccompilekvk.pas deleted file mode 100644 index facb82122ae..00000000000 --- a/developer/src/kmcomp/kccompilekvk.pas +++ /dev/null @@ -1,97 +0,0 @@ -unit kccompilekvk; - -interface - -function CompileVisualKeyboardFromKMX(FInFile, FOutFile: string): Boolean; - -implementation - -uses - System.Classes, - System.SysUtils, - - Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.Project.ProjectLogConsole, - KeyboardParser, - kmxfile, - kmxfileconsts, - main, - utilsystem, - VisualKeyboard; - -const - CWARN_KVKFileIsInSourceFormat = $000020A2; // from kmn_compiler_errors.h - -(** - Compiles the visual keyboard from xml to binary, and/or copies to destination folder - - Parameters: FInFile Source .kmn file name with path - FOutFile Destination .kmx file name with path - - Returns: True on success - - The input FInFile references - If the keyboard output is compiled to the same folder as the input, and a .kvk file - is referenced in the .kmn file (instead of a .kvks file), then this function will - take no action. This means that if the user has incorrectly saved a .kvk in XML, - the output .kvk file will be in the wrong format for older versions of Keyman (there - was a small window of time in May/June 2017 where .kvk files could be either XML or - binary). -*) -function CompileVisualKeyboardFromKMX(FInFile, FOutFile: string): Boolean; -var - FKVKInputFileName, FKVKOutputFileName: string; -begin - try - with TKeyboardParser.Create do // I4720 - try - LoadFromFile(FInFile); - FKVKInputFileName := GetSystemStoreValue(ssVisualKeyboard); - if FKVKInputFileName = '' then - begin - // Keyboard does not include a .kvk file - Exit(True); - end; - - FKVKInputFileName := ExpandFileNameClean(FInFile, FKVKInputFileName); - FKVKOutputFileName := ExpandFileNameClean(FOutFile, - ChangeFileExt(ExtractFileName(FKVKInputFileName), '.kvk')); - - with TVisualKeyboard.Create do - try - LoadFromFile(FKVKInputFileName); - if not SameFilename(FKVKInputFileName, FKVKOutputFileName) then - begin - // Source and destination files are different - SaveToFile(FKVKOutputFileName, kvksfBinary); - TProjectLogConsole.Instance.Log(plsSuccess, FKVKInputFileName, 'Visual keyboard '+FKVKInputFileName+' successfully compiled to '+FKVKOutputFileName, 0, 0); - end - else - begin - if LoadedFileFormat = kvksfXML then - TProjectLogConsole.Instance.Log(plsWarning, FKVKInputFileName, '.kvk file should be binary but is an XML file', CWARN_KVKFileIsInSourceFormat, 0) - else - TProjectLogConsole.Instance.Log(plsInfo, FKVKInputFileName, 'Visual keyboard '+FKVKInputFileName+' is valid', 0, 0); - end; - finally - Free; - end; - finally - Free; - end; - Result := True; - except - on E:EFCreateError do - begin - TProjectLogConsole.Instance.Log(plsFatal, FKVKInputFileName, E.Message, 0, 0); - Exit(False); - end; - on E:EVisualKeyboardLoader do - begin - TProjectLogConsole.Instance.Log(plsFatal, FKVKInputFileName, E.Message, 0, 0); - Exit(False); - end; - end; -end; - -end. diff --git a/developer/src/kmcomp/kccompilepackage.pas b/developer/src/kmcomp/kccompilepackage.pas deleted file mode 100644 index 8bdbcd26c79..00000000000 --- a/developer/src/kmcomp/kccompilepackage.pas +++ /dev/null @@ -1,111 +0,0 @@ -(* - Name: kccompilepackage - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 1 Aug 2006 - - Modified Date: 11 May 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 01 Aug 2006 - mcdurdin - LoadIni instead of Load - 30 Apr 2007 - mcdurdin - Load from XML instead of from INI for compiling packages - 30 May 2007 - mcdurdin - I817 - Build bootstrap installer - 04 Jun 2007 - mcdurdin - Remove KeymanPath reference, Add AInstallerMSI, AUpdate - 04 May 2015 - mcdurdin - I4694 - V9.0 - Split UI actions from non-UI actions in projects - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness -*) -unit kccompilepackage; - -interface - -uses - Windows, - SysUtils, - CompilePackage, - CompilePackageInstaller, - PackageInfo, - kpsfile; - -function DoKCCompilePackage(FileName: string; AWarnAsError, ACheckFilenameConventions, AInstaller: Boolean; - const AInstallerMSI: string; AUpdateInstaller: Boolean; const ASchemaPath: string): Boolean; // I4706 - -implementation - -uses - Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.Project.ProjectLogConsole, - Keyman.Developer.System.ValidateKpsFile; - -type - TKCCompilePackage = class - pack: TKPSFile; - procedure SelfMessage(Sender: TObject; msg: string; State: TProjectLogState); - end; - -procedure TKCCompilePackage.SelfMessage(Sender: TObject; msg: string; State: TProjectLogState); // I4706 -begin - TProjectLogConsole.Instance.Log(State, pack.Filename, Msg, 0, 0); -end; - -function DoKCCompilePackage(FileName: string; AWarnAsError, ACheckFilenameConventions, AInstaller: Boolean; - const AInstallerMSI: string; AUpdateInstaller: Boolean; const ASchemaPath: string): Boolean; // I4706 -var - tcp: TKCCompilePackage; - pack: TKPSFile; - buf: array[0..260] of char; - pbuf: PChar; -begin - Result := False; - pack := TKPSFile.Create; - try - GetFullPathName(PChar(FileName), 260, buf, pbuf); FileName := buf; - if not FileExists(FileName) then - begin - TProjectLogConsole.Instance.Log(plsFatal, FileName, 'Package file does not exist', 0, 0); - Exit; - end; - - if (ASchemaPath <> '') and (FileExists(ASchemaPath + 'kps.xsd')) then - begin - if not TValidateKpsFile.Execute(FileName, ASchemaPath + 'kps.xsd', - TProjectLogConsole.Instance.Log) then - begin - TProjectLogConsole.Instance.Log(plsFailure, FileName, 'Package '+FileName+' had validation errors.', 0, 0); - Exit; - end; - end; - - pack.FileName := FileName; - pack.LoadXML; - - tcp := TKCCompilePackage.Create; - try - tcp.pack := pack; - Result := DoCompilePackage(pack, tcp.SelfMessage, False, ACheckFilenameConventions, ChangeFileExt(pack.FileName, '.kmp')); // I4694 - if AWarnAsError and TProjectLogConsole.Instance.HasWarning then Result := False; - - if AInstaller and Result then - begin - Result := DoCompilePackageInstaller(pack, tcp.SelfMessage, False, AInstallerMSI, // I4694 - ChangeFileExt(pack.FileName, '.exe'), '', AUpdateInstaller, True, '', '', '', False, False); - if AWarnAsError and TProjectLogConsole.Instance.HasWarning then Result := False; - end; - - if Result - then TProjectLogConsole.Instance.Log(plsSuccess, FileName, 'Package '+FileName+' compiled successfully.', 0, 0) - else TProjectLogConsole.Instance.Log(plsFailure, FileName, 'Package '+FileName+' could not be compiled.', 0, 0); - finally - tcp.Free; - end; - finally - pack.Free; - end; -end; - -end. diff --git a/developer/src/kmcomp/kccompileproject.pas b/developer/src/kmcomp/kccompileproject.pas deleted file mode 100644 index 1bc4a5cc904..00000000000 --- a/developer/src/kmcomp/kccompileproject.pas +++ /dev/null @@ -1,138 +0,0 @@ -(* - Name: kccompileproject - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 5 May 2015 - - Modified Date: 11 May 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 05 May 2015 - mcdurdin - I4699 - V9.0 - Compile .kpj files from kmcomp - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness - 11 May 2015 - mcdurdin - I4709 - V9.0 - Use static hashing for id for project files to avoid unnecessary changes -*) -unit kccompileproject; // I4699 - -interface - -function DoKCCompileProject(AProjectFilename: string; ADebug, AClean, AWarnAsError, ACheckFilenameConventions: Boolean; ATarget: string): Boolean; // I4706 - -implementation - -uses - System.SysUtils, - - Keyman.Developer.System.Project.kmnProjectFileAction, - Keyman.Developer.System.Project.kpsProjectFileAction, - Keyman.Developer.System.Project.modelTsProjectFileAction, - Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.Project.ProjectLogConsole, - Keyman.Developer.System.Project.ProjectFile; - -type - TProjectConsole = class(TProject) - private - FSilent: Boolean; - FFullySilent: Boolean; - public - procedure Log(AState: TProjectLogState; Filename: string; Msg: string; MsgCode, Line: Integer); override; // I4706 - function Save: Boolean; override; // I4709 - - property Silent: Boolean read FSilent write FSilent; - property FullySilent: Boolean read FFullySilent write FFullySilent; - end; - -function DoKCCompileProject(AProjectFilename: string; ADebug, AClean, AWarnAsError, ACheckFilenameConventions: Boolean; ATarget: string): Boolean; // I4706 -var - i: Integer; - Found: Boolean; - kmn: TkmnProjectFileAction; - kps: TkpsProjectFileAction; - modelTs: TmodelTsProjectFileAction; - - function Matches(AFile: TProjectFile; AClass: TProjectFileClass): Boolean; - begin - Result := - (AFile is AClass) and - ((ATarget = '') or - SameText(ExtractFileName(ATarget), ExtractFileName(AFile.FileName))); - end; - -begin - Result := False; - Found := False; - with TProjectConsole.Create(ptUnknown, AProjectFilename, False) do - try - Options.CheckFilenameConventions := Options.CheckFilenameConventions or ACheckFilenameConventions; // never downgrade this option - for i := 0 to Files.Count - 1 do - if Matches(Files[i], TkmnProjectFileAction) then - begin - kmn := Files[i] as TkmnProjectFileAction; - kmn.Debug := ADebug; - kmn.WarnAsError := AWarnAsError; - if AClean then - begin - if not kmn.Clean then Exit; - end - else - if not kmn.CompileKeyboard then Exit; - Found := True; - end - else if Matches(Files[i], TmodelTsProjectFileAction) then - begin - modelTs := Files[i] as TmodelTsProjectFileAction; - modelTs.Debug := ADebug; - modelTs.WarnAsError := AWarnAsError; - if AClean then - begin - if not modelTs.Clean then Exit; - end - else - if not modelTs.CompileModel then Exit; - Found := True; - end; - - - for i := 0 to Files.Count - 1 do - if Matches(Files[i], TkpsProjectFileAction) then - begin - kps := Files[i] as TkpsProjectFileAction; - kps.WarnAsError := AWarnAsError; - if AClean then - begin - if not kps.Clean then Exit; - end - else - if not kps.CompilePackage then Exit; - Found := True; - end; - - finally - Free; - end; - - if not Found then - TProjectLogConsole.Instance.Log(plsFatal, AProjectFileName, 'Target not found (or project empty)', 0, 0); - Result := Found; -end; - -{ TProjectConsole } - -procedure TProjectConsole.Log(AState: TProjectLogState; Filename, Msg: string; MsgCode, Line: Integer); // I4706 -begin - TProjectLogConsole.Instance.Log(AState, Filename, Msg, MsgCode, Line); -end; - -function TProjectConsole.Save: Boolean; // I4709 -begin - // We don't modify the project file in the console - Result := True; -end; - -end. diff --git a/developer/src/kmcomp/kmcomp.dpr b/developer/src/kmcomp/kmcomp.dpr deleted file mode 100644 index 24da9a4a259..00000000000 --- a/developer/src/kmcomp/kmcomp.dpr +++ /dev/null @@ -1,147 +0,0 @@ -program kmcomp; - -{$APPTYPE CONSOLE} - -uses - System.SysUtils, - main in 'main.pas', - compile in '..\common\delphi\compiler\compile.pas', - VersionInfo in '..\..\..\common\windows\delphi\general\VersionInfo.pas', - RegistryKeys in '..\..\..\common\windows\delphi\general\RegistryKeys.pas', - kccompilepackage in 'kccompilepackage.pas', - CompilePackage in '..\common\delphi\compiler\CompilePackage.pas', - kpsfile in '..\common\delphi\packages\kpsfile.pas', - PackageInfo in '..\..\..\common\windows\delphi\packages\PackageInfo.pas', - PackageFileFormats in '..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', - kmpinffile in '..\..\..\common\windows\delphi\packages\kmpinffile.pas', - RedistFiles in '..\tike\main\RedistFiles.pas', - DebugPaths in '..\..\..\common\windows\delphi\general\DebugPaths.pas', - httpuploader in '..\..\..\common\windows\delphi\general\httpuploader.pas', - httpuploader_messageprocessor_forms in '..\..\..\common\windows\delphi\general\httpuploader_messageprocessor_forms.pas', - utilfiletypes in '..\..\..\common\windows\delphi\general\utilfiletypes.pas', - klog in '..\..\..\common\windows\delphi\general\klog.pas', - KeyNames in '..\..\..\common\windows\delphi\general\KeyNames.pas', - StockFileNames in '..\..\..\common\windows\delphi\general\StockFileNames.pas', - KeymanDeveloperOptions in '..\tike\main\KeymanDeveloperOptions.pas', - Upload_Settings in '..\..\..\common\windows\delphi\general\Upload_Settings.pas', - utilstr in '..\..\..\common\windows\delphi\general\utilstr.pas', - utilsystem in '..\..\..\common\windows\delphi\general\utilsystem.pas', - utildir in '..\..\..\common\windows\delphi\general\utildir.pas', - utilkeyboard in '..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', - Unicode in '..\..\..\common\windows\delphi\general\Unicode.pas', - utilhttp in '..\..\..\common\windows\delphi\general\utilhttp.pas', - GetOsVersion in '..\..\..\common\windows\delphi\general\GetOsVersion.pas', - UfrmTike in '..\tike\main\UfrmTike.pas' {TikeForm: TTntForm}, - CompilePackageInstaller in '..\common\delphi\compiler\CompilePackageInstaller.pas', - UTikeDebugMode in '..\tike\main\UTikeDebugMode.pas', - VisualKeyboard in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', - KeymanWebKeyCodes in '..\tike\compile\KeymanWebKeyCodes.pas', - ExtShiftState in '..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', - kmxfileconsts in '..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', - wininet5 in '..\..\..\common\windows\delphi\general\wininet5.pas', - kmxfileutils in '..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', - GlobalProxySettings in '..\..\..\common\windows\delphi\general\GlobalProxySettings.pas', - VKeyChars in '..\..\..\common\windows\delphi\general\VKeyChars.pas', - ErrorControlledRegistry in '..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - utilexecute in '..\..\..\common\windows\delphi\general\utilexecute.pas', - KeymanVersion in '..\..\..\common\windows\delphi\general\KeymanVersion.pas', - kmxfile in '..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - Glossary in '..\..\..\common\windows\delphi\general\Glossary.pas', - VKeys in '..\..\..\common\windows\delphi\general\VKeys.pas', - CompileErrorCodes in '..\common\delphi\compiler\CompileErrorCodes.pas', - TouchLayout in '..\tike\oskbuilder\TouchLayout.pas', - TouchLayoutDefinitions in '..\tike\oskbuilder\TouchLayoutDefinitions.pas', - KeyboardFonts in '..\common\delphi\general\KeyboardFonts.pas', - KeyboardParser in '..\tike\main\KeyboardParser.pas', - WindowsLanguages in '..\common\delphi\general\WindowsLanguages.pas', - TempFileManager in '..\..\..\common\windows\delphi\general\TempFileManager.pas', - JsonUtil in '..\..\..\common\windows\delphi\general\JsonUtil.pas', - TikeUnicodeData in '..\tike\main\TikeUnicodeData.pas', - UnicodeData in '..\..\..\common\windows\delphi\charmap\UnicodeData.pas', - ttinfo in '..\..\..\common\windows\delphi\general\ttinfo.pas', - ADODB_TLB in '..\..\..\common\windows\delphi\tlb\ADODB_TLB.pas', - ADOX_TLB in '..\..\..\common\windows\delphi\tlb\ADOX_TLB.pas', - UKeymanTargets in '..\common\delphi\general\UKeymanTargets.pas', - kccompileproject in 'kccompileproject.pas', - Keyman.Developer.System.Project.ProjectFile in '..\tike\project\Keyman.Developer.System.Project.ProjectFile.pas', - mrulist in '..\tike\main\mrulist.pas', - Keyman.Developer.System.Project.Project in '..\tike\project\Keyman.Developer.System.Project.Project.pas', - Keyman.Developer.System.Project.ProjectFiles in '..\tike\project\Keyman.Developer.System.Project.ProjectFiles.pas', - Keyman.Developer.System.Project.ProjectFileType in '..\tike\project\Keyman.Developer.System.Project.ProjectFileType.pas', - Keyman.Developer.System.Project.ProjectLoader in '..\tike\project\Keyman.Developer.System.Project.ProjectLoader.pas', - Keyman.Developer.System.Project.ProjectSaver in '..\tike\project\Keyman.Developer.System.Project.ProjectSaver.pas', - Keyman.Developer.System.Project.kmnProjectFile in '..\tike\project\Keyman.Developer.System.Project.kmnProjectFile.pas', - Keyman.Developer.System.Project.kmxProjectFile in '..\tike\project\Keyman.Developer.System.Project.kmxProjectFile.pas', - Keyman.Developer.System.Project.kpsProjectFile in '..\tike\project\Keyman.Developer.System.Project.kpsProjectFile.pas', - Keyman.Developer.System.Project.kvkProjectFile in '..\tike\project\Keyman.Developer.System.Project.kvkProjectFile.pas', - Keyman.Developer.System.Project.ProjectLog in '..\tike\project\Keyman.Developer.System.Project.ProjectLog.pas', - UserMessages in '..\..\..\common\windows\delphi\general\UserMessages.pas', - VisualKeyboardLoaderBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderBinary.pas', - VisualKeyboardLoaderXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderXML.pas', - VisualKeyboardSaverBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', - VisualKeyboardSaverXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', - kccompilekvk in 'kccompilekvk.pas', - MergeKeyboardInfo in '..\tike\compile\MergeKeyboardInfo.pas', - JsonExtractKeyboardInfo in '..\tike\compile\JsonExtractKeyboardInfo.pas', - Keyman.System.PackageInfoRefreshKeyboards in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', - Keyman.System.KeyboardJSInfo in '..\common\delphi\keyboards\Keyman.System.KeyboardJSInfo.pas', - Keyman.System.KeyboardUtils in '..\common\delphi\keyboards\Keyman.System.KeyboardUtils.pas', - Keyman.System.KMXFileLanguages in '..\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', - Keyman.System.Standards.ISO6393ToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', - Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', - Keyman.System.KeyboardInfoFile in '..\common\delphi\keyboards\Keyman.System.KeyboardInfoFile.pas', - BCP47Tag in '..\..\..\common\windows\delphi\general\BCP47Tag.pas', - Keyman.System.LanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.LanguageCodeUtils.pas', - Keyman.System.RegExGroupHelperRSP19902 in '..\..\..\common\windows\delphi\vcl\Keyman.System.RegExGroupHelperRSP19902.pas', - Keyman.System.Standards.BCP47SubtagRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SubtagRegistry.pas', - Keyman.System.Standards.BCP47SuppressScriptRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SuppressScriptRegistry.pas', - TextFileFormat in '..\common\delphi\general\TextFileFormat.pas', - Keyman.Developer.System.Project.kmnProjectFileAction in '..\tike\project\Keyman.Developer.System.Project.kmnProjectFileAction.pas', - Keyman.Developer.System.Project.kpsProjectFileAction in '..\tike\project\Keyman.Developer.System.Project.kpsProjectFileAction.pas', - Keyman.System.PackageInfoRefreshLexicalModels in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', - Keyman.Developer.System.Project.modelTsProjectFile in '..\tike\project\Keyman.Developer.System.Project.modelTsProjectFile.pas', - Keyman.Developer.System.Project.modelTsProjectFileAction in '..\tike\project\Keyman.Developer.System.Project.modelTsProjectFileAction.pas', - Keyman.Developer.System.LexicalModelCompile in '..\common\delphi\lexicalmodels\Keyman.Developer.System.LexicalModelCompile.pas', - Keyman.System.LexicalModelUtils in '..\common\delphi\lexicalmodels\Keyman.System.LexicalModelUtils.pas', - Keyman.Developer.System.Project.ProjectLogConsole in 'Keyman.Developer.System.Project.ProjectLogConsole.pas', - Sentry.Client in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.pas', - Sentry.Client.Console in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.Console.pas', - sentry in '..\..\..\common\windows\delphi\ext\sentry\sentry.pas', - Keyman.System.KeymanSentryClient in '..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas', - KeymanPaths in '..\..\..\common\windows\delphi\general\KeymanPaths.pas', - Keyman.System.CanonicalLanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas', - Keyman.System.Standards.LangTagsRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', - Keyman.Developer.System.Project.UrlRenderer in '..\tike\project\Keyman.Developer.System.Project.UrlRenderer.pas', - Keyman.Developer.System.ValidateRepoChanges in 'Keyman.Developer.System.ValidateRepoChanges.pas', - Keyman.Developer.System.KeymanDeveloperPaths in '..\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas', - Keyman.Developer.System.ValidateKpsFile in '..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas', - Keyman.Developer.System.KmcWrapper in '..\tike\compile\Keyman.Developer.System.KmcWrapper.pas'; - -{$R icons.RES} -{$R version.res} -{$R manifest.res} - -const -{$IFDEF WIN64} - LOGGER_DEVELOPER_TOOLS_KMCOMP = TKeymanSentryClient.LOGGER_DEVELOPER_TOOLS + '.kmcomp.x64'; -{$ELSE} - LOGGER_DEVELOPER_TOOLS_KMCOMP = TKeymanSentryClient.LOGGER_DEVELOPER_TOOLS + '.kmcomp'; -{$ENDIF} -begin - TKeymanSentryClient.Start(TSentryClientConsole, kscpDeveloper, LOGGER_DEVELOPER_TOOLS_KMCOMP); - try - try - TKeymanSentryClient.Validate; - Run; - except - on E: Exception do - if not SentryHandleException(E) then - begin - writeln(E.Message); - ExitCode := 99; - end; - end; - finally - TKeymanSentryClient.Stop; - end; -end. diff --git a/developer/src/kmcomp/kmcomp.dproj b/developer/src/kmcomp/kmcomp.dproj deleted file mode 100644 index 0feab699b43..00000000000 --- a/developer/src/kmcomp/kmcomp.dproj +++ /dev/null @@ -1,1172 +0,0 @@ - - - {CB0DF7C0-19BB-4F2B-AFCC-2E51DAB3A1AA} - kmcomp.dpr - True - Release - 3 - Console - None - 18.8 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - true - Cfg_2 - true - true - - - $(BDS)\bin\delphi_PROJECTICON.ico - $(BDS)\bin\delphi_PROJECTICNS.icns - kmcomp - true - KPSMALL;VERSION_KEYMAN_DEVELOPER;$(DCC_Define) - 00400000 - false - 14 - 0 - System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;System.Win;Vcl.Imaging;$(DCC_Namespace) - true - 1 - 3 - 8 - false - false - 3081 - false - true - false - true - CompanyName=CASEG Practice Computing;FileDescription=Blue Chip;FileVersion=0.8.0.14;InternalName=BlueChip;LegalCopyright=Copyright © 1997-1999 CASEG Practice Computing;LegalTrademarks=;OriginalFilename=BlueChip.exe;ProductName=Blue Chip;ProductVersion=1.0.0.0;Comments= - VCL40;VCLX40;VCLDB40;VCLDBX40;VCLSMP40;QRPT40;TEEUI40;TEEDB40;TEE40;ibevnt40;nmfast40;bcutil;VCLJPG40;bccomp;bcdb;bcacct;bcappt;$(DCC_UsePackage) - false - true - true - .\bin\$(Platform)\$(Config) - .\obj\$(Platform)\$(Config) - true - - - kmcomp_Icon.ico - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - kmcomp_Icon.ico - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) - Debug - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - 1033 - (None) - - - false - 0 - 0 - RELEASE;$(DCC_Define) - - - c:\temp\foo.kps -schema-path C:\Projects\keyman\app\common\schemas\kps\ - true - 2 - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - kmcomp_Icon.ico - true - 2 - true - - - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - kmcomp_Icon.ico - true - false - 1 - - - DEBUG;$(DCC_Define) - false - - - khmer_angkor.kpj - C:\Projects\keyman\keyboards\release\k\khmer_angkor - true - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - kmcomp_Icon.ico - - - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - kmcomp_Icon.ico - khmer_angkor.kpj - C:\Projects\keyman\keyboards\release\k\khmer_angkor - - - - MainSource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TikeForm
- TTntForm -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - -
- - Delphi.Personality.12 - VCLApplication - - - - kmcomp.dpr - - - $00000C09 - - - False - False - 0 - 8 - 0 - 14 - False - False - False - False - False - 3081 - 1252 - - - CASEG Practice Computing - Blue Chip - 0.8.0.14 - BlueChip - Copyright © 1997-1999 CASEG Practice Computing - - BlueChip.exe - Blue Chip - 1.0.0.0 - - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - True - True - - - - - true - - - - - kmcomp.exe - true - - - - - kmcomp.exe - true - - - - - kmcomp.exe - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - 12 - - - - -
diff --git a/developer/src/kmcomp/kmcomp.res b/developer/src/kmcomp/kmcomp.res deleted file mode 100644 index bfe8dde79b6a8eb07995cad2051814eadb789640..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 940 zcmah|J!{-R5Pgac!jn1H*a=z@+E|1q^H@L_hxnq4FHS@V;p$=&3?Yv@;)`59>&C! z@X)7O^LNVtzQ{7?_~ntvP}emH8A6%8PG4Vii`5-Ma8}^By=+wbOY#&hs z9R(t%)sjd>5Id9jzk>Un4pI>BbeIC8#tIs1HNf6!(I)*B!c(A<8LYta6chol7savV zoBLMH6tX;T93QJBDE6+uMm^sYMLH`gJ@BVVo8D}yyMb4#T~_*z^>|N5I)k3O3OnE^0h9hw|pPS?!yQe+ZG*z{wBbh{J|G0>%RW zU!XP+bkq^vBmEOS|1#gy^sC4><}onlDWpH5KY0ycLKyQ3pYax-a7MhtIsMiqJ9{D9 u;9)!-*{zT*@*r#R#`f691!8u&!bf(vWXCH;mpH`-p1$-ACwSg7pSfS$z!c~J diff --git a/developer/src/kmcomp/kmcomp_Icon.ico b/developer/src/kmcomp/kmcomp_Icon.ico deleted file mode 100644 index 1bed139f3c1acc52cfa22402bf91690c2a69ddf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcmah{v1-FG5WUm^Whi*-nvDI6WGK{wp}&&N3K^_|LEW-Oc<6T+4;IvrB_GkTV>UY^ zM13c_CX~=V`RRQ3-kmgvXoNMJjfwGfNA!UB2+qI-IHFD?Xt$w=@R3qoz>Z%~k+l|r z^B+0Ke;^i@c>9e+qOu1%L8Mywv@ZzV%KLrn7du1?1KR=1g_I3(2hRUB?5-woLwq%1 zK{T$tq4uyun4Rh->5pI?2AM3$8@vvK7DSUMj=f&*8o89DvaIg(SSCVGR&P;%scj&laoi(OB z<`}>YtVae}?sejZ>}YcMz1|y)DLUPEWGx)M&V|KGxXH~1;|8?d`9mK5wD*;xzuq8I z$lV`!1h0qCDH}M2Cjq9GX#Pnw#bv{u0y#7Ii|`6&3m-T-BVG{k$^vH*4^iOUSvYqI I5vPdrZ)Z6FrT_o{ diff --git a/developer/src/kmcomp/main.pas b/developer/src/kmcomp/main.pas deleted file mode 100644 index a0b5f9b865a..00000000000 --- a/developer/src/kmcomp/main.pas +++ /dev/null @@ -1,327 +0,0 @@ -(* - Name: main - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 20 Jun 2006 - - Modified Date: 24 Aug 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 20 Jun 2006 - mcdurdin - Initial version - 02 Aug 2006 - mcdurdin - Timeout when Beta expires - 25 Jan 2007 - mcdurdin - Add -x option to encrypt kmx and kct files - 30 Apr 2007 - mcdurdin - Initialize COM before building - 30 May 2007 - mcdurdin - I817 - build bootstrap installer option - 04 Jun 2007 - mcdurdin - I817 - support updating to a new msi file - 27 Mar 2008 - mcdurdin - I1369 - Support compiling KeymanWeb keyboards in KMCOMP - 25 May 2010 - mcdurdin - I2392 - Activation Client integration - 18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support - 08 Jun 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes - 13 Dec 2012 - mcdurdin - I3681 - V9.0 - KeymanWeb compiler should output formatted js when debug=1 - 05 May 2015 - mcdurdin - I4699 - V9.0 - Compile .kpj files from kmcomp - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness - 11 May 2015 - mcdurdin - I4707 - V9.0 - Add clean target to kmcomp - 22 Jun 2015 - mcdurdin - I4763 - Package compiler (buildpkg) needs to specify username and password on command line - 03 Aug 2015 - mcdurdin - I4825 - Make kmcomp command line details consistent - 24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project - 24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile - -*) -unit main; // I3306 - -interface - -procedure Run; - -implementation - -uses - System.AnsiStrings, - System.SysUtils, - Winapi.ActiveX, - Winapi.Windows, - - KeyboardParser, - kmxfileconsts, - Keyman.Developer.System.Project.ProjectLog, - Keyman.Developer.System.Project.ProjectLogConsole, - Keyman.Developer.System.ValidateRepoChanges, - VersionInfo, - compile, - KCCompilePackage, - KCCompileProject, - KCCompileKVK, - KeymanVersion, - JsonExtractKeyboardInfo, - MergeKeyboardInfo, - UKeymanTargets; - -function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; forward; // I4706 -//function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; forward; -procedure FixupPathSlashes(var path: string); forward; - -procedure Run; -var - hOutfile: THandle; - FNologo, FDebug, FSilent, FError: Boolean; - s, FParamTarget, FParamInfile, FParamOutfile, FParamDebugfile: string; - i: Integer; - FInstaller: Boolean; - FUpdateInstaller: Boolean; - FInstallerMSI: string; - FClean: Boolean; - FValidateRepoChanges, FFullySilent: Boolean; - FWarnAsError: Boolean; - FCheckFilenameConventions: Boolean; - FValidating: Boolean; - FMerging: Boolean; - FParamInfile2: string; - FParamJsonFields: string; - FJsonExtract: Boolean; - FParamDistribution: Boolean; - FMergingValidateIds: Boolean; - FShouldAddCompilerVersion: Boolean; - FJsonSchemaPath: string; - FParamSourcePath: string; - FParamHelpLink: string; - FColorMode: TProjectLogConsole.TColorMode; - cmd, spc: string; -begin - FSilent := False; - FFullySilent := False; - FDebug := False; - FError := False; - FInstaller := False; - FUpdateInstaller := False; - FClean := False; - FNologo := False; - FValidateRepoChanges := False; - FWarnAsError := False; - FCheckFilenameConventions := False; - FValidating := False; - FJsonExtract := False; - FMerging := False; - FMergingValidateIds := False; - FParamDistribution := False; - FInstallerMSI := ''; - FColorMode := cmDefault; - - FShouldAddCompilerVersion := True; - - FParamInfile := ''; - FParamOutfile := ''; - FParamDebugfile := ''; - FParamTarget := ''; // I4699 - - FJsonSchemaPath := ExtractFilePath(ParamStr(0)); - - i := 1; - while i <= ParamCount do - begin - s := LowerCase(ParamStr(i)); - if s = '-nologo' then // I4706 - FNologo := True - else if s = '-validate-repo-changes' then - FValidateRepoChanges := True - else if s = '-s' then FSilent := True // I4706 - else if s = '-ss' then // I4706 - begin - FSilent := True; - FFullySilent := True; - end - else if s = '-c' then FClean := True // I4707 - else if s = '-u' then FUpdateInstaller := True - else if s = '-d' then FDebug := True - else if s = '-w' then FWarnAsError := True // I4706 - else if s = '-cfc' then FCheckFilenameConventions := True - else if s = '-t' then // I4699 - begin - Inc(i); - if FParamTarget <> '' - then FError := True - else FParamTarget := ParamStr(i); - end - else if s = '-v' then FValidating := True - else if s = '-vs' then FValidating := True - else if s = '-vd' then begin FValidating := True; FParamDistribution := True; end - else if s = '-m' then - begin - FMerging := True; - if (FParamInfile <> '') and (FParamInfile2 = '') then - begin - Inc(i); - FParamInfile2 := ParamStr(i); - end; - end - else if s = '-source-path' then - begin - Inc(i); - FParamSourcePath := ParamStr(i); - end - else if s = '-add-help-link' then - begin - Inc(i); - FParamHelpLink := ParamStr(i); - end - else if s = '-schema-path' then - begin - Inc(i); - FJsonSchemaPath := IncludeTrailingPathDelimiter(ParamStr(i)); - end - else if s = '-m-validate-id' then - FMergingValidateIds := True - else if s = '-extract-keyboard-info' then - begin - FJsonExtract := True; - Inc(i); - FParamJsonFields := ParamStr(i); - end - else if s = '-color' then - FColorMode := cmForceColor - else if s = '-no-color' then - FColorMode := cmForceNoColor - else if s = '-no-compiler-version' then - FShouldAddCompilerVersion := False - else if (s = '-help') or (s = '-h') then - begin - // Force help - FParamInfile := ''; - Break; - end - else if FParamInfile = '' then FParamInfile := ParamStr(i) - else if FParamOutfile = '' then FParamOutfile := ParamStr(i) - else if FParamDebugfile = '' then FParamDebugfile := ParamStr(i) - else FError := True; - Inc(i); - end; - - if FUpdateInstaller and (FInstallerMSI = '') then - begin - writeln('Invalid arguments: -u cannot be specified if installer.msi is not.'); - ExitCode := 3; - Exit; - end; - - if (not FSilent and not FNologo) or FError then // I4706 - begin -{$IFDEF WIN64} - writeln(SKeymanDeveloperName + ' Compiler (64-bit)'); -{$ELSE} - writeln(SKeymanDeveloperName + ' Compiler (32-bit)'); -{$ENDIF} - writeln('Version ' + CKeymanVersionInfo.VersionWithTag + ', ' + GetVersionCopyright); - end; - - if FError or (FParamInfile = '') then - begin - cmd := ChangeFileExt(ExtractFileName(ParamStr(0)), ''); - spc := StringOfChar(' ', cmd.Length); - writeln(''); - writeln('Usage: '+cmd+' [-s[s]] [-nologo] [-c] [-d] [-w] [-cfc] [-v[s|d]] [-source-path path] [-schema-path path] '); - writeln(' '+spc+' [-m] infile [-m infile] [-t target] [outfile.kmx|outfile.js [error.log]]'); // I4699 - writeln(' '+spc+' [-add-help-link path] [-color|-no-color] [-no-compiler-version]'); - writeln(' '+spc+' [-extract-keyboard-info field[,field...]]'); - writeln(' infile can be a .kmn file (Keyboard Source, .kps file (Package Source), or .kpj (project)'); // I4699 // I4825 - writeln(' if -v specified, can also be a .keyboard_info file'); - writeln(' outfile.kmx can only be specified for a .kmn infile'); - writeln(' outfile.js write a KeymanWeb file'); - writeln(' error.log write to an error log; outfile must be specified'); // I4825 - writeln; - writeln(' -h, -help print this help information'); - writeln(' -s silent; don''t print information-level messages'); - writeln(' -ss fully silent; don''t print anything (except fatal errors)'); - writeln(' -nologo don''t print the compiler description'); - writeln(' -c clean target (only for .kpj)'); - writeln(' -d include debug information'); - writeln(' -w treat warnings as errors'); - writeln(' -cfc check filename conventions'); - writeln(' -t build only the target file from the project (only for .kpj)'); // I4699 - writeln(' -add-help-link path to help file on https://help.keyman.com/keyboards'); - writeln; - writeln(' -color If specified, forces color log messages on'); - writeln(' -no-color If specified, forces color log messages off. If neither specified,'); - writeln(' uses console mode to determine whether color should be used.'); - writeln; - writeln(' -no-compiler-version Don''t embed the compiler version stores, useful for regression tests.'); - writeln; - writeln(' JSON .keyboard_info compile targets:'); - writeln(' -v[s] validate infile against source schema'); - writeln(' -vd validate infile against distribution schema'); - writeln(' -m merge information from infile (can be .kmp and .js) into .keyboard_info output file'); - writeln(' -m-validate-id validate the id against the .js, .kmx and .kmp filenames when merging'); - writeln(' -extract-keyboard-info print json data .keyboard_info for build script integration'); - writeln(' -source-path specify path to add to the sourcePath field in the .keyboard_info output file'); - writeln(' -schema-path specify path to the keyboard_info json schema definitions'); - writeln(' if not specified, then defaults to same folder as kmcomp.exe'); - - if FError - then ExitCode := 2 - else ExitCode := 0; - Exit; - end; - - CoInitializeEx(nil, COINIT_APARTMENTTHREADED); - try - if not FSilent then writeln(''); - - FixupPathSlashes(FParamDebugFile); - FixupPathSlashes(FParamTarget); - FixupPathSlashes(FParamInfile); - FixupPathSlashes(FParamOutfile); - FixupPathSlashes(FParamInfile2); - FixupPathSlashes(FInstallerMSI); - FixupPathSlashes(FJsonSchemaPath); - - if FParamDebugfile <> '' then - begin - hOutfile := CreateFile(PChar(FParamDebugfile), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0); - if hOutfile = INVALID_HANDLE_VALUE then hOutfile := 0; - end - else - hOutfile := 0; - - TProjectLogConsole.Create(FSilent, FFullySilent, hOutfile, FColorMode); - - if FValidateRepoChanges then - FError := not TValidateRepoChanges.Execute(FParamInfile, FParamOutfile) - else if FMerging then - FError := not TMergeKeyboardInfo.Execute(FParamSourcePath, FParamInfile, FParamInfile2, FParamOutfile, FParamHelpLink, FMergingValidateIds, FSilent, TProjectLogConsole.Instance.Log) - else if FValidating then - FError := True - else if FJsonExtract then - FError := not TJsonExtractKeyboardInfo.Execute(FParamInfile, FParamJsonFields, FSilent, TProjectLogConsole.Instance.Log) - else if LowerCase(ExtractFileExt(FParamInfile)) = '.kpj' then // I4699 - Ferror := not DoKCCompileProject(FParamInfile, FDebug, FClean, FWarnAsError, FCheckFilenameConventions, FParamTarget) // I4706 // I4707 - else if LowerCase(ExtractFileExt(FParamInfile)) = '.kps' then - FError := not DoKCCompilePackage(FParamInfile, FWarnAsError, FInstaller, FCheckFilenameConventions, FInstallerMSI, FUpdateInstaller, FJsonSchemaPath) // I4706 - else - FError := not CompileKeyboard(FParamInfile, FParamOutfile, FDebug, FWarnAsError); // I4706 - - if hOutfile <> 0 then CloseHandle(hOutfile); - finally - CoUninitialize(); - end; - - if FError then - ExitCode := 1; -end; - -function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; // I4706 -begin - Result := False; -end; - -procedure FixupPathSlashes(var path: string); -begin - path := path.Replace('/', '\', [rfReplaceAll]); -end; - - -end. - diff --git a/developer/src/kmcomp/manifest.in b/developer/src/kmcomp/manifest.in deleted file mode 100644 index 11ef5edb716..00000000000 --- a/developer/src/kmcomp/manifest.in +++ /dev/null @@ -1,22 +0,0 @@ - - - - Keyman Developer Command Line Compiler - - - - - - - - - - - - - - - - - - diff --git a/developer/src/kmcomp/manifest.rc b/developer/src/kmcomp/manifest.rc deleted file mode 100644 index 984fce31b2c..00000000000 --- a/developer/src/kmcomp/manifest.rc +++ /dev/null @@ -1 +0,0 @@ -1 24 manifest.xml diff --git a/developer/src/kmcomp/version.rc b/developer/src/kmcomp/version.rc deleted file mode 100644 index c4bec5f02e7..00000000000 --- a/developer/src/kmcomp/version.rc +++ /dev/null @@ -1,32 +0,0 @@ -#include "../../../common/windows/cpp/include/keymanversion.h" - -1 VERSIONINFO - FILEVERSION KV_FILEVERSION - PRODUCTVERSION KV_PRODUCTVERSION - FILEFLAGSMASK 0x3fL - FILEFLAGS 0x0L - FILEOS 0x4L - FILETYPE 0x1L - FILESUBTYPE 0x0L - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "0C0904E4" - BEGIN - VALUE "CompanyName", KV_COMPANY_NAME - VALUE "FileDescription", "Keyman commandline compiler\0" - VALUE "FileVersion", KV_VERSION_STRING - VALUE "InternalName", "KMCOMP\0" - VALUE "LegalCopyright", KV_LEGAL_COPYRIGHT - VALUE "LegalTrademarks", KV_LEGAL_TRADEMARKS - VALUE "OriginalFilename", "KMCOMP.EXE\0" - VALUE "ProductName", "Keyman Developer\0" - VALUE "ProductVersion", KV_VERSION_STRING - VALUE "Comments", "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0xc09, 1252 - END - END diff --git a/developer/src/kmconvert/Keyman.Developer.System.KMConvertParameters.pas b/developer/src/kmconvert/Keyman.Developer.System.KMConvertParameters.pas index 6835abf78ba..b58e5dd8fb5 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.KMConvertParameters.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.KMConvertParameters.pas @@ -279,7 +279,7 @@ function TKMConvertParameters.ValidateBCP47Tag(const component, tag: string): Bo if not TCanonicalLanguageCodeUtils.IsCanonical(tag, msg, False, False) then begin // Just a warning, because it's not illegal, just generates a warning - // kmcomp-side + // kmc-side OutputText('Warning: '+component+': '+msg); end; diff --git a/developer/src/test/auto/Makefile b/developer/src/test/auto/Makefile index 03e66911013..68108dde1fe 100644 --- a/developer/src/test/auto/Makefile +++ b/developer/src/test/auto/Makefile @@ -15,11 +15,9 @@ test: developer-tests: \ compile-supplementary-support \ - keyboard-info \ keyboard-js-info \ keyboard-package-versions \ kmcomp \ - kmcomp-empty-store-names \ kmcomp-x64-structures \ kmconvert \ kmx-file-languages \ @@ -38,10 +36,6 @@ compile-supplementary-support: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\compile-supplementary-support $(MAKE) $(TARGET) -keyboard-info: .virtual - cd $(DEVELOPER_ROOT)\src\test\auto\keyboard-info - $(MAKE) $(TARGET) - keyboard-js-info: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\keyboard-js-info $(MAKE) $(TARGET) @@ -58,11 +52,6 @@ kmcomp: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\kmcomp $(MAKE) $(TARGET) -kmcomp-empty-store-names: .virtual - rem TODO -# cd $(DEVELOPER_ROOT)\src\test\auto\kmcomp-empty-store-names -# $(MAKE) $(TARGET) - kmcomp-x64-structures: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\kmcomp-x64-structures $(MAKE) $(TARGET) diff --git a/developer/src/test/auto/compile-supplementary-support/Makefile b/developer/src/test/auto/compile-supplementary-support/Makefile index b41430d3d9c..0899d09df5a 100644 --- a/developer/src/test/auto/compile-supplementary-support/Makefile +++ b/developer/src/test/auto/compile-supplementary-support/Makefile @@ -2,38 +2,31 @@ # test if KS=1 is in the right files, both in debug and non-debug versions # -KMCOMP="..\..\..\..\bin\kmcomp.exe" +KMCOMP="..\..\..\..\bin\kmc.cmd" build KMCOMPD=$(KMCOMP) -d test: test-nodebug test-debug +KMN=i3317_nosupp.kmn i3317_withsupp.kmn i3317_withsupp_incontext.kmn i3317_withsupp_inmatch.kmn i3317_withsupp_innomatch.kmn i3317_withsupp_instore.kmn + +# TODO: replace with build.sh script test-nodebug: - $(KMCOMP) i3317_nosupp.kmn i3317_nosupp-1.0.js - findstr /v /m "KS=1" i3317_nosupp-1.0.js - $(KMCOMP) i3317_withsupp.kmn i3317_withsupp-1.0.js - findstr /m "KS=1" i3317_withsupp-1.0.js - $(KMCOMP) i3317_withsupp_incontext.kmn i3317_withsupp_incontext-1.0.js - findstr /m "KS=1" i3317_withsupp_incontext-1.0.js - $(KMCOMP) i3317_withsupp_inmatch.kmn i3317_withsupp_inmatch-1.0.js - findstr /m "KS=1" i3317_withsupp_inmatch-1.0.js - $(KMCOMP) i3317_withsupp_innomatch.kmn i3317_withsupp_innomatch-1.0.js - findstr /m "KS=1" i3317_withsupp_innomatch-1.0.js - $(KMCOMP) i3317_withsupp_instore.kmn i3317_withsupp_instore-1.0.js - findstr /m "KS=1" i3317_withsupp_instore-1.0.js + $(KMCOMP) $(KMN) + findstr /v /m "KS=1" i3317_nosupp.js + findstr /m "KS=1" i3317_withsupp.js + findstr /m "KS=1" i3317_withsupp_incontext.js + findstr /m "KS=1" i3317_withsupp_inmatch.js + findstr /m "KS=1" i3317_withsupp_innomatch.js + findstr /m "KS=1" i3317_withsupp_instore.js test-debug: - $(KMCOMPD) i3317_nosupp.kmn i3317_nosupp-1.0.js - findstr /v /m "KS=1" i3317_nosupp-1.0.js - $(KMCOMPD) i3317_withsupp.kmn i3317_withsupp-1.0.js - findstr /m "KS=1" i3317_withsupp-1.0.js - $(KMCOMPD) i3317_withsupp_incontext.kmn i3317_withsupp_incontext-1.0.js - findstr /m "KS=1" i3317_withsupp_incontext-1.0.js - $(KMCOMPD) i3317_withsupp_inmatch.kmn i3317_withsupp_inmatch-1.0.js - findstr /m "KS=1" i3317_withsupp_inmatch-1.0.js - $(KMCOMPD) i3317_withsupp_innomatch.kmn i3317_withsupp_innomatch-1.0.js - findstr /m "KS=1" i3317_withsupp_innomatch-1.0.js - $(KMCOMPD) i3317_withsupp_instore.kmn i3317_withsupp_instore-1.0.js - findstr /m "KS=1" i3317_withsupp_instore-1.0.js + $(KMCOMPD) $(KMN) + findstr /v /m "KS=1" i3317_nosupp.js + findstr /m "KS=1" i3317_withsupp.js + findstr /m "KS=1" i3317_withsupp_incontext.js + findstr /m "KS=1" i3317_withsupp_inmatch.js + findstr /m "KS=1" i3317_withsupp_innomatch.js + findstr /m "KS=1" i3317_withsupp_instore.js clean: -del *.js diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_nosupp.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_nosupp.kmn index 6d1a8d29f83..5385ff3ba55 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_nosupp.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_nosupp.kmn @@ -1,12 +1,14 @@ c This should not generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - no supp chars' +store(&TARGETS) 'web' + begin Unicode > use(main) store(something) 'abc' group(main) using keys - + 'abc' + 'd' > 'efg' match > 'abc' diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp.kmn index 6aaec1c28a8..5ee0b474748 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp.kmn @@ -1,13 +1,15 @@ c This should generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - with supp chars' +store(&TARGETS) 'web' + begin Unicode > use(main) store(something) 'abc' group(main) using keys - -U+13000 'abc' + 'd' > 'efg' + +U+13000 'abc' + 'd' > 'efg' match > 'abc' nomatch > 'abc' diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_incontext.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_incontext.kmn index cfe2eda9d73..0e20dbde08f 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_incontext.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_incontext.kmn @@ -1,13 +1,15 @@ c This should generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - with supp chars in context' +store(&TARGETS) 'web' + begin Unicode > use(main) store(something) 'abc' group(main) using keys - -'abc' + 'd' > 'efg' U+13000 + +'abc' + 'd' > 'efg' U+13000 match > 'abc' nomatch > 'abc' diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_inmatch.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_inmatch.kmn index 58d8b27e184..9fd10da7c39 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_inmatch.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_inmatch.kmn @@ -1,14 +1,16 @@ c This should generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - with supp chars' +store(&TARGETS) 'web' + begin Unicode > use(main) store(something) 'abc' group(main) using keys - - 'abc' + 'd' > 'efg' + + 'abc' + 'd' > 'efg' match > 'abc' U+13000 nomatch > 'abc' diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_innomatch.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_innomatch.kmn index f7d03523de6..ab086ddf7c9 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_innomatch.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_innomatch.kmn @@ -1,6 +1,8 @@ c This should generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - with supp chars' +store(&TARGETS) 'web' + begin Unicode > use(main) @@ -8,8 +10,8 @@ begin Unicode > use(main) store(something) 'abc' group(main) using keys - - 'abc' + 'd' > 'efg' -match > 'abc' + 'abc' + 'd' > 'efg' + +match > 'abc' nomatch > 'abc' U+13000 diff --git a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_instore.kmn b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_instore.kmn index fcbe73efece..427dd6b8a91 100644 --- a/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_instore.kmn +++ b/developer/src/test/auto/compile-supplementary-support/i3317_withsupp_instore.kmn @@ -1,6 +1,8 @@ c This should generate KS=1 in the .js store(&VERSION) '9.0' store(&NAME) 'I3317 - with supp chars' +store(&TARGETS) 'web' + begin Unicode > use(main) @@ -8,8 +10,8 @@ begin Unicode > use(main) store(something) 'abc' U+13000 group(main) using keys - - 'abc' + 'd' > 'efg' -match > 'abc' -nomatch > 'abc' + 'abc' + 'd' > 'efg' + +match > 'abc' +nomatch > 'abc' diff --git a/developer/src/test/auto/keyboard-info/Makefile b/developer/src/test/auto/keyboard-info/Makefile deleted file mode 100644 index b6935b2958e..00000000000 --- a/developer/src/test/auto/keyboard-info/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# -# Test that .keyboard_info validation works correctly -# -# Uses kmcomp.exe rather than attempting to run a separate test -# - -!include ..\..\..\Defines.mak - -test: build - test.bat "$(DEVELOPER_PROGRAM)\kmcomp.exe" - -build: - rem Nothing to do - -clean: def-clean - -rd /s/q Win32 - -!include ..\..\..\Target.mak diff --git a/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info b/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info deleted file mode 100644 index 97459477c8a..00000000000 --- a/developer/src/test/auto/keyboard-info/test-invalid-language-code.keyboard_info +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "test", - "name": "test", - "license": "mit", - "languages": [ - "foooooooo=amh" - ], - "platformSupport": { - "windows": "full", - "macos": "full", - "desktopWeb": "full", - "mobileWeb": "full", - "ios": "full", - "android": "full" - }, - "lastModifiedDate": "2017-09-06" -} \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info b/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info deleted file mode 100644 index eb08779d8fb..00000000000 --- a/developer/src/test/auto/keyboard-info/test-non-canonical-language-code.keyboard_info +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "test", - "name": "test", - "license": "mit", - "languages": [ - "amh" - ], - "platformSupport": { - "windows": "full", - "macos": "full", - "desktopWeb": "full", - "mobileWeb": "full", - "ios": "full", - "android": "full" - }, - "lastModifiedDate": "2017-09-06" -} \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-info/test-valid.keyboard_info b/developer/src/test/auto/keyboard-info/test-valid.keyboard_info deleted file mode 100644 index 0997d5c4ad8..00000000000 --- a/developer/src/test/auto/keyboard-info/test-valid.keyboard_info +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "test-valid", - "name": "Balochi Phonetic", - "jsFilename": "balochi_phonetic-1.1.js", - "packageFilename": "balochi_phonetic.kmp", - "platformSupport": { - "windows": "full", - "macos": "full", - "desktopWeb": "full", - "mobileWeb": "full", - "ios": "full", - "android": "full" - }, - "license": "mit", - "languages": [ - "bal", - "bgn", - "bgp" - ], - "lastModifiedDate": "2017-09-06" -} \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-info/test.bat b/developer/src/test/auto/keyboard-info/test.bat deleted file mode 100644 index a08fd723c53..00000000000 --- a/developer/src/test/auto/keyboard-info/test.bat +++ /dev/null @@ -1,61 +0,0 @@ -@echo off -rem We use test.bat so we can test errorlevels -setlocal -set ESC= - -rem This is temporary until source keyboard_info files go away -copy "%KEYMAN_ROOT%\common\schemas\keyboard_info\keyboard_info.schema.json" "%KEYMAN_ROOT%\common\schemas\keyboard_info\keyboard_info.source.json" - -if "%1"=="-h" goto usage -if "%1"=="--help" goto usage -if "%1"=="-?" goto usage - -if "%1"=="-c" ( - set RED=%ESC%[1;31m - set GREEN=%ESC%[1;32m - set WHITE=%ESC%[0;37m - set BLUE=%ESC%[1;36m - shift -) -if "%1"=="" ( - set compiler=..\..\..\..\bin\kmcomp.exe -) else ( - set compiler=%1 -) - -:test-1 -call :should-pass "Valid keyboard info file" test-valid.keyboard_info || exit /b 1 - -call :should-fail "Keyboard info file with invalid BCP-47 code" test-invalid-language-code.keyboard_info || exit /b 1 - -:: in 13.0, this test was a should-fail -:: in 14.0, we made non-canonical bcp 47 codes a hint instead of an error -:: see #4689. -call :should-pass "Keyboard info file with non-canonical BCP-47 code" test-non-canonical-language-code.keyboard_info || exit /b 1 - -goto :eof - -:should-pass -echo %BLUE%TEST: %1 %WHITE% -"%compiler%" -vs -schema-path "%KEYMAN_ROOT%\common\schemas\keyboard_info" "%2" -if %ERRORLEVEL% EQU 0 ( - echo %GREEN%TEST PASSED%WHITE% - exit /b 0 -) -echo %RED%FAILED: expected %2 to be valid.%WHITE% 1>&2 -exit /b 1 - -:should-fail -echo %BLUE%TEST: %1 %WHITE% -"%compiler%" -s -vs -schema-path "%KEYMAN_ROOT%\common\schemas\keyboard_info" "%2" -if %ERRORLEVEL% GTR 0 ( - echo %GREEN%TEST PASSED%WHITE% - exit /b 0 -) -echo %RED%FAILED: expected %2 to be invalid.%WHITE% 1>&2 -exit /b 1 - -:usage -echo Usage: test.bat [-c] [path-to-kmcomp.exe] -echo -c will add colour via ANSI escapes (don't use in redirected scripts) -echo path-to-kmcomp.exe, if not included will default to ..\..\..\..\bin\kmcomp.exe diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr index bb50c6f6545..d3645dc38f6 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr @@ -65,7 +65,6 @@ uses Keyman.Developer.System.Project.ProjectLog in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectLog.pas', Keyman.Developer.System.Project.ProjectSaver in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectSaver.pas', UKeymanTargets in '..\..\..\common\delphi\general\UKeymanTargets.pas', - CompileKeymanWeb in '..\..\..\tike\compile\CompileKeymanWeb.pas', CompileErrorCodes in '..\..\..\common\delphi\compiler\CompileErrorCodes.pas', KeyboardParser in '..\..\..\tike\main\KeyboardParser.pas', WindowsLanguages in '..\..\..\common\delphi\general\WindowsLanguages.pas', diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj index 26c431d4724..1ebd5a77344 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj @@ -146,7 +146,6 @@ - diff --git a/developer/src/test/auto/kmcomp-empty-store-names/emptystorename.kmn b/developer/src/test/auto/kmcomp-empty-store-names/emptystorename.kmn deleted file mode 100644 index 4f3ce2b72ec..00000000000 --- a/developer/src/test/auto/kmcomp-empty-store-names/emptystorename.kmn +++ /dev/null @@ -1,8 +0,0 @@ -store(&NAME) 'Test Empty Store Name' -begin Unicode > use(main) - -group(main) using keys - -store(a) 'abc' - -any(a) + 'a' > index(a,1) diff --git a/developer/src/test/auto/kmcomp-x64-structures/Makefile b/developer/src/test/auto/kmcomp-x64-structures/Makefile index 2514106f442..c54d804c14e 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/Makefile +++ b/developer/src/test/auto/kmcomp-x64-structures/Makefile @@ -1,5 +1,5 @@ # -# Test that struct sizes in kmcomp and kmcmplib match +# Test that struct sizes in tike and kmcmplib match # for each of x86 and x64 # diff --git a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj index a54b7f38c60..6e43e65a0b0 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj +++ b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj @@ -23,32 +23,32 @@ {A2CAE0E1-703A-419C-B82D-24080839E395} Win32Proj cppstructsize - 10.0.17763.0 + 10.0 Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode Application true - v141 + v142 Unicode Application false - v141 + v142 true Unicode @@ -74,21 +74,25 @@ true $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)\..\..\..\..\..\..\common\include;$(ProjectDir)\..\..\..\..\kmcmplib\include;$(IncludePath) true $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)\..\..\..\..\..\..\common\include;$(ProjectDir)\..\..\..\..\kmcmplib\include;$(IncludePath) false $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)\..\..\..\..\..\..\common\include;$(ProjectDir)\..\..\..\..\kmcmplib\include;$(IncludePath) false $(ProjectDir)bin\$(Platform)\$(Configuration)\ $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)\..\..\..\..\..\..\common\include;$(ProjectDir)\..\..\..\..\kmcmplib\include;$(IncludePath) diff --git a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters index 7a0d5716b37..193b3bb1371 100644 --- a/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters +++ b/developer/src/test/auto/kmcomp-x64-structures/cppstructsize/cppstructsize.vcxproj.filters @@ -18,7 +18,7 @@ Header Files - + Header Files diff --git a/developer/src/test/auto/kmcomp/Makefile b/developer/src/test/auto/kmcomp/Makefile index 402991fb8de..2221cd78574 100644 --- a/developer/src/test/auto/kmcomp/Makefile +++ b/developer/src/test/auto/kmcomp/Makefile @@ -1,13 +1,13 @@ # -# Test that kmcomp passes various compile tests +# Test that kmc passes various compile tests # -# Uses kmcomp.exe rather than attempting to run a separate test +# Uses kmc.exe rather than attempting to run a separate test # !include ..\..\..\Defines.mak test: build - test.bat -c "$(DEVELOPER_PROGRAM)\kmcomp.exe" + test.bat -c $(DEVELOPER_PROGRAM)\kmc.cmd build: rem Nothing to do diff --git a/developer/src/test/auto/kmcomp/test.bat b/developer/src/test/auto/kmcomp/test.bat index d33b389d162..107e0268ce8 100644 --- a/developer/src/test/auto/kmcomp/test.bat +++ b/developer/src/test/auto/kmcomp/test.bat @@ -23,7 +23,7 @@ if "%1"=="-t" ( ) if "%1"=="" ( - set compiler=..\..\..\..\bin\kmcomp.exe + set compiler=..\..\..\..\bin\kmc.cmd ) else ( set compiler=%1 shift @@ -110,7 +110,7 @@ goto :eof :should-pass echo %BLUE%TEST: %1 %WHITE% -"%compiler%" -no-color -s -w tests.kpj -t "%2" +call "%compiler%" build --no-color --log-level hint --compiler-warnings-as-errors "%2" if !ERRORLEVEL! EQU 0 ( echo %GREEN%TEST PASSED%WHITE% exit /b 0 @@ -120,7 +120,7 @@ exit /b 1 :should-fail echo %BLUE%TEST: %1 %WHITE% -"%compiler%" -no-color -s -w tests.kpj -t "%2" +call "%compiler%" build --no-color --log-level hint --compiler-warnings-as-errors "%2" if !ERRORLEVEL! GTR 0 ( echo %GREEN%TEST PASSED%WHITE% exit /b 0 @@ -141,7 +141,7 @@ if %1 == -f ( echo %BLUE%TEST: %1 %WHITE% if exist error.log del error.log -"%compiler%" -no-color -nologo -ss -w tests.kpj -t "%2" > error.log +call "%compiler%" build --no-color --log-level hint --compiler-warnings-as-errors "%2" > error.log if !ERRORLEVEL! GTR 0 ( echo %RED%FAILED: expected %2 to be a valid keyboard.%WHITE% 1>&2 echo --------------------------------------------------------------------------- @@ -184,6 +184,6 @@ echo %GREEN%TEST PASSED%WHITE% exit /b 0 :usage -echo Usage: test.bat [-c] [path-to-kmcomp.exe] +echo Usage: test.bat [-c] [path-to-kmc.cmd] echo -c will add colour via ANSI escapes (don't use in redirected scripts) -echo path-to-kmcomp.exe, if not included will default to ..\..\..\..\bin\kmcomp.exe +echo path-to-kmc.cmd, if not included will default to ..\..\..\..\bin\kmc.cmd diff --git a/developer/src/test/auto/kmcomp/test_194_Filename_case.kvk b/developer/src/test/auto/kmcomp/test_194_Filename_case.kvk deleted file mode 100644 index a46a1fffae959d0c39646bdc37b3c9a105f5911e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2878 zcmYM#SB#Wp6o>IM--RI&5(E?FZcrd%!A4QB%%UQ?ir7#oAsC1nTcYvGm{CjTF|N*cZc0VgoAUMAeE0fPYqeVK5K-+M&HwKx$Dp`1Zpl?+qnz(w72|Sc z$NXjGoO~@Rxul2}4#~gsr>NwrBBsjK$)$2t@?Cjp@-?|4`MMmId`n)OoFUImz9KJ7 zzAevA&Xy-8zuCqu)^76hhZ~r z<=S0DQHe##PV%zk=z5XrmC2EEVe(kHAlXL_Ngi6RyQt*J7IBo-;mKa|ykxg>-9;r% zN*-zTgk(joOpcPrC2yA}CvTINB#)7UlWp>x0hS?!d>Y?tJ7 zTP2^nTN0!_k|4E79_&8JaaX&w-*Z#&=>C;7&aaX#`%Th=dn9?@Es4tSlIPqe3E@V` zbKWcIqqUMz@VDfQ_DK%qPf3TalSE~kBr4k_t+7dx_8pQjvOyBUKO}Qwy<~1|mh}D_ zNvHfJ>6D$4;kHZC-K~;&wN)~awn*~)k7Q8nZ!l^hJh0q;z7ZVRXN@ruwd3wA>6Din z*@z$;-%AF?G8YrzHC74mT1kM{NdmlH65tJz0B@88c#|Z+n|B$RvAEFSY<4JE*T;VB}3#*_cBCYl?;)V H_T&B!msuAW diff --git a/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt b/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt index 4bd39db1a8b..f4d233b724c 100644 --- a/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt +++ b/developer/src/test/auto/kmcomp/test_194_filename_case.out.txt @@ -1,11 +1,11 @@ -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.kvks' does not match case of 'test_194_Filename_case.kvks' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.html' does not match case of 'test_194_Filename_case.html' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.embed_js' does not match case of 'test_194_Filename_case.embed_js' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.embed.css' does not match case of 'test_194_Filename_case.embed.css' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case.keyman-touch-layout' does not match case of 'test_194_Filename_case.keyman-touch-layout' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case_call_0.call_js' does not match case of 'test_194_Filename_case_call_0.call_js' in source file; this is an error on platforms with case-sensitive filesystems. -test_194_filename_case.kmn: Hint: 5009 File on disk 'test_194_filename_case_call_1.call_js' does not match case of 'test_194_Filename_case_call_1.call_js' in source file; this is an error on platforms with case-sensitive filesystems. \ No newline at end of file +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.kvks' does not match case of 'test_194_Filename_case.kvks' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.bmp' does not match case of 'test_194_Filename_case.bmp' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.txt' does not match case of 'test_194_Filename_case.txt' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.html' does not match case of 'test_194_Filename_case.html' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.embed_js' does not match case of 'test_194_Filename_case.embed_js' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.embed.css' does not match case of 'test_194_Filename_case.embed.css' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case.keyman-touch-layout' does not match case of 'test_194_Filename_case.keyman-touch-layout' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case_call_0.call_js' does not match case of 'test_194_Filename_case_call_0.call_js' in source file; this is an error on platforms with case-sensitive filesystems. +test_194_filename_case.kmn - hint KM05009: File on disk 'test_194_filename_case_call_1.call_js' does not match case of 'test_194_Filename_case_call_1.call_js' in source file; this is an error on platforms with case-sensitive filesystems. diff --git a/developer/src/tike/compile/MergeKeyboardInfo.pas b/developer/src/tike/compile/MergeKeyboardInfo.pas deleted file mode 100644 index aa5360e6bfc..00000000000 --- a/developer/src/tike/compile/MergeKeyboardInfo.pas +++ /dev/null @@ -1,1134 +0,0 @@ -{ Merges a source .keyboard_info file with data programatically extracted from a .kmp file and a keyboard .js file - - - Command line parameters: - kmcomp -m [-add-help-link ] - - Note: if keyboard.kmp or keyboard.js do not exist, merge_compiled_keyboard_info will continue to work, just will - not attempt to pull data from them - - # - # There are a number of fields we can fill in programatically. - # - - # id -- from .keyboard_info name - # name -- from kmp.inf, js - # authorName -- from kmp.inf - # authorEmail -- from kmp.inf - # lastModifiedDate -- build time: this only gets refreshed when the version num increments so it's close enough then - # packageFilename -- from $keyboard_info_packageFilename - # packageFileSize -- get from the size of the file - # jsFilename -- from $keyboard_info_jsFilename - # jsFileSize -- get from the size of the file - # isRTL -- from .js, KRTL\s*=\s*1 - # encodings -- from .kmx (existence of .js implies unicode) - # packageIncludes -- from kmp.inf? - # version -- from kmp.inf, js - # minKeymanVersion -- from kmp.inf, kmx, js - # platformSupport -- deduce from whether kmp exists, js exists - # languages -- given the BCP 47 ids, generate the subtag names: - # displayName, languageName - (required) - # scriptName, regionName - if not blank -} -unit MergeKeyboardInfo; - -interface - -uses - System.Json, - compile, - kmpinffile, - packageinfo, - TempFileManager, - kmxfile, - kmxfileconsts, - Keyman.Developer.System.Project.ProjectLog, - UKeymanTargets; - -type - TKeyboardInfoMap = record - Filename: string; - Info: TKeyboardInfo; - end; - - TJSKeyboardInfoMap = record - Filename: string; - Data: string; - Standalone: Boolean; - end; - - TMergeKeyboardInfo = class - private - json: TJSONObject; - FSilent: Boolean; - FCallback: TProjectLogObjectEvent; - FBaseName, FJsFile, FKmpFile, FJsonFile: string; - FMergingValidateIds: Boolean; - FKMPInfFile: TKMPInfFile; - FPackageKMXFileInfos: array of TKeyboardInfoMap; - FPackageJSFileInfos: array of TKeyboardInfoMap; - FJSFileInfo: TJSKeyboardInfoMap; - FVersion: string; - FSourcePath: string; - FHelpLink: string; - function Failed(message: string): Boolean; - function Execute: Boolean; overload; - function LoadJsonFile: Boolean; - function LoadKMPFile: Boolean; - function LoadJSFile: Boolean; - constructor Create(ASourcePath, AJsFile, AKmpFile, AJsonFile, AHelpLink: string; AMergingValidateIds, ASilent: Boolean; ACallback: TProjectLogObjectEvent); - procedure AddAuthor; - procedure AddAuthorEmail; - procedure CheckOrAddEncodings; - procedure CheckOrAddFileSizes; - procedure CheckOrAddID; - procedure CheckOrAddJsFilename; - procedure AddLastModifiedDate; - procedure CheckOrAddMinKeymanVersion; - procedure AddName; - procedure CheckOrAddPackageFilename; - procedure AddPackageIncludes; - procedure AddHelpLink; - procedure AddPlatformSupport; - procedure CheckOrAddVersion; - procedure AddSubtagNames(id: String; o: TJSONObject); - procedure CheckOrMigrateLanguages; - function SaveJsonFile: Boolean; - procedure CheckPackageKeyboardFilenames; - procedure AddIsRTL; - procedure AddSourcePath; - public - destructor Destroy; override; - class function Execute(ASourcePath, AJsFile, AKmpFile, AJsonFile, AHelpLink: string; AMergingValidateIds, ASilent: Boolean; ACallback: TProjectLogObjectEvent): Boolean; overload; - end; - -implementation - -uses - Soap.XsBuiltIns, - - System.Classes, - System.Generics.Collections, - System.RegularExpressions, - System.SysUtils, - System.Zip, - - Keyman.System.RegExGroupHelperRSP19902, - - BCP47Tag, - JsonUtil, - Keyman.System.KeyboardInfoFile, - Keyman.System.KeyboardUtils, - Keyman.System.LanguageCodeUtils, - utilfiletypes, - VersionInfo; - -type - EInvalidKeyboardInfo = class(Exception) - end; - -{ TMergeKeyboardInfo } - -class function TMergeKeyboardInfo.Execute(ASourcePath, AJsFile, AKmpFile, AJsonFile, AHelpLink: string; - AMergingValidateIds, ASilent: Boolean; ACallback: TProjectLogObjectEvent): Boolean; -begin - with TMergeKeyboardInfo.Create(ASourcePath, AJsFile, AKmpFile, AJsonFile, AHelpLink, AMergingValidateIds, ASilent, ACallback) do - try - Result := Execute; - finally - Free; - end; -end; - -constructor TMergeKeyboardInfo.Create(ASourcePath, AJsFile, AKmpFile, AJsonFile, AHelpLink: string; - AMergingValidateIds, ASilent: Boolean; ACallback: TProjectLogObjectEvent); -begin - inherited Create; - - FSourcePath := ASourcePath; - FMergingValidateIds := AMergingValidateIds; - FHelpLink := AHelpLink; - FSilent := ASilent; - FCallback := ACallback; - - if not SameText(ExtractFileExt(AJsFile), '.js') then - begin - FKmpFile := AJsFile; - FJsFile := AKmpFile; - end - else - begin - FKmpFile := AKmpFile; - FJsFile := AJsFile; - end; - - FJsonFile := AJsonFile; - - FBaseName := ChangeFileExt(ExtractFileName(FJsonFile), ''); -end; - -destructor TMergeKeyboardInfo.Destroy; -begin - inherited Destroy; - json.Free; - FKMPInfFile.Free; -end; - -function TMergeKeyboardInfo.Execute: Boolean; -begin - try - if not LoadJsonFile then - Exit(Failed('Could not parse keyboard_info file '+FJsonFile)); - - if not LoadKMPFile then - Exit(Failed('Could not load KMP file '+FKmpFile)); - - if not LoadJSFile then - Exit(Failed('Could not load JS file '+FJsFile)); - - CheckPackageKeyboardFilenames; - - CheckOrAddID; - CheckOrMigrateLanguages; - AddName; - AddIsRTL; - AddAuthor; - AddAuthorEmail; - AddLastModifiedDate; - AddSourcePath; - - CheckOrAddVersion; // must be called before CheckOrAddJsFilename - - CheckOrAddPackageFilename; - CheckOrAddJsFilename; - CheckOrAddEncodings; - CheckOrAddFileSizes; - AddPackageIncludes; - CheckOrAddMinKeymanVersion; - AddHelpLink; - AddPlatformSupport; - - if not SaveJsonFile then - Exit(Failed('Could not save updated keyboard_info file '+FJsonFile)); - except - on E:EInvalidKeyboardInfo do - begin - Failed('Invalid .keyboard_info file: '+E.Message); - Exit(False); - end; - on E:Exception do - begin - Failed('Fatal error '+E.ClassName+': '+E.Message); - Exit(False); - end; - end; - - Result := True; -end; - -function TMergeKeyboardInfo.LoadJsonFile: Boolean; -begin - try - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(FJsonFile); - json := TJSONObject.ParseJsonValue(DataString) as TJSONObject; - finally - Free; - end; - except - on E:Exception do - Exit(Failed(E.Message)); - end; - - Result := Assigned(json); -end; - -function TMergeKeyboardInfo.LoadKMPFile: Boolean; -var - FKMXTempFile, FKMPInfTempFile: TTempFile; - i, j: Integer; - LocalHeader: TZipHeader; - Zip: TZipFile; - - procedure SaveMemberToFile(MemberFilename, OutFilename: string); - var - ZipMemberStream, OutFileStream: TStream; - begin - OutFileStream := TFileStream.Create(OutFilename, fmCreate); - try - Zip.Read(MemberFilename, ZipMemberStream, LocalHeader); - try - OutFileStream.CopyFrom(ZipMemberStream, 0); - finally - ZipMemberStream.Free; - end; - finally - OutFileStream.Free; - end; - end; - -begin - if FKMPFile = '' then - Exit(True); - - if not SameText(ExtractFileExt(FKMPFile), '.kmp') then - Exit(Failed('packageFile must be a .kmp file '+FKMPFile)); - - try - Zip := TZipFile.Create; - try - Zip.Open(FKMPFile, zmRead); - - FKMPInfFile := TKMPInfFile.Create; - - if Zip.IndexOf('kmp.json') >= 0 then - begin - FKMPInfTempFile := TTempFileManager.Get('.json'); - try - FKMPInfFile.FileName := FKMPInfTempFile.Name; - SaveMemberToFile('kmp.json', FKMPInfTempFile.Name); - FKMPInfFile.LoadJson; - finally - FKMPInfTempFile.Free; - end; - end - else - begin - FKMPInfTempFile := TTempFileManager.Get('.inf'); - try - FKMPInfFile.FileName := FKMPInfTempFile.Name; - SaveMemberToFile('kmp.inf', FKMPInfTempFile.Name); - FKMPInfFile.LoadIni; - finally - FKMPInfTempFile.Free; - end; - end; - - FKMXTempFile := TTempFileManager.Get('.kmx'); - try - if FKMPInfFile.Keyboards.Count > 0 then - begin - for i := 0 to FKMPInfFile.Keyboards.Count - 1 do - begin - for j := 0 to High(Zip.FileNames) do - begin - // Add the KMX to FPackageKMXFileinfos - if SameText(Zip.FileName[j], FKMPInfFile.Keyboards[i].ID + '.kmx') then - begin - SetLength(FPackageKMXFileInfos, Length(FPackageKMXFileInfos)+1); - SaveMemberToFile(Zip.FileNames[j], FKMXTempFile.Name); - FPackageKMXFileInfos[High(FPackageKMXFileInfos)].Filename := Zip.FileNames[j]; - GetKeyboardInfo(FKMXTempFile.Name, False, FPackageKMXFileInfos[High(FPackageKMXFileInfos)].Info, False); - end; - - // Add the JS to FPackageJSFileInfos - if SameText(Zip.FileNames[j], FKMPInfFile.Keyboards[i].ID + '.js') then - begin - SetLength(FPackageJSFileInfos, Length(FPackageJSFileInfos)+1); - FPackageJSFileInfos[High(FPackageJSFileInfos)].Filename := Zip.FileNames[j]; - - // Apply JS keyboard only to mobile targets, because web is not supported - // in a package. If a package does not support mobile, it should not include - // the .js. - // Not using GetKeyboardInfo because that only handles kmx files - FPackageJSFileInfos[High(FPackageJSFileInfos)].Info.Targets := 'mobile'; - end; - end; - end; - end - - // Handle keyboard packages which may not have [Keyboard] sections defined in kmp.inf. - // Note: They will never include any js keyboards. - else if FKMPInfFile.Keyboards.Count = 0 then - begin - for i := 0 to High(Zip.FileNames) do - begin - if IsKeyboardFile(Zip.FileName[i]) then - begin - SetLength(FPackageKMXFileInfos, Length(FPackageKMXFileInfos)+1); - SaveMemberToFile(Zip.FileNames[i], FKMXTempFile.Name); - FPackageKMXFileInfos[High(FPackageKMXFileInfos)].Filename := Zip.FileNames[i]; - GetKeyboardInfo(FKMXTempFile.Name, False, FPackageKMXFileInfos[High(FPackageKMXFileInfos)].Info, False); - end; - end; - end; - - finally - FKMXTempFile.Free; - end; - finally - FreeAndNil(Zip); - end; - except - on E:EZipException do - Exit(Failed(E.Message)); - end; - - Result := True; -end; - -function TMergeKeyboardInfo.LoadJSFile: Boolean; -begin - if FJsFile = '' then - Exit(True); - - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(FJsFile); - FJSFileInfo.Filename := FJsFile; - FJSFileInfo.Data := DataString; - finally - Free; - end; - - FJSFileInfo.Standalone := True; - Result := True; -end; - -function TMergeKeyboardInfo.SaveJsonFile: Boolean; -var - str: TStringList; -begin - str := TStringList.Create; - try - PrettyPrintJSON(json, str); - with TStringStream.Create(str.Text, TEncoding.UTF8) do - try - // Use TStringStream so we don't get a default BOM prolog - SaveToFile(FJsonFile); - finally - Free; - end; - finally - str.Free; - end; - Result := True; -end; - -function TMergeKeyboardInfo.Failed(message: string): Boolean; -begin - FCallback(plsError, FJsonFile, message, 0, 0); - Result := False; -end; - -// -// id -- from .keyboard_info name -// -procedure TMergeKeyboardInfo.CheckOrAddID; -var - v: TJSONValue; - FID: string; -begin - FID := ExtractFileName(ChangeFileExt(FJsonFile, '')); - - v := json.GetValue('id'); - if v <> nil then - begin - if v.Value <> FID then - raise EInvalidKeyboardInfo.CreateFmt('id field is "%s" but should be "%s"', [v.Value, FID]); - end - else - json.AddPair('id', FID); -end; - -procedure TMergeKeyboardInfo.AddSubtagNames(id: String; o: TJSONObject); -var - displayName, languageName, scriptName, regionName: String; - v: TJSONValue; - bcp47Tag: TBCP47Tag; -begin - if id = '' then - Exit; - bcp47Tag := TBCP47Tag.Create(id); - try - TLanguageCodeUtils.BCP47Languages.TryGetValue(bcp47Tag.Language, languageName); - TLanguageCodeUtils.BCP47Scripts.TryGetValue(bcp47Tag.Script, scriptName); - TLanguageCodeUtils.BCP47Regions.TryGetValue(bcp47Tag.Region, regionName); - finally - bcp47Tag.Free; - end; - - displayName := TLanguageCodeUtils.LanguageName(languageName, scriptName, regionName); - - v := o.Values[TKeyboardInfoFile.SDisplayName]; - if not Assigned(v) then - o.AddPair(TKeyboardInfoFile.SDisplayName, displayName); - - v := o.Values[TKeyboardInfoFile.SLanguageName]; - if not Assigned(v) then - o.AddPair(TKeyboardInfoFile.SLanguageName, languageName); - - if scriptName <> '' then - begin - v := o.Values[TKeyboardInfoFile.SScriptName]; - if not Assigned(v) then - o.AddPair(TKeyboardInfoFile.SScriptName, scriptName); - end; - - if regionName <> '' then - begin - v := o.Values[TKeyboardInfoFile.SRegionName]; - if not Assigned(v) then - o.AddPair(TKeyboardInfoFile.SRegionName, regionName); - end; -end; - -procedure TMergeKeyboardInfo.CheckOrMigrateLanguages; -var - v: TJSONValue; - alangs: TJSONArray; - olangs, o: TJSONObject; - pair: TJSONPair; - i: Integer; - id: string; -begin - v := json.GetValue(TKeyboardInfoFile.SLanguages); - - if v is TJSONArray then - begin - // Migrate languages[] array to Object - alangs := v as TJSONArray; - olangs := TJSONObject.Create; - for i := 0 to alangs.Count - 1 do - begin - id := alangs.Items[i].Value; - if id = '' then - continue; - - // Populate subtag names - o := TJSONObject.Create; - olangs.AddPair(id, o); - AddSubtagNames(id, o); - end; - - json.RemovePair(TKeyboardInfoFile.SLanguages); - json.AddPair(TKeyboardInfoFile.SLanguages, olangs); - end - else if v is TJSONObject then - begin - olangs := v as TJsonObject; - for i := 0 to olangs.Count - 1 do - begin - pair := olangs.Pairs[i]; - id := pair.JSONString.Value; - if id = '' then - continue; - - // Populate subtag names - o := pair.JsonValue as TJSONObject; - AddSubtagNames(id, o); - end; - end; -end; - -// -// name -- from kmp.inf, js -// -procedure TMergeKeyboardInfo.AddName; -var - FName: string; -begin - if json.GetValue('name') <> nil then Exit; - - if Assigned(FKMPInfFile) then - FName := FKMPInfFile.Info.Desc[PackageInfo_Name] - else if not FJSFileInfo.Data.IsEmpty then - begin - with TRegEx.Match(FJSFileInfo.Data, 'this\.KN="([^"]+)"') do - begin - if Success - then FName := TGroupHelperRSP19902.Create(Groups[1], FJSFileInfo.Data).FixedValue - else Exit; - end; - end - else - Exit; - - json.AddPair('name', FName); -end; - -// -// isRTL -- from js; if more than one, just first one -// -procedure TMergeKeyboardInfo.AddIsRTL; -begin - if json.GetValue('isRTL') <> nil then Exit; - - if (FJSFileInfo.Data <> '') then - begin - with TRegEx.Match(FJSFileInfo.Data, 'this\.KRTL=1') do - begin - if not Success then Exit; - end; - end - else - Exit; - - json.AddPair('isRTL', TJsonTrue.Create); -end; - -// -// authorName -- from kmp.inf -// -procedure TMergeKeyboardInfo.AddAuthor; -var - FName: string; -begin - if json.GetValue('authorName') <> nil then Exit; - if not Assigned(FKMPInfFile) then Exit; - - FName := Trim(FKMPInfFile.Info.Desc[PackageInfo_Author]); - if FName = '' then Exit; - json.AddPair('authorName', FName); -end; - -// -// authorEmail -- from kmp.inf -// -procedure TMergeKeyboardInfo.AddAuthorEmail; -var - FEmail: string; -begin - if json.GetValue('authorEmail') <> nil then Exit; - if not Assigned(FKMPInfFile) then Exit; - - FEmail := FKMPInfFile.Info.URL[PackageInfo_Author]; - if Copy(FEmail, 1, 7) <> 'mailto:' then Exit; - json.AddPair('authorEmail', Copy(FEMail,8,MaxInt)); -end; - -// -// lastModifiedDate -- build time? Is this -// good enough, and if not, how can we solve this? -// -procedure TMergeKeyboardInfo.AddLastModifiedDate; -var - FDateTime: string; -begin - if json.GetValue('lastModifiedDate') <> nil then Exit; - - with TXSDateTime.Create do - try - AsDateTime := Now; - AsUTCDateTime := AsUTCDateTime; // Converts to UTC - FDateTime := NativeToXS; - finally - Free; - end; - - json.AddPair('lastModifiedDate', FDateTime); -end; - -// -// packageFilename -- from $keyboard_info_packageFilename -// -procedure TMergeKeyboardInfo.CheckOrAddPackageFilename; -var - FFilename: string; - v: TJSONValue; -begin - FFilename := ExtractFileName(FKmpFile); - v := json.GetValue('packageFilename'); - if v <> nil then - begin - if FKmpFile = '' then - raise EInvalidKeyboardInfo.CreateFmt('packageFilename field is "%s" but that package is not present.', [v.Value]) - else if v.Value <> FFilename then - raise EInvalidKeyboardInfo.CreateFmt('packageFilename field is "%s" but should be "%s"', [v.Value, FFilename]); - end - else - begin - if FKmpFile = '' then Exit; - json.AddPair('packageFilename', FFilename); - end; - - // Check that the id of the keyboard matches the filename; used only for release/ keyboards - // in the keyboards repository - if FMergingValidateIds then - begin - if ChangeFileExt(FFilename, '') <> FBaseName then - raise EInvalidKeyboardInfo.CreateFmt('packageFilename field is "%s" but should be "%s.kmp"', - [FFilename, FBaseName]); - end; -end; - -// -// jsFilename -- from $keyboard_info_jsFilename -// -procedure TMergeKeyboardInfo.CheckOrAddJsFilename; -var - FFilename: string; - v: TJSONValue; -begin - FFilename := ExtractFileName(FJsFile); - v := json.GetValue('jsFilename'); - if v <> nil then - begin - if FJsFile = '' then - raise EInvalidKeyboardInfo.CreateFmt('jsFilename field is "%s" but that file is not present.', [v.Value]) - else if v.Value <> FFilename then - raise EInvalidKeyboardInfo.CreateFmt('jsFilename field is "%s" but should be "%s"', [v.Value, FFilename]); - end - else - begin - if FJsFile = '' then Exit; - json.AddPair('jsFilename', FFilename); - end; - - // Check that the id of the keyboard matches the filename; used only for release/ keyboards - // in the keyboards repository - if FMergingValidateIds then - begin - if ChangeFileExt(FFilename, '') <> FBaseName then - raise EInvalidKeyboardInfo.CreateFmt('jsFilename field is "%s" but should be "%s.js"', - [FFilename, FBaseName]); - end; -end; - -// -// encodings -- from .kmx (existence of .js implies unicode) -// -procedure TMergeKeyboardInfo.CheckOrAddEncodings; -var - i: Integer; - encodingsc, encodings: TKIEncodings; - v: TJSONArray; - vc: TJSONArray; -begin - encodings := []; - // For each .kmx, get encodings and add to the result - for i := 0 to High(FPackageKMXFileInfos) do - encodings := encodings + FPackageKMXFileInfos[i].Info.Encodings; - - if FJsFile <> '' then Include(encodings, keUnicode); - - - v := TJSONArray.Create; - if keANSI in encodings then - v.Add('ansi'); - - if keUnicode in encodings then - v.Add('unicode'); - - vc := json.GetValue('encodings') as TJSONArray; - if vc <> nil then - begin - encodingsc := []; - for i := 0 to vc.Count - 1 do - if vc.Items[i].Value = 'ansi' then Include(encodingsc, keANSI) - else if vc.Items[i].Value = 'unicode' then Include(encodingsc, keUnicode); - - if encodingsc <> encodings then - raise EInvalidKeyboardInfo.CreateFmt('encodings field is "%s" but should be "%s"', [vc.ToJSON, v.ToJSON]); - end - else - json.AddPair('encodings', v); -end; - -// -// packageFileSize, jsFileSize, all from the actual files -// -procedure TMergeKeyboardInfo.CheckOrAddFileSizes; - procedure DoFileSize(prefix: string); - var - vs, v: TJSONValue; - f: TSearchRec; - begin - v := json.GetValue(prefix+'Filename'); - if v <> nil then - begin - if FindFirst(ExtractFilePath(FJsonFile)+v.Value, 0, f) <> 0 then - raise EInvalidKeyboardInfo.CreateFmt('Unable to locate file %s to check its size', [v.Value]); - FindClose(f); - vs := json.GetValue(prefix+'FileSize'); - if vs = nil then - json.AddPair(prefix+'FileSize', TJSONNumber.Create(f.Size)) - else - begin - if f.Size <> (vs as TJSONNumber).AsInt64 then - raise EInvalidKeyboardInfo.CreateFmt('File size for %s is recorded as %d but should be %d.', - [v.Value, (vs as TJSONNumber).AsInt64, f.Size]); - end; - end; - end; - -begin - DoFileSize('js'); - DoFileSize('package'); -end; - -// -// packageIncludes -- from kmp.inf? -// -procedure TMergeKeyboardInfo.AddPackageIncludes; -var - i: Integer; - id, ext, name: string; - v: TJSONArray; - j: Integer; - Found: Boolean; -begin - if json.GetValue('packageIncludes') <> nil then Exit; - if not Assigned(FKMPInfFile) then Exit; - - //"welcome", "documentation", "fonts", "visualKeyboard" - - v := TJSONArray.Create; - for i := 0 to FKMPInfFile.Files.Count - 1 do - begin - name := FKMPInffile.Files[i].FileName; - ext := ExtractFileExt(name); - id := ''; - - if SameText(name, 'welcome.htm') then id := 'welcome' - else if SameText(ext, '.kvk') then id := 'visualKeyboard' - else if SameText(ext, '.rtf') then id := 'documentation' - else if SameText(ext, '.html') then id := 'documentation' - else if SameText(ext, '.htm') then id := 'documentation' - else if SameText(ext, '.pdf') then id := 'documentation' - else if SameText(ext, '.ttf') then id := 'fonts' - else if SameText(ext, '.otf') then id := 'fonts' - else if SameText(ext, '.ttc') then id := 'fonts'; - - if (id <> '') then - begin - Found := False; - for j := 0 to v.Count - 1 do - if v.Items[j].Value = id then - begin - Found := True; - Break; - end; - if not Found then - v.Add(id); - end; - end; - - json.AddPair('packageIncludes', v); -end; - -// -// version -- from kmp.inf, js (if more than 1, then just first) -// -procedure TMergeKeyboardInfo.CheckOrAddVersion; -var - v: TJSONValue; -begin - if Assigned(FKMPInfFile) then - begin - FVersion := FKMPInfFile.Info.Desc[PackageInfo_Version]; - end - else if (FJSFileInfo.Data <> '') then - begin - with TRegEx.Match(FJSFileInfo.Data, 'this\.KBVER=([''"])([^''"]+)(\1)') do - if Success then FVersion := TGroupHelperRSP19902.Create(Groups[2], FJSFileInfo.Data).FixedValue; - end; - - if FVersion = '' then - FVersion := '1.0'; - - v := json.GetValue('version'); - if v <> nil then - begin - if v.Value <> FVersion then - raise EInvalidKeyboardInfo.CreateFmt('version field is "%s" but should be "%s"', [v.Value, FVersion]); - end - else - json.AddPair('version', FVersion); -end; - -// -// minKeymanVersion -- from kmp.inf, kmx, js -// -procedure TMergeKeyboardInfo.CheckOrAddMinKeymanVersion; -var - i: Integer; - MinVersion: Cardinal; - MinVersionString: string; - FJSMinVersionString: string; - v: TJSONValue; - s: string; -begin - MinVersion := $0500; - // For each .kmx, get minimum version and add to the result - for i := 0 to High(FPackageKMXFileInfos) do - if FPackageKMXFileInfos[i].Info.FileVersion > MinVersion then - MinVersion := FPackageKMXFileInfos[i].Info.FileVersion; - - FJSMinVersionString := ''; - // See also Keyman.System.KeyboardJSInfo.pas, CompilePackage.pas - with TRegEx.Match(FJSFileInfo.Data, 'this.KMINVER\s*=\s*([''"])(.*?)\1') do - begin - if Success then - begin - s := TGroupHelperRSP19902.Create(Groups[2], FJSFileInfo.Data).FixedValue; - if (FJSMinVersionString = '') or (CompareVersions(FJSMinVersionString, s) > 0) then - FJSMinVersionString := s; - end; - end; - - MinVersionString := Format('%d.%d', [(MinVersion and VERSION_MASK_MAJOR) shr 8, (MinVersion and VERSION_MASK_MINOR)]); - if FJSMinVersionString <> '' then - if CompareVersions(MinVersionString, FJSMinVersionString) > 0 then - MinVersionString := FJSMinVersionString; - - v := json.GetValue('minKeymanVersion'); - if v <> nil then - begin - if v.Value <> MinVersionString then - raise EInvalidKeyboardInfo.CreateFmt('minKeymanVersion field is "%s" but should be "%s"', [v.Value, MinVersionString]); - end - else - json.AddPair('minKeymanVersion', MinVersionString); -end; - -// -// Add helpLink, from commandline parameter -// -procedure TMergeKeyboardInfo.AddHelpLink; -var - v: TJSONValue; -begin - // Validate pre-existing helpLink - v := json.GetValue('helpLink'); - if v <> nil then - begin - if v.Value.IndexOf('https://help.keyman.com/keyboard/') < 0 then - raise EInvalidKeyboardInfo.Create('helpLink should be on https://help.keyman.com/keyboard/'); - end - else - begin - if FHelpLink = '' then - Exit; - - json.AddPair('helpLink', FHelpLink); - end; -end; - -// -// platformSupport -- deduce from whether kmp exists, js exists -// -procedure TMergeKeyboardInfo.AddPlatformSupport; -var - keyboardFile: TKeyboardInfoMap; - target: TKeymanTarget; - targets: TKeymanTargets; - p, v: TJSONObject; - numberKMXFiles, numberJSFiles : Integer; - - procedure AddNewPair(PairName: string; value: string); - begin - try - if (v.GetValue(PairName) = nil) then - v.AddPair(PairName, value); - finally - end; - end; - - function packageContainsKeyboardJs(const id: string): Boolean; - var - kf: TKeyboardInfoMap; - begin - for kf in FPackageJSFileInfos do - if SameText(TKeyboardUtils.KeyboardFileNameToID(kf.Filename), id) then - Exit(True); - - Result := False; - end; - -begin - try - // Validate pre-existing platformSupport - p := json.GetValue('platformSupport') as TJSONObject; - if p <> nil then - begin - numberKMXFiles := Length(FPackageKMXFileInfos); - if FJsFile <> '' then - begin - numberJSFiles := 1; - end - else - begin - numberJSFiles := Length(FPackageJSFileInfos); - end; - - // Validate any target - if (p.GetValue('any') <> nil) and ((numberKMXFiles = 0) or (numberJSFiles = 0)) then - raise EInvalidKeyboardInfo.Create( - '"Any" target should have at least 1 .kmx and 1 .js keyboard file in the package'); - - // Validate desktop targets - if ((p.GetValue('windows') <> nil) or (p.GetValue('macos') <> nil) or - (p.GetValue('linux') <> nil) or (p.GetValue('desktop') <> nil)) and - (numberKMXFiles = 0) then - raise EInvalidKeyboardInfo.Create( - 'Desktop targets should have at least 1 .kmx keyboard file in the package'); - - // Validate web/mobile targets. No kmx implies web/mobile targets - if ((numberKMXFiles = 0) or (p.GetValue('web') <> nil) or - (p.GetValue('iphone') <> nil) or (p.GetValue('ipad') <> nil) or - (p.GetValue('androidphone') <> nil) or (p.GetValue('androidtablet') <> nil) or - (p.GetValue('mobile') <> nil) or (p.GetValue('tablet') <> nil)) and - (numberJSFiles = 0) then - raise EInvalidKeyboardInfo.Create( - 'Web/mobile targets should have at least 1 .js keyboard file in the package'); - - Exit; - end; - finally - end; - - // Parse targets to populate platformSuppport - v := TJSONObject.Create; - - for keyboardFile in FPackageKMXFileInfos do - begin - targets := StringToKeymanTargets(keyboardFile.Info.Targets); - if ktAny in targets then - begin - AddNewPair('windows', 'full'); - AddNewPair('macos', 'full'); - AddNewPair('linux', 'full'); - if packageContainsKeyboardJs(TKeyboardUtils.KeyboardFileNameToId(keyboardFile.Filename)) then - begin - AddNewPair('android', 'full'); - AddNewPair('ios', 'full'); - end; - end - else - begin - // If targets empty, then include all desktop targets - if (targets = []) then - begin - AddNewPair('windows', 'full'); - AddNewPair('macos', 'full'); - AddNewPair('linux', 'full'); - end; - - for target in targets do - begin - if target = ktDesktop then - begin - AddNewPair('windows', 'full'); - AddNewPair('macos', 'full'); - AddNewPair('linux', 'full'); - end; - if target = ktWindows then - AddNewPair('windows', 'full'); - if target = ktMacosx then - AddNewPair('macos', 'full'); - if target = ktLinux then - AddNewPair('linux', 'full'); - - if packageContainsKeyboardJs(TKeyboardUtils.KeyboardFileNameToId(keyboardFile.Filename)) then - begin - // FPackageKMXFileInfos can contain target information for web/mobile targets. - // This is a current limitation of FPackageJSFileInfos if there's no kmx files - if (target = ktMobile) then - begin - AddNewPair('android', 'full'); - AddNewPair('ios', 'full'); - end; - if (target = ktIphone) or (target = ktIpad) then - begin - AddNewPair('ios', 'full'); - end; - if (target = ktAndroidphone) or (target = ktAndroidtablet) then - begin - AddNewPair('android', 'full'); - end; - end; - end; - end; - end; - - for keyboardFile in FPackageJSFileInfos do - begin - targets := StringToKeymanTargets(keyboardFile.Info.Targets); - for target in targets do - begin - if (target = ktMobile) then - begin - AddNewPair('android', 'full'); - AddNewPair('ios', 'full'); - end; - if (target = ktIphone) or (target = ktIpad) then - begin - AddNewPair('ios', 'full'); - end; - if (target = ktAndroidphone) or (target = ktAndroidtablet) then - begin - AddNewPair('android', 'full'); - end; - end; - end; - - // Handle JS file not in kmp. Because it is isolated, we cannot detect - // whether it supports mobile vs desktop web because that is not included - // in the .js. So, for now we assume both. - // - // We no longer assume that the presence of a .js means support for - // native mobile apps. These apps now work on the basis of having a - // .kmp file available - if (FJsFile <> '') then - begin - AddNewPair('desktopWeb', 'full'); - AddNewPair('mobileWeb', 'full'); - end; - - json.AddPair('platformSupport', v); -end; - -// -// Add sourcePath field, from commandline parameter -// -procedure TMergeKeyboardInfo.AddSourcePath; -begin - if FSourcePath = '' then - Exit; - - if not TRegEx.IsMatch(FSourcePath, '^(release|legacy|experimental)\/.+\/.+$') then - raise EInvalidKeyboardInfo.CreateFmt('The source path "%s" is an invalid format, '+ - 'expecting "(release|legacy|experimental)/n/name".', [FSourcePath]); - json.AddPair('sourcePath', FSourcePath); -end; - -procedure TMergeKeyboardInfo.CheckPackageKeyboardFilenames; -var - numberKMXFiles, numberJSFiles : Integer; -begin - // Check that the id of the kmx/js files matches the base filename; used only for release/ keyboards - // in the keyboards repository. This implies there should be only 1 kmx and/or 1 js in release/ keyboard - // packages; this check would not be done in the packages folder. - if FMergingValidateIds and Assigned(FKMPInfFile) then - begin - numberKMXFiles := Length(FPackageKMXFileInfos); - numberJSFiles := Length(FPackageJSFileInfos); - if (numberKMXFiles = 0) and (numberJSFiles = 0) then - raise EInvalidKeyboardInfo.Create('There should be at least 1 .kmx or .js keyboard file in the package.'); - - if numberKMXFiles > 1 then - begin - raise EInvalidKeyboardInfo.Create('There should be at most 1 .kmx keyboard file in the packge.'); - end - else if (numberKMXFiles = 1) and (ChangeFileExt(FPackageKMXFileInfos[0].Filename, '') <> FBaseName) then - begin - raise EInvalidKeyboardInfo.CreateFmt('The file "%s" file in the package has the wrong filename. It should be "%s.kmx"', - [FPackageKMXFileInfos[0].Filename, FBaseName]); - end; - - if numberJSFiles > 1 then - begin - raise EInvalidKeyboardInfo.Create('There should be at most 1 .js keyboard file in the package.'); - end - else if (numberJSFiles = 1) and (ChangeFileExt(FPackageJSFileInfos[0].Filename, '') <> FBaseName) then - begin - raise EInvalidKeyboardInfo.CreateFmt('The file "%s" file in the package has the wrong filename. It should be "%s.js"', - [FPackageJSFileInfos[0].Filename, FBaseName]); - end; - - end; -end; - - -end. diff --git a/developer/src/tike/tests/Makefile b/developer/src/tike/tests/Makefile index a93126f3ab1..245e1bd6092 100644 --- a/developer/src/tike/tests/Makefile +++ b/developer/src/tike/tests/Makefile @@ -2,10 +2,10 @@ !include ..\..\Defines.mak all: - for %d in (*.kmn) do $(DEVELOPER_PROGRAM)\kmcomp "%d" + for %d in (*.kmn) do $(DEVELOPER_PROGRAM)\kmc.cmd build "%d" .kmn.kmx: - &$(DEVELOPER_PROGRAM)\kmcomp $** + &$(DEVELOPER_PROGRAM)\kmc.cmd build $** clean: -del *.kmx diff --git a/docs/build/macos.md b/docs/build/macos.md index d670001e074..f5ff0409640 100644 --- a/docs/build/macos.md +++ b/docs/build/macos.md @@ -101,8 +101,8 @@ These dependencies are also listed below if you'd prefer to install manually. sdkmanager --licenses ``` -* kmcomp (optional): WINE - - Required to build keyboards using kmcomp +* kmc (optional): node + - Required to build keyboards using kmc ```bash brew tap homebrew/cask-versions diff --git a/docs/build/old/core-desktop-notes.md b/docs/build/old/core-desktop-notes.md index 902aa7abed6..db72bd4f642 100644 --- a/docs/build/old/core-desktop-notes.md +++ b/docs/build/old/core-desktop-notes.md @@ -8,7 +8,7 @@ WARNING: these are old configuration notes. See [index.md](../index.md) for curr - ninja 1.8+ - C++14 or later compiler (VC++ 2019 or later for Windows). - lib std::fs -- kmcomp (for tests) -- must be added to path +- kmc (for tests) -- must be added to path For WASM builds: - Meson build system 0.56+ @@ -56,19 +56,6 @@ Windows SDK version, if it cannot be automatically detected. export SDKVER=10.0.19041.0 ``` -#### kmcomp - -Note on paths for kmcomp: - -The search path can be edited through System settings / Advanced system settings -/ Environment Variables / User environment variables. - -If you have Keyman Developer installed, kmcomp should be on the path already; -otherwise add `%KeymanDeveloperPath%` to your path. - -If you do not have Keyman Developer installed, add the path where you extracted -the kmcomp archive. - ### Linux #### Ubuntu and Debian @@ -88,7 +75,7 @@ the kmcomp archive. * Install Enscripten (including adding to path with `emsdk_env.sh`) (WASM builds): -You may also need the `kmcomp` wrapper - see below. +You may also need `kmc` - see below. #### Other Linux distributions @@ -109,22 +96,16 @@ You may also need the `kmcomp` wrapper - see below. * Add emcc to PATH (probably upstream/enscripten) -You may also need the `kmcomp` wrapper - see below. +You may also need the `kmc` - see below. -#### kmcomp - All Linux platforms +#### kmc - All Linux platforms -If you want to rebuild keyboards for tests, you need a wrapper `kmcomp` shell -script: +If you want to rebuild keyboards for tests, you need to install `kmc`: ```bash -#!/usr/bin/env bash -wine `dirname "$0"`/kmcomp.exe "$@" +npm i -g @keymanapp/kmc ``` -Place this in the same folder as you extracted kmcomp.exe, and -`chmod +x kmcomp`. Add the folder to the path (e.g. -`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`). - ### macOS * Install Python 3 @@ -141,26 +122,20 @@ Place this in the same folder as you extracted kmcomp.exe, and * Install Enscripten (including environment update): -#### kmcomp +#### kmc -If you want to rebuild keyboards for tests, you'll also need WINE: +If you want to rebuild keyboards for tests, you'll also need node: ```bash -brew tap homebrew/cask-versions -brew install --cask --no-quarantine wine-stable +# TODO: node install ``` -And you will also need a wrapper `kmcomp` shell script: +And you will also need to install `kmc`: ```bash -#!/usr/bin/env bash -wine64 `dirname "$0"`/kmcomp.exe "$@" +npm install -g @keymanapp/kmc ``` -Place this in the same folder as you extracted kmcomp.exe, and -`chmod +x kmcomp`. Add the folder to the path (e.g. -`export PATH=/path/to/kmcomp:$PATH`, which you can add to `.bashrc`). - ## Building -- all platforms On all platforms, use `build.sh`. diff --git a/resources/devbox/macos/macos.sh b/resources/devbox/macos/macos.sh index 19ebea01bc1..edbfb43a6fc 100755 --- a/resources/devbox/macos/macos.sh +++ b/resources/devbox/macos/macos.sh @@ -15,7 +15,7 @@ REQUIRE_MACOS=false REQUIRE_WEB=false # Optional components -REQUIRE_KMCOMP=false +REQUIRE_KMC=false REQUIRE_PANDOC=false REQUIRE_SENTRYCLI=false @@ -38,7 +38,7 @@ function print_help() { echo " android ios macos web" echo echo " optional targets:" - echo " kmcomp Keyman keyboard compiler" + echo " kmc Keyman keyboard compiler" echo " pandoc Documentation compiler" echo " sentry-cli sentry.keyman.com debug symbol uploader" echo @@ -69,8 +69,8 @@ while [[ $# -gt 0 ]] ; do REQUIRE_WEB=true PARAMFOUND=true ;; - kmcomp) - REQUIRE_KMCOMP=true + kmc) + REQUIRE_KMC=true PARAMFOUND=true ;; pandoc) @@ -93,7 +93,7 @@ while [[ $# -gt 0 ]] ; do REQUIRE_IOS=true REQUIRE_MACOS=true REQUIRE_WEB=true - REQUIRE_KMCOMP=true + REQUIRE_KMC=true REQUIRE_PANDOC=true REQUIRE_SENTRYCLI=true PARAMFOUND=true @@ -166,12 +166,6 @@ $REQUIRE_SENTRYCLI && brew install getsentry/tools/sentry-cli pyenv install 2.7.18 pyenv global 2.7.18 -# Install WINE -$REQUIRE_KMCOMP && ( - brew tap homebrew/cask-versions - brew install --cask --no-quarantine wine-stable -) - source "$THIS_DIR/keyman.macos.env.sh" $REQUIRE_ANDROID && ( diff --git a/web/src/test/manual/web/regression-tests/README.md b/web/src/test/manual/web/regression-tests/README.md index b818b2d8896..031f4694891 100644 --- a/web/src/test/manual/web/regression-tests/README.md +++ b/web/src/test/manual/web/regression-tests/README.md @@ -7,8 +7,7 @@ https://github.com/keymanapp/keyboards. Currently, the regression tests run only on Windows because we have a dependency on the `kmanalyze` program which is not yet available outside the keyman source repo. This -is the only Windows dependency (the `kmcomp` compiler is also used, but this runs under -WINE and is available in binary form outside the source repo). +is the only Windows dependency (the `kmc` compiler is also used). The test suite runs in Node.js and launches Chrome for the tests. In the future, the tests may be able to be run in native Node.js. @@ -34,7 +33,7 @@ the keyman repository, for example: - testing - regression-tests -Keyman Engine for Web, Keyman Developer (kmcomp, kmanalyze) must be built +Keyman Engine for Web, Keyman Developer (kmc, kmanalyze) must be built prior to running the tests. (If not testing source versions, only kmanalyze is required). See the build documentation for each project for details; summary below: diff --git a/windows/src/desktop/inst/Makefile b/windows/src/desktop/inst/Makefile index 50866447816..c247da987b6 100644 --- a/windows/src/desktop/inst/Makefile +++ b/windows/src/desktop/inst/Makefile @@ -31,7 +31,7 @@ desktop: prereq $(MAKE) -fdownload.mak candle $(WIXLIGHT) -dWixUILicenseRtf=License.rtf -out keymandesktop.msi -ext WixUIExtension $(DESKTOP_FILES) $(SIGNCODE) /d "Keyman" keymandesktop.msi -# TODO: replace buildpkg with kmcomp and remove buildpkg from repository +# TODO: replace buildpkg with kmc? Or promote it to developer? $(ROOT)\bin\buildtools\buildpkg -m keymandesktop.msi -s $(ROOT)\bin\desktop -l license.html $(SIGNCODE) /d "Keyman" keymandesktop.exe $(MAKE) -fdownload.mak copyredist-desktop diff --git a/windows/src/test/manual-tests/regressiontest/tests/Makefile b/windows/src/test/manual-tests/regressiontest/tests/Makefile index 61931f73167..01f87053998 100644 --- a/windows/src/test/manual-tests/regressiontest/tests/Makefile +++ b/windows/src/test/manual-tests/regressiontest/tests/Makefile @@ -2,10 +2,10 @@ !include ..\..\..\Defines.mak all: - for %d in (*.kmn) do $(PROGRAM)\dev\kmcomp "%d" + for %d in (*.kmn) do $(PROGRAM)\bin\kmc.cmd build "%d" .kmn.kmx: - &$(PROGRAM)\dev\kmcomp $** + &$(PROGRAM)\bin\kmc.cmd build $** clean: -del *.kmx From 1817a2d9d0f9f83bce3cfca868c0f944a68c7487 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 11 Sep 2023 03:37:19 +0700 Subject: [PATCH 047/207] chore(web): Add other non-printing characters from OSK font Addresses #6167 for KeymanWeb by defining PUA points per https://github.com/silnrsi/font-symchar/blob/v3.000/documentation/encoding.md --- web/src/engine/osk/src/specialCharacters.ts | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/web/src/engine/osk/src/specialCharacters.ts b/web/src/engine/osk/src/specialCharacters.ts index 0f2a3adb955..0a88283cb58 100644 --- a/web/src/engine/osk/src/specialCharacters.ts +++ b/web/src/engine/osk/src/specialCharacters.ts @@ -1,4 +1,4 @@ -// Defines the PUA code mapping for the various 'special' modifier/control keys on keyboards. +// Defines the PUA code mapping for the various 'special' modifier/control/non-printing keys on keyboards. // `specialCharacters` must be kept in sync with the same variable in builder.js. See also CompileKeymanWeb.pas: CSpecialText10 let specialCharacters = { '*Shift*': 8, @@ -40,9 +40,32 @@ let specialCharacters = { '*RTLBkSp*': 0x72, '*ShiftLock*': 0x73, '*ShiftedLock*': 0x74, - '*ZWNJ*': 0x75, // If this one is specified, auto-detection will kick in. + '*ZWNJ*': 0x79, // If this one is specified, auto-detection will kick in. '*ZWNJiOS*': 0x75, // The iOS version will be used by default, but the '*ZWNJAndroid*': 0x76, // Android platform has its own default glyph. + // Added in Keyman 17.0. + // Reference: https://github.com/silnrsi/font-symchar/blob/v3.000/documentation/encoding.md + '*Sp*': 0x80, // Space + '*NBSp*': 0x82, // No-break Space + '*NarNBSp*': 0x83, // Narrow No-break Space + '*EnQ*': 0x84, // En Quad + '*EmQ*': 0x85, // Em Quad + '*EnSp*': 0x86, // En Space + '*EmSp*': 0x87, // Em Space + // TODO: Skipping #-per-em-space + '*PunctSp*': 0x8c, // Punctuation Space + '*ThSp*': 0x8d, // Thin Space + '*HSp*': 0x8e, // Hair Space + '*ZWSp*': 0x81, // Zero Width Space + '*ZWJ*': 0x77, // Zero Width Joiner + '*WJ*': 0x78, // Word Joiner + '*CGJ*': 0x7a, // Combining Grapheme Joiner + '*LTRM*': 0x90, // Left-to-right Mark + '*RTLM*': 0x91, // Right-to-left Mark + '*SH*': 0xa1, // Soft Hyphen + '*HTab*': 0xa2, // Horizontal Tabulation + // TODO: Skipping size references + }; export default specialCharacters; \ No newline at end of file From 159e2df50a09b8b4f9e641560f0dd61904ae7886 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 11 Sep 2023 08:19:57 +0700 Subject: [PATCH 048/207] chore(web): Update test keyboard for non-printing space characters --- .../manual/web/issue9469/test9469/HISTORY.md | 4 + .../web/issue9469/test9469/build/test9469.js | 2 +- .../source/test9469.keyman-touch-layout | 78 +++++++++---------- .../issue9469/test9469/source/test9469.kmn | 7 +- .../web/issue9469/test9469/test9469.kpj | 2 +- 5 files changed, 50 insertions(+), 43 deletions(-) diff --git a/web/src/test/manual/web/issue9469/test9469/HISTORY.md b/web/src/test/manual/web/issue9469/test9469/HISTORY.md index 8e3fdbb7ced..d520e029426 100644 --- a/web/src/test/manual/web/issue9469/test9469/HISTORY.md +++ b/web/src/test/manual/web/issue9469/test9469/HISTORY.md @@ -1,6 +1,10 @@ test9469 Change History ==================== +1.1 (2023-09-12) +---------------- +* Add non-printing space characters + 1.0 (2023-08-17) ---------------- * Created by SIL International diff --git a/web/src/test/manual/web/issue9469/test9469/build/test9469.js b/web/src/test/manual/web/issue9469/test9469/build/test9469.js index 624b9990bd7..edbf705ebc0 100644 --- a/web/src/test/manual/web/issue9469/test9469/build/test9469.js +++ b/web/src/test/manual/web/issue9469/test9469/build/test9469.js @@ -1 +1 @@ -if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0000;this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"K_E","text":"e"},{"id":"K_R","text":"r"},{"id":"K_T","text":"t"},{"id":"K_Y","text":"y"},{"id":"K_U","text":"u"},{"id":"K_I","text":"i"},{"id":"K_O","text":"o"},{"id":"K_P","text":"p"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"a"},{"id":"K_S","text":"s"},{"id":"K_D","text":"d"},{"id":"K_F","text":"f"},{"id":"K_G","text":"g"},{"id":"K_H","text":"h"},{"id":"K_J","text":"j"},{"id":"K_K","text":"k"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"K_V","text":"v"},{"id":"K_B","text":"b"},{"id":"K_N","text":"n"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.141.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};} \ No newline at end of file +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.141.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout b/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout index b771afc4e7d..5edff501207 100644 --- a/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout +++ b/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout @@ -17,36 +17,36 @@ "text": "*TabLeft*" }, { - "id": "K_E", - "text": "e" + "id": "U_0020", + "text": "*Sp*" }, { - "id": "K_R", - "text": "r" + "id": "U_00A0", + "text": "*NBSp*" }, { - "id": "K_T", - "text": "t" + "id": "U_202F", + "text": "*NarNBSp*" }, { - "id": "K_Y", - "text": "y" + "id": "U_2000", + "text": "*EnQ*" }, { - "id": "K_U", - "text": "u" + "id": "U_2001", + "text": "*EmQ*" }, { - "id": "K_I", - "text": "i" + "id": "U_2002", + "text": "*EnSp*" }, { - "id": "K_O", - "text": "o" + "id": "U_2003", + "text": "*EmSp*" }, { - "id": "K_P", - "text": "p" + "id": "U_2008", + "text": "*PunctSp*" } ] }, @@ -54,37 +54,37 @@ "id": 2, "key": [ { - "id": "K_A", - "text": "a", + "id": "U_2009", + "text": "*ThSp*", "pad": 50 }, { - "id": "K_S", - "text": "s" + "id": "U_200A", + "text": "*HSp*" }, { - "id": "K_D", - "text": "d" + "id": "U_200B", + "text": "*ZWSp*" }, { - "id": "K_F", - "text": "f" + "id": "U_200E", + "text": "*LTRM*" }, { - "id": "K_G", - "text": "g" + "id": "U_200F", + "text": "*RTLM*" }, { - "id": "K_H", - "text": "h" + "id": "U_00AD", + "text": "*SH*" }, { - "id": "K_J", - "text": "j" + "id": "T_0009", + "text": "*HTab*" }, { - "id": "K_K", - "text": "k" + "id": "U_200F", + "text": "*RTLM*" }, { "id": "T_RTLBKSP", @@ -210,16 +210,16 @@ "text": "*ZWNJAndroid*" }, { - "id": "K_V", - "text": "v" + "id": "U_200D", + "text": "*ZWJ*" }, { - "id": "K_B", - "text": "b" + "id": "U_2060", + "text": "*WJ*" }, { - "id": "K_N", - "text": "n" + "id": "U_034F", + "text": "*CGJ*" }, { "id": "K_M", @@ -294,7 +294,7 @@ { "id": "K_SPACE", "text": "", - "width": 610, + "width": 590, "sp": 0 }, { diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn b/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn index 966efcc6f9a..d0f84c749af 100644 --- a/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn +++ b/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn @@ -3,7 +3,7 @@ c with name "test9469" store(&VERSION) '10.0' store(&NAME) 'test9469' store(©RIGHT) '© 2023 SIL International' -store(&KEYBOARDVERSION) '1.0' +store(&KEYBOARDVERSION) '1.1' store(&TARGETS) 'any' store(&BITMAP) 'test9469.ico' store(&VISUALKEYBOARD) 'test9469.kvks' @@ -14,4 +14,7 @@ begin Unicode > use(main) group(main) using keys -c This keyboard is just to visually inspect the touch layout key caps / longpress so none of the T_new_ keys are defined +c This keyboard is just to visually inspect the touch layout key caps / longpress so nearly all of the T_new_ keys are not defined ++ [T_ZWNJ] > U+200C ++ [T_ZWNJIOS] > U+200C ++ [T_ZWNJANDROID] > U+200C diff --git a/web/src/test/manual/web/issue9469/test9469/test9469.kpj b/web/src/test/manual/web/issue9469/test9469/test9469.kpj index a1608952d87..d5530227c2f 100644 --- a/web/src/test/manual/web/issue9469/test9469/test9469.kpj +++ b/web/src/test/manual/web/issue9469/test9469/test9469.kpj @@ -12,7 +12,7 @@ id_9f818ee943306f4c53feb65bb5cadcce test9469.kmn source\test9469.kmn - 1.0 + 1.1 .kmn
test9469 From dbd03aa320188d192c0a5ce11ec3db27d3ad626e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 11 Sep 2023 08:25:48 +0700 Subject: [PATCH 049/207] chore(android): Update test harness keyboard --- .../app/src/main/assets/test9469.kmp | Bin 4222 -> 4416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp b/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp index 100cc1d70b6adc30fa4502e58797f021908c5012..76f3480fac8195ec553e2b17a88f93915299b63b 100644 GIT binary patch delta 3425 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 delta 3229 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 From a0bdb617a0f90877e8111ce99ce44478ceae8fc4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 12 Sep 2023 17:15:35 +1000 Subject: [PATCH 050/207] chore(developer): Apply suggestions from code review Co-authored-by: Darcy Wong --- developer/src/kmc-model-info/src/model-info-compiler.ts | 2 +- resources/build/build-utils-ci.inc.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index 4563963aa13..63644b9ef09 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -19,7 +19,7 @@ export class ModelInfoSources { /** The data from the .kps file, transformed to kmp.json */ kmpJsonData: KmpJsonFile.KmpJsonFile; - /** The path in the keymanapp/lexical-models repo where this model may be found (optional) */ + /** The path in the keymanapp/lexical-models repo where this model may be found */ sourcePath: string; /** The compiled model filename and relative path (.js) */ diff --git a/resources/build/build-utils-ci.inc.sh b/resources/build/build-utils-ci.inc.sh index 973f6f8aca7..47ba7a587c4 100644 --- a/resources/build/build-utils-ci.inc.sh +++ b/resources/build/build-utils-ci.inc.sh @@ -168,7 +168,7 @@ function _builder_prepublish() { local link_source=node_modules/$package # lookup the link_target from top-level package.json/dependencies - local link_target="$(cat "$KEYMAN_ROOT/builder_package_publish.json" | jq -r .dependencies.\"$package\")" + local link_target="$(cat "$KEYMAN_ROOT/builder_package_publish.json" | "$JQ" -r .dependencies.\"$package\")" if [[ $link_target =~ ^file: ]]; then link_target="$KEYMAN_ROOT"/${link_target#file:} From b8cf546d38b54ee92208a5f66a63e5037e5111bc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 12 Sep 2023 17:18:02 +1000 Subject: [PATCH 051/207] chore: Update developer/src/kmc/src/util/baseOptions.ts --- developer/src/kmc/src/util/baseOptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc/src/util/baseOptions.ts b/developer/src/kmc/src/util/baseOptions.ts index 67c81032946..6d0c9a94b4e 100644 --- a/developer/src/kmc/src/util/baseOptions.ts +++ b/developer/src/kmc/src/util/baseOptions.ts @@ -24,7 +24,7 @@ export class BaseOptions { } public static addLogFormat(program: Command) { - return program.addOption(new Option('-l, --log-format ', 'Log format').choices(ALL_COMPILER_LOG_FORMATS).default('formatted')); + return program.addOption(new Option('--log-format ', 'Log format').choices(ALL_COMPILER_LOG_FORMATS).default('formatted')); } public static addOutFile(program: Command) { From ccfefa279fe0fd2ad76c568bdcdd2353563f5dfd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 12 Sep 2023 09:26:03 +0100 Subject: [PATCH 052/207] chore: make const for date format --- developer/src/kmc/src/util/getLastGitCommitDate.ts | 5 ++++- developer/src/kmc/test/test-getLastGitCommitDate.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/developer/src/kmc/src/util/getLastGitCommitDate.ts b/developer/src/kmc/src/util/getLastGitCommitDate.ts index 7cc730227ee..645fcc6820b 100644 --- a/developer/src/kmc/src/util/getLastGitCommitDate.ts +++ b/developer/src/kmc/src/util/getLastGitCommitDate.ts @@ -1,5 +1,8 @@ import { execFileSync } from 'child_process'; +// RFC3339 pattern, UTC +export const expectedGitDateFormat = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/; + /** * Returns the date and time of the last commit from git for the passed in path * @param path Path for which to retrieve the last commit message @@ -37,7 +40,7 @@ export function getLastGitCommitDate(path: string): string { // We'll only return the result if it walks like a date, swims like a date, // and quacks like a date. - if (!result.match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/)) { + if (!result.match(expectedGitDateFormat)) { return null; } diff --git a/developer/src/kmc/test/test-getLastGitCommitDate.ts b/developer/src/kmc/test/test-getLastGitCommitDate.ts index 69c2e92f5dc..3497ef309dc 100644 --- a/developer/src/kmc/test/test-getLastGitCommitDate.ts +++ b/developer/src/kmc/test/test-getLastGitCommitDate.ts @@ -1,13 +1,13 @@ import { assert } from 'chai'; import 'mocha'; import { makePathToFixture } from './helpers/index.js'; -import { getLastGitCommitDate } from '../src/util/getLastGitCommitDate.js'; +import { expectedGitDateFormat, getLastGitCommitDate } from '../src/util/getLastGitCommitDate.js'; describe('getLastGitCommitDate', function () { it('should return a valid date for a folder in this repo', async function() { const path = makePathToFixture('.'); const date = getLastGitCommitDate(path); - assert.match(date, /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$/); + assert.match(date, expectedGitDateFormat); }); it('should return null for a folder outside the repo', async function() { From 454448bc636843e4822ec3ed8cc358a73296e7e0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 16 Sep 2023 15:24:46 +0700 Subject: [PATCH 053/207] chore: use kmc instead of kmcomp to build test keyboards --- .../baseline/k_017___space_mnemonic_kbd.kmn | 3 +- .../baseline/k_018___nul_testing.kmn | 7 +- common/test/keyboards/build.sh | 158 ++++-------------- .../source/obolo_chwerty_6347.kps | 2 +- .../source/obolo_chwerty_6351.kps | 2 +- .../valid-keyboards/compile_legacy.bat | 2 + .../compile-supplementary-support/Makefile | 8 +- 7 files changed, 41 insertions(+), 141 deletions(-) diff --git a/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn b/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn index a66fac420a9..07e37ee681b 100644 --- a/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn +++ b/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn @@ -4,8 +4,7 @@ c keys: [K_A][K_B][K_SPACE][K_C][K_SPACE][K_D][K_SPACE][K_D][K_E] c expected: XYZ store(&VERSION) '9.0' -NAME "Alt key tests" -HOTKEY "[CTRL SHIFT K_A]" +store(&HOTKEY) "[CTRL SHIFT K_A]" store(&mnemoniclayout) "1" begin Unicode > use(main) diff --git a/common/test/keyboards/baseline/k_018___nul_testing.kmn b/common/test/keyboards/baseline/k_018___nul_testing.kmn index bc6e41206d5..20da4d4af70 100644 --- a/common/test/keyboards/baseline/k_018___nul_testing.kmn +++ b/common/test/keyboards/baseline/k_018___nul_testing.kmn @@ -3,11 +3,10 @@ c Description: Tests the processing of nul in LHS of rules c keys: [K_A][K_A] c expected: OKa -VERSION 9.0 -NAME "Nul test" -HOTKEY "[CTRL SHIFT K_N]" +store(&VERSION) "9.0" +store(&HOTKEY) "[CTRL SHIFT K_N]" -begin unicode > use(main) +begin unicode > use(main) group(Main) using keys nul + 'a' > 'OK' diff --git a/common/test/keyboards/build.sh b/common/test/keyboards/build.sh index 2b0a782b94d..958e6648ea2 100755 --- a/common/test/keyboards/build.sh +++ b/common/test/keyboards/build.sh @@ -1,24 +1,19 @@ #!/usr/bin/env bash - -set -e -set -u - +# TODO: builder script plz ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" ## END STANDARD BUILD SCRIPT INCLUDE +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" THIS_DIR="$(dirname "$THIS_SCRIPT")" display_usage() { echo "usage: build.sh [build options] [targets]" echo echo "Build options:" - echo " --clean, -c Clean instead of build" echo " --debug, -d Debug build" echo " --silent, -s Suppress information messages" - echo " --keyboard, -k Build only keyboards (not packages)" echo " --kmc path Specify path to kmc, defaults" echo " to developer/src/kmc/build/" echo " --zip-source Create zip file for source of each" @@ -29,7 +24,7 @@ display_usage() { for d in "$THIS_DIR/"*/; do d="$(basename "$d")" - if [ "$d" != "invalid" ]; then + if [ "$d" != "invalid" && "$d" != "issue"]; then echo " $d" fi done @@ -38,14 +33,12 @@ display_usage() { exit 0 } +readonly KMC_LAUNCHER=node QUIET=false DEBUG=false -CLEAN=false -KEYBOARDS_ONLY=false -CUSTOM_KMCOMP= INDEX=false ZIPSOURCE=false -KMCOMP="$KEYMAN_ROOT/developer/bin/kmcomp.exe" +KMC="$KEYMAN_ROOT/developer/src/kmc/build/src/kmc.js" TARGETS=() # Parse args @@ -57,28 +50,21 @@ while [[ $# -gt 0 ]] ; do --help|-h|-\?) display_usage ;; - --clean|-c) - CLEAN=true - ;; --debug|-d) DEBUG=true ;; --silent|-s) QUIET=true ;; - --keyboard|-k) - KEYBOARDS_ONLY=true - ;; --zip-source) ZIPSOURCE=true ;; --index) INDEX=true ;; - --kmcomp) + --kmc) shift - KMCOMP="$1" - CUSTOM_KMCOMP=true + KMC="$1" ;; *) TARGETS+=("$key") @@ -86,40 +72,6 @@ while [[ $# -gt 0 ]] ; do shift done -# TODO: while this is intended to be cross platform, we -# don't currently have a binary version of kmcomp available -# during Linux and macOS builds, so that will need to be -# manually sourced. -KMCOMP_LAUNCHER= - -if ! $CUSTOM_KMCOMP; then - case "${OSTYPE}" in - "cygwin") - ;; - "msys") - ;; - "darwin"*) - # For Catalina (10.15) onwards, must use wine64 - base_macos_ver=10.15 - macos_ver=$(sw_vers -productVersion) - if verlt "$macos_ver" "$base_macos_ver"; then - KMCOMP_LAUNCHER=wine - else - # On Catalina, and later versions: - # wine-4.12.1 works; wine-5.0, wine-5.7 do not. - # retrieve these from: - # `brew tap gcenx/wine && brew install --cask --no-quarantine wine-crossover` - # may also need to `sudo spctl --master-disable` - KMCOMP_LAUNCHER=wine64 - KMCOMP="$(dirname $KMCOMP)/kmcomp.x64.exe" - fi - ;; - *) - KMCOMP_LAUNCHER=wine - ;; - esac -fi - # Build list of available targets from subfolders, if none specified if [ ${#TARGETS[@]} == 0 ]; then for d in "$THIS_DIR/"*/; do @@ -132,56 +84,14 @@ fi if ! $QUIET; then displayInfo "" \ - "CLEAN: $CLEAN" \ "DEBUG: $DEBUG" \ "QUIET: $QUIET" \ - "KEYBOARDS_ONLY: $KEYBOARDS_ONLY" \ "TARGETS: ${TARGETS[@]}" \ "ZIPSOURCE: $ZIPSOURCE" \ "INDEX: $INDEX" \ "" fi -clean() { - local kpj="$1.kpj" ss= s= - if $QUIET; then - ss=-ss - s=-s - fi - pushd "$1" > /dev/null - if [ -f build.sh ]; then - ./build.sh -c $s - else - $KMCOMP_LAUNCHER "$KMCOMP" -c $ss "$kpj" - fi - popd > /dev/null -} - -build() { - local kpj="$1.kpj" d= t= ss= target= s= k= - if $KEYBOARDS_ONLY; then - k=-k - t=-t - target="$1.kmn" - fi - if $DEBUG; then - d=-d - fi - if $QUIET; then - s=-s - ss=-ss - fi - # -w - treat warnings as errors, we'll force this - # -cfc - check filename conventions - pushd "$1" > /dev/null - if [ -f build.sh ]; then - ./build.sh $d $k $s - else - $KMCOMP_LAUNCHER "$KMCOMP" $d $ss -w -cfc "$kpj" $t "$target" - fi - popd > /dev/null -} - zipsource() { local target="$1" pushd "$1" > /dev/null @@ -191,35 +101,26 @@ zipsource() { ### -for TARGET in "${TARGETS[@]}"; do - if $CLEAN; then - if ! $QUIET; then - echo - builder_heading "Cleaning target $TARGET" - echo - fi - clean "$TARGET" - else - if ! $QUIET; then - echo - builder_heading "Building target $TARGET" - echo - fi - build "$TARGET" +d= +ss= +if $DEBUG; then + d=-d +fi +if $QUIET; then + ss="--log-level silent" +fi +$KMC_LAUNCHER "$KMC" build $d $ss -w "${TARGETS[@]}" - if $ZIPSOURCE; then - zipsource "$TARGET" - fi +for TARGET in "${TARGETS[@]}"; do + if $ZIPSOURCE; then + zipsource "$TARGET" fi done ### if $INDEX; then - if $CLEAN; then - rm -f "$THIS_DIR/index.html" - else - cat << EOF > "$THIS_DIR/index.html" + cat << EOF > "$THIS_DIR/index.html" @@ -231,19 +132,18 @@ if $INDEX; then
    EOF - for TARGET in "${TARGETS[@]}"; do - if $ZIPSOURCE; then - echo "
  • $TARGET.kmp (source)
  • " >> "$THIS_DIR/index.html" - else - echo "
  • $TARGET.kmp
  • " >> "$THIS_DIR/index.html" - fi - done + for TARGET in "${TARGETS[@]}"; do + if $ZIPSOURCE; then + echo "
  • $TARGET.kmp (source)
  • " >> "$THIS_DIR/index.html" + else + echo "
  • $TARGET.kmp
  • " >> "$THIS_DIR/index.html" + fi + done - cat << 'EOF' >> "$THIS_DIR/index.html" + cat << 'EOF' >> "$THIS_DIR/index.html"
EOF - fi + fi -exit 0 diff --git a/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps b/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps index 9e3f5a437ab..ebf22548312 100644 --- a/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps +++ b/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps @@ -113,7 +113,7 @@ obolo_chwerty_6347 1.2.1 - Obolo (Andoni) + Obolo (Andoni) diff --git a/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps b/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps index 11e8de9c3bf..df9a3b81cc6 100644 --- a/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps +++ b/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps @@ -113,7 +113,7 @@ obolo_chwerty_6351 1.2.1 - Obolo (Andoni) + Obolo (Andoni) diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat index d1a8b9dd115..e21f85a4bca 100644 --- a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -2,4 +2,6 @@ echo Compiles the keyboards using the legacy kmcomp.exe echo to use as baseline comparisons for kmcmplib rem TODO: we'll need a binary download for kmcomp.exe +rem We can use the last version available on downloads.keyman.com +rem see keyboards repo for example of best way to download+extract for %%d in (*.kmn) do ..\..\..\..\..\bin\kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/test/auto/compile-supplementary-support/Makefile b/developer/src/test/auto/compile-supplementary-support/Makefile index 0899d09df5a..f2bff60933d 100644 --- a/developer/src/test/auto/compile-supplementary-support/Makefile +++ b/developer/src/test/auto/compile-supplementary-support/Makefile @@ -2,8 +2,8 @@ # test if KS=1 is in the right files, both in debug and non-debug versions # -KMCOMP="..\..\..\..\bin\kmc.cmd" build -KMCOMPD=$(KMCOMP) -d +KMC="..\..\..\..\bin\kmc.cmd" build +KMC_DEBUG=$(KMC) -d test: test-nodebug test-debug @@ -11,7 +11,7 @@ KMN=i3317_nosupp.kmn i3317_withsupp.kmn i3317_withsupp_incontext.kmn i3317_withs # TODO: replace with build.sh script test-nodebug: - $(KMCOMP) $(KMN) + $(KMC) $(KMN) findstr /v /m "KS=1" i3317_nosupp.js findstr /m "KS=1" i3317_withsupp.js findstr /m "KS=1" i3317_withsupp_incontext.js @@ -20,7 +20,7 @@ test-nodebug: findstr /m "KS=1" i3317_withsupp_instore.js test-debug: - $(KMCOMPD) $(KMN) + $(KMC_DEBUG) $(KMN) findstr /v /m "KS=1" i3317_nosupp.js findstr /m "KS=1" i3317_withsupp.js findstr /m "KS=1" i3317_withsupp_incontext.js From f140b5e18d18662aeb1cc1cadbab6a58763251a8 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 16 Sep 2023 15:41:07 +0700 Subject: [PATCH 054/207] chore: convert keyboards build.sh to builder script --- common/test/keyboards/build.sh | 171 +++++++++++---------------------- 1 file changed, 58 insertions(+), 113 deletions(-) diff --git a/common/test/keyboards/build.sh b/common/test/keyboards/build.sh index 958e6648ea2..49c46270cac 100755 --- a/common/test/keyboards/build.sh +++ b/common/test/keyboards/build.sh @@ -1,126 +1,42 @@ #!/usr/bin/env bash -# TODO: builder script plz ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" -THIS_DIR="$(dirname "$THIS_SCRIPT")" - -display_usage() { - echo "usage: build.sh [build options] [targets]" - echo - echo "Build options:" - echo " --debug, -d Debug build" - echo " --silent, -s Suppress information messages" - echo " --kmc path Specify path to kmc, defaults" - echo " to developer/src/kmc/build/" - echo " --zip-source Create zip file for source of each" - echo " keyboard for artifact tests" - echo " --index Build index.html for artifact tests" - echo - echo "Targets (all unless specified):" - - for d in "$THIS_DIR/"*/; do - d="$(basename "$d")" - if [ "$d" != "invalid" && "$d" != "issue"]; then - echo " $d" - fi - done - echo - exit 0 -} +cd "$THIS_SCRIPT_PATH" -readonly KMC_LAUNCHER=node -QUIET=false -DEBUG=false -INDEX=false -ZIPSOURCE=false -KMC="$KEYMAN_ROOT/developer/src/kmc/build/src/kmc.js" -TARGETS=() - -# Parse args -shopt -s nocasematch - -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - --help|-h|-\?) - display_usage - ;; - --debug|-d) - DEBUG=true - ;; - --silent|-s) - QUIET=true - ;; - --zip-source) - ZIPSOURCE=true - ;; - --index) - INDEX=true - ;; - --kmc) - shift - KMC="$1" - ;; - *) - TARGETS+=("$key") - esac - shift +targets=() +# Build list of available targets from subfolders, if none specified +for d in */; do + d="$(basename "$d")" + if [ "$d" != "invalid" ] && [ "$d" != "issue" ]; then + targets+=(":$d") + fi done -# Build list of available targets from subfolders, if none specified -if [ ${#TARGETS[@]} == 0 ]; then - for d in "$THIS_DIR/"*/; do - d="$(basename "$d")" - if [ "$d" != "invalid" ] && [ "$d" != "issue" ]; then - TARGETS+=("$d") - fi - done -fi - -if ! $QUIET; then - displayInfo "" \ - "DEBUG: $DEBUG" \ - "QUIET: $QUIET" \ - "TARGETS: ${TARGETS[@]}" \ - "ZIPSOURCE: $ZIPSOURCE" \ - "INDEX: $INDEX" \ - "" -fi - -zipsource() { +KMC="$KEYMAN_ROOT/developer/src/kmc/build/src/kmc.js" + +builder_describe "Test Keyboards" \ + clean configure build \ + ${targets[@]} \ + "--index Build index.html for artifact tests" \ + "--zip-source Create zip file for source of each keyboard for artifact tests" \ + "--kmc=KMC Specify path to kmc, defaults to developer/src/kmc/build/" \ + "--silent,-s Suppress information messages" + +builder_parse "$@" + +function zipsource() { local target="$1" pushd "$1" > /dev/null 7z a -r -x!build -x"!$target.kpj.user" "${target}_source.zip" . popd > /dev/null } -### - -d= -ss= -if $DEBUG; then - d=-d -fi -if $QUIET; then - ss="--log-level silent" -fi -$KMC_LAUNCHER "$KMC" build $d $ss -w "${TARGETS[@]}" - -for TARGET in "${TARGETS[@]}"; do - if $ZIPSOURCE; then - zipsource "$TARGET" - fi -done - -### - -if $INDEX; then - cat << EOF > "$THIS_DIR/index.html" +function build_index() { + cat << EOF > index.html @@ -132,18 +48,47 @@ if $INDEX; then
    EOF - for TARGET in "${TARGETS[@]}"; do - if $ZIPSOURCE; then - echo "
  • $TARGET.kmp (source)
  • " >> "$THIS_DIR/index.html" + for TARGET in "${targets[@]}"; do + if builder_has_option --zip-source; then + echo "
  • $TARGET.kmp (source)
  • " >> index.html else - echo "
  • $TARGET.kmp
  • " >> "$THIS_DIR/index.html" + echo "
  • $TARGET.kmp
  • " >> index.html fi done - cat << 'EOF' >> "$THIS_DIR/index.html" + cat << EOF >> index.html
EOF +} + +### + +function build() { + local active_targets=() + for TARGET in "${targets[@]}"; do + if builder_has_action build$TARGET; then + active_targets+=(${TARGET#:}) + fi + done + + local ss= + if builder_has_option --silent; then + ss="--log-level silent" + fi + + node "$KMC" build $builder_debug $ss -w "${active_targets[@]}" + + if builder_has_option --zip-source; then + for TARGET in "${active_targets[@]}"; do + zipsource "$TARGET" + done + fi + + if builder_has_option --index; then + build_index + fi +} -fi +builder_run_action build build \ No newline at end of file From 7f3d7ce17da8e39b68b0a3dc5429f069bd991472 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 16 Sep 2023 15:46:37 +0700 Subject: [PATCH 055/207] chore: fixup targets for build_index --- common/test/keyboards/build.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/test/keyboards/build.sh b/common/test/keyboards/build.sh index 49c46270cac..af6a9702d5f 100755 --- a/common/test/keyboards/build.sh +++ b/common/test/keyboards/build.sh @@ -36,6 +36,7 @@ function zipsource() { } function build_index() { + local active_targets=($*) cat << EOF > index.html @@ -48,7 +49,7 @@ function build_index() {
    EOF - for TARGET in "${targets[@]}"; do + for TARGET in "${active_targets[@]}"; do if builder_has_option --zip-source; then echo "
  • $TARGET.kmp (source)
  • " >> index.html else @@ -87,7 +88,7 @@ function build() { fi if builder_has_option --index; then - build_index + build_index "${active_targets[@]}" fi } From a1a7b39481cd1e65a44bb25a45a46fa426c3d3ab Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 21 Sep 2023 17:35:16 +0700 Subject: [PATCH 056/207] feat(developer): Windows package installer compiler Replaces kmcomp's package installer build infrastructure. While this is mostly legacy, package installers are still used in adhoc distribution scenarios, so we need to continue to support with the new kmc. The package installer is a Windows self-extracting zip archive. The user is expected to find setup-redist.exe and keymandesktop.msi themselves, from the Keyman Developer release files. They are not included with Keyman Developer, but are available as standalone downloads from https://downloads.keyman.com/windows/. `kmc build` has been updated to include two subcommands: * `kmc build ldml-test-data` * `kmc build windows-package-installer` Both of these subcommands are a little long, but the use cases for them are fairly narrow, so I believe this is okay. `kmc build-test-data` has been removed, as `kmc build ldml-test-data` replaces it. --- common/web/types/src/package/kps-file.ts | 10 +- .../kmc-package/src/compiler/kmp-compiler.ts | 13 +- .../windows-package-installer-compiler.ts | 145 ++++++++++++++++++ developer/src/kmc-package/src/main.ts | 3 +- .../windows-installer/keymandesktop.txt | 3 + .../fixtures/windows-installer/license.txt | 3 + .../test/fixtures/windows-installer/setup.txt | 3 + ...test-windows-package-installer-compiler.ts | 79 ++++++++++ developer/src/kmc/README.md | 24 ++- developer/src/kmc/src/commands/build.ts | 21 ++- .../src/kmc/src/commands/buildTestData.ts | 13 -- .../buildWindowsPackageInstaller/index.ts | 42 +++++ developer/src/kmc/src/kmc.ts | 2 - 13 files changed, 334 insertions(+), 27 deletions(-) create mode 100644 developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts create mode 100644 developer/src/kmc-package/test/fixtures/windows-installer/keymandesktop.txt create mode 100644 developer/src/kmc-package/test/fixtures/windows-installer/license.txt create mode 100644 developer/src/kmc-package/test/fixtures/windows-installer/setup.txt create mode 100644 developer/src/kmc-package/test/test-windows-package-installer-compiler.ts delete mode 100644 developer/src/kmc/src/commands/buildTestData.ts create mode 100644 developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 036a9ca167d..976ea79954d 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -160,8 +160,14 @@ export interface KpsFileStartMenuItems { } export interface KpsFileStrings { - //TODO: validate this structure - string: string[] | string; + string: KpsFileString[] | KpsFileString; +} + +export interface KpsFileString { + $: { + name: string; + value: string; + } } export interface KpsFileLanguageExamples { diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index c384ded31a0..d3560007327 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -22,8 +22,13 @@ export class KmpCompiler { } public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { + const kps = this.loadKpsFile(kpsFilename); + return this.transformKpsFileToKmpObject(kpsFilename, kps); + } + + public loadKpsFile(kpsFilename: string): KpsFile.KpsFile { // Load the KPS data from XML as JS structured data. - const buffer = this.callbacks.loadFile(kpsFilename); + const buffer = this.callbacks.loadFile(kpsFilename, false); if(!buffer) { this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename: kpsFilename})); return null; @@ -41,7 +46,11 @@ export class KmpCompiler { return a; })(); - let kps: KpsFile.KpsFile = kpsPackage.package; + const kps: KpsFile.KpsFile = kpsPackage.package; + return kps; + } + + public transformKpsFileToKmpObject(kpsFilename: string, kps: KpsFile.KpsFile): KmpJsonFile.KmpJsonFile { // // To convert to kmp.json, we need to: diff --git a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts new file mode 100644 index 00000000000..d2f93994bbe --- /dev/null +++ b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts @@ -0,0 +1,145 @@ +/** + * Create a .exe installer that bundles one or more .kmp files, together with + * setup.exe, keymandesktop.msi, and generates and includes a setup.inf also. + * + * This module is effectively deprecated, but is present to keep parity with the + * legacy kmcomp compiler. Thus, it is included as part of the package compiler, + * and will be removed in a future version. + * + * This tool assumes that the installer .msi is the same version as the + * compiler, unlike the legacy compiler which read this metadata from the .msi. + */ + +import JSZip from 'jszip'; +import { CompilerCallbacks, KeymanFileTypes, KmpJsonFile, KpsFile } from "@keymanapp/common-types"; +import KEYMAN_VERSION from "@keymanapp/keyman-version"; +import { KmpCompiler } from "./kmp-compiler.js"; +import { CompilerMessages } from "./messages.js"; + +const SETUP_INF_FILENAME = 'setup.inf'; +const PRODUCT_NAME = 'Keyman'; + +export interface WindowsPackageInstallerSources { + msiFilename: string; + setupExeFilename: string; + licenseFilename: string; // MIT license + titleImageFilename?: string; + + appName?: string; + startDisabled: boolean; + startWithConfiguration: boolean; +}; + +export class WindowsPackageInstallerCompiler { + private kmpCompiler: KmpCompiler; + + constructor(private callbacks: CompilerCallbacks) { + this.kmpCompiler = new KmpCompiler(this.callbacks); + } + + public async compile(kpsFilename: string, sources: WindowsPackageInstallerSources): Promise { + const kps = this.kmpCompiler.loadKpsFile(kpsFilename); + + // Check existence of required files + for(const filename of [sources.licenseFilename, sources.msiFilename, sources.setupExeFilename]) { + if(!this.callbacks.fs.existsSync(filename)) { + this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename})); + return null; + } + } + + // Check existence of optional files + for(const filename of [sources.titleImageFilename]) { + if(filename && !this.callbacks.fs.existsSync(filename)) { + this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename})); + return null; + } + } + + // Note: we never use the MSIFileName field from the .kps any more + // Nor do we use the MSIOptions field. + + // Build the zip + const zipBuffer = await this.buildZip(kps, kpsFilename, sources); + if(!zipBuffer) { + // Error messages already reported by buildZip + return null; + } + + // Build the sfx + const sfxBuffer = this.buildSfx(zipBuffer, sources); + return sfxBuffer; + } + + private async buildZip(kps: KpsFile.KpsFile, kpsFilename: string, sources: WindowsPackageInstallerSources): Promise { + const kmpJson: KmpJsonFile.KmpJsonFile = this.kmpCompiler.transformKpsFileToKmpObject(kpsFilename, kps); + if(!kmpJson.info?.name?.description) { + this.callbacks.reportMessage(CompilerMessages.Error_PackageNameCannotBeBlank()); + return null; + } + + const kmpFilename = this.callbacks.path.basename(kpsFilename, KeymanFileTypes.Source.Package) + KeymanFileTypes.Binary.Package; + const setupInfBuffer = this.buildSetupInf(sources, kmpJson, kmpFilename, kps); + const kmpBuffer = await this.kmpCompiler.buildKmpFile(kpsFilename, kmpJson); + + // Note that this does not technically generate a "valid" sfx according to + // the zip spec, because the offsets in the .zip are relative to the start + // of the zip, rather than to the start of the sfx. However, as Keyman's + // installer chops out the zip from the sfx and loads it in a new stream, it + // works as expected. + const zip = JSZip(); + zip.file(SETUP_INF_FILENAME, setupInfBuffer); + zip.file(kmpFilename, kmpBuffer); + zip.file(this.callbacks.path.basename(sources.msiFilename), this.callbacks.loadFile(sources.msiFilename)); + zip.file(this.callbacks.path.basename(sources.licenseFilename), this.callbacks.loadFile(sources.licenseFilename)); + if(sources.titleImageFilename) { + zip.file(this.callbacks.path.basename(sources.titleImageFilename), this.callbacks.loadFile(sources.titleImageFilename)); + } + + return zip.generateAsync({type: 'uint8array', compression:'DEFLATE'}); + } + + private buildSetupInf(sources: WindowsPackageInstallerSources, kmpJson: KmpJsonFile.KmpJsonFile, kmpFilename: string, kps: KpsFile.KpsFile) { + let setupInf = `[Setup] +Version=${KEYMAN_VERSION.VERSION} +MSIFileName=${this.callbacks.path.basename(sources.msiFilename)} +MSIOptions= +AppName=${sources.appName ?? PRODUCT_NAME} +License=${this.callbacks.path.basename(sources.licenseFilename)} +`; + if (sources.titleImageFilename) { + setupInf += `TitleImage=${this.callbacks.path.basename(sources.titleImageFilename)}\n`; + } + if (kmpJson.options.graphicFile) { + setupInf += `BitmapFileName=${this.callbacks.path.basename(kmpJson.options.graphicFile)}\n`; + } + if (sources.startDisabled) { + setupInf += `StartDisabled=True\n`; + } + if (sources.startWithConfiguration) { + setupInf += `StartWithConfiguration=True\n`; + } + + setupInf += `\n[Packages]\n`; + setupInf += kmpFilename + '\n'; + // TODO: multiple packages? + const strings = !kps.strings?.string ? [] : (Array.isArray(kps.strings.string) ? kps.strings.string : [kps.strings.string]); + if (strings.length) { + setupInf += `\n[Strings]\n`; + for (const str of strings) { + setupInf += `${str.$?.name}=${str.$?.value}\n`; + } + } + + const setupInfBuffer = new TextEncoder().encode(setupInf); + return setupInfBuffer; + } + + private buildSfx(zipBuffer: Uint8Array, sources: WindowsPackageInstallerSources): Uint8Array { + const setupRedistBuffer = this.callbacks.loadFile(sources.setupExeFilename); + const buffer = new Uint8Array(setupRedistBuffer.length + zipBuffer.length); + buffer.set(setupRedistBuffer, 0); + buffer.set(zipBuffer, setupRedistBuffer.length); + return buffer; + } +} diff --git a/developer/src/kmc-package/src/main.ts b/developer/src/kmc-package/src/main.ts index 5903a59de30..481db13ce31 100644 --- a/developer/src/kmc-package/src/main.ts +++ b/developer/src/kmc-package/src/main.ts @@ -1,2 +1,3 @@ export { KmpCompiler } from "./compiler/kmp-compiler.js"; -export { PackageValidation } from "./compiler/package-validation.js"; \ No newline at end of file +export { PackageValidation } from "./compiler/package-validation.js"; +export { WindowsPackageInstallerSources, WindowsPackageInstallerCompiler } from "./compiler/windows-package-installer-compiler.js"; \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/windows-installer/keymandesktop.txt b/developer/src/kmc-package/test/fixtures/windows-installer/keymandesktop.txt new file mode 100644 index 00000000000..96d012d7d1a --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/windows-installer/keymandesktop.txt @@ -0,0 +1,3 @@ +This placeholder file represents keymandesktop.msi, the Windows Installer package for Keyman for Windows. + +(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test) \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/windows-installer/license.txt b/developer/src/kmc-package/test/fixtures/windows-installer/license.txt new file mode 100644 index 00000000000..cb2aa8919fb --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/windows-installer/license.txt @@ -0,0 +1,3 @@ +This placeholder file represents license.txt. + +(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test) \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/windows-installer/setup.txt b/developer/src/kmc-package/test/fixtures/windows-installer/setup.txt new file mode 100644 index 00000000000..1a72b86ef09 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/windows-installer/setup.txt @@ -0,0 +1,3 @@ +This placeholder file represents setup-redist.exe, the sfx loader. + +(Not including the real file to reduce file size in repo, as it is not necessary in order to complete the unit test) \ No newline at end of file diff --git a/developer/src/kmc-package/test/test-windows-package-installer-compiler.ts b/developer/src/kmc-package/test/test-windows-package-installer-compiler.ts new file mode 100644 index 00000000000..8ed2fee0e7b --- /dev/null +++ b/developer/src/kmc-package/test/test-windows-package-installer-compiler.ts @@ -0,0 +1,79 @@ +import 'mocha'; +import * as fs from 'fs'; +import * as path from 'path'; +import { assert } from 'chai'; +import JSZip from 'jszip'; +import KEYMAN_VERSION from "@keymanapp/keyman-version"; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { WindowsPackageInstallerCompiler, WindowsPackageInstallerSources } from '../src/compiler/windows-package-installer-compiler.js'; + +describe('WindowsPackageInstallerCompiler', function () { + it(`should build an SFX archive`, async function () { + this.timeout(10000); // this test can take a little while to run + + const callbacks = new TestCompilerCallbacks(); + let compiler = new WindowsPackageInstallerCompiler(callbacks); + + const kpsPath = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); + const sources: WindowsPackageInstallerSources = { + licenseFilename: makePathToFixture('windows-installer', 'license.txt'), + msiFilename: makePathToFixture('windows-installer', 'keymandesktop.txt'), + setupExeFilename: makePathToFixture('windows-installer', 'setup.txt'), + startDisabled: false, + startWithConfiguration: true, + appName: 'Testing', + }; + + const sfxBuffer = await compiler.compile(kpsPath, sources); + + // This returns a buffer with a SFX loader and a zip suffix. For the sake of repository size + // we actually provide a stub SFX loader and a stub MSI file, which is enough to verify that + // the compiler is generating what it thinks is a valid file. + + const zip = JSZip(); + + // Check that file.kmp contains just 3 files - setup.inf, keymandesktop.msi, and khmer_angkor.kmp, + // and that they match exactly what we expect + const setupExeSize = fs.statSync(sources.setupExeFilename).size; + const setupExe = sfxBuffer.slice(0, setupExeSize); + const zipBuffer = sfxBuffer.slice(setupExeSize); + + // Verify setup.exe sfx loader + const setupExeFixture = fs.readFileSync(sources.setupExeFilename); + assert.deepEqual(setupExe, setupExeFixture); + + // Load the zip from the buffer + const zipFile = await zip.loadAsync(zipBuffer, {checkCRC32: true}); + + // Verify setup.inf; note that BitmapFileName splash.gif comes from the .kmp + const setupInfFixture = `[Setup] +Version=${KEYMAN_VERSION.VERSION} +MSIFileName=${path.basename(sources.msiFilename)} +MSIOptions= +AppName=${sources.appName} +License=${path.basename(sources.licenseFilename)} +BitmapFileName=splash.gif +StartWithConfiguration=True + +[Packages] +khmer_angkor.kmp +`; + + const setupInf = new TextDecoder().decode(await zipFile.file('setup.inf').async('uint8array')); + assert.equal(setupInf.trim(), setupInfFixture.trim()); + + const verifyFile = async (filename: string) => { + const fixture = fs.readFileSync(filename); + const file = await zipFile.file(path.basename(filename)).async('uint8array'); + assert.deepEqual(file, fixture, `File in zip '${filename}' did not match fixture`); + }; + + await verifyFile(sources.msiFilename); + await verifyFile(sources.licenseFilename); + + // We only test for existence of the file in the zip for now + const kmp = await zipFile.file('khmer_angkor.kmp').async('uint8array'); + assert.isNotEmpty(kmp); + }); +}); diff --git a/developer/src/kmc/README.md b/developer/src/kmc/README.md index 448f42e4b43..31022383d0a 100644 --- a/developer/src/kmc/README.md +++ b/developer/src/kmc/README.md @@ -38,17 +38,29 @@ To see more command line options by using the `--help` option: kmc --help -kmlmc Usage ------------ +--- -To compile a lexical model from its `.model.ts` source, use `kmlmc`: +To compile a lexical model from its `.model.ts` source, use `kmc`: - kmlmc my-lexical-model.model.ts --outFile my-lexical-model.js + kmc build my-lexical-model.model.ts --outFile my-lexical-model.model.js To see more command line options by using the `--help` option: - kmlmc --help - kmlmp --help + kmc --help + +--- + +kmc can now build package installers for Windows. Example usage (Bash on +Windows, using 'node .' instead of 'kmc' to run the local build): + +``` +node . build windows-package-installer \ + $KEYMAN_ROOT/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps \ + --msi /c/Program\ Files\ \(x86\)/Common\ Files/Keyman/Cached\ Installer\ Files/keymandesktop.msi \ + --exe $KEYMAN_ROOT/windows/bin/desktop/setup-redist.exe \ + --license $KEYMAN_ROOT/LICENSE.md \ + --out-file ./khmer.exe +``` How to build from source ------------------------ diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 931833c714a..a5e4646d582 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -9,6 +9,8 @@ import { CompilerFileCallbacks, CompilerOptions, KeymanFileTypes } from '@keyman import { BaseOptions } from '../util/baseOptions.js'; import { expandFileLists } from '../util/fileLists.js'; import { isProject } from '../util/projectLoader.js'; +import { buildTestData } from './buildTestData/index.js'; +import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js'; function commandOptionsToCompilerOptions(options: any): CompilerOptions { @@ -30,7 +32,7 @@ function commandOptionsToCompilerOptions(options: any): CompilerOptions { } export function declareBuild(program: Command) { - BaseOptions.addAll(program + const command = BaseOptions.addAll(program .command('build [infile...]') .description(`Compile one or more source files or projects.`) .addHelpText('after', ` @@ -74,6 +76,23 @@ If no input file is supplied, kmc will build the current folder.`) } } }); + + command + .command('ldml-test-data') + .description('Convert LDML keyboard test .xml to .json') + .action(buildTestData); + + command + .command('windows-package-installer ') + .description('Build an executable installer for Windows for a Keyman package') + .option('--msi ', 'Location of keymandesktop.msi') + .option('--exe ', 'Location of setup.exe') + .option('--license ', 'Location of license.txt') + .option('--title-image [titleImageFilename]', 'Location of title image') + .option('--app-name [applicationName]', 'Installer property: name of the application to be installed', 'Keyman') + .option('--start-disabled', 'Installer property: do not enable keyboards after installation completes') + .option('--start-with-configuration', 'Installer property: start Keyman Configuration after installation completes') + .action(buildWindowsPackageInstaller); } async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, options: CompilerOptions): Promise { diff --git a/developer/src/kmc/src/commands/buildTestData.ts b/developer/src/kmc/src/commands/buildTestData.ts deleted file mode 100644 index b78d1177383..00000000000 --- a/developer/src/kmc/src/commands/buildTestData.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Command } from 'commander'; -import { buildTestData } from './buildTestData/index.js'; -import { BaseOptions } from '../util/baseOptions.js'; - -export function declareBuildTestData(program: Command) { - BaseOptions.addAll(program - .command('build-test-data ') - .description('Convert keyboard test .xml to .json') - ) - .action(buildTestData); -} - - diff --git a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts new file mode 100644 index 00000000000..25acfcda41c --- /dev/null +++ b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts @@ -0,0 +1,42 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { CompilerBaseOptions, CompilerCallbacks, defaultCompilerOptions } from '@keymanapp/common-types'; +import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; +import { WindowsPackageInstallerCompiler, WindowsPackageInstallerSources } from '@keymanapp/kmc-package'; + +interface WindowsPackageInstallerOptions extends CompilerBaseOptions { + msi: string; + exe: string; + license: string; + titleImage?: string; + appName?: string; + startDisabled: boolean; + startWithConfiguration: boolean; +}; + +export async function buildWindowsPackageInstaller(infile: string, options: WindowsPackageInstallerOptions) { + const sources: WindowsPackageInstallerSources = { + licenseFilename: options.license, + msiFilename: options.msi, + setupExeFilename: options.exe, + startDisabled: options.startDisabled, + startWithConfiguration: options.startWithConfiguration, + appName: options.appName, + titleImageFilename: options.titleImage + } + + const callbacks: CompilerCallbacks = new NodeCompilerCallbacks({...defaultCompilerOptions, ...options}); + const compiler = new WindowsPackageInstallerCompiler(callbacks); + + const buffer = await compiler.compile(infile, sources); + if(!buffer) { + // errors will have been reported already + process.exit(1); + } + + const fileBaseName = options.outFile ?? infile; + const outFileBase = path.basename(fileBaseName, path.extname(fileBaseName)); + const outFileDir = path.dirname(fileBaseName); + const outFileExe = path.join(outFileDir, outFileBase + '.exe'); + fs.writeFileSync(outFileExe, buffer); +} diff --git a/developer/src/kmc/src/kmc.ts b/developer/src/kmc/src/kmc.ts index d22fc223485..bcae5707ac4 100644 --- a/developer/src/kmc/src/kmc.ts +++ b/developer/src/kmc/src/kmc.ts @@ -5,7 +5,6 @@ import { Command } from 'commander'; import { declareBuild } from './commands/build.js'; -import { declareBuildTestData } from './commands/buildTestData.js'; import { declareAnalyze } from './commands/analyze.js'; import { BaseOptions } from './util/baseOptions.js'; import { KeymanSentry } from './util/KeymanSentry.js'; @@ -34,7 +33,6 @@ async function run() { } declareBuild(program); - declareBuildTestData(program); // TODO: consider renaming this (build vs build-test-data is confusing) declareAnalyze(program); /* Future commands: From db6a836e31fe0427eb6efab7eeba29b2438a6ca3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 21 Sep 2023 17:44:08 +0700 Subject: [PATCH 057/207] chore(developer): remove extra parameter --- developer/src/kmc-package/src/compiler/kmp-compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index d3560007327..3859cbac4dd 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -28,7 +28,7 @@ export class KmpCompiler { public loadKpsFile(kpsFilename: string): KpsFile.KpsFile { // Load the KPS data from XML as JS structured data. - const buffer = this.callbacks.loadFile(kpsFilename, false); + const buffer = this.callbacks.loadFile(kpsFilename); if(!buffer) { this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename: kpsFilename})); return null; From 301cd687e8fa8104ccf75503670bd398f94ac39c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 21 Sep 2023 20:07:47 +0700 Subject: [PATCH 058/207] chore(web): Update keymanweb-osk.ttf to 4.0 --- web/src/resources/osk/keymanweb-osk.ttf | Bin 43568 -> 35164 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web/src/resources/osk/keymanweb-osk.ttf b/web/src/resources/osk/keymanweb-osk.ttf index 5b3c6219797cb4a0348d0e5ece31df8dedd4e002..e0651b14d4f770d2a9b15e8850203c05112a23d7 100644 GIT binary patch delta 3545 zcmaJD3s6+o_1yb@%hz3&-30{p%d)`oaXod1vS>B`#3voqTyLPu9 z>MvUXz|{gY9O-ClX)Ss=k zku#uU3~+{y!3~*?1j0e?Dl`LxbpW9~^Z*+34@d=&kX1rowG$Xb+k=?k}N}Yrug&h&aaJo{IM--(V`RBm;LV7=T5`0g?g<0aZW~ zNDtTo?!clzLttB=C-7w8Sl}mtUj)tv-VTS63hDeMT(&RrmxR9pFl6CMj~wAk|FI0| z_v?@9d-PrUGZAF~CPbFvDu*So1{x9SfW7#hedd7^Tu=8HWR9J=z7lH>?!ctg< zK5IY=I#5FzMj<7lTZ1?aWI-*eItIUiQTU2fkv1|&J|?$lBF&@^(v$QJdV@)39Lz@M zb>AAFk_+>Rd3Ku zr$ehGyU4B|PE3&2YI{LdRe`;lKFwGrZ_uL92dxFw)ddCBRS_v(s>G6skwU&EMM;!B zFT6mCtc^7_>(|%RG;&Hh7^)+EQb*`7H4P0l)r}2UpfCm(m^9{)zgl4}Su#y4c|_t7 zFNh*_L`g%(h*Ap53z^6OT;SIu({V+vZ-yzP6q%84W1qdU?h~ibdYgH^&wFV)@?8SXcN+ zYi_Q?_Qd9jdW}10(foz`va{!xJ8V`*3K6EQ5w`1eYnSDFRO%HaYq||OPkym`YrWg1 zx2lpSu1s4=!9~5~lF*NhS&PlftkN+?jnPe<>RB&WXi0BV;cKK&>LBcrv}p}o7FFw{ z(fT@ac4F7)P{ZxQQf4i;0hWZ}GrK(@KQ?(0;lv^hr|_HBQuAm~{sG z)(sS$i1}&-Bf67|?Z`zxL^ZgxZ8Q0Nekz~isBXR?~1h?;OZvm~NsG42dBOINvWK@ss#jbT+8RXIkLF*Y%pxzve` ztZU*DeSKP+bXDQHb4e8HCcV?TfD7v+gAZ<@PYm`mJvPqy_{mZ^RgB3h*Rov9T1_g? z=Q>K=F6-2#;XQuqSxIwAZD&nQXYJIxeD&(`3XiAa8*nT6?t9PjAp(t!V^pMEls=HYPmYmEa!j(tJv-hl)DZ(QgobIY z6!04oht>d63+YYCmMZ6XfULtL2NgYCkom`F)mXS?|dnA-90YY zMe*({q6nZs{f4OixfpFT=1>8~-1&dD@T}Qn$t}}rosQD79Jf=$e8ZZu))!+g^L&wX z|KhvS{o?k#k7fHMhdBGhcyjO5j8o6vyh%D{*Zt<3HwDGbo4|xGN;ZazL|VX(5Q#K{ zjZ2V&T~5XxlLM_7TzIEW@qj3f$zcWt#~>S&f1OG0b{s*bP=Jg{Ps_I?Ge-I3OFH*Z z@-3P_fO&rnKQajzj+nwB*QQgL-i~Ijzh1S^S5G;b$Bbepo{J3Mk&|RL!7!_$DM`6+ z&M*Qc;S%ZzpTdd&9a>&O4I_AY1Z-gu`k*(0$6WE*#OJ0yC3u8GKp$Yt0GxPHHWA}T zcK>%{aH09-KN;tH`)~5(zDi zTkLw;E3nvJ0GrFX_fiIz`xtP9ON6`pI1Vo&Jc+sKut192MZsb=u|PGcDJiV3nwF`} zxn)k=BPFfV>a@I&%wh+2I?8nX)R()ODJ!++%GHoe1;L4ExiriSPX#T;7u&%UH}(U8 z=gZ`X`&`u&TZASxnX;P8q=TgN_e-~>zROpnKI!(!$FC6GtMir=$365^a>IheoK?$; z+e^hzcs#K4p#4TUI*Qobw zUM^+sKHTuQy{q(S_tr;?z9*D=9XhAiSF_>Kbl19(lO%;~yBur4YtkPi>Gj;AQ*SlU zt(m1Qzw}pF=FQPqo$5{V8UvqSdo@q5%Qlu5m$~QmUH#`L-uYV$`HuR7&s7#=q*uph z%nAMJwDifQM6&U{6Z=1Hwm64h+&x}rE#JEO@aoC~Pb@qBO!?uSRp7&mpM&!rPQxrV zBOEyKMk6qRWc)V;Q-qtNFoSdCjwsB=&~T6eZ$GHqz%2k$ZBc1+@r#m@*TWj0SbMKsPWdvqtdl$O>UnG){ A(f|Me delta 12017 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 Date: Fri, 22 Sep 2023 08:22:45 +0700 Subject: [PATCH 059/207] chore(core): update kmc call --- core/tests/unit/ldml/keyboards/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 54ab22f2e85..b098a543179 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -69,7 +69,7 @@ endforeach foreach kbd : tests_with_testdata configure_file( - command: kmc_cmd + ['build-test-data', '@INPUT@', '--out-file', '@OUTPUT@'], + command: kmc_cmd + ['build', 'ldml-test-data', '@INPUT@', '--out-file', '@OUTPUT@'], input: kbd + '-test.xml', output: kbd + '-test.json', ) @@ -88,7 +88,7 @@ foreach kbd : tests_from_cldr output: kbd + '.kmx', ) configure_file( - command: kmc_cmd + ['build-test-data', '@INPUT@', '--out-file', '@OUTPUT@'], + command: kmc_cmd + ['build', 'ldml-test-data', '@INPUT@', '--out-file', '@OUTPUT@'], input: join_paths(ldml_testdata, kbd + '-test.xml'), output: kbd + '-test.json', ) From a9fb7a189f271544bb79fdd7ddd2386ae1f82ba7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 22 Sep 2023 09:27:37 +0700 Subject: [PATCH 060/207] chore(developer): add missing parameter to ldml-test-data --- developer/src/kmc/src/commands/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index a5e4646d582..b196ab25394 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -78,7 +78,7 @@ If no input file is supplied, kmc will build the current folder.`) }); command - .command('ldml-test-data') + .command('ldml-test-data ') .description('Convert LDML keyboard test .xml to .json') .action(buildTestData); From c95b0dd421c4f17d214aaf15b374965415085d9a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 22 Sep 2023 09:54:22 +0700 Subject: [PATCH 061/207] chore(developer): normalize case on input filenames in kmc Fixes #9611. --- developer/src/kmc/src/commands/build.ts | 4 ++++ .../kmc/src/commands/buildWindowsPackageInstaller/index.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index b196ab25394..0cebc78f078 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -108,6 +108,10 @@ async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, o return false; } + // Normalize case for the filename and expand the path; this avoids false + // positive case mismatches on input filenames and glommed paths + filename = fs.realpathSync.native(filename); + let builder = null; // If infile is a directory, then we treat that as a project and build it diff --git a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts index 25acfcda41c..bb65cb10291 100644 --- a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts +++ b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts @@ -25,6 +25,10 @@ export async function buildWindowsPackageInstaller(infile: string, options: Wind titleImageFilename: options.titleImage } + // Normalize case for the filename and expand the path; this avoids false + // positive case mismatches on input filenames and glommed paths + infile = fs.realpathSync.native(infile); + const callbacks: CompilerCallbacks = new NodeCompilerCallbacks({...defaultCompilerOptions, ...options}); const compiler = new WindowsPackageInstallerCompiler(callbacks); From e1087ed7810774a09fd5aabdf23fdb7022f59ecf Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 22 Sep 2023 11:20:48 +0700 Subject: [PATCH 062/207] chore(developer): fix commander global options --- developer/src/kmc/src/commands/analyze.ts | 5 +- developer/src/kmc/src/commands/build.ts | 50 +++++++++++-------- .../kmc/src/commands/buildTestData/index.ts | 4 +- .../buildWindowsPackageInstaller/index.ts | 3 +- developer/src/kmc/src/kmc.ts | 20 ++++++-- developer/src/kmc/src/util/baseOptions.ts | 16 ------ 6 files changed, 51 insertions(+), 47 deletions(-) diff --git a/developer/src/kmc/src/commands/analyze.ts b/developer/src/kmc/src/commands/analyze.ts index eceb29f7bf2..e878cc49221 100644 --- a/developer/src/kmc/src/commands/analyze.ts +++ b/developer/src/kmc/src/commands/analyze.ts @@ -22,14 +22,12 @@ interface AnalysisActivityOptions /* not inheriting from CompilerBaseOptions */ export function declareAnalyze(program: Command) { let command = program.command('analyze [infile...]'); - BaseOptions.addVersion(command); declareOskCharUse(command); declareOskRewrite(command); } function declareOskCharUse(command: Command) { let subCommand = command.command('osk-char-use'); - BaseOptions.addVersion(subCommand); BaseOptions.addLogLevel(subCommand); subCommand .description('Analyze On Screen Keyboards for character usage') @@ -37,7 +35,8 @@ function declareOskCharUse(command: Command) { .option('--include-counts', 'Include number of times each character is referenced', false) .option('--strip-dotted-circle', 'Strip U+25CC (dotted circle base) from results', false) .addOption(new Option('-m, --mapping-file ', 'Result file to write to (.json, .md, or .txt)').makeOptionMandatory()) - .action(async (filenames: string[], options: any) => { + .action(async (filenames: string[], _options: any, commander: any) => { + const options = commander.optsWithGlobals(); if(!filenames.length) { // If there are no filenames provided, then we are building the current // folder ('.') as a project-style build diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index b196ab25394..21d1ba9680e 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -11,6 +11,7 @@ import { expandFileLists } from '../util/fileLists.js'; import { isProject } from '../util/projectLoader.js'; import { buildTestData } from './buildTestData/index.js'; import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js'; +//import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js'; function commandOptionsToCompilerOptions(options: any): CompilerOptions { @@ -32,31 +33,38 @@ function commandOptionsToCompilerOptions(options: any): CompilerOptions { } export function declareBuild(program: Command) { - const command = BaseOptions.addAll(program - .command('build [infile...]') - .description(`Compile one or more source files or projects.`) + const buildCommand = program + .command('build') + .option('--color', 'Force colorization for log messages') + .option('--no-color', 'No colorization for log messages; if both omitted, detects from console') + + // These options are only used with build file but are included here so that + // they are visible in `kmc build --help` + .option('-d, --debug', 'Include debug information in output') + .option('-w, --compiler-warnings-as-errors', 'Causes warnings to fail the build; overrides project-level warnings-as-errors option') + .option('-W, --no-compiler-warnings-as-errors', 'Warnings do not fail the build; overrides project-level warnings-as-errors option') + .option('--no-compiler-version', 'Exclude compiler version metadata from output') + .option('--no-warn-deprecated-code', 'Turn off warnings for deprecated code styles'); + + BaseOptions.addAll(buildCommand); + + buildCommand.command('file [infile...]', {isDefault: true}) + .description(`Compile one or more source files or projects ('file' subcommand is default).`) .addHelpText('after', ` Supported file types: - * folder: Keyman project in folder - * .kpj: Keyman project - * .kmn: Keyman keyboard - * .xml: LDML keyboard - * .model.ts: Keyman lexical model - * .kps: Keyman keyboard or lexical model package +* folder: Keyman project in folder +* .kpj: Keyman project +* .kmn: Keyman keyboard +* .xml: LDML keyboard +* .model.ts: Keyman lexical model +* .kps: Keyman keyboard or lexical model package File lists can be referenced with @filelist.txt. If no input file is supplied, kmc will build the current folder.`) - ) - .option('-d, --debug', 'Include debug information in output') - .option('-w, --compiler-warnings-as-errors', 'Causes warnings to fail the build; overrides project-level warnings-as-errors option') - .option('-W, --no-compiler-warnings-as-errors', 'Warnings do not fail the build; overrides project-level warnings-as-errors option') - .option('--no-compiler-version', 'Exclude compiler version metadata from output') - .option('--no-warn-deprecated-code', 'Turn off warnings for deprecated code styles') - .option('--color', 'Force colorization for log messages') - .option('--no-color', 'No colorization for log messages; if both omitted, detects from console') - .action(async (filenames: string[], options: any) => { - options = commandOptionsToCompilerOptions(options); + + .action(async (filenames: string[], _options: any, commander: any) => { + const options = commandOptionsToCompilerOptions(commander.optsWithGlobals()); const callbacks = new NodeCompilerCallbacks(options); if(!filenames.length) { @@ -77,12 +85,12 @@ If no input file is supplied, kmc will build the current folder.`) } }); - command + buildCommand .command('ldml-test-data ') .description('Convert LDML keyboard test .xml to .json') .action(buildTestData); - command + buildCommand .command('windows-package-installer ') .description('Build an executable installer for Windows for a Keyman package') .option('--msi ', 'Location of keymandesktop.msi') diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index b50c63c1d6e..97650d272ae 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -5,7 +5,9 @@ import { CompilerBaseOptions, CompilerCallbacks, defaultCompilerOptions, LDMLKey import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; import { fileURLToPath } from 'url'; -export function buildTestData(infile: string, options: CompilerBaseOptions) { +export function buildTestData(infile: string, _options: any, commander: any) { + const options: CompilerBaseOptions = commander.optsWithGlobals(); + let compilerOptions: kmcLdml.LdmlCompilerOptions = { ...defaultCompilerOptions, ...options, diff --git a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts index 25acfcda41c..eca8b0d4f6a 100644 --- a/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts +++ b/developer/src/kmc/src/commands/buildWindowsPackageInstaller/index.ts @@ -14,7 +14,8 @@ interface WindowsPackageInstallerOptions extends CompilerBaseOptions { startWithConfiguration: boolean; }; -export async function buildWindowsPackageInstaller(infile: string, options: WindowsPackageInstallerOptions) { +export async function buildWindowsPackageInstaller(infile: string, _options: any, commander: any) { + const options: WindowsPackageInstallerOptions = commander.optsWithGlobals(); const sources: WindowsPackageInstallerSources = { licenseFilename: options.license, msiFilename: options.msi, diff --git a/developer/src/kmc/src/kmc.ts b/developer/src/kmc/src/kmc.ts index bcae5707ac4..049dc7c6aad 100644 --- a/developer/src/kmc/src/kmc.ts +++ b/developer/src/kmc/src/kmc.ts @@ -3,11 +3,11 @@ * kmc - Keyman Next Generation Compiler */ -import { Command } from 'commander'; +import { Command, Option } from 'commander'; import { declareBuild } from './commands/build.js'; import { declareAnalyze } from './commands/analyze.js'; -import { BaseOptions } from './util/baseOptions.js'; import { KeymanSentry } from './util/KeymanSentry.js'; +import KEYMAN_VERSION from "@keymanapp/keyman-version"; await KeymanSentry.runTestIfCLRequested(); try { @@ -24,9 +24,19 @@ async function run() { /* Arguments */ const program = new Command(); - program.description('Keyman Developer Command Line Interface'); - BaseOptions.addVersion(program); - BaseOptions.addSentry(program); + program + .description('Keyman Developer Command Line Interface') + .configureHelp({ + showGlobalOptions: true + }) + .version(KEYMAN_VERSION.VERSION_WITH_TAG) + + // This corresponds to an option tested in KeymanSentry.ts, which is + // searched for in process.argv, in order to avoid depending on Commander to + // start Sentry, and to ensure that we capture errors as early as possible + // in launch + .addOption(new Option('--no-error-reporting', 'Disable error reporting to keyman.com (overriding user settings)')) + .addOption(new Option('--error-reporting', 'Enable error reporting to keyman.com (overriding user settings)')); if(await KeymanSentry.isEnabled()) { KeymanSentry.init(); diff --git a/developer/src/kmc/src/util/baseOptions.ts b/developer/src/kmc/src/util/baseOptions.ts index 67c81032946..376c6c47aba 100644 --- a/developer/src/kmc/src/util/baseOptions.ts +++ b/developer/src/kmc/src/util/baseOptions.ts @@ -1,24 +1,9 @@ import { ALL_COMPILER_LOG_FORMATS, ALL_COMPILER_LOG_LEVELS } from "@keymanapp/common-types"; import { Command, Option } from "commander"; -import KEYMAN_VERSION from "@keymanapp/keyman-version"; // These options map to CompilerBaseOptions export class BaseOptions { - public static addVersion(program: Command) { - return program.version(KEYMAN_VERSION.VERSION_WITH_TAG); - } - - public static addSentry(program: Command) { - // This corresponds to an option tested in KeymanSentry.ts, which is - // searched for in process.argv, in order to avoid depending on Commander to - // start Sentry, and to ensure that we capture errors as early as possible - // in launch - return program - .addOption(new Option('--no-error-reporting', 'Disable error reporting to keyman.com (overriding user settings)')) - .addOption(new Option('--error-reporting', 'Enable error reporting to keyman.com (overriding user settings)')); - } - public static addLogLevel(program: Command) { return program.addOption(new Option('-l, --log-level ', 'Log level').choices(ALL_COMPILER_LOG_LEVELS).default('info')); } @@ -33,7 +18,6 @@ export class BaseOptions { public static addAll(program: Command) { return [ - this.addVersion, this.addLogLevel, this.addLogFormat, this.addOutFile, From 5ddc8c8e336d6a9a669dd5806e39a566658cc3fc Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 22 Sep 2023 15:07:25 -0500 Subject: [PATCH 063/207] =?UTF-8?q?chore(resources):=20ldml=20update=20key?= =?UTF-8?q?board=20->=20keyboard3=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - unicode-org/cldr:f16d66601f309f29a64a897282fdcab440d42661 - No local modifications neded1 --- .../ldml-keyboards/techpreview/3.0/pcm.xml | 7 ++-- .../ldml-keyboards/techpreview/cldr_info.json | 6 +-- .../techpreview/import/scanCodes-implied.xml | 37 ++++++++++++++++--- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/resources/standards-data/ldml-keyboards/techpreview/3.0/pcm.xml b/resources/standards-data/ldml-keyboards/techpreview/3.0/pcm.xml index f175fbd039a..5ef43e5d682 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/3.0/pcm.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/3.0/pcm.xml @@ -11,6 +11,7 @@ + @@ -34,15 +35,15 @@ - + - - + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json index 601f341a251..c481be521b0 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json +++ b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json @@ -1,5 +1,5 @@ { - "sha": "f3daab445e2e3ee88f8b94c6b46d380777035051", - "description": "release-44-alpha3-4-gf3daab445e", - "date": "Wed, 20 Sep 2023 22:27:35 +0000" + "sha": "f16d66601f309f29a64a897282fdcab440d42661", + "description": "release-44-alpha3-6-gf16d66601f", + "date": "Fri, 22 Sep 2023 20:06:24 +0000" } diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml index 2ebb0ac85aa..73414c54c75 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml @@ -9,6 +9,7 @@ Values are space separated hex bytes. Frame keys are not included. --> +
    @@ -16,13 +17,37 @@ - -
    - - + + + + + + + +
    + + + + + + +
    + + + + + + +
    + + + + + + - \ No newline at end of file + From 6a54ebdd57aa751767feedee7d30c2a80a189e79 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 22 Sep 2023 15:20:20 -0500 Subject: [PATCH 064/207] =?UTF-8?q?feat(developer):=20ldml=20scancodes=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - read implied imports for scancodes --- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 16 ++++++++++++++++ core/include/ldml/keyboardprocessor_ldml.h | 1 + core/include/ldml/keyboardprocessor_ldml.ts | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 3fdd9a74025..5bec1cde012 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -49,6 +49,14 @@ export class LDMLKeyboardXMLSourceFileReader { if (!source.keyboard3.keys.import) { source.keyboard3.keys.import = []; } + if (!source.keyboard3.forms) { + source.keyboard3.forms = { + form: [], + }; + } + if (!source.keyboard3.forms.import) { + source.keyboard3.forms.import = []; + } } boxXmlArray(source?.keyboard3, 'layers'); boxXmlArray(source?.keyboard3?.displays, 'display'); @@ -152,6 +160,14 @@ export class LDMLKeyboardXMLSourceFileReader { })) { return false; } + } else if (subtag === 'forms') { + // + if (!this.resolveOneImport(obj, subtag, { + base: constants.cldr_import_base, + path: constants.cldr_implied_forms_import + })) { + return false; + } } return true; } diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyboardprocessor_ldml.h index ae244ffbe75..4c629ae77b9 100644 --- a/core/include/ldml/keyboardprocessor_ldml.h +++ b/core/include/ldml/keyboardprocessor_ldml.h @@ -16,6 +16,7 @@ #pragma once #define LDML_BKSP_FLAGS_ERROR 0x1 +#define LDML_CLDR_IMPLIED_FORMS_IMPORT "techpreview/scanCodes-implied.xml" #define LDML_CLDR_IMPLIED_KEYS_IMPORT "techpreview/keys-Latn-implied.xml" #define LDML_CLDR_IMPORT_BASE "cldr" #define LDML_CLDR_VERSION_LATEST "techpreview" diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyboardprocessor_ldml.ts index ed9ad899ed3..d19784b110c 100644 --- a/core/include/ldml/keyboardprocessor_ldml.ts +++ b/core/include/ldml/keyboardprocessor_ldml.ts @@ -74,6 +74,10 @@ class Constants { * implied keys file */ readonly cldr_implied_keys_import = `${this.cldr_version_techpreview}/keys-Latn-implied.xml`; + /** + * implied scancodes file + */ + readonly cldr_implied_forms_import = `${this.cldr_version_techpreview}/scanCodes-implied.xml`; /** * Length of a raw section header, in bytes */ From 3a677da0c6e76e7812264ea34ff7b0a1c2d40208 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 22 Sep 2023 16:48:07 -0500 Subject: [PATCH 065/207] =?UTF-8?q?feat(common):=20ldml=20scancodes=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - use Symbols to track import status --- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 17 ++++++++++---- .../src/ldml-keyboard/ldml-keyboard-xml.ts | 22 ++++++++++++++++++- .../test-ldml-keyboard-xml-reader.ts | 14 +++++++++++- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 5bec1cde012..87f2cbbdbae 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,5 +1,5 @@ import * as xml2js from 'xml2js'; -import { LDMLKeyboardXMLSourceFile, LKImport } from './ldml-keyboard-xml.js'; +import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js'; import { default as AjvModule } from 'ajv'; const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; @@ -157,7 +157,7 @@ export class LDMLKeyboardXMLSourceFileReader { if (!this.resolveOneImport(obj, subtag, { base: constants.cldr_import_base, path: constants.cldr_implied_keys_import - })) { + }, true)) { return false; } } else if (subtag === 'forms') { @@ -165,7 +165,7 @@ export class LDMLKeyboardXMLSourceFileReader { if (!this.resolveOneImport(obj, subtag, { base: constants.cldr_import_base, path: constants.cldr_implied_forms_import - })) { + }, true)) { return false; } } @@ -176,9 +176,10 @@ export class LDMLKeyboardXMLSourceFileReader { * @param obj the object being imported into * @param subtag obj's element tag, e.g. `keys` * @param asImport the import structure + * @param implied true if it is an implied import * @returns true on success, false on failure */ - private resolveOneImport(obj: any, subtag: string, asImport: LKImport) : boolean { + private resolveOneImport(obj: any, subtag: string, asImport: LKImport, implied? : boolean) : boolean { const { base, path } = asImport; if (base !== constants.cldr_import_base) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); @@ -205,12 +206,20 @@ export class LDMLKeyboardXMLSourceFileReader { // pull all children of importXml[subtag] into obj for (const subsubtag of Object.keys(importRootNode).reverse()) { // e.g. const subsubval = importRootNode[subsubtag]; + const basePath = `${base}/${path}`; if (!Array.isArray(subsubval)) { // This is somewhat of an internal error, indicating that a non-mergeable XML file was imported // Not exercisable with the standard LDML imports. this.callbacks.reportMessage(CommonTypesMessages.Error_ImportMergeFail({base, path, subtag, subsubtag})); return false; } + // Mark all children as an import + subsubval.forEach(o => o[ImportStatus.import] = basePath); + if (implied) { + // mark all children as an implied import + subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath); + } + if (!obj[subsubtag]) { obj[subsubtag] = []; // start with empty array } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 629b8e8aa24..0a6b2b0924e 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -12,7 +12,7 @@ export interface LDMLKeyboardXMLSourceFile { * -- the root element. */ keyboard3: LKKeyboard; -} +}; export interface LKKeyboard { locale?: string; @@ -193,3 +193,23 @@ export interface LKDisplays { display?: LKDisplay[]; displayOptions?: LKDisplayOptions; }; + +/** + * Utilities for determining the import status of items + */ +export class ImportStatus { + /** item came in via implied (spec based) import, such as keys-Latn-implied.xml */ + static impliedImport = Symbol.for('@keymanapp:implied_import'); + /** item came in via import */ + static import = Symbol.for('@keymanapp:import'); + + /** @returns true if the object was loaded through an implied import */ + static isImpliedImport(o : any) : boolean { + return o && !!o[ImportStatus.impliedImport]; + } + /** @returns true if the object was loaded through an explicit import */ + static isImport(o : any) : boolean { + return o && !!o[ImportStatus.import]; + } +}; + diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index 4da7a006636..296e36a8cc7 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -1,4 +1,4 @@ -import { LKKey } from './../../src/ldml-keyboard/ldml-keyboard-xml.js'; +import { LKKey, ImportStatus } from './../../src/ldml-keyboard/ldml-keyboard-xml.js'; import 'mocha'; import {assert} from 'chai'; import { CommonTypesMessages } from '../../src/util/common-events.js'; @@ -40,6 +40,9 @@ describe('ldml keyboard xml reader tests', function () { {id: 'b', to: 'b'}, {id: 'c', to: 'c'}, ]); + // all of the keys are implied imports here + assert.isTrue(ImportStatus.isImpliedImport(source?.keyboard3?.keys.key.find(({id}) => id === 'a'))); + assert.isTrue(ImportStatus.isImport(source?.keyboard3?.keys.key.find(({id}) => id === 'a'))); }, }, { @@ -81,6 +84,15 @@ describe('ldml keyboard xml reader tests', function () { {id: 'zz', to: 'zz'}, // new key {id: 'hash', to: '##'}, // override ]); + // 'a' is an implied import + assert.isTrue(ImportStatus.isImpliedImport(k.find(({id}) => id === 'a'))); + assert.isTrue(ImportStatus.isImport(k.find(({id}) => id === 'a'))); + // 'hash' is an import but not implied + assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'hash'))); + assert.isTrue(ImportStatus.isImport(k.find(({id}) => id === 'hash'))); + // 'zz' is not imported + assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'zz'))); + assert.isFalse(ImportStatus.isImport(k.find(({id}) => id === 'zz'))); }, }, { From 44f061f293ece3da5e17845841c6cbadde402598 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 22 Sep 2023 17:33:20 -0500 Subject: [PATCH 066/207] =?UTF-8?q?feat(common):=20ldml=20scancodes=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - improve how forms are read in the XML structure - warnings when custom (non-default-import) scancodes are loaded - error, as usual, when an unknown form is present --- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 6 +++++ .../src/ldml-keyboard/ldml-keyboard-xml.ts | 14 +++++++++++ developer/src/kmc-ldml/src/compiler/layr.ts | 8 +++++- .../src/kmc-ldml/src/compiler/messages.ts | 4 +++ .../sections/layr/error-custom-form.xml | 24 ++++++++++++++++++ .../sections/layr/hint-custom-form.xml | 25 +++++++++++++++++++ developer/src/kmc-ldml/test/test-layr.ts | 19 +++++++++++++- 7 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 87f2cbbdbae..60c77acc223 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -76,6 +76,12 @@ export class LDMLKeyboardXMLSourceFileReader { } } } + if(source?.keyboard3?.forms?.form) { + boxXmlArray(source?.keyboard3?.forms, 'form'); + for(let form of source?.keyboard3?.forms?.form) { + boxXmlArray(form, 'scanCodes'); + } + } if(source?.keyboard3?.keys?.flicks) { for(let flicks of source?.keyboard3?.keys?.flicks) { boxXmlArray(flicks, 'flick'); diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 0a6b2b0924e..80bbb2e4d47 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -24,6 +24,7 @@ export interface LKKeyboard { names?: LKNames; settings?: LKSettings; keys?: LKKeys; + forms?: LKForms; displays?: LKDisplays; layers?: LKLayers[]; vkeys?: LKVkeys; @@ -194,6 +195,19 @@ export interface LKDisplays { displayOptions?: LKDisplayOptions; }; +export interface LKForms { + form?: LKForm[]; +}; + +export interface LKForm { + id?: string; + scanCodes?: LKScanCodes[]; +}; + +export interface LKScanCodes { + codes?: string; +}; + /** * Utilities for determining the import status of items */ diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index 281ca8d5db9..07dfbcf92d5 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -1,5 +1,5 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; -import { KMXPlus } from '@keymanapp/common-types'; +import { KMXPlus, LDMLKeyboard } from '@keymanapp/common-types'; import { CompilerMessages } from './messages.js'; import { SectionCompiler } from "./section-compiler.js"; import { translateLayerAttrToModifier, validModifier } from '../util/util.js'; @@ -22,6 +22,12 @@ export class LayrCompiler extends SectionCompiler { let totalLayerCount = 0; let hardwareLayers = 0; // let touchLayers = 0; + this.keyboard3.forms?.form?.forEach((form) => { + // Just check whether it's NOT an implied import + if (!LDMLKeyboard.ImportStatus.isImpliedImport(form)) { + this.callbacks.reportMessage(CompilerMessages.Hint_UnsupportedCustomForm({id: form.id})); + } + }); this.keyboard3.layers?.forEach((layers) => { const { form } = layers; if (form === 'touch') { diff --git a/developer/src/kmc-ldml/src/compiler/messages.ts b/developer/src/kmc-ldml/src/compiler/messages.ts index 804ec1d02dd..37dfd7772c6 100644 --- a/developer/src/kmc-ldml/src/compiler/messages.ts +++ b/developer/src/kmc-ldml/src/compiler/messages.ts @@ -149,5 +149,9 @@ export class CompilerMessages { m(this.ERROR_DisplayNeedsToOrId, `display ${CompilerMessages.toOrId(o)} needs to= or id=, but not both`); static ERROR_DisplayNeedsToOrId = SevError | 0x0022; + static Hint_UnsupportedCustomForm = (o:{id: string}) => + m(this.HINT_UnsupportedCustomForm, `Custom
    element is not supported.`); + static HINT_UnsupportedCustomForm = SevHint | 0x0023; } + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml new file mode 100644 index 00000000000..5aa93783dee --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml new file mode 100644 index 00000000000..813a2dbf2e0 --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + +
    + + +
    + + + + + + +
    diff --git a/developer/src/kmc-ldml/test/test-layr.ts b/developer/src/kmc-ldml/test/test-layr.ts index 86c7e2729bd..97b11799c3c 100644 --- a/developer/src/kmc-ldml/test/test-layr.ts +++ b/developer/src/kmc-ldml/test/test-layr.ts @@ -121,6 +121,23 @@ describe('layr', function () { errors: [ CompilerMessages.Error_MustBeAtLeastOneLayerElement(), ], - } + }, + { + // warning on custom form + subpath: 'sections/layr/hint-custom-form.xml', + warnings: [ + CompilerMessages.Hint_UnsupportedCustomForm({id: "us"}), + ], + }, + { + // error on unknown form + subpath: 'sections/layr/error-custom-form.xml', + warnings: [ + CompilerMessages.Hint_UnsupportedCustomForm({id: "zzz"}), + ], + errors: [CompilerMessages.Error_InvalidHardware({ + form: 'zzz', + }),], + }, ]); }); From f56e4ffa8514efcb57b684eb72f0fa98a21027e0 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 22 Sep 2023 17:56:35 -0500 Subject: [PATCH 067/207] =?UTF-8?q?feat(common):=20ldml=20scancodes=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - unit test the set and row count for codes - fix errors! --- .../types/src/consts/virtual-key-constants.ts | 18 ++++++- .../test-ldml-keyboard-xml-reader.ts | 29 ++++++++++ developer/src/kmc-ldml/test/test-layr.ts | 54 +++++++++---------- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index f93155f1ff5..39eb4d6d704 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -159,6 +159,19 @@ export const USVirtualKeyMap: KeyMap = [ [ k.K_SPACE ], ]; +export const KSVirtualKeyMap: KeyMap = [ + // ` 1 2 3 4 5 6 7 8 9 0 - = [bksp] + [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], + // [tab] Q W E R T Y U I O P [ ] \ + [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_LBRKT, k.K_RBRKT, k.K_BKSLASH ], + // [caps] A S D F G H J K L ; ' [enter] + [ k.K_A, k.K_S, k.K_D, k.K_F, k.K_G, k.K_H, k.K_J, k.K_K, k.K_L, k.K_COLON, k.K_QUOTE ], + // [shift] Z X C V B N M , . / [shift] *=oE2 + [ k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH ], + // space + [ k.K_SPACE ], +]; + export const ISOVirtualKeyMap: KeyMap = [ // ` 1 2 3 4 5 6 7 8 9 0 - = [bksp] [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL ], @@ -174,13 +187,13 @@ export const ISOVirtualKeyMap: KeyMap = [ export const JISVirtualKeyMap: KeyMap = [ // [Hankaku/Zenkaku] 1 2 3 4 5 6 7 8 9 0 - ^ ¥ [bksp] - [ k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], + [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], // [tab] Q W E R T Y U I O P @«`» [ [enter] [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_BKQUOTE, k.K_LBRKT ], // [caps] A S D F G H J K L ; : ] [enter] [ k.K_A, k.K_S, k.K_D, k.K_F, k.K_G, k.K_H, k.K_J, k.K_K, k.K_L, k.K_COLON, k.K_QUOTE, k.K_RBRKT ], // [shift] Z X C V B N M , . / _ [shift] - [ k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH, k.K_oE2 /* ろ */ ], + [ k.K_oE2, k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH, k.k_oC1 /* ろ */ ], // space [ k.K_SPACE ], ]; @@ -208,6 +221,7 @@ export const HardwareToKeymap: Map = new Map( ["iso", ISOVirtualKeyMap], ["jis", JISVirtualKeyMap], ["abnt2", ABNT2VirtualKeyMap], + ["ks", KSVirtualKeyMap], ] ); diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index 296e36a8cc7..1fe589a98c0 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -3,6 +3,7 @@ import 'mocha'; import {assert} from 'chai'; import { CommonTypesMessages } from '../../src/util/common-events.js'; import { testReaderCases } from '../helpers/reader-callback-test.js'; +import { HardwareToKeymap } from '../../src/consts/virtual-key-constants.js'; function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) { return keys.filter(({id}) => ids.indexOf(id) !== -1); @@ -141,3 +142,31 @@ describe('ldml keyboard xml reader tests', function () { }, ]); }); + +describe('verify scancodes', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + testReaderCases([ + { + // We've read this above, but we're going to test for scancodes here + subpath: 'import-minimal.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard3?.forms?.form); + + const ldmlFormIds = source?.keyboard3?.forms?.form.map(f => f.id); + const kmcFormIds = Array.from(HardwareToKeymap.keys()); + + assert.sameDeepMembers(ldmlFormIds, kmcFormIds, "LDML and kmc form ids"); + + source?.keyboard3?.forms?.form.forEach((form) => { + const {id, scanCodes} = form; + const km = HardwareToKeymap.get(id); + assert.ok(km, `kmc's ${id}`); + const ldmlRowCounts = scanCodes.map(o => o.codes.split(" ").length); + const kmcRowCounts = km.map(o => o.length); + assert.deepEqual(ldmlRowCounts, kmcRowCounts, `ldml/kmc counts for form ${id}`); + }); + }, + }, + ]); +}); diff --git a/developer/src/kmc-ldml/test/test-layr.ts b/developer/src/kmc-ldml/test/test-layr.ts index 97b11799c3c..899d5d8666d 100644 --- a/developer/src/kmc-ldml/test/test-layr.ts +++ b/developer/src/kmc-ldml/test/test-layr.ts @@ -2,7 +2,7 @@ import 'mocha'; import { assert } from 'chai'; import { LayrCompiler } from '../src/compiler/layr.js'; import { CompilerMessages } from '../src/compiler/messages.js'; -import { compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js'; +import { compilerTestCallbacks, testCompilationCases } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; import { constants } from '@keymanapp/ldml-keyboard-constants'; @@ -20,30 +20,31 @@ function allKeysOk(row : LayrRow, str : string, msg? : string) { describe('layr', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while - // reuse the keys minimal file - it('should compile minimal keys data', async function () { - let layr = await loadSectionFixture(LayrCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Layr; - assert.ok(layr); - assert.equal(compilerTestCallbacks.messages.length, 0); - - assert.equal(layr.lists?.length, 1); - const list0 = layr.lists[0]; - assert.ok(list0); - assert.equal(list0.layers.length, 1); - assert.equal(list0.hardware, constants.layr_list_hardware_us); - const layer0 = list0.layers[0]; - assert.ok(layer0); - assert.equal(layer0.rows.length, 1); - const row0 = layer0.rows[0]; - assert.ok(row0); - assert.equal(row0.keys.length, 2); + testCompilationCases(LayrCompiler, [ + { + subpath: 'sections/keys/minimal.xml', + callback(sect) { + const layr = sect; + assert.ok(layr); + assert.equal(compilerTestCallbacks.messages.length, 0); - assert.equal(layer0.id.value, 'base'); - assert.equal(layer0.mod, constants.keys_mod_none); - assert.equal(row0.keys[0]?.value, 'grave'); - }); + assert.equal(layr.lists?.length, 1); + const list0 = layr.lists[0]; + assert.ok(list0); + assert.equal(list0.layers.length, 1); + assert.equal(list0.hardware, constants.layr_list_hardware_us); + const layer0 = list0.layers[0]; + assert.ok(layer0); + assert.equal(layer0.rows.length, 1); + const row0 = layer0.rows[0]; + assert.ok(row0); + assert.equal(row0.keys.length, 2); - testCompilationCases(LayrCompiler, [ + assert.equal(layer0.id.value, 'base'); + assert.equal(layer0.mod, constants.keys_mod_none); + assert.equal(row0.keys[0]?.value, 'grave'); + }, + }, { subpath: 'sections/keys/maximal.xml', callback(sect) { @@ -115,13 +116,6 @@ describe('layr', function () { subpath: 'sections/layr/invalid-missing-layer.xml', errors: [CompilerMessages.Error_MustBeAtLeastOneLayerElement()], }, - { - // missing layers element completely - subpath: 'sections/layr/invalid-missing-layer.xml', - errors: [ - CompilerMessages.Error_MustBeAtLeastOneLayerElement(), - ], - }, { // warning on custom form subpath: 'sections/layr/hint-custom-form.xml', From 20d5d551e7e6a60802120a77eac25a8cd4cc7a5e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Sat, 23 Sep 2023 10:30:54 +0700 Subject: [PATCH 068/207] chore(web): Update comments --- web/src/engine/osk/src/specialCharacters.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/engine/osk/src/specialCharacters.ts b/web/src/engine/osk/src/specialCharacters.ts index 0a88283cb58..faa5fd697b8 100644 --- a/web/src/engine/osk/src/specialCharacters.ts +++ b/web/src/engine/osk/src/specialCharacters.ts @@ -1,5 +1,5 @@ // Defines the PUA code mapping for the various 'special' modifier/control/non-printing keys on keyboards. -// `specialCharacters` must be kept in sync with the same variable in builder.js. See also CompileKeymanWeb.pas: CSpecialText10 +// `specialCharacters` must be kept in sync with the same variable in constants.js. See also CompileKeymanWeb.pas: CSpecialText10 let specialCharacters = { '*Shift*': 8, '*Enter*': 5, @@ -44,7 +44,7 @@ let specialCharacters = { '*ZWNJiOS*': 0x75, // The iOS version will be used by default, but the '*ZWNJAndroid*': 0x76, // Android platform has its own default glyph. // Added in Keyman 17.0. - // Reference: https://github.com/silnrsi/font-symchar/blob/v3.000/documentation/encoding.md + // Reference: https://github.com/silnrsi/font-symchar/blob/v4.000/documentation/encoding.md '*Sp*': 0x80, // Space '*NBSp*': 0x82, // No-break Space '*NarNBSp*': 0x83, // Narrow No-break Space From 9571be670d548df1bc1b64c87b56553bc1114b23 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 05:43:26 +0700 Subject: [PATCH 069/207] feat(windows): create Keyman for Windows installer without buildpkg --- docs/build/windows.md | 6 +++--- windows/src/desktop/inst/Makefile | 14 ++++++++++++-- windows/src/desktop/inst/download.in | 12 ++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/build/windows.md b/docs/build/windows.md index 9faebc3d632..81a96a4a209 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -101,7 +101,7 @@ Building: * Ant * Gradle * Maven -* OpenJDK 11 +* Optional: OpenJDK 11 (https://learn.microsoft.com/en-us/java/openjdk/download) ```ps1 # Elevated PowerShell @@ -254,8 +254,8 @@ choco install openjdk choco install visualstudio2019community visualstudio2019-workload-nativedesktop visualstudio2019buildtools ``` * Verify required build tools are installed - * Run `Visual Studio Installer` - * Check the `Individual components` tab + * Run `Visual Studio Installer` + * Check the `Individual components` tab * Verify `MSVC v142 - VS 2019 c++ x64/x86 build tools (Latest)` is installed. If not, install it. Recommended: configure Visual Studio to use two-space tab stops: diff --git a/windows/src/desktop/inst/Makefile b/windows/src/desktop/inst/Makefile index c247da987b6..68bb12a7352 100644 --- a/windows/src/desktop/inst/Makefile +++ b/windows/src/desktop/inst/Makefile @@ -28,11 +28,19 @@ prereq-resources: $(SIGNCODE) /d "Keyman Resources" desktop_resources.dll desktop: prereq + rem compile .msi $(MAKE) -fdownload.mak candle $(WIXLIGHT) -dWixUILicenseRtf=License.rtf -out keymandesktop.msi -ext WixUIExtension $(DESKTOP_FILES) $(SIGNCODE) /d "Keyman" keymandesktop.msi -# TODO: replace buildpkg with kmc? Or promote it to developer? - $(ROOT)\bin\buildtools\buildpkg -m keymandesktop.msi -s $(ROOT)\bin\desktop -l license.html + + rem build self-extracting archive + $(MAKE) -fdownload.mak setup-inf + $(WZZIP) keymandesktop.zip keymandesktop.msi license.html setup.inf + -del setup.inf + $(COPY) /b $(ROOT)\bin\desktop\setup-redist.exe + keymandesktop.zip keymandesktop.exe + -del keymandesktop.zip + + rem sign and copy files $(SIGNCODE) /d "Keyman" keymandesktop.exe $(MAKE) -fdownload.mak copyredist-desktop @@ -51,6 +59,8 @@ clean: -del /Q desktopui.wxs -del /Q cef.wxs -del /Q locale.wxs + -del keymandesktop.zip + -del setup.inf check: if not exist $(ROOT)\src\engine\inst\keymanengine.msm $(MAKE) check-engine diff --git a/windows/src/desktop/inst/download.in b/windows/src/desktop/inst/download.in index 0bf062eeba4..3fc996840d4 100644 --- a/windows/src/desktop/inst/download.in +++ b/windows/src/desktop/inst/download.in @@ -63,3 +63,15 @@ candle-locale: # locale files are in desktop/locale/* $(WIXHEAT) dir ..\kmshell\locale -o locale.wxs -ag -cg Locale -dr INSTALLDIR -var var.LOCALESOURCE -wx -nologo $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dPRODUCTID=$GUID1 -dLOCALESOURCE=..\kmshell\locale locale.wxs + +# +# Build setup.inf +# + +setup-inf: + echo [Setup] > setup.inf + echo Version=$VersionWin >> setup.inf + echo MSIFileName=keymandesktop.msi >> setup.inf + echo MSIOptions= >> setup.inf + echo License=license.html >> setup.inf + echo [Packages] >> setup.inf From 10b2fa4fb598b7e12609f7bbe50d458faa699d72 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 05:43:42 +0700 Subject: [PATCH 070/207] feat(windows): create FV installer without buildpkg --- oem/firstvoices/windows/src/inst/Makefile | 15 +++++++++++---- oem/firstvoices/windows/src/inst/download.in | 12 ++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/oem/firstvoices/windows/src/inst/Makefile b/oem/firstvoices/windows/src/inst/Makefile index 2f3be6990b9..488b837d255 100644 --- a/oem/firstvoices/windows/src/inst/Makefile +++ b/oem/firstvoices/windows/src/inst/Makefile @@ -8,9 +8,9 @@ DESKTOP_FILES=firstvoices.wixobj desktopui.wixobj MSI=firstvoices.msi +EXE_ZIP=firstvoices.zip EXE=firstvoices.exe KMP=fv_all.kmp -INTEXE=firstvoices-fv_all.exe APPTITLE="Keyman for FirstVoices" TITLEIMAGE=setuptitle.png @@ -48,12 +48,19 @@ prereq: cd $(FVROOT)\src\inst desktop: prereq + rem compile .msi $(MAKE) -fdownload.mak candle-desktop $(WIXLIGHT) -dWixUILicenseRtf=License.rtf -out $(MSI) -ext WixUIExtension $(DESKTOP_FILES) $(SIGNCODE) /d $(APPTITLE) $(MSI) - $(ROOT)\bin\buildtools\buildpkg -m $(MSI) -s $(ROOT)\bin\desktop -l license.html -a $(APPTITLE) -i $(TITLEIMAGE) -n "FirstVoices Keyboards" -startDisabled -startWithConfiguration $(KMP) - if exist $(EXE) del $(EXE) - ren $(INTEXE) $(EXE) + + rem build self-extracting archive + $(MAKE) -fdownload.mak setup-inf + $(WZZIP) $(EXE_ZIP) $(MSI) license.html setup.inf $(TITLEIMAGE) $(KMP) + -del setup.inf + $(COPY) /b $(ROOT)\bin\desktop\setup-redist.exe + $(EXE_ZIP) $(EXE) + -del $(EXE_ZIP) + + rem sign and copy files $(SIGNCODE) /d $(APPTITLE) $(EXE) $(MAKE) -fdownload.mak copyredist-desktop diff --git a/oem/firstvoices/windows/src/inst/download.in b/oem/firstvoices/windows/src/inst/download.in index f31c2fe3c40..98d23e1e286 100644 --- a/oem/firstvoices/windows/src/inst/download.in +++ b/oem/firstvoices/windows/src/inst/download.in @@ -22,3 +22,15 @@ candle-desktop: $(WIXHEAT) dir ..\xml -o desktopui.wxs -ag -cg DesktopUI -dr INSTALLDIR -suid -var var.DESKTOPUISOURCE -wx -nologo $(WIXCANDLE) -dOEMNAME="$(OEMNAME)" -dPRODUCTNAME="$(PRODUCTNAME)" -dROOT="$(ROOT)" -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dPRODUCTID=$GUID1 -dDESKTOPUISOURCE=..\xml firstvoices.wxs desktopui.wxs +setup-inf: + echo [Setup] > setup.inf + echo Version=$VersionWin >> setup.inf + echo MSIFileName=firstvoices.msi >> setup.inf + echo MSIOptions= >> setup.inf + echo AppName=Keyman for FirstVoices >> setup.inf + echo License=license.html >> setup.inf + echo TitleImage=setuptitle.png >> setup.inf + echo StartDisabled=True >> setup.inf + echo StartWithConfiguration=True >> setup.inf + echo [Packages] >> setup.inf + echo fv_all.kmp=FirstVoices Keyboards >> setup.inf From 7dc8743cd3c8a837ce48c3be6459fa22bf2d494b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 06:00:39 +0700 Subject: [PATCH 071/207] chore(windows): remove buildpkg --- developer/src/README.md | 1 - windows/src/buildtools/Makefile | 9 +- windows/src/buildtools/buildpkg/Makefile | 20 - windows/src/buildtools/buildpkg/buildpkg.dpr | 74 -- .../src/buildtools/buildpkg/buildpkg.dproj | 1053 ----------------- windows/src/buildtools/buildpkg/buildpkg.res | Bin 57140 -> 0 bytes .../src/buildtools/buildpkg/buildpkgmain.pas | 214 ---- windows/src/buildtools/buildpkg/version.rc | 32 - 8 files changed, 2 insertions(+), 1401 deletions(-) delete mode 100644 windows/src/buildtools/buildpkg/Makefile delete mode 100644 windows/src/buildtools/buildpkg/buildpkg.dpr delete mode 100644 windows/src/buildtools/buildpkg/buildpkg.dproj delete mode 100644 windows/src/buildtools/buildpkg/buildpkg.res delete mode 100644 windows/src/buildtools/buildpkg/buildpkgmain.pas delete mode 100644 windows/src/buildtools/buildpkg/version.rc diff --git a/developer/src/README.md b/developer/src/README.md index 05afec59633..5d923dd4646 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -8,7 +8,6 @@ We do not have 100% clean separation between Keyman Developer and Keyman for Windows. Shared units are intended to live in common, however the following projects will need to be updated in the future. -* buildpkg in Windows depends on various compiler units in multiple folders. * kmbrowserhost in Windows is included as part of Keyman Developer. # Folders diff --git a/windows/src/buildtools/Makefile b/windows/src/buildtools/Makefile index d6621a889de..06053170500 100644 --- a/windows/src/buildtools/Makefile +++ b/windows/src/buildtools/Makefile @@ -7,7 +7,7 @@ NOTARGET_SIGNCODE=yes !ifdef NODELPHI TARGETS=.virtual !else -TARGETS=common buildpkg +TARGETS=common !endif CLEANS=clean-buildtools @@ -20,15 +20,10 @@ common: .virtual cd $(KEYMAN_ROOT)\common\windows\delphi\tools $(MAKE) $(TARGET) -buildpkg: .virtual - cd $(ROOT)\src\buildtools\buildpkg - $(MAKE) $(TARGET) - # ---------------------------------------------------------------------- clean-buildtools: - cd $(ROOT)\src\buildtools - -del version.txt + rem no action !include ..\Target.mak diff --git a/windows/src/buildtools/buildpkg/Makefile b/windows/src/buildtools/buildpkg/Makefile deleted file mode 100644 index 056011b0bdd..00000000000 --- a/windows/src/buildtools/buildpkg/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# -# Buildpkg Makefile -# - -!include ..\..\Defines.mak - -build: version.res dirs - $(DELPHI_MSBUILD) buildpkg.dproj "/p:Platform=Win32" - $(SENTRYTOOL_DELPHIPREP) $(WIN32_TARGET_PATH)\buildpkg.exe -dpr buildpkg.dpr - $(TDS2DBG) $(WIN32_TARGET_PATH)\buildpkg.exe - $(COPY) $(WIN32_TARGET_PATH)\Buildpkg.exe $(PROGRAM)\online - $(COPY) $(WIN32_TARGET_PATH)\Buildpkg.exe $(PROGRAM)\buildtools - if exist $(WIN32_TARGET_PATH)\buildpkg.dbg $(COPY) $(WIN32_TARGET_PATH)\buildpkg.dbg $(DEBUGPATH)\buildtools - -clean: def-clean - -signcode: - $(SIGNCODE) /d "Package Installer Creator for Server" $(PROGRAM)\online\Buildpkg.exe - -!include ..\..\Target.mak diff --git a/windows/src/buildtools/buildpkg/buildpkg.dpr b/windows/src/buildtools/buildpkg/buildpkg.dpr deleted file mode 100644 index d2c7d21cfa7..00000000000 --- a/windows/src/buildtools/buildpkg/buildpkg.dpr +++ /dev/null @@ -1,74 +0,0 @@ -program buildpkg; - -{$APPTYPE CONSOLE} - -uses - SysUtils, - buildpkgmain in 'buildpkgmain.pas', - CompilePackageInstaller in '..\..\..\..\developer\src\common\delphi\compiler\CompilePackageInstaller.pas', - utilsystem in '..\..\..\..\common\windows\delphi\general\utilsystem.pas', - CompilePackage in '..\..\..\..\developer\src\common\delphi\compiler\CompilePackage.pas', - kmpinffile in '..\..\..\..\common\windows\delphi\packages\kmpinffile.pas', - kpsfile in '..\..\..\..\developer\src\common\delphi\packages\kpsfile.pas', - PackageInfo in '..\..\..\..\common\windows\delphi\packages\PackageInfo.pas', - utildir in '..\..\..\..\common\windows\delphi\general\utildir.pas', - utilstr in '..\..\..\..\common\windows\delphi\general\utilstr.pas', - utilfiletypes in '..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', - Unicode in '..\..\..\..\common\windows\delphi\general\Unicode.pas', - VersionInfo in '..\..\..\..\common\windows\delphi\general\VersionInfo.pas', - PackageFileFormats in '..\..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', - GetOsVersion in '..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', - RegistryKeys in '..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', - KeymanDeveloperOptions in '..\..\..\..\developer\src\tike\main\KeymanDeveloperOptions.pas', - RedistFiles in '..\..\..\..\developer\src\tike\main\RedistFiles.pas', - DebugPaths in '..\..\..\..\common\windows\delphi\general\DebugPaths.pas', - CustomisationStorage in '..\..\global\delphi\cust\CustomisationStorage.pas', - StockFileNames in '..\..\..\..\common\windows\delphi\general\StockFileNames.pas', - klog in '..\..\..\..\common\windows\delphi\general\klog.pas', - httpuploader in '..\..\..\..\common\windows\delphi\general\httpuploader.pas', - Upload_Settings in '..\..\..\..\common\windows\delphi\general\Upload_Settings.pas', - UfrmTike in '..\..\..\..\developer\src\tike\main\UfrmTike.pas' {TikeForm: TTntForm}, - utilhttp in '..\..\..\..\common\windows\delphi\general\utilhttp.pas', - kmxfile in '..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - utilkeyboard in '..\..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', - KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', - wininet5 in '..\..\..\..\common\windows\delphi\general\wininet5.pas', - GlobalProxySettings in '..\..\..\..\common\windows\delphi\general\GlobalProxySettings.pas', - ErrorControlledRegistry in '..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - utilexecute in '..\..\..\..\common\windows\delphi\general\utilexecute.pas', - KeymanVersion in '..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', - Glossary in '..\..\..\..\common\windows\delphi\general\Glossary.pas', - Keyman.Developer.System.Project.ProjectLog in '..\..\..\..\developer\src\tike\project\Keyman.Developer.System.Project.ProjectLog.pas', - UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas', - VisualKeyboard in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', - VisualKeyboardLoaderBinary in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderBinary.pas', - VisualKeyboardLoaderXML in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderXML.pas', - VisualKeyboardSaverBinary in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', - VisualKeyboardSaverXML in '..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', - TempFileManager in '..\..\..\..\common\windows\delphi\general\TempFileManager.pas', - ExtShiftState in '..\..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', - VKeyChars in '..\..\..\..\common\windows\delphi\general\VKeyChars.pas', - VKeys in '..\..\..\..\common\windows\delphi\general\VKeys.pas', - JsonUtil in '..\..\..\..\common\windows\delphi\general\JsonUtil.pas', - Keyman.System.PackageInfoRefreshKeyboards in '..\..\..\..\developer\src\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', - Keyman.System.KMXFileLanguages in '..\..\..\..\developer\src\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', - Keyman.System.Standards.ISO6393ToBCP47Registry in '..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', - Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', - Keyman.System.KeyboardJSInfo in '..\..\..\..\developer\src\common\delphi\keyboards\Keyman.System.KeyboardJSInfo.pas', - Keyman.System.KeyboardUtils in '..\..\..\..\developer\src\common\delphi\keyboards\Keyman.System.KeyboardUtils.pas', - Keyman.System.LanguageCodeUtils in '..\..\..\..\common\windows\delphi\general\Keyman.System.LanguageCodeUtils.pas', - Keyman.System.RegExGroupHelperRSP19902 in '..\..\..\..\common\windows\delphi\vcl\Keyman.System.RegExGroupHelperRSP19902.pas', - kmxfileconsts in '..\..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', - BCP47Tag in '..\..\..\..\common\windows\delphi\general\BCP47Tag.pas', - Keyman.System.Standards.BCP47SubtagRegistry in '..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SubtagRegistry.pas', - Keyman.System.Standards.BCP47SuppressScriptRegistry in '..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SuppressScriptRegistry.pas', - Keyman.System.CanonicalLanguageCodeUtils in '..\..\..\..\common\windows\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas', - Keyman.System.LexicalModelUtils in '..\..\..\..\developer\src\common\delphi\lexicalmodels\Keyman.System.LexicalModelUtils.pas', - Keyman.System.PackageInfoRefreshLexicalModels in '..\..\..\..\developer\src\common\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', - Keyman.System.Standards.LangTagsRegistry in '..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', - KeymanPaths in '..\..\..\..\common\windows\delphi\general\KeymanPaths.pas', - Keyman.Developer.System.KeymanDeveloperPaths in '..\..\..\..\developer\src\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas'; - -begin - Run; -end. diff --git a/windows/src/buildtools/buildpkg/buildpkg.dproj b/windows/src/buildtools/buildpkg/buildpkg.dproj deleted file mode 100644 index 16dbddf62fb..00000000000 --- a/windows/src/buildtools/buildpkg/buildpkg.dproj +++ /dev/null @@ -1,1053 +0,0 @@ - - - {662A6FEA-4D2E-42E8-9292-1B3EE5A1E480} - buildpkg.dpr - True - Debug - 1 - Console - None - 18.8 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - buildpkg - $(BDS)\bin\delphi_PROJECTICON.ico - $(BDS)\bin\delphi_PROJECTICNS.icns - true - true - $(DELPHI)\lib;$(DCC_UnitSearchPath) - false - 28 - true - 5 - System;Xml;Data;Datasnap;Web;Soap;Winapi;Vcl;System.Win;Vcl.Imaging;$(DCC_Namespace) - true - VISUALKEYBOARD_NOCUSTOMBITMAP;VERSION_KEYMAN_REDIST;RELEASE_KEYMAN;EXCMAGIC_GUI;$(DCC_Define) - 1 - 3 - false - false - 3081 - false - true - false - CompanyName=;FileDescription=Tavultesoft Keyboard Manager;FileVersion=5.0.0.28;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= - Vcl40;Vclx40;vcljpg40;comp51;VCLZipD4;dclkmn;Vcl50;Vclx50;vclie50;Inetdb50;Inet50;Vcldb50;$(DCC_UsePackage) - 2C400000 - false - true - None - true - .\obj\$(Platform)\$(Config) - .\bin\$(Platform)\$(Config) - - - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - 1033 - - - false - 0 - 0 - RELEASE;$(DCC_Define) - - - DEBUG;$(DCC_Define) - false - - - -m c:\temp\xx\keymandesktop90.msi -l c:\temp\xx\license.html -n "GFF Amharic" -s c:\temp\xx\ c:\temp\xx\gff-amh-powerpack-7.kmp - c:\temp\xx\ - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - 1033 - - - - MainSource - - - - - - - - - - - - - - - - - - - - - - - - - -
    TikeForm
    - TTntForm -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - -
    - - Delphi.Personality.12 - - - - - buildpkg.dpr - - - $00000C09 - - - False - True - 5 - 0 - 0 - 32 - False - False - False - False - False - 3081 - 1252 - - - - Tavultesoft Keyboard Manager - 5.0.0.32 - - - - - - 1.0.0.0 - - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - True - False - - - - - true - - - - - true - - - - - true - - - - - true - - - - - true - - - - - buildpkg.exe - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - 12 - - - - -
    diff --git a/windows/src/buildtools/buildpkg/buildpkg.res b/windows/src/buildtools/buildpkg/buildpkg.res deleted file mode 100644 index 6876088a6648ed90b09c480197508e4404c57dbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57140 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 I2>8kW0h0=QvH$=8 diff --git a/windows/src/buildtools/buildpkg/buildpkgmain.pas b/windows/src/buildtools/buildpkg/buildpkgmain.pas deleted file mode 100644 index 29882895a62..00000000000 --- a/windows/src/buildtools/buildpkg/buildpkgmain.pas +++ /dev/null @@ -1,214 +0,0 @@ -(* - Name: buildpkgmain - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 23 Aug 2007 - - Modified Date: 22 Jun 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 23 Aug 2007 - mcdurdin - Initial version - 17 Sep 2007 - mcdurdin - Support output path parameter - 30 Dec 2010 - mcdurdin - I2562 - EULA as part of setup bootstrapper - 04 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls - 23 Feb 2015 - mcdurdin - I4598 - V9.0 - Keyman installer does not show EULA when bundled with a keyboard - 04 May 2015 - mcdurdin - I4694 - V9.0 - Split UI actions from non-UI actions in projects - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness - 22 Jun 2015 - mcdurdin - I4763 - Package compiler (buildpkg) needs to specify username and password on command line - 22 Jun 2015 - mcdurdin - I4764 - Buildpkg needs to support different paths for msi and kmp files - -*) -unit buildpkgmain; // I3306 - -interface - -procedure Run; - -implementation - -uses - Winapi.Windows, - - CompilePackageInstaller, - kpsfile, - Keyman.Developer.System.Project.ProjectLog, - SysUtils; - -type - TParams = class - private - TooManyParams: Boolean; - public - OutputPath, RedistPath, MSIFileName, KMPFileName, KMPName, AppName, LicenseFileName, TitleImageFileName: WideString; // I2562 // I4763 - StartDisabled, StartWithConfiguration: Boolean; - constructor Create; - function Validate: Boolean; - function Completed: Boolean; - - procedure CompilerMessage(Sender: TObject; msg: string; State: TProjectLogState); // I4706 - end; - -procedure Run; -var - Params: TParams; - pack: TKPSFile; -begin - Params := TParams.Create; - - if not Params.Completed then - begin - writeln('buildpkg.exe 1.0'); - writeln('Usage: '+ExtractFileName(ParamStr(0))+' -m [-l ] [-i ] [-a ] (-s )|(-n )'); // I2562 // I4763 - writeln('If setup-redist.exe is in setup folder location, it will be used in preference to setup.exe'); - ExitCode := 3; - Exit; - end; - - if not Params.Validate then - begin - writeln('buildpkg.exe 1.0'); - ExitCode := 4; - Exit; - end; - - if Params.KMPFileName <> '' then - begin - pack := TKPSFile.Create; - pack.KPSOptions.MSIFileName := Params.MSIFileName; - pack.FileName := ChangeFileExt(Params.KMPFileName, '.kps'); - pack.Info.Desc['Name'] := Params.KMPName; - - try - if not DoCompilePackageInstaller(pack, Params.CompilerMessage, False, Params.MSIFileName, - ChangeFileExt(Params.MSIFileName,'')+'-'+ChangeFileExt(ExtractFileName(Params.KMPFileName),'')+'.exe', Params.RedistPath, - False, False, Params.LicenseFileName, Params.TitleImageFileName, Params.AppName, - Params.StartDisabled, Params.StartWithConfiguration) then // I4598 // I4694 // I4764 - ExitCode := 1 - else - ExitCode := 0; - except - on E:Exception do - begin - writeln(E.Message); - ExitCode := 2; - end; - end; - end - else - begin - try - - if Params.OutputPath = '' then - Params.OutputPath := ChangeFileExt(Params.MSIFileName, '.exe'); - - if not DoCompileMSIInstaller(Params.CompilerMessage, False, Params.MSIFileName, Params.OutputPath, Params.RedistPath, - Params.LicenseFileName, Params.TitleImageFileName, Params.AppName, - Params.StartDisabled, Params.StartWithConfiguration) then // I2562 - ExitCode := 1 - else - ExitCode := 0; - except - on E:Exception do - begin - writeln(E.Message); - ExitCode := 2; - end; - end; - end; -end; - -{ TParams } - -procedure TParams.CompilerMessage(Sender: TObject; msg: string; - State: TProjectLogState); // I4706 -begin - if State in [plsError, plsFatal] - then raise Exception.Create(msg) - else writeln(msg); -end; - -function TParams.Completed: Boolean; -begin - Result := - not TooManyParams and - (MSIFileName <> '') and - ( - ((KMPFileName = '') and (RedistPath <> '')) or - ((KMPFileName <> '') and (KMPName <> '')) - ); -end; - -constructor TParams.Create; -var - i: Integer; - Flag: WideString; -begin - { Takes a .kmp file and turns it into a .msi installer } - i := 1; - while i <= ParamCount do - begin - Flag := ParamStr(i); Inc(i); - if Flag = '-m' then - MSIFileName := ParamStr(i) - else if Flag = '-n' then - KMPName := ParamStr(i) - else if Flag = '-s' then - RedistPath := IncludeTrailingPathDelimiter(ParamStr(i)) - else if Flag = '-o' then - OutputPath := ParamStr(i) - else if Flag = '-l' then // I2562 - LicenseFileName := ParamStr(i) - else if Flag = '-i' then - TitleImageFileName := ParamStr(i) - else if Flag = '-a' then - AppName := ParamStr(i) - else if SameText(Flag, '-startDisabled') then - begin - Dec(i); StartDisabled := True; - end - else if SameText(Flag, '-startWithConfiguration') then - begin - Dec(i); StartWithConfiguration := True; - end - else - begin - KMPFileName := Flag; - Break; - end; - Inc(i); - end; - TooManyParams := i <= ParamCount; -end; - -function TParams.Validate: Boolean; -begin - Result := True; - if not FileExists(MSIFileName) then - begin - Result := False; - writeln('File '+MSIFileName+' does not exist.'); - end; - if (KMPFileName <> '') and not FileExists(KMPFileName) then - begin - Result := False; - writeln('File '+KMPFileName+' does not exist.'); - end; - if (LicenseFileName <> '') and not FileExists(LicenseFileName) then // I2562 - begin - Result := False; - writeln('License File '+LicenseFileName+' does not exist.'); - end; - if (RedistPath <> '') and (not FileExists(RedistPath + 'setup.exe') or not FileExists(RedistPath + 'setup-redist.exe')) then - begin - Result := False; - writeln('File '+RedistPath+'setup.exe and '+RedistPath+'setup-redist.exe do not exist.'); - end; -end; - -end. diff --git a/windows/src/buildtools/buildpkg/version.rc b/windows/src/buildtools/buildpkg/version.rc deleted file mode 100644 index 134c4bf8c3c..00000000000 --- a/windows/src/buildtools/buildpkg/version.rc +++ /dev/null @@ -1,32 +0,0 @@ -#include "../../../../common/windows/cpp/include/keymanversion.h" - -1 VERSIONINFO - FILEVERSION KV_FILEVERSION - PRODUCTVERSION KV_PRODUCTVERSION - FILEFLAGSMASK 0x3fL - FILEFLAGS 0x0L - FILEOS 0x4L - FILETYPE 0x1L - FILESUBTYPE 0x0L - BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "0C0904E4" - BEGIN - VALUE "CompanyName", KV_COMPANY_NAME - VALUE "FileDescription", "Buildpkg\0" - VALUE "FileVersion", KV_VERSION_STRING - VALUE "InternalName", "BUILDPKG\0" - VALUE "LegalCopyright", KV_LEGAL_COPYRIGHT - VALUE "LegalTrademarks", KV_LEGAL_TRADEMARKS - VALUE "OriginalFilename", "BUILDPKG.EXE\0" - VALUE "ProductName", "Keyman Build Environment\0" - VALUE "ProductVersion", KV_VERSION_STRING - VALUE "Comments", "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0xc09, 1252 - END - END From 5a6758d12ea350d34b6c4fa5ecdb8a3befb8fe98 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 08:44:11 +0700 Subject: [PATCH 072/207] chore(developer): move package version unit tests into Typescript --- .../versioning}/test-1.0/test-1.0.kpj | 0 .../test/fixtures/versioning/test-1.0/test.js | 1 + .../fixtures/versioning}/test-1.0/test.kmn | 0 .../fixtures/versioning/test-1.0/test.kmx | Bin 0 -> 370 bytes .../versioning}/test-2.0/test-2.0.kpj | 0 .../test/fixtures/versioning/test-2.0/test.js | 1 + .../fixtures/versioning}/test-2.0/test.kmn | 0 .../fixtures/versioning/test-2.0/test.kmx | Bin 0 -> 370 bytes .../test-keyboard-1-vs-package-2.kps | 0 .../test-package-1-vs-keyboard-2.kps | 0 .../test/fixtures/versioning}/test1-2.kps | 0 .../test/fixtures/versioning}/test1.kps | 0 .../test/fixtures/versioning}/test2-1.kps | 0 .../test/fixtures/versioning}/test2.kps | 0 .../src/kmc-package/test/test-versioning.ts | 42 + developer/src/test/auto/Makefile | 5 - .../test/auto/developer-test-auto.groupproj | 18 +- .../auto/keyboard-package-versions/.gitignore | 3 - .../KeyboardPackageVersionsTestSuite.dpr | 142 --- .../KeyboardPackageVersionsTestSuite.dproj | 1034 ----------------- .../KeyboardPackageVersionsTestSuite.res | Bin 57140 -> 0 bytes ...st.System.CompilePackageVersioningTest.pas | 155 --- .../auto/keyboard-package-versions/Makefile | 24 - 23 files changed, 47 insertions(+), 1378 deletions(-) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-1.0/test-1.0.kpj (100%) create mode 100644 developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.js rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-1.0/test.kmn (100%) create mode 100644 developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.kmx rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-2.0/test-2.0.kpj (100%) create mode 100644 developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.js rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-2.0/test.kmn (100%) create mode 100644 developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.kmx rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-keyboard-1-vs-package-2.kps (100%) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test-package-1-vs-keyboard-2.kps (100%) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test1-2.kps (100%) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test1.kps (100%) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test2-1.kps (100%) rename developer/src/{test/auto/keyboard-package-versions => kmc-package/test/fixtures/versioning}/test2.kps (100%) create mode 100644 developer/src/kmc-package/test/test-versioning.ts delete mode 100644 developer/src/test/auto/keyboard-package-versions/.gitignore delete mode 100644 developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr delete mode 100644 developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj delete mode 100644 developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.res delete mode 100644 developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas delete mode 100644 developer/src/test/auto/keyboard-package-versions/Makefile diff --git a/developer/src/test/auto/keyboard-package-versions/test-1.0/test-1.0.kpj b/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test-1.0.kpj similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-1.0/test-1.0.kpj rename to developer/src/kmc-package/test/fixtures/versioning/test-1.0/test-1.0.kpj diff --git a/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.js b/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.js new file mode 100644 index 00000000000..9ee538d9ae4 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.js @@ -0,0 +1 @@ +KeymanWeb.KR(new Keyboard_test());function Keyboard_test(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test";this.KN="Keyboard Version Test";this.KMINVER="9.0";this.KV=null;this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0;this.KVER="17.0.159.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};} \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-package-versions/test-1.0/test.kmn b/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.kmn similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-1.0/test.kmn rename to developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.kmn diff --git a/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.kmx b/developer/src/kmc-package/test/fixtures/versioning/test-1.0/test.kmx new file mode 100644 index 0000000000000000000000000000000000000000..9e4bd15d391dea7a1b7e234e8f4f9ee80541836b GIT binary patch literal 370 zcmZvYO$$Lm6o%is!p_3S)4l&6lDtbj2`G0q?@%bfVlSjM&#>Fd| literal 0 HcmV?d00001 diff --git a/developer/src/test/auto/keyboard-package-versions/test-2.0/test-2.0.kpj b/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test-2.0.kpj similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-2.0/test-2.0.kpj rename to developer/src/kmc-package/test/fixtures/versioning/test-2.0/test-2.0.kpj diff --git a/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.js b/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.js new file mode 100644 index 00000000000..788a3d58ffa --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.js @@ -0,0 +1 @@ +KeymanWeb.KR(new Keyboard_test());function Keyboard_test(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test";this.KN="Keyboard Version Test";this.KMINVER="9.0";this.KV=null;this.KDU=0;this.KH='';this.KM=0;this.KBVER="2.0";this.KMBM=0x0;this.KVER="17.0.159.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};} \ No newline at end of file diff --git a/developer/src/test/auto/keyboard-package-versions/test-2.0/test.kmn b/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.kmn similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-2.0/test.kmn rename to developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.kmn diff --git a/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.kmx b/developer/src/kmc-package/test/fixtures/versioning/test-2.0/test.kmx new file mode 100644 index 0000000000000000000000000000000000000000..0efeffe974381d8411569d8e7d6b39d67935eb92 GIT binary patch literal 370 zcmZvYy$%6U5QWcH=oEfhg(w9fB2h?er%>34=m-gkzw8Rxr_gvOZ=}%h&AJNWBs24! zd+*G>yMswEQbroyvKJxG5=s%qJz@$x5vZ&s6uN`?x7C$eaD1m09Y3fx%ao?4{6;n5 zisLSvbL_)?g<5K+BYB~c&RWAOXuwLWEqgnhk2?_??RB7rteFoL(Xmg?)YVp1HKMMT qItdG}uUQ{Ar)x&P-8%l66yPJ?AvN3-dqs@@e}{e0=PwzXJmLl3Gb;7~ literal 0 HcmV?d00001 diff --git a/developer/src/test/auto/keyboard-package-versions/test-keyboard-1-vs-package-2.kps b/developer/src/kmc-package/test/fixtures/versioning/test-keyboard-1-vs-package-2.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-keyboard-1-vs-package-2.kps rename to developer/src/kmc-package/test/fixtures/versioning/test-keyboard-1-vs-package-2.kps diff --git a/developer/src/test/auto/keyboard-package-versions/test-package-1-vs-keyboard-2.kps b/developer/src/kmc-package/test/fixtures/versioning/test-package-1-vs-keyboard-2.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test-package-1-vs-keyboard-2.kps rename to developer/src/kmc-package/test/fixtures/versioning/test-package-1-vs-keyboard-2.kps diff --git a/developer/src/test/auto/keyboard-package-versions/test1-2.kps b/developer/src/kmc-package/test/fixtures/versioning/test1-2.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test1-2.kps rename to developer/src/kmc-package/test/fixtures/versioning/test1-2.kps diff --git a/developer/src/test/auto/keyboard-package-versions/test1.kps b/developer/src/kmc-package/test/fixtures/versioning/test1.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test1.kps rename to developer/src/kmc-package/test/fixtures/versioning/test1.kps diff --git a/developer/src/test/auto/keyboard-package-versions/test2-1.kps b/developer/src/kmc-package/test/fixtures/versioning/test2-1.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test2-1.kps rename to developer/src/kmc-package/test/fixtures/versioning/test2-1.kps diff --git a/developer/src/test/auto/keyboard-package-versions/test2.kps b/developer/src/kmc-package/test/fixtures/versioning/test2.kps similarity index 100% rename from developer/src/test/auto/keyboard-package-versions/test2.kps rename to developer/src/kmc-package/test/fixtures/versioning/test2.kps diff --git a/developer/src/kmc-package/test/test-versioning.ts b/developer/src/kmc-package/test/test-versioning.ts new file mode 100644 index 00000000000..e9771a369ab --- /dev/null +++ b/developer/src/kmc-package/test/test-versioning.ts @@ -0,0 +1,42 @@ +import 'mocha'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { KmpCompiler } from '../src/compiler/kmp-compiler.js'; +import { KmpJsonFile } from '@keymanapp/common-types'; + +// This unit test was translated from a Delphi test +// Keyman.Test.System.CompilePackageVersioningTest, but note the difference in +// test results documented below. + +describe('package versioning', function () { + + const cases: [string,string][] = [ + ['test-single-version-1-package', 'test1.kps'], + ['test-single-version-2-package', 'test2.kps'], + + // The test result for the following two packages has changed in Keyman 17, + // in commit 92c8c6497e987a0583c62c91515884e6e36ece23, to 'shouldPass': + // + // `WARN_KeyboardVersionsDoNotMatchPackageVersion` has been removed, because + // it did not really make sense; if 'FollowKeyboardVersion' is set, it could + // not be raised, and otherwise, the author may wish to have separate + // keyboard + package versions anyway. + + ['test-version-2-1-mismatch', 'test2-1.kps'], + ['test-version-1-2-mismatch', 'test1-2.kps'], + + ['test-keyboard-1-package-2', 'test-keyboard-1-vs-package-2.kps'], + ['test-package-1-keyboard-2', 'test-package-1-vs-keyboard-2.kps'], + ]; + + for(const [ caseTitle, filename ] of cases) { + it(caseTitle, async function () { + const callbacks = new TestCompilerCallbacks(); + const kmpCompiler = new KmpCompiler(callbacks); + const kpsPath = makePathToFixture('versioning', filename); + const kmpJson: KmpJsonFile.KmpJsonFile = kmpCompiler.transformKpsToKmpObject(kpsPath); + assert.isTrue(kmpJson !== null); + }); + } +}); diff --git a/developer/src/test/auto/Makefile b/developer/src/test/auto/Makefile index 68108dde1fe..8f815f3b7b7 100644 --- a/developer/src/test/auto/Makefile +++ b/developer/src/test/auto/Makefile @@ -16,7 +16,6 @@ test: developer-tests: \ compile-supplementary-support \ keyboard-js-info \ - keyboard-package-versions \ kmcomp \ kmcomp-x64-structures \ kmconvert \ @@ -44,10 +43,6 @@ kmx-file-languages: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\kmx-file-languages $(MAKE) $(TARGET) -keyboard-package-versions: .virtual - cd $(DEVELOPER_ROOT)\src\test\auto\keyboard-package-versions - $(MAKE) $(TARGET) - kmcomp: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\kmcomp $(MAKE) $(TARGET) diff --git a/developer/src/test/auto/developer-test-auto.groupproj b/developer/src/test/auto/developer-test-auto.groupproj index cf31a8f8ced..b680dfb6aec 100644 --- a/developer/src/test/auto/developer-test-auto.groupproj +++ b/developer/src/test/auto/developer-test-auto.groupproj @@ -6,9 +6,6 @@ - - - @@ -41,15 +38,6 @@ - - - - - - - - - @@ -96,13 +84,13 @@ - + - + - + diff --git a/developer/src/test/auto/keyboard-package-versions/.gitignore b/developer/src/test/auto/keyboard-package-versions/.gitignore deleted file mode 100644 index 09bf1459b58..00000000000 --- a/developer/src/test/auto/keyboard-package-versions/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.kmp -test-?.0/*.js -test-?.0/*.kmx diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr deleted file mode 100644 index d3645dc38f6..00000000000 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr +++ /dev/null @@ -1,142 +0,0 @@ -program KeyboardPackageVersionsTestSuite; - -{$IFNDEF TESTINSIGHT} -{$APPTYPE CONSOLE} -{$ENDIF}{$STRONGLINKTYPES ON} -uses - System.SysUtils, - {$IFDEF TESTINSIGHT} - TestInsight.DUnitX, - {$ENDIF } - DUnitX.Loggers.Console, - DUnitX.Loggers.Xml.NUnit, - DUnitX.TestFramework, - Keyman.Test.System.CompilePackageVersioningTest in 'Keyman.Test.System.CompilePackageVersioningTest.pas', - CompilePackage in '..\..\..\common\delphi\compiler\CompilePackage.pas', - VisualKeyboard in '..\..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', - kmpinffile in '..\..\..\..\..\common\windows\delphi\packages\kmpinffile.pas', - kpsfile in '..\..\..\common\delphi\packages\kpsfile.pas', - PackageFileFormats in '..\..\..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', - PackageInfo in '..\..\..\..\..\common\windows\delphi\packages\PackageInfo.pas', - utilfiletypes in '..\..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', - StockFileNames in '..\..\..\..\..\common\windows\delphi\general\StockFileNames.pas', - utilstr in '..\..\..\..\..\common\windows\delphi\general\utilstr.pas', - Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas', - JsonUtil in '..\..\..\..\..\common\windows\delphi\general\JsonUtil.pas', - KeymanVersion in '..\..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', - utildir in '..\..\..\..\..\common\windows\delphi\general\utildir.pas', - utilsystem in '..\..\..\..\..\common\windows\delphi\general\utilsystem.pas', - RegistryKeys in '..\..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', - utilexecute in '..\..\..\..\..\common\windows\delphi\general\utilexecute.pas', - GetOsVersion in '..\..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', - VersionInfo in '..\..\..\..\..\common\windows\delphi\general\VersionInfo.pas', - ExtShiftState in '..\..\..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', - VisualKeyboardLoaderBinary in '..\..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderBinary.pas', - VisualKeyboardLoaderXML in '..\..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderXML.pas', - VisualKeyboardSaverBinary in '..\..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', - VisualKeyboardSaverXML in '..\..\..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', - VKeyChars in '..\..\..\..\..\common\windows\delphi\general\VKeyChars.pas', - VKeys in '..\..\..\..\..\common\windows\delphi\general\VKeys.pas', - ErrorControlledRegistry in '..\..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - kmxfile in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - KeyNames in '..\..\..\..\..\common\windows\delphi\general\KeyNames.pas', - KeymanDeveloperOptions in '..\..\..\tike\main\KeymanDeveloperOptions.pas', - Keyman.System.PackageInfoRefreshKeyboards in '..\..\..\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', - Keyman.System.KeyboardJSInfo in '..\..\..\common\delphi\keyboards\Keyman.System.KeyboardJSInfo.pas', - Keyman.System.KeyboardUtils in '..\..\..\common\delphi\keyboards\Keyman.System.KeyboardUtils.pas', - Keyman.System.KMXFileLanguages in '..\..\..\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', - Keyman.System.Standards.ISO6393ToBCP47Registry in '..\..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', - Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', - TempFileManager in '..\..\..\..\..\common\windows\delphi\general\TempFileManager.pas', - RedistFiles in '..\..\..\tike\main\RedistFiles.pas', - DebugPaths in '..\..\..\..\..\common\windows\delphi\general\DebugPaths.pas', - Upload_Settings in '..\..\..\..\..\common\windows\delphi\general\Upload_Settings.pas', - klog in '..\..\..\..\..\common\windows\delphi\general\klog.pas', - compile in '..\..\..\common\delphi\compiler\compile.pas', - Keyman.Developer.System.Project.kmnProjectFile in '..\..\..\tike\project\Keyman.Developer.System.Project.kmnProjectFile.pas', - Keyman.Developer.System.Project.kmnProjectFileAction in '..\..\..\tike\project\Keyman.Developer.System.Project.kmnProjectFileAction.pas', - Keyman.Developer.System.Project.Project in '..\..\..\tike\project\Keyman.Developer.System.Project.Project.pas', - Keyman.Developer.System.Project.ProjectFile in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectFile.pas', - Keyman.Developer.System.Project.ProjectFiles in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectFiles.pas', - Keyman.Developer.System.Project.ProjectFileType in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectFileType.pas', - mrulist in '..\..\..\tike\main\mrulist.pas', - kmxfileconsts in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', - Keyman.Developer.System.Project.ProjectLoader in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectLoader.pas', - Keyman.Developer.System.Project.ProjectLog in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectLog.pas', - Keyman.Developer.System.Project.ProjectSaver in '..\..\..\tike\project\Keyman.Developer.System.Project.ProjectSaver.pas', - UKeymanTargets in '..\..\..\common\delphi\general\UKeymanTargets.pas', - CompileErrorCodes in '..\..\..\common\delphi\compiler\CompileErrorCodes.pas', - KeyboardParser in '..\..\..\tike\main\KeyboardParser.pas', - WindowsLanguages in '..\..\..\common\delphi\general\WindowsLanguages.pas', - KeymanWebKeyCodes in '..\..\..\tike\compile\KeymanWebKeyCodes.pas', - kmxfileutils in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', - TouchLayoutDefinitions in '..\..\..\tike\oskbuilder\TouchLayoutDefinitions.pas', - TouchLayout in '..\..\..\tike\oskbuilder\TouchLayout.pas', - KeyboardFonts in '..\..\..\common\delphi\general\KeyboardFonts.pas', - Keyman.Developer.System.Project.kpsProjectFile in '..\..\..\tike\project\Keyman.Developer.System.Project.kpsProjectFile.pas', - Keyman.Developer.System.Project.kpsProjectFileAction in '..\..\..\tike\project\Keyman.Developer.System.Project.kpsProjectFileAction.pas', - CompilePackageInstaller in '..\..\..\common\delphi\compiler\CompilePackageInstaller.pas', - utilhttp in '..\..\..\..\..\common\windows\delphi\general\utilhttp.pas', - Keyman.System.LanguageCodeUtils in '..\..\..\..\..\common\windows\delphi\general\Keyman.System.LanguageCodeUtils.pas', - Keyman.System.RegExGroupHelperRSP19902 in '..\..\..\..\..\common\windows\delphi\vcl\Keyman.System.RegExGroupHelperRSP19902.pas', - DUnitX.Loggers.TeamCity in '..\..\..\..\..\common\windows\delphi\general\DUnitX.Loggers.TeamCity.pas', - BCP47Tag in '..\..\..\..\..\common\windows\delphi\general\BCP47Tag.pas', - Keyman.System.Standards.BCP47SubtagRegistry in '..\..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SubtagRegistry.pas', - Keyman.System.Standards.BCP47SuppressScriptRegistry in '..\..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SuppressScriptRegistry.pas', - Keyman.System.CanonicalLanguageCodeUtils in '..\..\..\..\..\common\windows\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas', - TextFileFormat in '..\..\..\common\delphi\general\TextFileFormat.pas', - Keyman.System.LexicalModelUtils in '..\..\..\common\delphi\lexicalmodels\Keyman.System.LexicalModelUtils.pas', - Keyman.System.PackageInfoRefreshLexicalModels in '..\..\..\common\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', - Keyman.System.Standards.LangTagsRegistry in '..\..\..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', - Keyman.Developer.System.Project.UrlRenderer in '..\..\..\tike\project\Keyman.Developer.System.Project.UrlRenderer.pas', - KeymanPaths in '..\..\..\..\..\common\windows\delphi\general\KeymanPaths.pas', - Keyman.Developer.System.ValidateKpsFile in '..\..\..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas', - Keyman.Developer.System.KeymanDeveloperPaths in '..\..\..\tike\main\Keyman.Developer.System.KeymanDeveloperPaths.pas', - Keyman.Developer.System.KmcWrapper in '..\..\..\tike\compile\Keyman.Developer.System.KmcWrapper.pas'; - -var - runner : ITestRunner; - results : IRunResults; - logger : ITestLogger; - nunitLogger : ITestLogger; -begin -{$IFDEF TESTINSIGHT} - TestInsight.DUnitX.RunRegisteredTests; - exit; -{$ENDIF} - try - //Check command line options, will exit if invalid - TDUnitX.CheckCommandLine; - //Create the test runner - runner := TDUnitX.CreateRunner; - //Tell the runner to use RTTI to find Fixtures - runner.UseRTTI := True; - //tell the runner how we will log things - //Log to the console window - logger := TDUnitXConsoleLogger.Create(true); - runner.AddLogger(logger); - //Generate an NUnit compatible XML File - nunitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile); - runner.AddLogger(nunitLogger); - runner.FailsOnNoAsserts := False; //When true, Assertions must be made during tests; - - //Run tests - results := runner.Execute; - if not results.AllPassed then - System.ExitCode := EXIT_ERRORS; - - {$IFNDEF CI} - //We don't want this happening when running under CI. - if TDUnitX.Options.ExitBehavior = TDUnitXExitBehavior.Pause then - begin - System.Write('Done.. press key to quit.'); - System.Readln; - end; - {$ENDIF} - - DUnitX.Loggers.TeamCity.ReportToTeamCity; - except - on E: Exception do - System.Writeln(E.ClassName, ': ', E.Message); - end; -end. diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj deleted file mode 100644 index 1ebd5a77344..00000000000 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj +++ /dev/null @@ -1,1034 +0,0 @@ - - - {7A8E88E4-B407-442B-9A53-DE6F43F1C0F1} - 18.8 - VCL - KeyboardPackageVersionsTestSuite.dpr - True - Debug - Win32 - 1 - Console - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - .\obj\$(Platform)\$(Config) - .\bin\$(Platform)\$(Config) - false - false - false - false - false - RESTComponents;FireDAC;FireDACSqliteDriver;soaprtl;FireDACIBDriver;soapmidas;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;$(DCC_UsePackage) - System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) - true - $(BDS)\bin\delphi_PROJECTICON.ico - $(BDS)\bin\delphi_PROJECTICNS.icns - $(DUnitX);$(DCC_UnitSearchPath) - KeyboardPackageVersionsTestSuite - $(CI);$(DCC_Define) - 3081 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - - - DBXSqliteDriver;bindcompdbx;IndyIPCommon;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;mbColorLibD10;dsnapcon;FireDACADSDriver;scFontCombo;DCPdelphi2009;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;vcltouch;JvCore;vcldb;bindcompfmx;svn;FireDACPgDriver;inetdb;DbxCommonDriver;fmx;fmxdae;xmlrtl;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;IndyIPClient;bindcompvcl;EmbeddedWebBrowser_XE;VCLRESTComponents;dbxcds;VclSmp;JvDocking;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;keyman_components;FireDACCommonODBC;fmxase;$(DCC_UsePackage) - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - Debug - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - 1033 - - - DBXSqliteDriver;bindcompdbx;IndyIPCommon;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;vcltouch;vcldb;bindcompfmx;FireDACPgDriver;inetdb;DbxCommonDriver;fmx;fmxdae;xmlrtl;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;IndyIPClient;bindcompvcl;VCLRESTComponents;dbxcds;VclSmp;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonODBC;fmxase;$(DCC_UsePackage) - - - DEBUG;$(DCC_Define) - true - false - true - true - true - - - false - -l:Information - 1033 - (None) - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - - MainSource - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - Console - - - - KeyboardPackageVersionsTestSuite.dpr - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - - - true - - - - - true - - - - - true - - - - - true - - - - - KeyboardPackageVersionsTestSuite.exe - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - True - False - - - 12 - - - - - diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.res b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.res deleted file mode 100644 index 6876088a6648ed90b09c480197508e4404c57dbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57140 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 I2>8kW0h0=QvH$=8 diff --git a/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas b/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas deleted file mode 100644 index 8fd0c993f79..00000000000 --- a/developer/src/test/auto/keyboard-package-versions/Keyman.Test.System.CompilePackageVersioningTest.pas +++ /dev/null @@ -1,155 +0,0 @@ -unit Keyman.Test.System.CompilePackageVersioningTest; - -interface - -uses - DUnitX.TestFramework, - Keyman.Developer.System.Project.ProjectLog; - -type - - [TestFixture] - TCompilePackageVersioningTest = class(TObject) - private - FRoot: string; - procedure PackageMessage(Sender: TObject; msg: string; - State: TProjectLogState); - public - [Setup] - procedure Setup; - [TearDown] - procedure TearDown; - // Sample Methods - // Simple single Test - // Test with TestCase Attribute to supply parameters. - [Test] - [TestCase('test-single-version-1-package', 'test1.kps,False')] - [TestCase('test-single-version-2-package', 'test2.kps,False')] - [TestCase('test-version-2-1-mismatch', 'test2-1.kps,True')] - [TestCase('test-version-1-2-mismatch', 'test1-2.kps,True')] - [TestCase('test-keyboard-1-package-2', 'test-keyboard-1-vs-package-2.kps,False')] - [TestCase('test-package-1-keyboard-2', 'test-package-1-vs-keyboard-2.kps,False')] - procedure TestPackageCompile(Path: string; ExpectError: Boolean); - end; - -implementation - -uses - System.SysUtils, - Winapi.ActiveX, - - compile, - CompilePackage, - Keyman.Developer.System.Project.kmnProjectFileAction, - Keyman.Developer.System.Project.ProjectFile, - kpsfile; - -type - TProjectConsole = class(TProject) - private - FSilent: Boolean; - FFullySilent: Boolean; - public - procedure Log(AState: TProjectLogState; Filename: string; Msg: string; MsgCode, line: Integer); override; // I4706 - function Save: Boolean; override; // I4709 - - property Silent: Boolean read FSilent write FSilent; - property FullySilent: Boolean read FFullySilent write FFullySilent; - end; - -procedure TCompilePackageVersioningTest.Setup; -var - p: TProjectConsole; - i: Integer; -begin - Assert.IgnoreCaseDefault := False; - - FRoot := ExtractFileDir(ExtractFileDir(ExtractFileDir(ExtractFileDir(ParamStr(0))))); - - p := TProjectConsole.Create(ptUnknown, FRoot+'\test-1.0\test-1.0.kpj', False); - try - for i := 0 to p.Files.Count - 1 do - if p.Files[i] is TkmnProjectFileAction then - Assert.IsTrue((p.Files[i] as TkmnProjectFileAction).CompileKeyboard, 'Could not compile keyboard'); - finally - p.Free; - end; - - p := TProjectConsole.Create(ptUnknown, FRoot+'\test-2.0\test-2.0.kpj', False); - try - for i := 0 to p.Files.Count - 1 do - if p.Files[i] is TkmnProjectFileAction then - Assert.IsTrue((p.Files[i] as TkmnProjectFileAction).CompileKeyboard, 'Could not compile keyboard'); - finally - p.Free; - end; -end; - -procedure TCompilePackageVersioningTest.TearDown; -begin -end; - -procedure TCompilePackageVersioningTest.PackageMessage(Sender: TObject; msg: string; State: TProjectLogState); -const - Map: array[TProjectLogState] of TLogLevel = ( - {plsInfo} TLogLevel.Information, - {plsHint} TLogLevel.Information, - {plsWarning} TLogLevel.Warning, - {plsError} TLogLevel.Error, - {plsFatal} TLogLevel.Error, - {plsSuccess} TLogLevel.Information, - {plsFailure} TLogLevel.Error - ); -begin - Log(Map[State], msg); -end; - -procedure TCompilePackageVersioningTest.TestPackageCompile(Path: string; ExpectError: Boolean); -var - pack: TKPSFile; - res: Boolean; -begin - pack := TKPSFile.Create; - try - pack.FileName := FRoot+'\'+Path; - pack.LoadXML; - res := DoCompilePackage(pack, PackageMessage, False, False, FRoot+'\'+ChangeFileExt(Path,'.kmp')); - Assert.AreEqual(not ExpectError, res); - finally - pack.Free; - end; -end; - -procedure TProjectConsole.Log(AState: TProjectLogState; Filename, Msg: string; MsgCode, line: Integer); // I4706 -begin -{$IFDEF DEBUG_PROJECT} - case AState of - plsInfo, - plsSuccess, - plsFailure: - if not FSilent then - writeln(ExtractFileName(Filename)+': '+Msg); - plsWarning: - if not FFullySilent then - writeln(ExtractFileName(Filename)+': Warning: '+Msg); - plsError: - if not FFullySilent then - writeln(ExtractFileName(Filename)+': Error: '+Msg); - plsFatal: - writeln(ExtractFileName(Filename)+': Fatal error: '+Msg); - end; -{$ENDIF} -end; - -function TProjectConsole.Save: Boolean; // I4709 -begin - // We don't modify the project file in the console - Result := True; -end; - -initialization - CoInitializeEx(nil, COINIT_APARTMENTTHREADED); - TDUnitX.RegisterTestFixture(TCompilePackageVersioningTest); -finalization - CoUninitialize; -end. diff --git a/developer/src/test/auto/keyboard-package-versions/Makefile b/developer/src/test/auto/keyboard-package-versions/Makefile deleted file mode 100644 index c07f744018f..00000000000 --- a/developer/src/test/auto/keyboard-package-versions/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# -# Test that package compiler manages keyboard and package versions correctly -# -# NOTE: The .dproj needs $(CI) added to the Delphi Compiler/Conditional defines (All -# configurations - all platforms) section in order for the CI flag to be passed in. -# (It's best to make this change in Delphi IDE). -# - -!include ..\..\..\Defines.mak - -test: build - $(WIN32_TARGET_PATH)\keyboardpackageversionstestsuite.exe -b -exit:continue - -build: - $(DELPHI_MSBUILD) "/p:CI=CI" keyboardpackageversionstestsuite.dproj - -clean: def-clean - -del *.kmp - -del test-1.0\*.kmx - -del test-2.0\*.kmx - -del test-1.0\*.js - -del test-2.0\*.js - -!include ..\..\..\Target.mak From fd9b1313072bdeb31f79e962cb820b7e28dd653b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 10:27:59 +0700 Subject: [PATCH 073/207] chore(developer): remove Package Installer UI from TIKE --- .../common/delphi/compiler/CompilePackage.pas | 540 ------------------ .../compiler/CompilePackageInstaller.pas | 446 --------------- .../src/tike/child/UfrmPackageEditor.dfm | 96 +--- .../src/tike/child/UfrmPackageEditor.pas | 39 -- ...er.System.Project.kpsProjectFileAction.pas | 57 -- ...man.Developer.UI.Project.ProjectFileUI.pas | 2 +- ...eyman.Developer.UI.Project.UfrmProject.pas | 15 - ....Developer.UI.Project.kpsProjectFileUI.pas | 19 - developer/src/tike/tike.dpr | 2 - developer/src/tike/tike.dproj | 2 - .../src/tike/xml/project/distribution.xsl | 4 - developer/src/tike/xml/project/packages.xsl | 24 +- 12 files changed, 15 insertions(+), 1231 deletions(-) delete mode 100644 developer/src/common/delphi/compiler/CompilePackage.pas delete mode 100644 developer/src/common/delphi/compiler/CompilePackageInstaller.pas diff --git a/developer/src/common/delphi/compiler/CompilePackage.pas b/developer/src/common/delphi/compiler/CompilePackage.pas deleted file mode 100644 index c7c5e071235..00000000000 --- a/developer/src/common/delphi/compiler/CompilePackage.pas +++ /dev/null @@ -1,540 +0,0 @@ -(* - Name: CompilePackage - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 20 Jun 2006 - - Modified Date: 11 May 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 20 Jun 2006 - mcdurdin - Initial version - 04 Dec 2006 - mcdurdin - Add message for missing files when compiling package - 04 Jun 2007 - mcdurdin - Remove unused KeymanPath - 20 Jun 2007 - mcdurdin - Remove unused ActivationManager reference - 19 Nov 2007 - mcdurdin - I1157 - const string parameters - 04 May 2015 - mcdurdin - I4688 - V9.0 - Add build path to project settings - 04 May 2015 - mcdurdin - I4690 - V9.0 - Pull keyboard version into package version when adding a keyboard - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness -*) -unit CompilePackage; - -interface - -uses - kpsfile, - kmpinffile, - PackageInfo, - Keyman.Developer.System.Project.ProjectLog; - -function DoCompilePackage(pack: TKPSFile; AMessageEvent: TCompilePackageMessageEvent; ASilent, ACheckFilenameConventions: Boolean; const AOutputFileName: string): Boolean; // I4688 - -implementation - -uses - Winapi.Windows, - System.Classes, - System.Generics.Collections, - System.SysUtils, - System.IniFiles, - System.Zip, - - VisualKeyboard, - utilfiletypes, - ErrorControlledRegistry, - RegistryKeys, - kmxfile, - KeymanDeveloperOptions, - KeymanVersion, - - Keyman.System.KeyboardUtils, - Keyman.System.LexicalModelUtils, - Keyman.System.CanonicalLanguageCodeUtils, - Keyman.System.PackageInfoRefreshKeyboards, - Keyman.System.PackageInfoRefreshLexicalModels, - - RedistFiles, - TempFileManager, - VersionINfo; - -type - TCompilePackage = class - private - FSilent: Boolean; - FOnMessage: TCompilePackageMessageEvent; - FTempPath: string; - //FRuntimeSourcePath: string; - pack: TKPSFile; - - FOutputFileName: string; - FTempFiles: TTempFiles; - FCheckFilenameConventions: Boolean; - - procedure FatalMessage(const msg: string); - procedure WriteMessage(AState: TProjectLogState; const msg: string); // I4706 - - function BuildKMP: Boolean; - - constructor Create(APack: TKPSFile; AMessageEvent: TCompilePackageMessageEvent; ASilent, ACheckFilenameConventions: Boolean; const AOutputFileName: string); // I4688 - destructor Destroy; override; - function Compile: Boolean; - procedure CheckForDangerousFiles; - procedure CheckKeyboardVersions; - procedure CheckKeyboardLanguages; - procedure CheckFilenameConventions; - function CheckLexicalModels: Boolean; - procedure CheckForDuplicatedLanguages(const resourceType, id: string; languages: TPackageKeyboardLanguageList); - end; - -function DoCompilePackage(pack: TKPSFile; AMessageEvent: TCompilePackageMessageEvent; ASilent, ACheckFilenameConventions: Boolean; const AOutputFileName: string): Boolean; // I4688 -begin - with TCompilePackage.Create(pack, AMessageEvent, ASilent, ACheckFilenameConventions, AOutputFileName) do // I4688 - try - Result := Compile; - finally - Free; - end; -end; - - -constructor TCompilePackage.Create(APack: TKPSFile; AMessageEvent: TCompilePackageMessageEvent; ASilent, ACheckFilenameConventions: Boolean; const AOutputFileName: string); // I4688 -begin - inherited Create; - pack := APack; - FOnMessage := AMessageEvent; - FSilent := ASilent; - FOutputFileName := AOutputFileName; - FTempFiles := TTempFiles.Create; - FCheckFilenameConventions := ACheckFilenameConventions; -end; - -destructor TCompilePackage.Destroy; -begin - FreeAndNil(FTempFiles); - inherited Destroy; -end; - -procedure TCompilePackage.CheckFilenameConventions; -var - i: Integer; -begin - if not FCheckFilenameConventions then - Exit; - - if pack.LexicalModels.Count > 0 then - begin - if not TLexicalModelUtils.DoesPackageFilenameFollowLexicalModelConventions(pack.FileName) then - WriteMessage(plsWarning, Format(TKeyboardUtils.SPackageNameDoesNotFollowLexicalModelConventions_Message, [ExtractFileName(pack.FileName)])); - end - else - begin - if not TKeyboardUtils.DoesKeyboardFilenameFollowConventions(pack.FileName) then - WriteMessage(plsWarning, Format(TKeyboardUtils.SKeyboardNameDoesNotFollowConventions_Message, [ExtractFileName(pack.FileName)])); - end; - - for i := 0 to pack.Files.Count - 1 do - begin - if not TKeyboardUtils.DoesFilenameFollowConventions(pack.Files[i].FileName) then - WriteMessage(plsWarning, Format(TKeyboardUtils.SFilenameDoesNotFollowConventions_Message, [ExtractFileName(pack.Files[i].FileName)])); - end; -end; - -function TCompilePackage.CheckLexicalModels: Boolean; -var - model: TPackageLexicalModel; -begin - if pack.LexicalModels.Count > 0 then - begin - if pack.Keyboards.Count > 0 then - begin - FatalMessage('The package contains both lexical models and keyboards, which is not permitted.'); - Exit(False); - end; - end; - - for model in pack.LexicalModels do - begin - CheckForDuplicatedLanguages('model', model.id, model.Languages); - end; - - - Exit(True); -end; - -function TCompilePackage.Compile: Boolean; -var - buf: array[0..260] of Char; - n: Integer; - b: Boolean; - f: TSearchRec; -begin - Result := False; - - if pack.FileName = '' then - begin - FatalMessage('You need to save the package before building.'); - Exit; - end; - - WriteMessage(plsInfo, 'Compiling package ' + ExtractFileName(pack.FileName) + '...'); - - if Trim(pack.Info.Desc[PackageInfo_Name]) = '' then - begin - FatalMessage('You need to fill in the package name before building.'); - Exit; - end; - - CheckFilenameConventions; - CheckForDangerousFiles; - CheckKeyboardVersions; - CheckKeyboardLanguages; - if not CheckLexicalModels then Exit; - - GetTempPath(260, buf); - FTempPath := buf; - - GetTempFileName(PChar(FTempPath), 'kmn', 0, buf); - FTempPath := buf + '.dir'; - b := CreateDir(FTempPath); - if not b then - begin - FatalMessage('Unable to create temp folder '+FTempPath); - if FileExists(buf) then DeleteFile(buf); - Exit; - end; - - try - Result := BuildKMP; - finally - n := FindFirst(FTempPath + '\*.*', 0, f); - if n = 0 then - begin - while n = 0 do - begin - DeleteFile(FTempPath + '\' + f.Name); - n := FindNext(f); - end; - FindClose(f); - end; - RemoveDirectory(PChar(FTempPath)); - if FileExists(buf) then DeleteFile(buf); - end; -end; - - -function TCompilePackage.BuildKMP: Boolean; -var - kmpinf: TKMPInfFile; - psf: TPackageContentFile; - FPackageVersion: string; - i: Integer; - f: TTempFile; -begin - Result := False; - - kmpinf := TKMPInfFile.Create; - try - { Create KMP.INF and KMP.JSON } - - kmpinf.Assign(pack); - - // Add keyboard information to the package 'for free' - // Note: this does not get us very far for mobile keyboards as - // they still require the .js to be added by the developer at this stage. - // But it ensures that all keyboards in the package are listed in the - // {Keyboards} section - - with TPackageInfoRefreshKeyboards.Create(kmpinf) do - try - OnError := Self.FOnMessage; - if not Execute then - begin - WriteMessage(plsError, 'The package build was not successful.'); - Exit; - end; - finally - Free; - end; - - with TPackageInfoRefreshLexicalModels.Create(kmpinf) do - try - OnError := Self.FOnMessage; - if not Execute then - begin - WriteMessage(plsError, 'The package build was not successful.'); - Exit; - end; - finally - Free; - end; - - // - // Update the package version to the current compiled - // keyboard version. - // - - if pack.KPSOptions.FollowKeyboardVersion then - begin - if kmpinf.Keyboards.Count = 0 then - begin - FatalMessage('The option "Follow Keyboard Version" is set but there are no keyboards in the package.'); - Exit; - end; - - FPackageVersion := kmpinf.Keyboards[0].Version; - for i := 1 to kmpinf.Keyboards.Count - 1 do - if kmpinf.Keyboards[i].Version <> FPackageVersion then - begin - FatalMessage( - 'The option "Follow Keyboard Version" is set but the package contains more than one keyboard, '+ - 'and the keyboards have mismatching versions.'); - Exit; - end; - - kmpinf.Info.Desc[PackageInfo_Version] := FPackageVersion; - end; - - kmpinf.RemoveFilePaths; - - // - // Find the minimum package version based on keyboard version - // See also MergeKeyboardInfo.pas - // - - if kmpinf.LexicalModels.Count = 0 then - begin - kmpinf.Options.FileVersion := SKeymanVersion70; - - for i := 0 to kmpinf.Keyboards.Count - 1 do - begin - if CompareVersions(kmpinf.Options.FileVersion, kmpinf.Keyboards[i].MinKeymanVersion) > 0 then - begin - // Keyman for Windows 14 and earlier only accepted version 7.0 for keyboard - // packages, so we must not write any other version in order to allow - // earlier versions of Keyman to load the package. - if CompareVersions(kmpinf.Keyboards[i].MinKeymanVersion, SKeymanVersion150) <= 0 then - begin - kmpinf.Options.FileVersion := kmpinf.Keyboards[i].MinKeymanVersion; - end; - end; - end; - - psf := TPackageContentFile.Create(kmpinf); - psf.FileName := 'kmp.inf'; - psf.Description := 'Package information'; - psf.CopyLocation := pfclPackage; - - kmpinf.Files.Add(psf); - end - else - begin - // TODO: min model version is always 12.0 currently - // but we may need to support this in future - kmpinf.Options.FileVersion := SKeymanVersion120; - end; - - // - // Prepare and save - // - - psf := TPackageContentFile.Create(kmpinf); - psf.FileName := 'kmp.json'; - psf.Description := 'Package information (JSON)'; - psf.CopyLocation := pfclPackage; - - kmpinf.Files.Add(psf); - - kmpinf.FileName := FTempPath + '\kmp.inf'; - kmpinf.SaveIni; - - kmpinf.FileName := FTempPath + '\kmp.json'; - kmpinf.SaveJSON; - finally - kmpinf.Free; - end; - - try - ForceDirectories(ExtractFileDir(FOutputFilename)); - if FileExists(FOutputFilename) then - DeleteFile(FOutputFilename); - - { Create output file } - - try - - if FileExists(FOutputFilename) then DeleteFile(FOutputFilename); - - with TZipFile.Create do - try - Open(FOutputFilename, TZipMode.zmWrite); - Add(FTempPath + '\kmp.inf'); - Add(FTempPath + '\kmp.json'); - for i := 0 to pack.Files.Count - 1 do - begin - if not FileExists(pack.Files[i].FileName) then - WriteMessage(plsWarning, 'File '+pack.Files[i].FileName+' does not exist.') - else - begin - // When compiling the package, save the kvk keyboard into binary for delivery - if pack.Files[i].FileType = ftVisualKeyboard then - begin - f := TTempFileManager.Get('.kvk'); - FTempFiles.Add(f); - with TVisualKeyboard.Create do - try - try - LoadFromFile(pack.Files[i].FileName); - except - on E:EVisualKeyboardLoader do - begin - WriteMessage(plsError, pack.Files[i].FileName+' is invalid: '+E.Message); - Continue; - end; - end; - SaveToFile(f.Name, kvksfBinary); - finally - Free; - end; - Add(f.Name, ExtractFileName(pack.Files[i].FileName)); - end - else - Add(pack.Files[i].FileName); - end; - end; - if FileCount < pack.Files.Count + 2 then - WriteMessage(plsError, 'The build was not successful. Some files were skipped.') - else - begin - Result := True; - end; - Close; - finally - Free; - end; - except - on E:EZipException do - begin - WriteMessage(plsError, E.Message); - end; - end; - finally - if not Result then - if FileExists(FOutputFilename) then DeleteFile(FOutputFilename); - end; -end; - -procedure TCompilePackage.FatalMessage(const msg: string); -begin - FOnMessage(Self, msg, plsFatal); // I4706 -end; - -procedure TCompilePackage.WriteMessage(AState: TProjectLogState; const msg: string); // I4706 -begin - FOnMessage(Self, msg, AState); // I4706 -end; - -const - SKKeymanRedistFileShouldNotBeInPackage = 'The Keyman system file ''%0:s'' should not be compiled into the package. Use a redistributable installer instead.'; - SKDocFilesDangerous = 'Microsoft Word .doc or .docx files (''%0:s'') are not portable. You should instead use HTML or PDF format.'; - -procedure TCompilePackage.CheckForDangerousFiles; -var - i, j: Integer; - s: string; -begin - for i := 0 to pack.Files.Count - 1 do - begin - s := LowerCase(ExtractFileName(pack.Files[i].FileName)); - for j := Low(CRedistFiles) to High(CRedistFiles) do - if s = CRedistFiles[j].FileName then - WriteMessage(plsWarning, Format(SKKeymanRedistFileShouldNotBeInPackage, [s])); - for j := Low(CRuntimeFiles) to High(CRuntimeFiles) do - if s = CRuntimeFiles[j].FileName then - WriteMessage(plsWarning, Format(SKKeymanRedistFileShouldNotBeInPackage, [s])); - if (ExtractFileExt(s) = '.doc') or (ExtractFileExt(s) = '.docx') then WriteMessage(plsWarning, Format(SKDocFilesDangerous, [s])); - end; -end; - -const - SKKeyboardPackageVersionMismatch = 'The keyboard %0:s has version %1:s, which differs from the package version %2:s.'; - -procedure TCompilePackage.CheckKeyboardVersions; // I4690 -var - n, i: Integer; - ki: TKeyboardInfo; -begin - n := 0; - - // The version information is checked separately if we use - // 'follow keyboard version' - if pack.KPSOptions.FollowKeyboardVersion then - Exit; - - for i := 0 to pack.Files.Count - 1 do - if pack.Files[i].FileType = ftKeymanFile then Inc(n); - - // We only test for keyboard <> package version if 1 keyboard in package - // For multi-keyboard packages, it isn't sensible to do the same - if n <> 1 then - Exit; - - for i := 0 to pack.Files.Count - 1 do - begin - if pack.Files[i].FileType <> ftKeymanFile then Continue; - if not FileExists(pack.Files[i].FileName) then Continue; - - GetKeyboardInfo(pack.Files[i].FileName, True, ki); - if ki.KeyboardVersion <> pack.Info.Desc[PackageInfo_Version] then - WriteMessage(plsWarning, Format(SKKeyboardPackageVersionMismatch, - [ExtractFileName(pack.Files[i].FileName), ki.KeyboardVersion, pack.Info.Desc[PackageInfo_Version]])); - ki.MemoryDump.Free; - end; -end; - -const - SKKeyboardPackageLanguageNonCanonical = 'The keyboard %0:s has a non-canonical language tag "%1:s" (%2:s), should be "%3:s".'; - SKKeyboardShouldHaveAtLeastOneLanguage = 'The keyboard %0:s has no language tags. It should have at least one language tag.'; - SKPackageShouldNotRepeatLanguages = 'The %0:s %1:s has a repeated language "%2:s".'; - -procedure TCompilePackage.CheckKeyboardLanguages; -var - k: TPackageKeyboard; -begin - for k in pack.Keyboards do - begin - if k.Languages.Count = 0 then - WriteMessage(plsWarning, Format(SKKeyboardShouldHaveAtLeastOneLanguage, [k.ID])); - CheckForDuplicatedLanguages('keyboard', k.ID, k.Languages); - end; -end; - -procedure TCompilePackage.CheckForDuplicatedLanguages(const resourceType, id: string; languages: TPackageKeyboardLanguageList); -var - tags: TDictionary; - lang: TPackageKeyboardLanguage; -begin - tags := TDictionary.Create; - try - for lang in languages do - begin - if tags.ContainsKey(lang.ID.ToLower) then - begin - WriteMessage(plsWarning, Format(SKPackageShouldNotRepeatLanguages, [resourceType, id, lang.ID])); - end - else - begin - tags.Add(lang.ID.ToLower, 0); - end; - end; - finally - tags.Free; - end; -end; - -end. - diff --git a/developer/src/common/delphi/compiler/CompilePackageInstaller.pas b/developer/src/common/delphi/compiler/CompilePackageInstaller.pas deleted file mode 100644 index aae46e6732e..00000000000 --- a/developer/src/common/delphi/compiler/CompilePackageInstaller.pas +++ /dev/null @@ -1,446 +0,0 @@ -(* - Name: CompilePackageInstaller - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 4 Jun 2007 - - Modified Date: 6 Jun 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 04 Jun 2007 - mcdurdin - Initial version - 05 Jun 2007 - mcdurdin - I817 - Fix Unicode .inf file (not available on 9x) - 19 Jun 2007 - mcdurdin - I817 - Use Unicode .inf file again - 20 Jun 2007 - mcdurdin - Support compiling package installer from a .kmp file - 23 Aug 2007 - mcdurdin - Support building an exe installer without any embedded kmp files - 19 Nov 2007 - mcdurdin - I1157 - const string parameters - 28 Jul 2008 - mcdurdin - I1558 - Embed graphic file into setup.exe - 28 Jul 2008 - mcdurdin - I1559 - Support custom setup.exe strings - 05 Aug 2008 - mcdurdin - Don't crash if FPack is not assigned - 30 Dec 2010 - mcdurdin - I2562 - EULA as part of setup bootstrapper - 04 Nov 2011 - mcdurdin - I3126 - Add flag to allow install with full msi UI - 04 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support - 08 Jun 2012 - mcdurdin - I3337 - V9.0 - Review of input/output for Unicode - 06 Feb 2012 - mcdurdin - I2971 - TIKE maintains an open handle to .msi when compiling package installer - 04 Nov 2012 - mcdurdin - I3543 - V9.0 - Merge of I2971 - TIKE maintains an open handle to .msi when compiling package installer - 23 Feb 2015 - mcdurdin - I4598 - V9.0 - Keyman installer does not show EULA when bundled with a keyboard - 04 May 2015 - mcdurdin - I4688 - V9.0 - Add build path to project settings - 11 May 2015 - mcdurdin - I4706 - V9.0 - Update compile logging for silent and warning-as-error cleanness - 06 Jun 2015 - mcdurdin - I4741 - Package installer compiler references wrong path for compiled package file -*) -unit CompilePackageInstaller; // I3306 - -interface - -uses Windows, kpsfile, kmpinffile, PackageInfo, CompilePackage, - Keyman.Developer.System.Project.Projectlog, - jwaMsi, jwaMsiQuery, SysUtils; - -function DoCompilePackageInstaller(pack: TKPSFile; FMessageEvent: TCompilePackageMessageEvent; FSilent: Boolean; - AInstallerMSI, AOutputFilename, ARedistSetupPath: string; - AUpdateInstaller: Boolean; ABuildPackage: Boolean; ALicense, ATitleImage, AAppName: string; - AStartDisabled, AStartWithConfiguration: Boolean): Boolean; // I4598 // I4688 -function DoCompileMSIInstaller(FMessageEvent: TCompilePackageMessageEvent; FSilent: Boolean; - AInstallerMSI, AOutputFilename, ARedistSetupPath, ALicense, ATitleImage, AAppName: string; - AStartDisabled, AStartWithConfiguration: Boolean): Boolean; // I2562 - -implementation - -uses - System.Classes, - System.Zip, - RedistFiles, - utildir, - utilsystem; - -type - ECompilePackageInstaller = class(Exception); - - TCompilePackageInstaller = class - private - FOnMessage: TCompilePackageMessageEvent; - FPack: TKPSFile; - FSilent: Boolean; - FVersion: WideString; - FTempPath: WideString; - FProductName: WideString; - FRedistSetupPath: string; - FInstallerMSI: WideString; - FUpdateInstaller: Boolean; - FBuildPackage: Boolean; - FOutputFilename: string; - FLicense: string; // I2562 - FTitleImage: string; - FAppName: string; - FStartDisabled: Boolean; - FStartWithConfiguration: Boolean; - procedure FatalMessage(msg: string); - procedure WriteMessage(msg: string); - function GetMSIOnlineProductID: Boolean; - public - constructor Create(APack: TKPSFile; ASilent: Boolean; AInstallerMSI: string; AUpdateInstaller, ABuildPackage: Boolean; AOutputFilename, ARedistSetupPath, ALicense, ATitleImage, AAppName: string; AStartDisabled, AStartWithConfiguration: Boolean); // I2562 - procedure Run; - property OnMessage: TCompilePackageMessageEvent read FOnMessage write FOnMessage; - end; - -function DoCompilePackageInstaller(pack: TKPSFile; FMessageEvent: TCompilePackageMessageEvent; FSilent: Boolean; - AInstallerMSI, AOutputFilename, ARedistSetupPath: string; AUpdateInstaller: Boolean; ABuildPackage: Boolean; - ALicense, ATitleImage, AAppName: string; AStartDisabled, AStartWithConfiguration: Boolean): Boolean; // I4598 // I4688 -begin - with TCompilePackageInstaller.Create(pack, FSilent, AInstallerMSI, AUpdateInstaller, ABuildPackage, - AOutputFilename, ARedistSetupPath, ALicense, ATitleImage, AAppName, - AStartDisabled, AStartWithConfiguration) do // I2562 // I4598 // I4688 - try - OnMessage := FMessageEvent; - Run; - Result := True; - finally - Free; - end; -end; - -function DoCompileMSIInstaller(FMessageEvent: TCompilePackageMessageEvent; FSilent: Boolean; AInstallerMSI: string; - AOutputFilename, ARedistSetupPath, ALicense, ATitleImage, AAppName: string; - AStartDisabled, AStartWithConfiguration: Boolean): Boolean; // I2562 -begin - with TCompilePackageInstaller.Create(nil, FSilent, AInstallerMSI, False, False, AOutputFileName, ARedistSetupPath, - ALicense, ATitleImage, AAppName, AStartDisabled, AStartWithConfiguration) do // I2562 - try - OnMessage := FMessageEvent; - Run; - Result := True; - finally - Free; - end; -end; - -{ TCompilePackageInstaller } - -constructor TCompilePackageInstaller.Create(APack: TKPSFile; ASilent: Boolean; AInstallerMSI: string; AUpdateInstaller, ABuildPackage: Boolean; AOutputFileName, ARedistSetupPath, ALicense, ATitleImage, AAppName: string; AStartDisabled, AStartWithConfiguration: Boolean); // I2562 -begin - inherited Create; - FPack := APack; - FSilent := ASilent; - FInstallerMSI := AInstallerMSI; - FUpdateInstaller := AUpdateInstaller; - FBuildPackage := ABuildPackage; - FOutputFilename := AOutputFilename; - FRedistSetupPath := ARedistSetupPath; - FLicense := ALicense; // I2562 - FTitleImage := ATitleImage; - FAppName := AAppName; - FStartDisabled := AStartDisabled; - FStartWithConfiguration := AStartWithConfiguration; - - if (FLicense <> '') and not FileExists(FLicense) then - raise ECompilePackageInstaller.CreateFmt('License file %s could not be found.', [FLicense]); - - if (FTitleImage <> '') and not FileExists(FTitleImage) then - raise ECompilePackageInstaller.CreateFmt('Title iamge file %s could not be found.', [FTitleImage]); - - if not Assigned(FPack) then - begin - if (FInstallerMSI = '') or (FUpdateInstaller) or (FBuildPackage) or (FOutputFilename = '') then - raise ECompilePackageInstaller.Create('Invalid arguments - if Pack is nil then FUpdateInstaller, FBuildPackage must be false and FInstallerMSI and FOutputFilename must not be empty.'); - end - else if FOutputFilename = '' then // I4688 - raise ECompilePackageInstaller.Create('Invalid arguments - if Pack is not nil then FOutputFilename must not be empty.'); -end; - -procedure TCompilePackageInstaller.FatalMessage(msg: string); -begin - FOnMessage(Self, msg, plsFatal); // I4706 -end; - -function TCompilePackageInstaller.GetMSIOnlineProductID: Boolean; -var - hDatabase: MSIHANDLE; - - procedure CheckResult(v: UINT); - begin - if v <> ERROR_SUCCESS then RaiseLastOSError; - end; - - function GetMSIProperty(PropertyName: WideString): WideString; - const - SQLPropertyQuery: WideString = 'SELECT Value FROM Property WHERE Property = ''%0:s'''; - var - hView, hRecord: MSIHANDLE; - buf: array[0..260] of WideChar; - sz: Cardinal; - FResult: Cardinal; - begin - CheckResult(MsiDatabaseOpenViewW(hDatabase, PWideChar(WideFormat(SQLPropertyQuery, [PropertyName])), hView)); - - try - CheckResult(MsiViewExecute(hView, 0)); - try - hRecord := 0; - FResult := MsiViewFetch(hView, hRecord); // I2971 // I3543 - try - case FResult of - ERROR_NO_MORE_ITEMS: - begin - Result := ''; - end; - ERROR_SUCCESS: - begin - sz := 260; - CheckResult(MsiRecordGetStringW(hRecord, 1, buf, sz)); - Result := Copy(buf,1,sz); - end; - else - RaiseLastOSError; - end; - finally - if hRecord <> 0 then MsiCloseHandle(hRecord); // I2971 // I3543 - end; - finally - MsiViewClose(hView); - end; - finally - MsiCloseHandle(hView); // I2971 // I3543 - end; - end; - -begin - Result := False; - CheckResult(MsiOpenDatabaseW(PWideChar(FInstallerMSI), nil, hDatabase)); - - try - FVersion := GetMSIProperty('ProductVersion'); - if FVersion = '' then - begin - FatalMessage('No ProductVersion found in the MSI file.'); - Exit; - end; - - FProductName := GetMSIProperty('ProductName'); - if FProductName = '' then - begin - FatalMessage('No ProductName found in the MSI file.'); - Exit; - end; - - Result := True; - finally - MsiCloseHandle(hDatabase); - end; -end; - -procedure TCompilePackageInstaller.Run; -var - FDestFileName: WideString; - fs: TFileStream; - s: WideString; - i: Integer; - FMSIOptions: WideString; - FRedistSetupFile, FPackageOutputFileName: string; // I3126 -begin - { Check that the .msi is setup for the package } - - FTempPath := CreateTempPath; - try - try - if Assigned(FPack) then - begin - if (FPack.FileName = '') and FBuildPackage then - begin - FatalMessage('You need to save the package before building.'); - Exit; - end; - - WriteMessage('Compiling package ' + ExtractFileName(FPack.FileName) + '...'); - - if FPack.Info.Desc['Name'] = '' then - begin - FatalMessage('You need to fill in the package name before building.'); - Exit; - end; - - if FInstallerMSI <> '' then - FPack.KPSOptions.MSIFileName := ExpandFileName(FInstallerMSI); - - FInstallerMSI := FPack.KPSOptions.MSIFileName; - FMSIOptions := FPack.KPSOptions.MSIOptions; // I3126 - -// FOutputFileName := FPack.InstallerFileName; - end - else - begin - WriteMessage('Compiling installer '+ExtractFileName(FOutputFileName)+'...'); - FInstallerMSI := ExpandFileName(FInstallerMSI); - FMSIOptions := ''; // I3126 - end; - - if not FileExists(FInstallerMSI) then - begin - FatalMessage('The installer could not be built because the MSI file was missing.'); - Exit; - end; - - if (FInstallerMSI <> '') and FUpdateInstaller and Assigned(FPack) then - FPack.SaveXML; - - if FRedistSetupPath = '' then - begin - FRedistSetupPath := GetRedistSetupPath; - WriteMessage('Redist path not specified, loading default ('+FRedistSetupPath+')'); - end; - - if not FileExists(FRedistSetupPath + 'setup.exe') and - not FileExists(FRedistSetupPath + 'setup-redist.exe') then - begin - FatalMessage('Neither setup.exe nor setup-redist.exe are present in redist ('+FRedistSetupPath+').'); - Exit; - end; - - { Check the product ID for the package } - if not GetMSIOnlineProductID then Exit; - - { TODO: if the package consists of *only* kmp files, then just add those to the installer } - - { Build the package } - if Assigned(FPack) then - begin - FPackageOutputFileName := ExtractFilePath(FOutputFilename) + ChangeFileExt(ExtractFileName(FPack.FileName), '.kmp'); // I4741 - if FBuildPackage then - begin - if not DoCompilePackage(FPack, FOnMessage, True, False, FPackageOutputFileName) then Exit; - end - else if not FileExists(FPackageOutputFileName) then - begin - FatalMessage('The package file '+FPackageOutputFileName+' does not exist'); - Exit; - end; - - { Test if the keyboards in the package need to be encrypted for the installer } - - FDestFileName := FPackageOutputFileName; - end - else - FDestFileName := ''; - - { Build setup.inf } - with TStringList.Create do - try - s := '[Setup]'#13#10 + - 'Version=' + FVersion + #13#10 + - 'MSIFileName='+ExtractFileName(FInstallerMSI) + #13#10 + - 'MSIOptions='+FMSIOptions + #13#10; // I3126 - - if FAppName <> '' then - s := s + 'AppName='+FAppName + #13#10; - - if FLicense <> '' then // I2562 - s := s + 'License='+ExtractFileName(FLicense) + #13#10; - - if FTitleImage <> '' then - s := s + 'TitleImage='+ExtractFileName(FTitleImage) + #13#10; - - - if Assigned(FPack) and (FPack.KPSOptions.GraphicFile <> nil) and - SameText(ExtractFileExt(FPack.KPSOptions.GraphicFile.FileName), '.bmp') then - begin - s := s + 'BitmapFileName='+ExtractFileName(FPack.KPSOptions.GraphicFile.FileName); - end; - - if FStartDisabled then - s := s + 'StartDisabled=True'#13#10; - - if FStartWithConfiguration then - s := s + 'StartWithConfiguration=True'#13#10; - - s := s + #13#10 + - '[Packages]'#13#10; - - if Assigned(FPack) then - s := s + ExtractFileName(FDestFileName)+'='+FPack.Info.Desc[PackageInfo_Name]; - - { Iterate through the strings } - - if Assigned(FPack) then - begin - s := s + #13#10#13#10'[Strings]'#13#10; - for i := 0 to FPack.Strings.Count - 1 do - s := s + FPack.Strings[i] + #13#10; - end; - - Text := s; - - SaveToFile(FTempPath + '\setup.inf', TEncoding.UTF8); // We want UTF-8 so Title can be Unicode // I3337 - finally - Free; - end; - - { Build zip file } - - try - with TZipFile.Create do - try - Open(FTempPath + '\setup.zip', TZipMode.zmWrite); - Add(FTempPath + '\setup.inf'); - Add(FInstallerMSI); - if FLicense <> '' then Add(FLicense); // I2562 - if FTitleImage <> '' then Add(FTitleImage); - if Assigned(FPack) and (FPack.KPSOptions.GraphicFile <> nil) and - SameText(ExtractFileExt(FPack.KPSOptions.GraphicFile.FileName), '.bmp') then - Add(FPack.KPSOptions.GraphicFile.FileName); - if FDestFileName <> '' then Add(FDestFileName); - Close; - finally - Free; - end; - except - on E:EZipException do - begin - FatalMessage(E.Message); - Exit; - end; - end; - - { Create the self-extracting archive } - - with TFileStream.Create(FOutputFileName, fmCreate) do - try - // A separate version of setup.exe which doesn't include a digital signature - // is included here, so that it can be bundled with a zip and the result signed. - if FileExists(FRedistSetupPath + 'setup-redist.exe') - then FRedistSetupFile := FRedistSetupPath + 'setup-redist.exe' - else FRedistSetupFile := FRedistSetupPath + 'setup.exe'; - fs := TFileStream.Create(FRedistSetupFile, fmOpenRead or fmShareDenyWrite); - try - CopyFrom(fs, 0); - finally - fs.Free; - end; - - fs := TFileStream.Create(FTempPath + '\setup.zip', fmOpenRead or fmShareDenyWrite); - try - CopyFrom(fs, 0); - finally - fs.Free; - end; - finally - Free; - end; - except - on E:EOSError do - FatalMessage(E.Message); - end; - finally - DeleteTempPath(FTempPath); - end; -end; - -procedure TCompilePackageInstaller.WriteMessage(msg: string); -begin - FOnMessage(Self, msg, plsInfo); // I4706 -end; - -end. diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index 4c6967d4618..07567cfa6fd 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -1134,7 +1134,6 @@ inherited frmPackageEditor: TfrmPackageEditor Anchors = [akLeft, akTop, akRight] TabOrder = 2 OnClick = cbLicenseClick - ExplicitWidth = 482 end end end @@ -1351,7 +1350,7 @@ inherited frmPackageEditor: TfrmPackageEditor BevelOuter = bvNone Color = 15921906 ParentBackground = False - TabOrder = 4 + TabOrder = 3 object lblDebugHostCaption: TLabel Left = 12 Top = 70 @@ -1438,7 +1437,7 @@ inherited frmPackageEditor: TfrmPackageEditor Left = 15 Top = 274 Width = 295 - Height = 124 + Height = 276 BevelOuter = bvNone Color = 15921906 ParentBackground = False @@ -1484,74 +1483,6 @@ inherited frmPackageEditor: TfrmPackageEditor OnClick = cmdUninstallClick end end - object panBuildWindowsInstaller: TPanel - Left = 15 - Top = 418 - Width = 295 - Height = 132 - BevelOuter = bvNone - Color = 15921906 - ParentBackground = False - TabOrder = 3 - object Label9: TLabel - Left = 9 - Top = 6 - Width = 124 - Height = 17 - Caption = 'Windows Installer' - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -14 - Font.Name = 'Tahoma' - Font.Style = [fsBold] - ParentFont = False - end - object lblBootstrapMSI: TLabel - Left = 9 - Top = 69 - Width = 64 - Height = 13 - Caption = 'Keyman MSI:' - FocusControl = editBootstrapMSI - end - object lblInstallerOutputFilename: TLabel - Left = 9 - Top = 37 - Width = 82 - Height = 13 - Caption = 'Target filename:' - FocusControl = editInstallerOutputFilename - end - object editBootstrapMSI: TEdit - Left = 125 - Top = 66 - Width = 156 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 1 - end - object editInstallerOutputFilename: TEdit - Left = 125 - Top = 34 - Width = 156 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 0 - end - object cmdInstallWith: TButton - Left = 125 - Top = 96 - Width = 156 - Height = 25 - Caption = 'Find Keyman MSI...' - TabOrder = 2 - OnClick = cmdInstallWithClick - end - end object panOpenInExplorer: TPanel Left = 15 Top = 187 @@ -1641,22 +1572,13 @@ inherited frmPackageEditor: TfrmPackageEditor ReadOnly = True TabOrder = 0 end - object cmdCompileInstaller: TButton - Left = 148 - Top = 32 - Width = 133 - Height = 25 - Caption = 'Compile I&nstaller' - TabOrder = 1 - OnClick = cmdCompileInstallerClick - end object cmdAddToProject: TButton - Left = 287 + Left = 148 Top = 32 Width = 133 Height = 25 Action = modActionsMain.actProjectAddCurrentEditorFile - TabOrder = 2 + TabOrder = 1 end object cmdBuildPackage: TButton Left = 9 @@ -1664,7 +1586,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 133 Height = 25 Caption = 'Compile &Package' - TabOrder = 3 + TabOrder = 2 OnClick = cmdBuildPackageClick end end @@ -1685,12 +1607,4 @@ inherited frmPackageEditor: TfrmPackageEditor Left = 32 Top = 532 end - object dlgOpenProductInstaller: TOpenDialog - DefaultExt = 'msi' - Filter = 'Product Installer Files (*.msi)|*.msi|All Files (*.*)|*.*' - Options = [ofHideReadOnly, ofPathMustExist, ofFileMustExist, ofEnableSizing] - Title = 'Select Product Installer' - Left = 32 - Top = 456 - end end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index d11cee99abe..7d4faabe90c 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -69,7 +69,6 @@ interface TfrmPackageEditor = class(TfrmTikeEditor) // I4689 dlgFiles: TOpenDialog; dlgNewCustomisation: TSaveDialog; - dlgOpenProductInstaller: TOpenDialog; pages: TLeftTabbedPageControl; pageFiles: TTabSheet; pageDetails: TTabSheet; @@ -169,13 +168,6 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 lblCompileTargetHeader: TLabel; cmdInstall: TButton; cmdUninstall: TButton; - panBuildWindowsInstaller: TPanel; - Label9: TLabel; - lblBootstrapMSI: TLabel; - lblInstallerOutputFilename: TLabel; - editBootstrapMSI: TEdit; - editInstallerOutputFilename: TEdit; - cmdInstallWith: TButton; pageLexicalModels: TTabSheet; panLexicalModels: TPanel; lblLexlicalModels: TLabel; @@ -200,7 +192,6 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 panFileActions: TPanel; lblFileActions: TLabel; editOutPath: TEdit; - cmdCompileInstaller: TButton; cmdAddToProject: TButton; cmdBuildPackage: TButton; Label5: TLabel; @@ -256,8 +247,6 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure cmdUninstallClick(Sender: TObject); procedure cmdOpenContainingFolderClick(Sender: TObject); procedure cmdOpenFileClick(Sender: TObject); - procedure cmdCompileInstallerClick(Sender: TObject); - procedure cmdInstallWithClick(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure pagesChanging(Sender: TObject; var AllowChange: Boolean); @@ -395,7 +384,6 @@ implementation CharMapDropTool, CharMapInsertMode, - CompilePackageInstaller, Keyman.Developer.System.Project.kpsProjectFile, Keyman.Developer.System.Project.kpsProjectFileAction, Keyman.Developer.System.ServerAPI, @@ -492,17 +480,6 @@ procedure TfrmPackageEditor.SourceChanged(Sender: TObject); Modified := True; end; -procedure TfrmPackageEditor.cmdCompileInstallerClick(Sender: TObject); -begin - if Untitled or Modified then // I2178 - ShowMessage('You must save the package before you can build it.') - else - begin - frmMessages.Clear; - DoAction(pfaCompileInstaller); - end; -end; - procedure TfrmPackageEditor.cmdCopyDebuggerLinkClick(Sender: TObject); begin try @@ -1267,17 +1244,6 @@ procedure TfrmPackageEditor.cmdInstallClick(Sender: TObject); DoAction(pfaInstall); end; -procedure TfrmPackageEditor.cmdInstallWithClick(Sender: TObject); -begin - dlgOpenProductInstaller.FileName := pack.KPSOptions.MSIFileName; - if dlgOpenProductInstaller.Execute then - begin - pack.KPSOptions.MSIFileName := dlgOpenProductInstaller.FileName; - Modified := True; - UpdateData; - end; -end; - {------------------------------------------------------------------------------- - Display refresh routines - -------------------------------------------------------------------------------} @@ -1350,10 +1316,6 @@ procedure TfrmPackageEditor.UpdateData; editStartMenuPath.Text := pack.StartMenu.Path; editOutPath.Text := (ProjectFile as TkpsProjectFile).TargetFilename; // I4688 - editBootstrapMSI.Text := pack.KPSOptions.MSIFileName; - if pack.KPSOptions.MSIFileName = '' - then editInstallerOutputFilename.Text := '' - else editInstallerOutputFilename.Text := (ProjectFile as TkpsProjectFile).TargetInstallerFileName; // I4688 if lbFiles.Items.Count > 0 then lbFiles.ItemIndex := 0; lbFilesClick(lbFiles); @@ -1983,7 +1945,6 @@ procedure TfrmPackageEditor.RefreshTargetPanels; end; panBuildDesktop.Visible := FHasDesktopTarget; - panBuildWindowsInstaller.Visible := FHasDesktopTarget; panBuildMobile.Visible := FHasMobileTarget; end; diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas index 1f681f28c07..c7a7cafcc5c 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kpsProjectFileAction.pas @@ -15,24 +15,13 @@ interface type TkpsProjectFileAction = class(TkpsProjectFile) - private - procedure SelfMessage(Sender: TObject; msg: string; State: TProjectLogState); // I4706 - public - function CompilePackageInstaller(APack: TKPSFile; FSilent: Boolean): Boolean; function CompilePackage: Boolean; function Clean: Boolean; end; implementation -uses - compile, - CompilePackageInstaller, - Keyman.Developer.System.ValidateKpsFile, - PackageInfo, - utilexecute; - function TkpsProjectFileAction.CompilePackage: Boolean; var w: TKmcWrapper; @@ -46,45 +35,6 @@ function TkpsProjectFileAction.CompilePackage: Boolean; end; end; -function TkpsProjectFileAction.CompilePackageInstaller(APack: TKPSFile; FSilent: Boolean): Boolean; -var - pack: TKPSFile; -begin - HasCompileWarning := False; // I4706 - - if APack = nil then - begin - pack := TKPSFile.Create; - pack.FileName := FileName; - pack.LoadXML; - end - else - pack := APack; - - try - try - Result := DoCompilePackageInstaller(pack, SelfMessage, FSilent, '', TargetInstallerFilename, '', False, True, '', '', '', False, False); - if HasCompileWarning and (WarnAsError or OwnerProject.Options.CompilerWarningsAsErrors) then // I4706 - Result := False; - - if Result - then Log(plsSuccess, '''' + FileName + ''' compiled successfully.', 0, 0) - else Log(plsFailure, '''' + FileName + ''' was not compiled successfully.', 0, 0); - except - on E:Exception do - begin - Log(plsError, E.Message, CERR_ERROR, 0); - Log(plsFailure, '''' + FileName + ''' was not compiled successfully.', 0, 0); - Result := False; - end; - end; - - finally - if APack = nil then - pack.Free; - end; -end; - function TkpsProjectFileAction.Clean: Boolean; begin CleanFile(OutputFileName); @@ -92,13 +42,6 @@ function TkpsProjectFileAction.Clean: Boolean; Result := True; end; -procedure TkpsProjectFileAction.SelfMessage(Sender: TObject; msg: string; State: TProjectLogState); // I4706 -begin - if State = plsWarning then - HasCompileWarning := True; - Log(State, msg, 0, 0); -end; - initialization RegisterProjectFileType('.kps', TkpsProjectFileAction); end. diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectFileUI.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectFileUI.pas index 50321c6bf4c..6026fb2377e 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectFileUI.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectFileUI.pas @@ -34,7 +34,7 @@ interface type TProjectFileAction = (pfaCompile, pfaInstall, pfaUninstall, pfaDebug, - pfaTestKeymanWeb, pfaCompileInstaller, pfaFontHelper, pfaFontDialog, pfaClean); // I4057 + pfaTestKeymanWeb, pfaFontHelper, pfaFontDialog, pfaClean); // I4057 TProjectUI = class(TProject) private diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmProject.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmProject.pas index ad0fc42ec32..992a4491c36 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmProject.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmProject.pas @@ -549,21 +549,6 @@ procedure TfrmProject.WebCommandProject(Command: WideString; Params: TStringList (FGlobalProject.Files[i].UI as TProjectFileUI).DoAction(pfaCompile, False); end; end - else if Command = 'package_compileallinstallers' then // I4734 - begin - ClearMessages; - for i := 0 to FGlobalProject.Files.Count - 1 do - begin - if FGlobalProject.Files[i] is TkpsProjectFile then - (FGlobalProject.Files[i].UI as TProjectFileUI).DoAction(pfaCompileInstaller, False); - end; - end - else if Command = 'package_compileinstaller' then - begin - ClearMessages; - pf := SelectedProjectFile; - if Assigned(pf) then (pf.UI as TProjectFileUI).DoAction(pfaCompileInstaller, False); - end else if Command = 'package_cleanall' then // I4692 begin ClearMessages; diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas index 672a8c92bfa..410a2727252 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.kpsProjectFileUI.pas @@ -35,9 +35,7 @@ TkpsProjectFileUI = class(TOpenableProjectFileUI) function InstallPackage: Boolean; function UninstallPackage: Boolean; function CompilePackage: Boolean; - function CompilePackageInstaller(FSilent: Boolean): Boolean; - function GetPack: TKPSFile; function GetProjectFile: TkpsProjectFileAction; function TestPackageState(FCompiledName: string): Boolean; public @@ -83,22 +81,12 @@ function TkpsProjectFileUI.CompilePackage: Boolean; TestPackageOnline; end; -function TkpsProjectFileUI.CompilePackageInstaller(FSilent: Boolean): Boolean; -begin - Result := False; - if ProjectFile.Modified then - if not modActionsMain.actFileSave.Execute then Exit; - - Result := ProjectFile.CompilePackageInstaller(GetPack, FSilent); -end; - function TkpsProjectFileUI.DoAction(action: TProjectFileAction; FSilent: Boolean): Boolean; begin case action of pfaCompile: Result := CompilePackage; pfaInstall: Result := InstallPackage; pfaUninstall: Result := UninstallPackage; - pfaCompileInstaller: Result := CompilePackageInstaller(FSilent); pfaClean: Result := ProjectFile.Clean; pfaTestKeymanWeb: Result := TestPackageOnline; else @@ -106,13 +94,6 @@ function TkpsProjectFileUI.DoAction(action: TProjectFileAction; FSilent: Boolean end; end; -function TkpsProjectFileUI.GetPack: TKPSFile; -begin - if Assigned(MDIChild) - then with MDIChild as TfrmPackageEditor do Result := GetPack - else Result := nil; -end; - function TkpsProjectFileUI.GetProjectFile: TkpsProjectFileAction; begin Result := FOwner as TkpsProjectFileAction; diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index c3c417a8170..15993827af9 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -16,7 +16,6 @@ uses compile in '..\common\delphi\compiler\compile.pas', KeymanDeveloperOptions in 'main\KeymanDeveloperOptions.pas', UfrmKeyTest in 'debug\UfrmKeyTest.pas' {frmKeyTest}, - CompilePackage in '..\common\delphi\compiler\CompilePackage.pas', KeymanDeveloperUtils in 'main\KeymanDeveloperUtils.pas', UfrmEditor in 'child\UfrmEditor.pas' {frmEditor}, MenuImgList in '..\common\delphi\components\MenuImgList.pas', @@ -138,7 +137,6 @@ uses VisualKeyboardExportPNG in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportPNG.pas', MSXML2_TLB in '..\..\..\common\windows\delphi\tlb\MSXML2_TLB.pas', CharacterInfo in 'main\CharacterInfo.pas', - CompilePackageInstaller in '..\common\delphi\compiler\CompilePackageInstaller.pas', UTikeDebugMode in 'main\UTikeDebugMode.pas', kmxfileconsts in '..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', kmxfileutils in '..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index f1af6898566..1f5ff54b221 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -139,7 +139,6 @@

    frmKeyTest -
    frmEditor
    @@ -319,7 +318,6 @@ - diff --git a/developer/src/tike/xml/project/distribution.xsl b/developer/src/tike/xml/project/distribution.xsl index 6b78ee608f9..998065b8427 100644 --- a/developer/src/tike/xml/project/distribution.xsl +++ b/developer/src/tike/xml/project/distribution.xsl @@ -97,10 +97,6 @@ Build keyman:compilefile?id= - - Build installer - keyman:package_compileinstaller?id= - Clean keyman:cleanfile?id= diff --git a/developer/src/tike/xml/project/packages.xsl b/developer/src/tike/xml/project/packages.xsl index 94d45113ccf..ff5a8e520a6 100644 --- a/developer/src/tike/xml/project/packages.xsl +++ b/developer/src/tike/xml/project/packages.xsl @@ -9,7 +9,7 @@

    - +
    New package... @@ -90,7 +90,7 @@ auto - +

    @@ -106,7 +106,7 @@
    - + @@ -121,12 +121,6 @@ Build keyman:compilefile?id=
    - - - Build installer - keyman:package_compileinstaller?id= - - Clean keyman:cleanfile?id= @@ -154,7 +148,7 @@ - + @@ -196,5 +190,5 @@
    - + \ No newline at end of file From 87d1a4407dfa9846effbdce1d4bba45499237cc3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 25 Sep 2023 10:54:34 +0700 Subject: [PATCH 074/207] fix(web): fixes toolbar refocus timing after a keyboard change --- web/src/app/ui/kmwuitoolbar.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/app/ui/kmwuitoolbar.ts b/web/src/app/ui/kmwuitoolbar.ts index 0be3f2cf0f3..68799cd35cc 100644 --- a/web/src/app/ui/kmwuitoolbar.ts +++ b/web/src/app/ui/kmwuitoolbar.ts @@ -824,10 +824,12 @@ if(!keyman?.ui?.name) { this.selectedLanguage = kbd.LanguageCode; // Return focus to input area and activate the selected keyboard - this.setLastFocus(); //*****this seems out of sequence??? this.addKeyboardToList(lang, kbd); if(updateKeyman) { - keymanweb.setActiveKeyboard(kbd.InternalName, kbd.LanguageCode); + keymanweb.setActiveKeyboard(kbd.InternalName, kbd.LanguageCode).then(() => { + // Restore focus _after_ the keyboard finishes loading. + this.setLastFocus(); + }); } this.listedKeyboards[this.findListedKeyboard(lang)].buttonNode.className = 'kmw_button_selected'; From a093507a4cbbc8b52f1a76c3362b6b0e611953d4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 12:19:26 +0700 Subject: [PATCH 075/207] chore(developer): remove .keyboard_info sources Fixes #9551. Removes various .keyboard_info files from test fixtures and also removes generation of .keyboard_info source files from kmconvert, alongside now- redundant tests (cvt.dpr). --- .../caps_lock_layer_3620.keyboard_info | 7 - .../caps_lock_layer_3620.kpj | 7 - .../full_caps_3620_3621.keyboard_info | 7 - .../full_caps_3620_3621.kpj | 7 - .../obolo_chwerty_6347.keyboard_info | 7 - .../obolo_chwerty_6347/obolo_chwerty_6347.kpj | 7 - .../obolo_chwerty_6351.keyboard_info | 7 - .../obolo_chwerty_6351/obolo_chwerty_6351.kpj | 7 - .../start_of_sentence_3621.keyboard_info | 7 - .../start_of_sentence_3621.kpj | 7 - .../u_xxxx_yyyy_2858.keyboard_info | 7 - .../u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj | 7 - .../web_context_tests/web_context_tests.kpj | 7 - ...veloper.System.KeyboardProjectTemplate.pas | 14 - .../basic-keyboard/keyboard.keyboard_info | 7 - .../tike/compile/JsonExtractKeyboardInfo.pas | 68 -- ....Developer.System.Project.ProjectFiles.pas | 15 +- .../testvariable/testvariable.keyboard_info | 7 - .../source/testvariable/testvariable.kpj | 7 - .../issue3701/test3701/test3701.keyboard_info | 7 - .../web/issue3701/test3701/test3701.kpj | 7 - .../test_917/test_917.keyboard_info | 7 - .../test_917/test_917.kpj | 7 - .../cv.pas | 129 ---- .../cvt.dpr | 31 - .../cvt.dproj | 589 ------------------ .../cvt.res | Bin 96 -> 0 bytes .../context_mismatch_with_if.keyboard_info | 7 - .../context_mismatch_with_if.kpj | 7 - .../caps_always_off.keyboard_info | 7 - .../caps_always_off/caps_always_off.kpj | 7 - .../capslock/capslock.keyboard_info | 7 - .../caps-lock-stores/capslock/capslock.kpj | 7 - .../shift_frees_caps.keyboard_info | 7 - .../shift_frees_caps/shift_frees_caps.kpj | 7 - 35 files changed, 7 insertions(+), 1035 deletions(-) delete mode 100644 common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.keyboard_info delete mode 100644 common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.keyboard_info delete mode 100644 common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.keyboard_info delete mode 100644 common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.keyboard_info delete mode 100644 common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.keyboard_info delete mode 100644 common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info delete mode 100644 developer/src/kmconvert/data/basic-keyboard/keyboard.keyboard_info delete mode 100644 developer/src/tike/compile/JsonExtractKeyboardInfo.pas delete mode 100644 web/src/test/manual/web/issue2924/source/testvariable/testvariable.keyboard_info delete mode 100644 web/src/test/manual/web/issue3701/test3701/test3701.keyboard_info delete mode 100644 web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.keyboard_info delete mode 100644 windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cv.pas delete mode 100644 windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr delete mode 100644 windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj delete mode 100644 windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.res delete mode 100644 windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.keyboard_info delete mode 100644 windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info delete mode 100644 windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info delete mode 100644 windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.keyboard_info diff --git a/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.keyboard_info b/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.keyboard_info deleted file mode 100644 index 7cabc2dfde2..00000000000 --- a/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "Caps Lock Layer 3620 generated from template" -} diff --git a/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.kpj b/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.kpj index 57877e2346b..7868db1fad5 100644 --- a/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.kpj +++ b/common/test/keyboards/caps_lock_layer_3620/caps_lock_layer_3620.kpj @@ -51,13 +51,6 @@ .md - - id_fff8738ee01fad8282ccd871d1b1d7ec - caps_lock_layer_3620.keyboard_info - caps_lock_layer_3620.keyboard_info - - .keyboard_info - id_6beb4ed096b4f0d03913a0cf0dc1c10b caps_lock_layer_3620.kmx diff --git a/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.keyboard_info b/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.keyboard_info deleted file mode 100644 index 3bcee188375..00000000000 --- a/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "full_caps_3620_3621 generated from template" -} diff --git a/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.kpj b/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.kpj index 96cb56925cd..b7aad105733 100644 --- a/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.kpj +++ b/common/test/keyboards/full_caps_3620_3621/full_caps_3620_3621.kpj @@ -51,13 +51,6 @@ .md - - id_da795226173614238dd92754311e5f15 - full_caps_3620_3621.keyboard_info - full_caps_3620_3621.keyboard_info - - .keyboard_info - id_3087832d59de33f9903fc21ba620b576 full_caps_3620_3621.kmx diff --git a/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.keyboard_info b/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.keyboard_info deleted file mode 100644 index 1513b8bb473..00000000000 --- a/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "ann-Latn" - ], - "description": "Obolo Chwerty Keyboard: A keyboard layout for the Obolo language of Nigeria. It covers the need of every dialect in that language" -} diff --git a/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.kpj b/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.kpj index 2205ec839ed..228ef013817 100644 --- a/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.kpj +++ b/common/test/keyboards/obolo_chwerty_6347/obolo_chwerty_6347.kpj @@ -52,13 +52,6 @@ .md - - id_fa9ac62e52c7671ac61602e3583305fd - obolo_chwerty_6347.keyboard_info - obolo_chwerty_6347.keyboard_info - - .keyboard_info - id_8ec79e2ed4a2ffef6c8f0645d5ff022f obolo_chwerty_6347.ico diff --git a/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.keyboard_info b/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.keyboard_info deleted file mode 100644 index 1513b8bb473..00000000000 --- a/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "ann-Latn" - ], - "description": "Obolo Chwerty Keyboard: A keyboard layout for the Obolo language of Nigeria. It covers the need of every dialect in that language" -} diff --git a/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.kpj b/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.kpj index cffa8ad084e..3e3e5defd93 100644 --- a/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.kpj +++ b/common/test/keyboards/obolo_chwerty_6351/obolo_chwerty_6351.kpj @@ -52,13 +52,6 @@ .md - - id_fa9ac62e52c7671ac61602e3583305fd - obolo_chwerty_6351.keyboard_info - obolo_chwerty_6351.keyboard_info - - .keyboard_info - id_425d4d61d84287d1991f48eb0158c78c obolo_chwerty_6351.ico diff --git a/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.keyboard_info b/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.keyboard_info deleted file mode 100644 index 70b79803fa4..00000000000 --- a/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "start_of_sentence_3621 generated from template" -} diff --git a/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.kpj b/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.kpj index 064471c9506..af45b2b3716 100644 --- a/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.kpj +++ b/common/test/keyboards/start_of_sentence_3621/start_of_sentence_3621.kpj @@ -51,13 +51,6 @@ .md - - id_31c9e544d5ff29ebb5ee3006e39f5624 - start_of_sentence_3621.keyboard_info - start_of_sentence_3621.keyboard_info - - .keyboard_info - id_8b42366ff2333c0b6d85e2d23cf930d7 start_of_sentence_3621.kmx diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info deleted file mode 100644 index 03c280d3093..00000000000 --- a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - - ], - "description": "U_xxxx_yyyy_2858 generated from template" -} diff --git a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj index f2b44703b1e..1c576713382 100644 --- a/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj +++ b/common/test/keyboards/u_xxxx_yyyy_2858/u_xxxx_yyyy_2858.kpj @@ -51,13 +51,6 @@ .md - - id_10a5cd9d0c42cd03b9838916f6f0bd3d - u_xxxx_yyyy_2858.keyboard_info - u_xxxx_yyyy_2858.keyboard_info - - .keyboard_info - id_a1200478ad69b7f058fdce369fb1d960 u_xxxx_yyyy_2858.kmx diff --git a/common/test/keyboards/web_context_tests/web_context_tests.kpj b/common/test/keyboards/web_context_tests/web_context_tests.kpj index 05146c1a3f4..1ec5a1c65f6 100644 --- a/common/test/keyboards/web_context_tests/web_context_tests.kpj +++ b/common/test/keyboards/web_context_tests/web_context_tests.kpj @@ -51,13 +51,6 @@ .md - - id_41e1ad22438964b0b5b84605df1c667d - web_context_tests.keyboard_info - web_context_tests.keyboard_info - - .keyboard_info - id_fa6acb8dd8884fe0c6c6262bb4477d27 web_context_tests.kmx diff --git a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas index 3982366e223..09aa463909b 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas @@ -35,11 +35,9 @@ TKeyboardProjectTemplate = class(TProjectTemplate) procedure WriteKPJ; procedure WriteKVKS; procedure WriteTouchLayout; - procedure WriteKeyboardInfo; procedure WriteIcon; protected const - SFileTemplate_KeyboardInfo = '%s.keyboard_info'; // in root SDataPath_BasicKeyboard = 'basic-keyboard\'; function DataPath: string; override; @@ -104,8 +102,6 @@ procedure TKeyboardProjectTemplate.Generate; WriteKPJ; WriteRepositoryMetadata; - - WriteKeyboardInfo; end; function TKeyboardProjectTemplate.GetIconFilename: string; @@ -150,15 +146,6 @@ function TKeyboardProjectTemplate.HasTouchLayout: Boolean; Result := (TouchKeymanTargets+[ktAny]) * Targets <> []; end; -procedure TKeyboardProjectTemplate.WriteKeyboardInfo; -begin - // Write keyboardid.keyboard_info - Transform( - Format(SFileTemplate_KeyboardInfo, ['keyboard']), - Format(SFileTemplate_KeyboardInfo, [ID]) - ); -end; - procedure TKeyboardProjectTemplate.WriteKMN; var FKeyboardSource: string; @@ -227,7 +214,6 @@ procedure TKeyboardProjectTemplate.WriteKPJ; kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_HistoryMD, nil)); kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_LicenseMD, nil)); kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + SFile_ReadmeMD, nil)); - kpj.Files.Add(TOpenableProjectFile.Create(kpj, BasePath + ID + '\' + Format(SFileTemplate_KeyboardInfo, [ID]), nil)); kpj.Save; finally diff --git a/developer/src/kmconvert/data/basic-keyboard/keyboard.keyboard_info b/developer/src/kmconvert/data/basic-keyboard/keyboard.keyboard_info deleted file mode 100644 index 62777e5e129..00000000000 --- a/developer/src/kmconvert/data/basic-keyboard/keyboard.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - $LANGUAGES_KEYBOARD_INFO - ], - "description": "$NAME generated from template" -} diff --git a/developer/src/tike/compile/JsonExtractKeyboardInfo.pas b/developer/src/tike/compile/JsonExtractKeyboardInfo.pas deleted file mode 100644 index 7239b36b8c7..00000000000 --- a/developer/src/tike/compile/JsonExtractKeyboardInfo.pas +++ /dev/null @@ -1,68 +0,0 @@ -unit JsonExtractKeyboardInfo; - -interface - -uses - Keyman.Developer.System.Project.ProjectLog; - -type - TJsonExtractKeyboardInfo = class - class function Execute(JsonFile, Fields: string; FSilent: Boolean; FCallback: TProjectLogObjectEvent): Boolean; - end; - -implementation - -uses - System.Classes, - System.Json, - System.StrUtils, - System.SysUtils, - utilstr; - -{ TJsonExtractKeyboardInfo } - -class function TJsonExtractKeyboardInfo.Execute(JsonFile, Fields: string; - FSilent: Boolean; FCallback: TProjectLogObjectEvent): Boolean; -var - json: TJSONObject; - s: string; - v: TJSONValue; -begin - try - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(JsonFile); - json := TJSONObject.ParseJsonValue(DataString) as TJSONObject; - if not Assigned(json) then - begin - FCallback(plsError, JsonFile, 'Could not parse keyboard_info', 0, 0); - Exit(False); - end; - - try - while Fields <> '' do - begin - s := StrToken(Fields,','); - v := json.GetValue(s); - if Assigned(v) then - begin - writeln(s+'="'+ReplaceStr(v.Value, '"', '\"')+'"'); - end; - end; - finally - json.Free; - end; - finally - Free; - end; - except - on E:Exception do - begin - FCallback(plsError, JsonFile, E.Message, 0, 0); - Exit(False); - end; - end; - Result := True; -end; - -end. diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas index e17f366fb46..2f79dccbe38 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.ProjectFiles.pas @@ -1,18 +1,18 @@ (* Name: Keyman.Developer.System.Project.ProjectFiles Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 1 Aug 2006 Modified Date: 4 May 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 01 Aug 2006 - mcdurdin - Initial version 04 Dec 2006 - mcdurdin - Test if file is open - WindowOpen 16 Jan 2009 - mcdurdin - I1769 - Add support for HTM, HTLM, XML to open for edit in Keyman Developer @@ -65,7 +65,6 @@ initialization RegisterProjectFileType('.ico', TOpenableProjectFile); RegisterProjectFileType('.txt', TOpenableProjectFile); RegisterProjectFileType('.md', TOpenableProjectFile); - RegisterProjectFileType('.keyboard_info', TOpenableProjectFile); RegisterProjectFileType('.model_info', TOpenableProjectFile); RegisterProjectFileType('.htm', TOpenableProjectFile); // I1769 RegisterProjectFileType('.html', TOpenableProjectFile); // I1769 diff --git a/web/src/test/manual/web/issue2924/source/testvariable/testvariable.keyboard_info b/web/src/test/manual/web/issue2924/source/testvariable/testvariable.keyboard_info deleted file mode 100644 index 5e543492076..00000000000 --- a/web/src/test/manual/web/issue2924/source/testvariable/testvariable.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "testvariable generated from template" -} diff --git a/web/src/test/manual/web/issue2924/source/testvariable/testvariable.kpj b/web/src/test/manual/web/issue2924/source/testvariable/testvariable.kpj index a138eda11b6..bc3e6a52ff1 100644 --- a/web/src/test/manual/web/issue2924/source/testvariable/testvariable.kpj +++ b/web/src/test/manual/web/issue2924/source/testvariable/testvariable.kpj @@ -51,13 +51,6 @@ .md - - id_efaa79249320cda8d8e2886ddbf0475e - testvariable.keyboard_info - testvariable.keyboard_info - - .keyboard_info - id_0c280146c90477c06029a32b48581f90 testvariable.kmx diff --git a/web/src/test/manual/web/issue3701/test3701/test3701.keyboard_info b/web/src/test/manual/web/issue3701/test3701/test3701.keyboard_info deleted file mode 100644 index b2b40a5d1e8..00000000000 --- a/web/src/test/manual/web/issue3701/test3701/test3701.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - - ], - "description": "test3701 generated from template" -} diff --git a/web/src/test/manual/web/issue3701/test3701/test3701.kpj b/web/src/test/manual/web/issue3701/test3701/test3701.kpj index 5db48cd1155..39071cf00b3 100644 --- a/web/src/test/manual/web/issue3701/test3701/test3701.kpj +++ b/web/src/test/manual/web/issue3701/test3701/test3701.kpj @@ -51,13 +51,6 @@ .md - - id_e18bba9285544da2bad5cb8bf0b153d6 - test3701.keyboard_info - test3701.keyboard_info - - .keyboard_info - id_e944ee9b3d06f36294030601f3bd33ae test3701.js diff --git a/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.keyboard_info b/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.keyboard_info deleted file mode 100644 index ac397323086..00000000000 --- a/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - - ], - "description": "test_917 generated from template" -} diff --git a/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.kpj b/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.kpj index 9c3cf4ad2fb..ca0459a53bf 100644 --- a/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.kpj +++ b/web/src/test/manual/web/issue917-context-and-notany/test_917/test_917.kpj @@ -51,13 +51,6 @@ .md - - id_e7fcb5e5546db6914d2f9d00de067f23 - test_917.keyboard_info - test_917.keyboard_info - - .keyboard_info - id_00188325e13e88dc8f51b78b3fcd756e test_917.kmx diff --git a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cv.pas b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cv.pas deleted file mode 100644 index 56649c862a1..00000000000 --- a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cv.pas +++ /dev/null @@ -1,129 +0,0 @@ -unit cv; - -interface - -procedure Run; - -implementation - -uses - System.Classes, - System.JSON, - System.SysUtils, - - JsonUtil, - - Keyman.System.KMXFileLanguages; - - -function LoadJsonFile(f: string): TJSONObject; -begin - with TStringStream.Create('', TEncoding.UTF8) do - try - LoadFromFile(f); - Result := TJSONObject.ParseJsonValue(DataString) as TJSONObject; - finally - Free; - end; -end; - -procedure SaveJsonFile(o: TJSONObject; f: string); -var - str: TStringList; -begin - str := TStringList.Create; - try - PrettyPrintJSON(o, str); - with TStringStream.Create(str.Text, TEncoding.UTF8) do - try - // Use TStringStream so we don't get a default BOM prolog - SaveToFile(f); - finally - Free; - end; - finally - str.Free; - end; -end; - -procedure ProcessFile(f: string); -var - o: TJSONObject; - a, b: TJSONArray; - i: Integer; - FNewCode: string; - FChanged: Boolean; - olin, o2, ol: TJSONObject; -begin - writeln(f); - FChanged := False; - b := nil; - ol := nil; - o := LoadJsonFile(f); - if o.Values['languages'] is TJSONArray then - begin - a := o.Values['languages'] as TJSONArray; - b := TJSONArray.Create; - for i := 0 to a.Count - 1 do - begin - FNewCode := TKMXFileLanguages.TranslateISO6393ToBCP47(a.Items[i].Value); - if FNewCode <> a.Items[i].Value then - FChanged := True; - b.Add(FNewCode); - end; - end - else if o.Values['languages'] is TJSONObject then - begin - olin := o.Values['languages'] as TJSONObject; - ol := TJSONObject.Create; - for i := 0 to olin.Count - 1 do - begin - FNewCode := TKMXFileLanguages.TranslateISO6393ToBCP47(olin.Pairs[i].JsonString.Value); - ol.AddPair(FNewCode, olin.Pairs[i].JsonValue.Clone as TJSONValue); - if FNewCode <> olin.Pairs[i].JsonString.Value then - FChanged := True; - end; - end; - - if not FChanged then - Exit; - - o2 := TJSONObject.Create; - for i := 0 to o.Count - 1 do - begin - if o.Pairs[i].JsonString.Value = 'languages' then - if Assigned(b) then - o2.AddPair(o.Pairs[i].JsonString.Value, b) - else - o2.AddPair(o.Pairs[i].JsonString.Value, ol) - else - o2.AddPair(o.Pairs[i].JsonString.Value, o.Pairs[i].JsonValue.Clone as TJSONValue); - end; - - if FChanged then - SaveJsonFile(o2, f); -end; - -procedure ProcessFolder(f: string); -var - ff: TSearchRec; -begin - if FindFirst(f+'\*', faDirectory, ff) = 0 then - begin - repeat - if (ff.Name <> '.') and (ff.Name <> '..') and ((ff.Attr and faDirectory) <> 0) then - ProcessFolder(f+'\'+ff.Name) - else if SameText(ExtractFileExt(ff.Name), '.keyboard_info') then - ProcessFile(f+'\'+ff.Name) - until FindNext(ff) <> 0; - FindClose(ff); - end; -end; - -procedure Run; -begin -// ProcessFile('c:\temp\convert-bcp47-for-keyboards\sample.keyboard_info'); - ProcessFolder('c:\projects\keyman\keyboards'); -end; - -end. diff --git a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr deleted file mode 100644 index 975eda527d0..00000000000 --- a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr +++ /dev/null @@ -1,31 +0,0 @@ -program cvt; - -{$APPTYPE CONSOLE} - -{$R *.res} - -uses - System.SysUtils, - cv in 'cv.pas', - Keyman.System.Standards.ISO6393ToBCP47Registry in 'C:\Projects\keyman\open\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', - Keyman.System.KMXFileLanguages in 'C:\Projects\keyman\open\windows\src\global\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', - utilstr in 'C:\Projects\keyman\open\common\windows\delphi\general\utilstr.pas', - Keyman.System.Standards.LCIDToBCP47Registry in 'C:\Projects\keyman\open\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', - utilfiletypes in 'C:\Projects\keyman\open\common\windows\delphi\general\utilfiletypes.pas', - utildir in 'C:\Projects\keyman\open\common\windows\delphi\general\utildir.pas', - kmxfile in 'C:\Projects\keyman\open\common\windows\delphi\keyboards\kmxfile.pas', - KeyNames in 'C:\Projects\keyman\open\common\windows\delphi\general\KeyNames.pas', - KeymanVersion in 'C:\Projects\keyman\open\common\windows\delphi\general\KeymanVersion.pas', - StockFileNames in 'C:\Projects\keyman\open\windows\src\..\..\common\windows\delphi\general\StockFileNames.pas', - Unicode in 'C:\Projects\keyman\open\common\windows\delphi\general\Unicode.pas', - JsonUtil in 'C:\Projects\keyman\open\common\windows\delphi\general\JsonUtil.pas'; - -begin - try - Run; - { TODO -oUser -cConsole Main : Insert code here } - except - on E: Exception do - Writeln(E.ClassName, ': ', E.Message); - end; -end. diff --git a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj deleted file mode 100644 index 2c70ec7ca61..00000000000 --- a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj +++ /dev/null @@ -1,589 +0,0 @@ - - - {A273278D-9A16-4CEE-ACDA-89B30B8B61F1} - 18.2 - None - cvt.dpr - True - Debug - Win32 - 1 - Console - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - .\$(Platform)\$(Config) - .\$(Platform)\$(Config) - false - false - false - false - false - RESTComponents;FireDAC;FireDACSqliteDriver;soaprtl;FireDACIBDriver;soapmidas;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;$(DCC_UsePackage) - System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - cvt - - - DBXSqliteDriver;bindcompdbx;IndyIPCommon;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;bindcompfmx;FireDACPgDriver;inetdb;DbxCommonDriver;fmx;fmxdae;xmlrtl;fmxobj;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;IndyIPClient;dbxcds;bindengine;DBXMySQLDriver;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonODBC;fmxase;$(DCC_UsePackage) - true - - - DBXSqliteDriver;bindcompdbx;IndyIPCommon;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;mbColorLibD10;dsnapcon;FireDACADSDriver;scFontCombo;DCPdelphi2009;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;vcltouch;JvCore;vcldb;bindcompfmx;svn;FireDACPgDriver;inetdb;DbxCommonDriver;fmx;fmxdae;xmlrtl;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;IndyIPClient;bindcompvcl;EmbeddedWebBrowser_XE;VCLRESTComponents;dbxcds;VclSmp;JvDocking;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;keyman_components;FireDACCommonODBC;fmxase;$(DCC_UsePackage) - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - Debug - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - 1033 - true - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - - - DBXSqliteDriver;bindcompdbx;IndyIPCommon;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;Jcl;vcltouch;vcldb;bindcompfmx;FireDACPgDriver;inetdb;DbxCommonDriver;fmx;fmxdae;xmlrtl;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;IndyIPClient;bindcompvcl;VCLRESTComponents;dbxcds;VclSmp;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonODBC;fmxase;$(DCC_UsePackage) - true - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - - - DEBUG;$(DCC_Define) - true - false - true - true - true - - - false - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - - MainSource - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - Application - - - - cvt.dpr - - - - - - true - - - - - true - - - - - true - - - - - true - - - - - cvt.exe - true - - - - - 1 - - - Contents\MacOS - 1 - - - Contents\MacOS - 0 - - - - - classes - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - - - res\values - 1 - - - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - - - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - Contents\MacOS - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - Contents\MacOS - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - Contents\Resources\StartUp\ - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - 1 - - - 1 - - - - - ..\ - 1 - - - ..\ - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - ..\ - 1 - - - - - Contents - 1 - - - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - Contents\MacOS - 1 - - - 0 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - False - False - True - False - - - 12 - - - - - diff --git a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.res b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.res deleted file mode 100644 index 743599575b02e97248bade49ed2e3eabafe25a0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmZQzU|>)H;{X347|28cOhBFu5dZ(r#Sp;Y!{Epe!r;c>&k)4m3uHM0X?F%!AS)QE O%YcEC1!e#vkO2UW7YiT& diff --git a/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.keyboard_info b/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.keyboard_info deleted file mode 100644 index 5ce314be007..00000000000 --- a/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "din-Latn" - ], - "description": "context_mismatch_with_if generated from template" -} diff --git a/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.kpj b/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.kpj index 88a7bc90ba1..78dff6f55f8 100644 --- a/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.kpj +++ b/windows/src/test/manual-tests/GH-4275 - contextex-mismatch-with-if/context_mismatch_with_if/context_mismatch_with_if.kpj @@ -51,13 +51,6 @@ .md - - id_1213256b2009e2b32ee7d551a63402c6 - context_mismatch_with_if.keyboard_info - context_mismatch_with_if.keyboard_info - - .keyboard_info - id_ea1380e11b2491a95b183d9ea2bcd813 context_mismatch_with_if.kmx diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info deleted file mode 100644 index bb9f739f704..00000000000 --- a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "Caps Always Off generated from template" -} diff --git a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj index 095264252bb..a153f343167 100644 --- a/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj +++ b/windows/src/test/manual-tests/caps-lock-stores/caps_always_off/caps_always_off.kpj @@ -91,12 +91,5 @@ .md - - id_60f11cd4dd88c1ad232f0099b86b071f - caps_always_off.keyboard_info - caps_always_off.keyboard_info - - .keyboard_info - diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info deleted file mode 100644 index b05d23ac133..00000000000 --- a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "Caps Lock generated from template" -} diff --git a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj index 34c03b5ce43..3700856001e 100644 --- a/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj +++ b/windows/src/test/manual-tests/caps-lock-stores/capslock/capslock.kpj @@ -51,13 +51,6 @@ .md - - id_621d2fc48f94f0e3862c706c37ff7a8b - capslock.keyboard_info - capslock.keyboard_info - - .keyboard_info - id_894c97d2a53dc029f8ca18fd3e0c2ff0 capslock.kmx diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.keyboard_info b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.keyboard_info deleted file mode 100644 index 4be7edab2c9..00000000000 --- a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.keyboard_info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "license": "mit", - "languages": [ - "en" - ], - "description": "CapsOnOnly and ShiftFreesCaps features generated from template" -} diff --git a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.kpj b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.kpj index 86e840119a8..1fd0f54cf1a 100644 --- a/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.kpj +++ b/windows/src/test/manual-tests/caps-lock-stores/shift_frees_caps/shift_frees_caps.kpj @@ -51,13 +51,6 @@ .md - - id_5e7df09f2deea5c5789153c0e7bca89f - shift_frees_caps.keyboard_info - shift_frees_caps.keyboard_info - - .keyboard_info - id_f5c18d42ae1a4a0cad4fa87bc4e65767 shift_frees_caps.kmx From 94be7bb60ce5226d6e491f1bd185a87d8e192d4d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 25 Sep 2023 15:53:20 +0700 Subject: [PATCH 076/207] chore(developer): validate emitted .keyboard_info and fix keys Fixes #9620. .keyboard_info keys emitted from kmc-keyboard-info had the wrong format for the example.keys object, which I discovered when I applied the validation to the kmc-keyboard-info output. --- .../keyboard_info/keyboard_info.schema.json | 4 +- common/web/types/src/package/kmp-json-file.ts | 12 +++- common/web/types/src/package/kps-file.ts | 12 +++- ...eyman.Developer.System.ValidateKpsFile.pas | 70 ------------------- developer/src/kmc-keyboard-info/package.json | 4 +- .../src/kmc-keyboard-info/src/example-keys.ts | 23 ++++++ developer/src/kmc-keyboard-info/src/index.ts | 28 +++++++- .../src/keyboard-info-file.ts | 7 +- .../src/kmc-keyboard-info/src/messages.ts | 14 +++- .../build/khmer_angkor.keyboard_info | 8 ++- .../test/test-keyboard-info-compiler.ts | 18 +++-- package-lock.json | 60 +++++++++++++++- 12 files changed, 172 insertions(+), 88 deletions(-) delete mode 100644 developer/src/common/delphi/compiler/Keyman.Developer.System.ValidateKpsFile.pas create mode 100644 developer/src/kmc-keyboard-info/src/example-keys.ts diff --git a/common/schemas/keyboard_info/keyboard_info.schema.json b/common/schemas/keyboard_info/keyboard_info.schema.json index b56b8dfe158..54a8a3f5848 100644 --- a/common/schemas/keyboard_info/keyboard_info.schema.json +++ b/common/schemas/keyboard_info/keyboard_info.schema.json @@ -22,8 +22,8 @@ "jsFilename": { "type": "string", "pattern": "\\.js$" }, "jsFileSize": { "type": "number" }, "isRTL": { "type": "boolean" }, - "encodings": { "type": "array", "items": { "type": "string", "enum": ["ansi", "unicode"] }, "additionalItems": false }, - "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["welcome", "documentation", "fonts", "visualKeyboard"] }, "additionalItems": false }, + "encodings": { "type": "array", "items": { "type": "string", "enum": ["ansi", "unicode"] } }, + "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["welcome", "documentation", "fonts", "visualKeyboard"] } }, "version": { "type": "string" }, "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.\\d$" }, "helpLink": { "type": "string", "pattern": "^https://help\\.keyman\\.com/keyboard/" }, diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 1450e2418cc..219cbc5fbf9 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -97,7 +97,17 @@ export interface KmpJsonFileExample { */ id: string; /** - * A space-separated list of keys, modifiers indicated with "+", spacebar is "space", plus key is "shift+=" or "plus" + * A space-separated list of keys. + * - modifiers indicated with "+" + * - spacebar is "space" + * - plus key is "shift+=" or "plus" on US English (all other punctuation as per key cap). + * - Hardware modifiers are: "shift", "ctrl", "alt", "left-ctrl", + * "right-ctrl", "left-alt", "right-alt" + * - Key caps should generally be their character for desktop (Latin script + * case insensitive), or the actual key cap for touch + * - Caps Lock should be indicated with "caps-on", "caps-off" + * + * e.g. "shift+a b right-alt+c space plus z z z" represents something like: "Ab{AltGr+C} +zzz" */ keys: string; /** diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 976ea79954d..ebf6ad6e153 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -184,7 +184,17 @@ export interface KpsFileLanguageExample { */ ID: string; /** - * A space-separated list of keys, modifiers indicated with "+", spacebar is "space", plus key is "shift+=" or "plus" + * A space-separated list of keys. + * - modifiers indicated with "+" + * - spacebar is "space" + * - plus key is "shift+=" or "plus" on US English (all other punctuation as per key cap). + * - Hardware modifiers are: "shift", "ctrl", "alt", "left-ctrl", + * "right-ctrl", "left-alt", "right-alt" + * - Key caps should generally be their character for desktop (Latin script + * case insensitive), or the actual key cap for touch + * - Caps Lock should be indicated with "caps-on", "caps-off" + * + * e.g. "shift+a b right-alt+c space plus z z z" represents something like: "Ab{AltGr+C} +zzz" */ Keys: string; /** diff --git a/developer/src/common/delphi/compiler/Keyman.Developer.System.ValidateKpsFile.pas b/developer/src/common/delphi/compiler/Keyman.Developer.System.ValidateKpsFile.pas deleted file mode 100644 index 991fe1249f8..00000000000 --- a/developer/src/common/delphi/compiler/Keyman.Developer.System.ValidateKpsFile.pas +++ /dev/null @@ -1,70 +0,0 @@ -unit Keyman.Developer.System.ValidateKpsFile; - -interface - -uses - Keyman.Developer.System.Project.ProjectLog; - -type - TValidateKpsFile = class - private - FOnLogEvent: TProjectLogObjectEvent; - FKpsFile: string; - FKpsXsdPath: string; - constructor Create(const AKpsFile, AKpsXsdPath: string); - function Validate: Boolean; - property OnLogEvent: TProjectLogObjectEvent read FOnLogEvent write FOnLogEvent; - public - class function Execute(const KpsFile, KpsXsdPath: string; FCallback: TProjectLogObjectEvent): Boolean; - end; - -implementation - -uses - Winapi.MsXml; - -{ TValidateKpsFile } - -constructor TValidateKpsFile.Create(const AKpsFile, AKpsXsdPath: string); -begin - inherited Create; - FKpsFile := AKpsFile; - FKpsXsdPath := AKpsXsdPath; -end; - -class function TValidateKpsFile.Execute(const KpsFile, KpsXsdPath: string; - FCallback: TProjectLogObjectEvent): Boolean; -var - v: TValidateKpsFile; -begin - v := TValidateKpsFile.Create(KpsFile, KpsXsdPath); - try - v.OnLogEvent := FCallback; - Result := v.Validate; - finally - v.Free; - end; -end; - -function TValidateKpsFile.Validate: Boolean; -var - xml, xsd: IXMLDOMDocument2; - cache: IXMLDOMSchemaCollection; -begin - xsd := CoDOMDocument60.Create; - xsd.Async := False; - xsd.load(FKpsXsdPath); - - cache := CoXMLSchemaCache60.Create; - cache.add('', xsd); - - xml := CoDOMDocument60.Create; - xml.async := False; - xml.schemas := cache; - - Result := xml.load(FKpsFile); - if not Result then - FOnLogEvent(plsError, FKpsFile, 'Validation error: '+xml.parseError.reason, 1, xml.parseError.line); -end; - -end. diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index f1f787b9cd2..a354c942e53 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -26,7 +26,9 @@ "dependencies": { "@keymanapp/common-types": "*", "@keymanapp/kmc-package": "*", - "@keymanapp/developer-utils": "*" + "@keymanapp/developer-utils": "*", + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1" }, "bundleDependencies": [ "@keymanapp/developer-utils" diff --git a/developer/src/kmc-keyboard-info/src/example-keys.ts b/developer/src/kmc-keyboard-info/src/example-keys.ts new file mode 100644 index 00000000000..e9a8bb34ccd --- /dev/null +++ b/developer/src/kmc-keyboard-info/src/example-keys.ts @@ -0,0 +1,23 @@ +import { KeyboardInfoFileExampleKey } from "./keyboard-info-file.js"; + +/** + * Converts a .kps or .kmp example keys string into an array of key objects + * matching the .keyboard_info example file format + * @param keysString + * @returns + */ +export function packageKeysExamplesToKeyboardInfo(keysString: string): KeyboardInfoFileExampleKey[] { + const items = keysString.trim().split(/ +/); + const result: KeyboardInfoFileExampleKey[] = []; + for(const item of items) { + const keyAndModifiers = item.split('+'); + if(keyAndModifiers.length > 0) { + const key: KeyboardInfoFileExampleKey = {key: keyAndModifiers.pop()} + if(keyAndModifiers.length) { + key.modifiers = [...keyAndModifiers]; + }; + result.push(key); + } + } + return result; +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index a677f5f5a9f..c36e9b82fe5 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -11,6 +11,14 @@ import langtags from "./imports/langtags.js"; import { validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; +import AjvModule from 'ajv'; +import AjvFormatsModule from 'ajv-formats'; +const Ajv = AjvModule.default; // The actual expected Ajv type. +const ajvFormats = AjvFormatsModule.default; + +import { Schemas } from "@keymanapp/common-types"; +import { packageKeysExamplesToKeyboardInfo } from "./example-keys.js"; + const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); const scriptNames = new Intl.DisplayNames(['en'], { type: "script" }); const langtagsByTag = {}; @@ -281,6 +289,20 @@ export class KeyboardInfoCompiler { } const jsonOutput = JSON.stringify(keyboard_info, null, 2); + + // TODO: look at performance improvements by precompiling Ajv schemas on first use + const ajv = new Ajv({ logger: { + log: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_OutputValidation({message})), + warn: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Warn_OutputValidation({message})), + error: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_OutputValidation({message})), + }}); + ajvFormats.default(ajv); + if(!ajv.validate(Schemas.default.keyboard_info, keyboard_info)) { + // This is an internal fatal error; we should not be capable of producing + // invalid output, so it is best to throw and die + throw new Error(ajv.errorsText()); + } + return new TextEncoder().encode(jsonOutput); } @@ -380,9 +402,9 @@ export class KeyboardInfoCompiler { if(example.id == bcp47) { language.examples.push({ // we don't copy over example.id - keys:example.keys, - note:example.note, - text:example.text + keys: packageKeysExamplesToKeyboardInfo(example.keys), + note: example.note, + text: example.text }); } } diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts index e8a9b2bd09a..60c25a13c0e 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts @@ -49,7 +49,12 @@ export interface KeyboardInfoFileLanguageFont { } export interface KeyboardInfoFileExample { - keys?: string; + keys?: KeyboardInfoFileExampleKey[]; text?: string; note?: string; } + +export interface KeyboardInfoFileExampleKey { + key: string; + modifiers?: string[]; +} diff --git a/developer/src/kmc-keyboard-info/src/messages.ts b/developer/src/kmc-keyboard-info/src/messages.ts index 72a03278ac6..fb2000fca4f 100644 --- a/developer/src/kmc-keyboard-info/src/messages.ts +++ b/developer/src/kmc-keyboard-info/src/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.KeyboardInfoCompiler; // const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -45,5 +45,17 @@ export class KeyboardInfoCompilerMessages { static Error_NoLicenseFound = () => m(this.ERROR_NoLicenseFound, `No license for the keyboard was found. MIT license is required for publication to Keyman keyboards repository.`); static ERROR_NoLicenseFound = SevError | 0x000A; + + static Hint_OutputValidation = (o:{message: any}) => m(this.HINT_OutputValidation, + `Validating output: ${o.message}.`); + static HINT_OutputValidation = SevHint | 0x000B; + + static Warn_OutputValidation = (o:{message: any}) => m(this.WARN_OutputValidation, + `Validating output: ${o.message}.`); + static WARN_OutputValidation = SevWarn | 0x000C; + + static Error_OutputValidation = (o:{message: any}) => m(this.ERROR_OutputValidation, + `Validating output: ${o.message}.`); + static ERROR_OutputValidation = SevError | 0x000D; } diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info index 7d85bbfb6cf..98806bacecc 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info @@ -15,7 +15,13 @@ ] }, "examples": [{ - "keys": "x j m E r", + "keys": [ + { "key": "x" }, + { "key": "j" }, + { "key": "m" }, + { "key": "E" }, + { "key": "r" } + ], "text": "\u1781\u17D2\u1798\u17C2\u179A", "note": "Name of language" }], diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 939b76407ab..0e718991849 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -19,12 +19,18 @@ describe('keyboard-info-compiler', function () { const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.keyboard_info'); const compiler = new KeyboardInfoCompiler(callbacks); - const data = compiler.writeKeyboardInfoFile({ - kmpFilename, - sourcePath: 'release/k/khmer_angkor', - kpsFilename, - jsFilename: jsFilename, - }); + let data = null; + try { + data = compiler.writeKeyboardInfoFile({ + kmpFilename, + sourcePath: 'release/k/khmer_angkor', + kpsFilename, + jsFilename: jsFilename, + }); + } catch(e) { + callbacks.printMessages(); + throw e; + } if(data == null) { callbacks.printMessages(); } diff --git a/package-lock.json b/package-lock.json index 3e0c5a43c14..fbcf72c79f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1160,7 +1160,9 @@ "dependencies": { "@keymanapp/common-types": "*", "@keymanapp/developer-utils": "*", - "@keymanapp/kmc-package": "*" + "@keymanapp/kmc-package": "*", + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1" }, "devDependencies": { "@types/chai": "^4.3.5", @@ -1186,6 +1188,21 @@ "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==", "dev": true }, + "developer/src/kmc-keyboard-info/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "developer/src/kmc-keyboard-info/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1239,6 +1256,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "developer/src/kmc-keyboard-info/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "developer/src/kmc-keyboard-info/node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -4370,6 +4392,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-colors": { "version": "4.1.1", "dev": true, From 27266049f723aa28541643fce676fc0fa2e2f682 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 25 Sep 2023 15:58:05 -0500 Subject: [PATCH 077/207] =?UTF-8?q?feat(common):=20ldml=20import=20infrast?= =?UTF-8?q?ructure=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - Just use a local Symbol() instead of a named one, there's no reason for a namespace here. --- common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 80bbb2e4d47..5f954c1605d 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -213,9 +213,9 @@ export interface LKScanCodes { */ export class ImportStatus { /** item came in via implied (spec based) import, such as keys-Latn-implied.xml */ - static impliedImport = Symbol.for('@keymanapp:implied_import'); + static impliedImport = Symbol('LDML implied import'); /** item came in via import */ - static import = Symbol.for('@keymanapp:import'); + static import = Symbol('LDML import'); /** @returns true if the object was loaded through an implied import */ static isImpliedImport(o : any) : boolean { From 87c18fdb42e4a1adf76997a71f49d3c2b303b5b3 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 25 Sep 2023 16:13:17 -0500 Subject: [PATCH 078/207] =?UTF-8?q?feat(developer):=20scancode=20hint=20to?= =?UTF-8?q?=20warning=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - fix a bad comment - Warn_UnsupportedCustomForm (formerly hint) --- developer/src/kmc-ldml/src/compiler/layr.ts | 4 ++-- developer/src/kmc-ldml/src/compiler/messages.ts | 8 ++++---- .../test/fixtures/sections/layr/hint-custom-form.xml | 4 ++-- developer/src/kmc-ldml/test/test-layr.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index 07dfbcf92d5..879d362a04a 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -23,9 +23,9 @@ export class LayrCompiler extends SectionCompiler { let hardwareLayers = 0; // let touchLayers = 0; this.keyboard3.forms?.form?.forEach((form) => { - // Just check whether it's NOT an implied import if (!LDMLKeyboard.ImportStatus.isImpliedImport(form)) { - this.callbacks.reportMessage(CompilerMessages.Hint_UnsupportedCustomForm({id: form.id})); + // If it's not an implied import, give a warning. + this.callbacks.reportMessage(CompilerMessages.Warn_UnsupportedCustomForm({ id: form.id })); } }); this.keyboard3.layers?.forEach((layers) => { diff --git a/developer/src/kmc-ldml/src/compiler/messages.ts b/developer/src/kmc-ldml/src/compiler/messages.ts index 37dfd7772c6..5bc3fba784f 100644 --- a/developer/src/kmc-ldml/src/compiler/messages.ts +++ b/developer/src/kmc-ldml/src/compiler/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler; -// const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler; +const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevError = CompilerErrorSeverity.Error | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevFatal = CompilerErrorSeverity.Fatal | CompilerErrorNamespace.LdmlKeyboardCompiler; @@ -149,9 +149,9 @@ export class CompilerMessages { m(this.ERROR_DisplayNeedsToOrId, `display ${CompilerMessages.toOrId(o)} needs to= or id=, but not both`); static ERROR_DisplayNeedsToOrId = SevError | 0x0022; - static Hint_UnsupportedCustomForm = (o:{id: string}) => - m(this.HINT_UnsupportedCustomForm, `Custom
    element is not supported.`); - static HINT_UnsupportedCustomForm = SevHint | 0x0023; + static Warn_UnsupportedCustomForm = (o:{id: string}) => + m(this.WARN_UnsupportedCustomForm, `Custom element is ignored. Key layout may not be as expected.`); + static WARN_UnsupportedCustomForm = SevWarn | 0x0023; } diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml index 813a2dbf2e0..f7122d10a8f 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml @@ -11,12 +11,12 @@ - + - + diff --git a/developer/src/kmc-ldml/test/test-layr.ts b/developer/src/kmc-ldml/test/test-layr.ts index 899d5d8666d..86f16ff27bf 100644 --- a/developer/src/kmc-ldml/test/test-layr.ts +++ b/developer/src/kmc-ldml/test/test-layr.ts @@ -120,14 +120,14 @@ describe('layr', function () { // warning on custom form subpath: 'sections/layr/hint-custom-form.xml', warnings: [ - CompilerMessages.Hint_UnsupportedCustomForm({id: "us"}), + CompilerMessages.Warn_UnsupportedCustomForm({id: "us"}), ], }, { // error on unknown form subpath: 'sections/layr/error-custom-form.xml', warnings: [ - CompilerMessages.Hint_UnsupportedCustomForm({id: "zzz"}), + CompilerMessages.Warn_UnsupportedCustomForm({id: "zzz"}), ], errors: [CompilerMessages.Error_InvalidHardware({ form: 'zzz', From 71aee7a7fad1528ace5e99d30c77e8b2b843f72e Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 25 Sep 2023 17:08:19 -0500 Subject: [PATCH 079/207] =?UTF-8?q?feat(common):=20scancode=20fixes=20for?= =?UTF-8?q?=20ks=20and=20iso=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - per review comments - track CLDR PR 3282 --- common/web/types/src/consts/virtual-key-constants.ts | 10 +++++----- .../techpreview/import/scanCodes-implied.xml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 39eb4d6d704..3971329edd7 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -160,13 +160,13 @@ export const USVirtualKeyMap: KeyMap = [ ]; export const KSVirtualKeyMap: KeyMap = [ - // ` 1 2 3 4 5 6 7 8 9 0 - = [bksp] + // ` 1 2 3 4 5 6 7 8 9 0 - = YEN [bksp] [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], // [tab] Q W E R T Y U I O P [ ] \ - [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_LBRKT, k.K_RBRKT, k.K_BKSLASH ], + [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_LBRKT, k.K_RBRKT, k.k_oC1 ], // [caps] A S D F G H J K L ; ' [enter] [ k.K_A, k.K_S, k.K_D, k.K_F, k.K_G, k.K_H, k.K_J, k.K_K, k.K_L, k.K_COLON, k.K_QUOTE ], - // [shift] Z X C V B N M , . / [shift] *=oE2 + // [shift] Z X C V B N M , . / [shift] [ k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH ], // space [ k.K_SPACE ], @@ -188,12 +188,12 @@ export const ISOVirtualKeyMap: KeyMap = [ export const JISVirtualKeyMap: KeyMap = [ // [Hankaku/Zenkaku] 1 2 3 4 5 6 7 8 9 0 - ^ ¥ [bksp] [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], - // [tab] Q W E R T Y U I O P @«`» [ [enter] + // [tab] Q W E R T Y U I O P @ [ [enter] [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_BKQUOTE, k.K_LBRKT ], // [caps] A S D F G H J K L ; : ] [enter] [ k.K_A, k.K_S, k.K_D, k.K_F, k.K_G, k.K_H, k.K_J, k.K_K, k.K_L, k.K_COLON, k.K_QUOTE, k.K_RBRKT ], // [shift] Z X C V B N M , . / _ [shift] - [ k.K_oE2, k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH, k.k_oC1 /* ろ */ ], + [ k.K_Z, k.K_X, k.K_C, k.K_V, k.K_B, k.K_N, k.K_M, k.K_COMMA, k.K_PERIOD, k.K_SLASH, k.k_oC1 /* ろ */ ], // space [ k.K_SPACE ], ]; diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml index 73414c54c75..23ee29d2cbc 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml @@ -35,7 +35,7 @@ - +
    From 2c588edbba9fbb08ff47c60fa1aec1080542c59d Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 25 Sep 2023 17:35:44 -0500 Subject: [PATCH 080/207] =?UTF-8?q?feat(common):=20scancode=20fixes=20for?= =?UTF-8?q?=20ks=20and=20jis=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - use vkey k.K_oE2 for 0x7D in KS and JIS keyboards For: #9403 --- common/web/types/src/consts/virtual-key-constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 3971329edd7..929182c4810 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -161,7 +161,7 @@ export const USVirtualKeyMap: KeyMap = [ export const KSVirtualKeyMap: KeyMap = [ // ` 1 2 3 4 5 6 7 8 9 0 - = YEN [bksp] - [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], + [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_oE2 /* YEN */ ], // [tab] Q W E R T Y U I O P [ ] \ [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_LBRKT, k.K_RBRKT, k.k_oC1 ], // [caps] A S D F G H J K L ; ' [enter] @@ -187,7 +187,7 @@ export const ISOVirtualKeyMap: KeyMap = [ export const JISVirtualKeyMap: KeyMap = [ // [Hankaku/Zenkaku] 1 2 3 4 5 6 7 8 9 0 - ^ ¥ [bksp] - [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_BKSLASH /* YEN */ ], + [ k.K_BKQUOTE, k.K_1, k.K_2, k.K_3, k.K_4, k.K_5, k.K_6, k.K_7, k.K_8, k.K_9, k.K_0, k.K_HYPHEN, k.K_EQUAL, k.K_oE2 /* YEN */ ], // [tab] Q W E R T Y U I O P @ [ [enter] [ k.K_Q, k.K_W, k.K_E, k.K_R, k.K_T, k.K_Y, k.K_U, k.K_I, k.K_O, k.K_P, k.K_BKQUOTE, k.K_LBRKT ], // [caps] A S D F G H J K L ; : ] [enter] From 70a204131f75e4cee16df5c2f02b9c5ce10ebff7 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 27 Sep 2023 06:08:25 +0700 Subject: [PATCH 081/207] chore(web,android): Update test9469 keyboard --- .../app/src/main/assets/test9469.kmp | Bin 4416 -> 4416 bytes .../web/issue9469/test9469/build/test9469.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp b/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp index 76f3480fac8195ec553e2b17a88f93915299b63b..b83a0c509b5e08d0484358b273280c642eccb998 100644 GIT binary patch delta 2391 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[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.141.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.139.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file From ff81276d10f54c77cfe2fa793958b3a6c3d778e1 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 28 Sep 2023 15:30:30 +0700 Subject: [PATCH 082/207] chore(web): Add ZWNJGeneric --- web/src/engine/osk/src/specialCharacters.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/specialCharacters.ts b/web/src/engine/osk/src/specialCharacters.ts index faa5fd697b8..ebd17ab06cb 100644 --- a/web/src/engine/osk/src/specialCharacters.ts +++ b/web/src/engine/osk/src/specialCharacters.ts @@ -40,11 +40,12 @@ let specialCharacters = { '*RTLBkSp*': 0x72, '*ShiftLock*': 0x73, '*ShiftedLock*': 0x74, - '*ZWNJ*': 0x79, // If this one is specified, auto-detection will kick in. + '*ZWNJ*': 0x75, // If this one is specified, auto-detection will kick in. '*ZWNJiOS*': 0x75, // The iOS version will be used by default, but the '*ZWNJAndroid*': 0x76, // Android platform has its own default glyph. // Added in Keyman 17.0. // Reference: https://github.com/silnrsi/font-symchar/blob/v4.000/documentation/encoding.md + '*ZWNJGeneric*': 0x79, // Generic version of ZWNJ (no override) '*Sp*': 0x80, // Space '*NBSp*': 0x82, // No-break Space '*NarNBSp*': 0x83, // Narrow No-break Space From 5beaf32ad4fac54d681f673069ec66ab83746692 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 28 Sep 2023 15:35:48 +0700 Subject: [PATCH 083/207] chore(web): Update test9469 to use ZWNJGeneric --- web/src/test/manual/web/issue9469/test9469/build/test9469.js | 2 +- .../web/issue9469/test9469/source/test9469.keyman-touch-layout | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/test/manual/web/issue9469/test9469/build/test9469.js b/web/src/test/manual/web/issue9469/test9469/build/test9469.js index f081026931f..45ed85b5454 100644 --- a/web/src/test/manual/web/issue9469/test9469/build/test9469.js +++ b/web/src/test/manual/web/issue9469/test9469/build/test9469.js @@ -1 +1 @@ -if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":this._v>13?"*ZWNJ*":"<|>"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.139.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":"*ZWNJGeneric*"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.138.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout b/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout index 5edff501207..5f8ac2101a1 100644 --- a/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout +++ b/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout @@ -199,7 +199,7 @@ }, { "id": "T_ZWNJ", - "text": "*ZWNJ*" + "text": "*ZWNJGeneric*" }, { "id": "T_ZWNJIOS", From 2d8013360ff0ba6f3da97b7b775454de28d43189 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 28 Sep 2023 15:36:04 +0700 Subject: [PATCH 084/207] chore(android): Update test9469 to use ZWNJGeneric --- .../app/src/main/assets/test9469.kmp | Bin 4416 -> 4417 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp b/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp index b83a0c509b5e08d0484358b273280c642eccb998..f09531b5651019e83d56cc684f04cdb8f88feae7 100644 GIT binary patch delta 3778 zcmZvf2T&8tw#P${^dh3tq)P7w6i`AB9jT#%)L^JVX_5e!q9n8+MT*j;m(T?xiqg9v z9U_AC-s{DC-@Nbp-n=ujv$H$r%8>FG2W-EfKv~0{yk=J86`vH+&byA@uBmz@MPoqWGPoYvlZEjK)!ELK~ z`);7^&g9f#Mb!J>hbOKIfl55(U>^sk#C66_tGmh@U(TZiR-m;~#B9dUK7+M)>vwFm zi#lh}g&ZV$<1T)Rvjq9`wbq{RNP{8nbCN}pYJ;cLJ_=vG$G78?7RGMkGPiU-C$*tU zgZM0Mk(c0sG^tR0b`JJ*dw9NRH372BuN%vkAP|JL)T=z`$yMPFIoNp+N`Z?c~z4b0JsAL0H^@~0K&-^abE^> zU(6luzkmh18-cG!=)ZY~TX6-VjI1#<=?rw9uEZG`qYZCWJ2 z_SuoItLGzIz;E^o@{dbfbX$^9?E?MR7*f94SDoB2iy?IWmO<(g0Ld09@`xNlO&)>gN~Vu&x1Q!M(PfB(j}CGCSVI(y+d z{L7J4oR_ej?QvFBp6Av~5EEhJbos(9K%xK6+b7w8Lt=achO{B&sG`a!iz^H#$nscb!kaRoPoDKl zPO8him}Jh2+?LaQd_Dh-7!w($n8WddVKgQ`y7u|)mNTDjuWb%lfxG7NHzz?YLl~Y_ z-OXyw&3Rg6iEN9}p2vpF!7@cAe!6&XS$C7a<#O%$*oENbYwNh2Sy=Y2G|4Vqu719> z$}Zi>1%KV^#fQ%xv5#%q;_&QvqJKU+m&W{G%oG3soE-q5`unqk`A$R6eJ0fDk<4oF z@LyfUq~wzZU&)Z^M=HT%sCj-+ZUUk5-VH`YQc!r>GRdCcDX-!+t};E&2DgG?x1Ff` z>Di6-E8A?l#nT7XiC1*U8gMAJMCSmv+;Zk?)EWeNvzA0h9#G!~N9{fA&b~ z7|Jp{ldC&))tNq;0ZDb6nNmv?MnlPghU;E80-ZqwcSnWaUKNOrZBoTIP?Gk_GT!|m zuJD)%tCx$*nV-ph?Pl?DOKtZ*8~xNX94iW^Sq)u? zGY6DbAl(H;L}6L=4@cxFSM@@8PV+<_CHtb$->5CWMtW4=2fxk_hFZYsb0(IAy0>Zz zgr@k3rl{~4tEpfu<|i+>R$)%mFrRt4!sqx;hzllU385F}~9o*$0|- zP@7L(aNAU>NC{I9UtY)q=xj!O`or5ihnt^Tk&BCdKahheZ5t=!CTq&xcb3f5L5tK* zB5yhZy*QpPyxImQ2z}cS(}%^T3yas!TjtLGwzaK_BUF9vjazhTi!mC$c_)?yVoisL z`VvgW;}R)KUg1y6`|{8T;iDT|!P%c=<$*m{GPkS<6QH0n;^|^}c{a%lRWuySI#?+1 zL$;P)X4HSUFsk(%a(nxhlz+_}=g&q2` z_*t}UNAqsJSf#3#7C!icBZjML3#X@3dCvCAiTe)oO-kc3voeP4aGR;nXMrc@v>zCQ z;r%0^70w_-RT2yvs%ey0P@G8fxw2b{0dlg~@T58E61e|B52ZPvHrSAbGlp~K5wS#a_EK%XkT-tDFh@1^V(;1MR;2Lh152mCye5BpO*c3um z?Zf0-)Uh`2m>Pmz5*Xu0cY$Fs$sM3gMGO!IJ4{q6V8WCBFvsIU;;A&UpzEE;Ocw8| z5P+EG>L_JIxTsi1vl|P6nE4Yiv~6@&y!3}NLtcZWqp?k+~%A>}TtAX0uu<4bpasRi00K{p_^A2E??p!IU^SHMC4R_Ep zszg_kOSL(?-}KX(xDjL?*RNpvmZea8j+>x~l$X>as-q$>Qw37z>FyP8cK%%40n=8K zr0Vzg5>tUCW}Cb;mh~O7W|Wc}Q4xCB7S9_GqVGjJREydlH^664@!OR}(Qu8~65|Qe z+H}Nc{!Yz8SmF=kMETr`xy^BWITE} z?^OaD`Gtjt>pfu+kgs8U26=O8BI@%V?WWIk^pIFCS3QEh=6%sUz>S<`P z1{_55fx;n1aOA~34_(aMTO7Q2)w30UD^1(+*CicA$}d&g3!zv{yp>!nT!uoS)2kg- zo)G1;)oFzC*}~u`7Ut3mfD3aZ1*8je;ZB@gtrT5EN#UCbDgqR3Jo$qrS_vC`Lq86o zel*ql+Baw&H$RB5bxhDf<>Jc@p$)kH6c@kB*EMSxq#3@coOtwcwN?9rPpd!t&|4Yo z*Bg`6L((y(tM6OvsvM72zl!=T?uE1y@A=5?&PZFDw5YxT6VU@OLxSeEt#3(%sn+ol4_$Dpd8)Q1xKErNtlGC-_cM~!0mae zk6LsxnVQ;?B)e7y_3q`7vLJ{}dF|J1exe)h8k(fDG5&f?u+#5g(N<_>YU~lOk}~~8 zNtD6nn>e?%@g#lnHJOIp<=Hvi;fm*i^*LX|IO`?IgZ3gTy?2_ivu#EEAH@_KLv4jr zG7&cGvKyvXCjtP3uKKw@MfJJU({^YahwPhaVGkHA~BCb%`9x&Hn0J;NpB{&=*ex^TdC>;yi`80VT zK3|*XC~)JZ$b(YeS* z2)YHs3P5x$m@>f`LB6{RmnWsoN?M{m-^-*_1N#}YRmtIj;J zG~duN-yPjp(qWY%7oL|)I$c1$UStleq}>3~xxXLek{IRt`l2Ls@CAnMFYteW%PO^- z30wgu5Fr5o$}8eNPWF!f1KjXGz?%<+lfC$KjZB!~SSyqYA8-6pMDFTSM(vPJ=n4JL z@FSX!XkBIPuSB+`2+6J{s+vkaj%-X5@vG{aM2_ST)KgX0 zgqeYEj36~UVf|I2g2PB5UCCz57 z9i<&IHg6n>Pzb#(N)9$xX3nth^Y8WQ2@6R*KKWP5K2PcWc{?&emxv24Y%6;IlM!ZHLML|Zj}H8gOu=%|OLG2M{on5Kiu{$`xf<$NZ~EILS3cNS xdRC%;VT3S0z~YLqGv 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 B@YMhS From 893c492580aed2a6f655d3d690e098ff25bcd6c0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 28 Sep 2023 16:04:05 +0700 Subject: [PATCH 085/207] chore(developer): remove obsolete files --- .../src/tike/Profiling/AQtimeModule1.aqt | Bin 90112 -> 0 bytes .../src/tike/compile/KeymanWebKeyCodes.pas | 297 ------------------ developer/src/tike/tike.dpr | 2 - developer/src/tike/tike.dproj | 2 - 4 files changed, 301 deletions(-) delete mode 100644 developer/src/tike/Profiling/AQtimeModule1.aqt delete mode 100644 developer/src/tike/compile/KeymanWebKeyCodes.pas diff --git a/developer/src/tike/Profiling/AQtimeModule1.aqt b/developer/src/tike/Profiling/AQtimeModule1.aqt deleted file mode 100644 index bfb4e702131175930f1013343d3087f4390201d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 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 G3H(1dH?xoc diff --git a/developer/src/tike/compile/KeymanWebKeyCodes.pas b/developer/src/tike/compile/KeymanWebKeyCodes.pas deleted file mode 100644 index ce087c3e90f..00000000000 --- a/developer/src/tike/compile/KeymanWebKeyCodes.pas +++ /dev/null @@ -1,297 +0,0 @@ -(* - Name: KeymanWebKeyCodes - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 20 Jun 2006 - - Modified Date: 20 Jun 2006 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 20 Jun 2006 - mcdurdin - Initial version -*) -unit KeymanWebKeyCodes; - -interface - -const - CKeymanWebKeyCodes: array[0..255] of Integer = ( - $FF, // L"K_?00", // &H0 - $FF, // L"K_LBUTTON", // &H1 - $FF, // L"K_RBUTTON", // &H2 - $FF, // L"K_CANCEL", // &H3 - $FF, // L"K_MBUTTON", // &H4 - $FF, // L"K_?05", // &H5 - $FF, // L"K_?06", // &H6 - $FF, // L"K_?07", // &H7 - $FF, // L"K_BKSP", // &H8 - $FF, // L"K_TAB", // &H9 - $FF, // L"K_?0A", // &HA - $FF, // L"K_?0B", // &HB - $FF, // L"K_KP5", // &HC - $FF, // L"K_ENTER", // &HD - $FF, // L"K_?0E", // &HE - $FF, // L"K_?0F", // &HF - $FF, // L"K_SHIFT", // &H10 - $FF, // L"K_CONTRO$00, // L", // &H11 - $FF, // L"K_ALT", // &H12 - $FF, // L"K_PAUSE", // &H13 - $FF, // L"K_CAPS", // &H14 - $FF, // L"K_KANJI?15", // &H15 - $FF, // L"K_KANJI?16", // &H16 - $FF, // L"K_KANJI?17", // &H17 - $FF, // L"K_KANJI?18", // &H18 - $FF, // L"K_KANJI?19", // &H19 - $FF, // L"K_?1A", // &H1A - $FF, // L"K_ESC", // &H1B - $FF, // L"K_KANJI?1C", // &H1C - $FF, // L"K_KANJI?1D", // &H1D - $FF, // L"K_KANJI?1E", // &H1E - $FF, // L"K_KANJI?1F", // &H1F - $40, // L"K_SPACE", // &H20 - $FF, // L"K_PGUP", // &H21 - $FF, // L"K_PGDN", // &H22 - $FF, // L"K_END", // &H23 - $FF, // L"K_HOME", // &H24 - $FF, // L"K_LEFT", // &H25 - $FF, // L"K_UP", // &H26 - $FF, // L"K_RIGHT", // &H27 - $FF, // L"K_DOWN", // &H28 - $FF, // L"K_SEL", // &H29 - $FF, // L"K_PRINT", // &H2A - $FF, // L"K_EXEC", // &H2B - $FF, // L"K_PRTSCN", // &H2C - $FF, // L"K_INS", // &H2D - $FF, // L"K_DEL", // &H2E - $FF, // L"K_HELP", // &H2F - $0A, // L"K_0", // &H30 - $01, // L"K_1", // &H31 - $02, // L"K_2", // &H32 - $03, // L"K_3", // &H33 - $04, // L"K_4", // &H34 - $05, // L"K_5", // &H35 - $06, // L"K_6", // &H36 - $07, // L"K_7", // &H37 - $08, // L"K_8", // &H38 - $09, // L"K_9", // &H39 - $FF, // L"K_?3A", // &H3A - $FF, // L"K_?3B", // &H3B - $FF, // L"K_?3C", // &H3C - $FF, // L"K_?3D", // &H3D - $FF, // L"K_?3E", // &H3E - $FF, // L"K_?3F", // &H3F - $FF, // L"K_?40", // &H40 - - $20, // L"K_A", // &H41 - $35, // L"K_B", // &H42 - $33, // L"K_C", // &H43 - $22, // L"K_D", // &H44 - $12, // L"K_E", // &H45 - $23, // L"K_F", // &H46 - $24, // L"K_G", // &H47 - $25, // L"K_H", // &H48 - $17, // L"K_I", // &H49 - $26, // L"K_J", // &H4A - $27, // L"K_K", // &H4B - $28, // L"K_L", // &H4C - $37, // L"K_M", // &H4D - $36, // L"K_N", // &H4E - $18, // L"K_O", // &H4F - $19, // L"K_P", // &H50 - $10, // L"K_Q", // &H51 - $13, // L"K_R", // &H52 - $21, // L"K_S", // &H53 - $14, // L"K_T", // &H54 - $16, // L"K_U", // &H55 - $34, // L"K_V", // &H56 - $11, // L"K_W", // &H57 - $32, // L"K_X", // &H58 - $15, // L"K_Y", // &H59 - $31, // L"K_Z", // &H5A - $FF, // L"K_?5B", // &H5B - $FF, // L"K_?5C", // &H5C - $FF, // L"K_?5D", // &H5D - $FF, // L"K_?5E", // &H5E - $FF, // L"K_?5F", // &H5F - $FF, // L"K_NP0", // &H60 - $FF, // L"K_NP1", // &H61 - $FF, // L"K_NP2", // &H62 - $FF, // L"K_NP3", // &H63 - $FF, // L"K_NP4", // &H64 - $FF, // L"K_NP5", // &H65 - $FF, // L"K_NP6", // &H66 - $FF, // L"K_NP7", // &H67 - $FF, // L"K_NP8", // &H68 - $FF, // L"K_NP9", // &H69 - $FF, // L"K_NPSTAR", // &H6A - $FF, // L"K_NPPLUS", // &H6B - $FF, // L"K_SEPARATOR", // &H6C - $FF, // L"K_NPMINUS", // &H6D - $FF, // L"K_NPDOT", // &H6E - $FF, // L"K_NPSLASH", // &H6F - $FF, // L"K_F1", // &H70 - $FF, // L"K_F2", // &H71 - $FF, // L"K_F3", // &H72 - $FF, // L"K_F4", // &H73 - $FF, // L"K_F5", // &H74 - $FF, // L"K_F6", // &H75 - $FF, // L"K_F7", // &H76 - $FF, // L"K_F8", // &H77 - $FF, // L"K_F9", // &H78 - $FF, // L"K_F10", // &H79 - $FF, // L"K_F11", // &H7A - $FF, // L"K_F12", // &H7B - $FF, // L"K_F13", // &H7C - $FF, // L"K_F14", // &H7D - $FF, // L"K_F15", // &H7E - $FF, // L"K_F16", // &H7F - $FF, // L"K_F17", // &H80 - $FF, // L"K_F18", // &H81 - $FF, // L"K_F19", // &H82 - $FF, // L"K_F20", // &H83 - $FF, // L"K_F21", // &H84 - $FF, // L"K_F22", // &H85 - $FF, // L"K_F23", // &H86 - $FF, // L"K_F24", // &H87 - - $FF, // L"K_?88", // &H88 - $FF, // L"K_?89", // &H89 - $FF, // L"K_?8A", // &H8A - $FF, // L"K_?8B", // &H8B - $FF, // L"K_?8C", // &H8C - $FF, // L"K_?8D", // &H8D - $FF, // L"K_?8E", // &H8E - $FF, // L"K_?8F", // &H8F - - $FF, // L"K_NUMLOCK", // &H90 - $FF, // L"K_SCROL$00, // L", // &H91 - - $FF, // L"K_?92", // &H92 - $FF, // L"K_?93", // &H93 - $FF, // L"K_?94", // &H94 - $FF, // L"K_?95", // &H95 - $FF, // L"K_?96", // &H96 - $FF, // L"K_?97", // &H97 - $FF, // L"K_?98", // &H98 - $FF, // L"K_?99", // &H99 - $FF, // L"K_?9A", // &H9A - $FF, // L"K_?9B", // &H9B - $FF, // L"K_?9C", // &H9C - $FF, // L"K_?9D", // &H9D - $FF, // L"K_?9E", // &H9E - $FF, // L"K_?9F", // &H9F - $FF, // L"K_?A0", // &HA0 - $FF, // L"K_?A1", // &HA1 - $FF, // L"K_?A2", // &HA2 - $FF, // L"K_?A3", // &HA3 - $FF, // L"K_?A4", // &HA4 - $FF, // L"K_?A5", // &HA5 - $FF, // L"K_?A6", // &HA6 - $FF, // L"K_?A7", // &HA7 - $FF, // L"K_?A8", // &HA8 - $FF, // L"K_?A9", // &HA9 - $FF, // L"K_?AA", // &HAA - $FF, // L"K_?AB", // &HAB - $FF, // L"K_?AC", // &HAC - $FF, // L"K_?AD", // &HAD - $FF, // L"K_?AE", // &HAE - $FF, // L"K_?AF", // &HAF - $FF, // L"K_?B0", // &HB0 - $FF, // L"K_?B1", // &HB1 - $FF, // L"K_?B2", // &HB2 - $FF, // L"K_?B3", // &HB3 - $FF, // L"K_?B4", // &HB4 - $FF, // L"K_?B5", // &HB5 - $FF, // L"K_?B6", // &HB6 - $FF, // L"K_?B7", // &HB7 - $FF, // L"K_?B8", // &HB8 - $FF, // L"K_?B9", // &HB9 - - $29, // L"K_COLON", // &HBA - $0C, // L"K_EQUA$00, // L", // &HBB - $38, // L"K_COMMA", // &HBC - $0B, // L"K_HYPHEN", // &HBD - $39, // L"K_PERIOD", // &HBE - $3A, // L"K_SLASH", // &HBF - $00, // L"K_BKQUOTE", // &HC0 - - $00, // L"K_?C1", // &HC1 - $00, // L"K_?C2", // &HC2 - $00, // L"K_?C3", // &HC3 - $00, // L"K_?C4", // &HC4 - $00, // L"K_?C5", // &HC5 - $00, // L"K_?C6", // &HC6 - $00, // L"K_?C7", // &HC7 - $00, // L"K_?C8", // &HC8 - $00, // L"K_?C9", // &HC9 - $00, // L"K_?CA", // &HCA - $00, // L"K_?CB", // &HCB - $00, // L"K_?CC", // &HCC - $00, // L"K_?CD", // &HCD - $00, // L"K_?CE", // &HCE - $00, // L"K_?CF", // &HCF - $00, // L"K_?D0", // &HD0 - $00, // L"K_?D1", // &HD1 - $00, // L"K_?D2", // &HD2 - $00, // L"K_?D3", // &HD3 - $00, // L"K_?D4", // &HD4 - $00, // L"K_?D5", // &HD5 - $00, // L"K_?D6", // &HD6 - $00, // L"K_?D7", // &HD7 - $00, // L"K_?D8", // &HD8 - $00, // L"K_?D9", // &HD9 - $00, // L"K_?DA", // &HDA - - $1A, // L"K_LBRKT", // &HDB - $1C, // L"K_BKSLASH", // &HDC - $1B, // L"K_RBRKT", // &HDD - $2A, // L"K_QUOTE", // &HDE - $00, // L"K_oDF", // &HDF - $00, // L"K_oE0", // &HE0 - $00, // L"K_oE1", // &HE1 - $30, // L"K_oE2", // &HE2 - $00, // L"K_oE3", // &HE3 - $00, // L"K_oE4", // &HE4 - - $00, // L"K_?E5", // &HE5 - - $00, // L"K_oE6", // &HE6 - - $00, // L"K_?E7", // &HE7 - $00, // L"K_?E8", // &HE8 - - $00, // L"K_oE9", // &HE9 - $00, // L"K_oEA", // &HEA - $00, // L"K_oEB", // &HEB - $00, // L"K_oEC", // &HEC - $00, // L"K_oED", // &HED - $00, // L"K_oEE", // &HEE - $00, // L"K_oEF", // &HEF - $00, // L"K_oF0", // &HF0 - $00, // L"K_oF1", // &HF1 - $00, // L"K_oF2", // &HF2 - $00, // L"K_oF3", // &HF3 - $00, // L"K_oF4", // &HF4 - $00, // L"K_oF5", // &HF5 - - $00, // L"K_?F6", // &HF6 - $00, // L"K_?F7", // &HF7 - $00, // L"K_?F8", // &HF8 - $00, // L"K_?F9", // &HF9 - $00, // L"K_?FA", // &HFA - $00, // L"K_?FB", // &HFB - $00, // L"K_?FC", // &HFC - $00, // L"K_?FD", // &HFD - $00, // L"K_?FE", // &HFE - $00 // L"K_?FF" // &HFF - ); - -implementation - -end. - diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index 15993827af9..11ac819f88b 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -88,7 +88,6 @@ uses utilfiletypes in '..\..\..\common\windows\delphi\general\utilfiletypes.pas', UfrmNewFileDetails in 'dialogs\UfrmNewFileDetails.pas' {frmNewFileDetails}, StockFileNames in '..\..\..\common\windows\delphi\general\StockFileNames.pas', - KeymanWebKeyCodes in 'compile\KeymanWebKeyCodes.pas', utildir in '..\..\..\common\windows\delphi\general\utildir.pas', ADOX_TLB in '..\..\..\common\windows\delphi\tlb\ADOX_TLB.pas', ADODB_TLB in '..\..\..\common\windows\delphi\tlb\ADODB_TLB.pas', @@ -276,7 +275,6 @@ uses UfrmDebugStatus_Platform in 'debug\UfrmDebugStatus_Platform.pas' {frmDebugStatus_Platform}, UfrmDebugStatus_Options in 'debug\UfrmDebugStatus_Options.pas' {frmDebugStatus_Options}, Keyman.Developer.System.KeymanDeveloperPaths in 'main\Keyman.Developer.System.KeymanDeveloperPaths.pas', - Keyman.Developer.System.ValidateKpsFile in '..\common\delphi\compiler\Keyman.Developer.System.ValidateKpsFile.pas', Keyman.Developer.UI.UfrmServerOptions in 'dialogs\Keyman.Developer.UI.UfrmServerOptions.pas' {frmServerOptions}, Keyman.Developer.System.ServerAPI in 'http\Keyman.Developer.System.ServerAPI.pas', Keyman.System.FontLoadUtil in 'main\Keyman.System.FontLoadUtil.pas', diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index 1f5ff54b221..3ff3d319ca9 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -235,7 +235,6 @@ frmNewFileDetails - @@ -541,7 +540,6 @@ dfm -
    frmServerOptions dfm From 4a7820fdeb4f4b91f26fb24c6917f532e0742a0c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 28 Sep 2023 16:05:43 +0700 Subject: [PATCH 086/207] chore(developer): remove obsolete menu item --- developer/src/tike/main/UfrmMain.dfm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/developer/src/tike/main/UfrmMain.dfm b/developer/src/tike/main/UfrmMain.dfm index 0b88243caf9..6339840fac8 100644 --- a/developer/src/tike/main/UfrmMain.dfm +++ b/developer/src/tike/main/UfrmMain.dfm @@ -3127,10 +3127,6 @@ inherited frmKeymanDeveloper: TfrmKeymanDeveloper Caption = '&Exception Test' OnClick = mnuToolsDebugTestsExceptionTestClick end - object mnuToolsDebugTestsCompilerExceptionTest: TMenuItem - Caption = '&Compiler Exception Test' - OnClick = mnuToolsDebugTestsCompilerExceptionTestClick - end end end object Help1: TMenuItem From a86bcb9248dbce46344306daa507dcf6da6c9f34 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 28 Sep 2023 16:10:46 +0700 Subject: [PATCH 087/207] chore(common): fixup legacy test keyboards --- .../test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn | 3 +-- common/test/keyboards/baseline/k_018___nul_testing.kmn | 7 +++---- .../obolo_chwerty_6347/source/obolo_chwerty_6347.kps | 2 +- .../obolo_chwerty_6351/source/obolo_chwerty_6351.kps | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn b/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn index a66fac420a9..07e37ee681b 100644 --- a/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn +++ b/common/test/keyboards/baseline/k_017___space_mnemonic_kbd.kmn @@ -4,8 +4,7 @@ c keys: [K_A][K_B][K_SPACE][K_C][K_SPACE][K_D][K_SPACE][K_D][K_E] c expected: XYZ store(&VERSION) '9.0' -NAME "Alt key tests" -HOTKEY "[CTRL SHIFT K_A]" +store(&HOTKEY) "[CTRL SHIFT K_A]" store(&mnemoniclayout) "1" begin Unicode > use(main) diff --git a/common/test/keyboards/baseline/k_018___nul_testing.kmn b/common/test/keyboards/baseline/k_018___nul_testing.kmn index bc6e41206d5..20da4d4af70 100644 --- a/common/test/keyboards/baseline/k_018___nul_testing.kmn +++ b/common/test/keyboards/baseline/k_018___nul_testing.kmn @@ -3,11 +3,10 @@ c Description: Tests the processing of nul in LHS of rules c keys: [K_A][K_A] c expected: OKa -VERSION 9.0 -NAME "Nul test" -HOTKEY "[CTRL SHIFT K_N]" +store(&VERSION) "9.0" +store(&HOTKEY) "[CTRL SHIFT K_N]" -begin unicode > use(main) +begin unicode > use(main) group(Main) using keys nul + 'a' > 'OK' diff --git a/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps b/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps index 9e3f5a437ab..ebf22548312 100644 --- a/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps +++ b/common/test/keyboards/obolo_chwerty_6347/source/obolo_chwerty_6347.kps @@ -113,7 +113,7 @@ obolo_chwerty_6347 1.2.1 - Obolo (Andoni) + Obolo (Andoni) diff --git a/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps b/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps index 11e8de9c3bf..df9a3b81cc6 100644 --- a/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps +++ b/common/test/keyboards/obolo_chwerty_6351/source/obolo_chwerty_6351.kps @@ -113,7 +113,7 @@ obolo_chwerty_6351 1.2.1 - Obolo (Andoni) + Obolo (Andoni) From a5cbf6aab864b822a3a589f30cee96c33bd2fc41 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 28 Sep 2023 16:40:29 +0700 Subject: [PATCH 088/207] fix(web): test page path on the index page --- web/src/test/manual/web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/manual/web/index.html b/web/src/test/manual/web/index.html index 6d5c4bb4660..2597d8caac5 100644 --- a/web/src/test/manual/web/index.html +++ b/web/src/test/manual/web/index.html @@ -64,7 +64,7 @@

    Test Caps Lock Layer (#3620)

    Test Start of Sentence (#3621)

    Test start of sentence keyboard rules (#5963)

    Tests predictive text & other handling of rule matching when the final rule group does not match (#6005)

    -

    Tests handling of new default-subkey feature (#9430)

    +

    Tests handling of new default-subkey feature (#9430)

    Test special characters rendering with keymanweb-osk.ttf (#9469)

    Other

    Keystroke processing regression test engine.

    From 08f0b4dfed7efefb23db3007be3daf7aa723fdec Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 19 Sep 2023 19:08:22 +0200 Subject: [PATCH 089/207] =?UTF-8?q?chore(linux):=20Extract=20free=5Finfo?= =?UTF-8?q?=20method=20in=20kmpdetails.c=20=F0=9F=8F=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add guard to kmpdetails.h. --- linux/ibus-keyman/src/kmpdetails.c | 28 ++++++++++++++++++++-------- linux/ibus-keyman/src/kmpdetails.h | 7 ++++++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/linux/ibus-keyman/src/kmpdetails.c b/linux/ibus-keyman/src/kmpdetails.c index eba95f4ef97..18ffde73ac8 100644 --- a/linux/ibus-keyman/src/kmpdetails.c +++ b/linux/ibus-keyman/src/kmpdetails.c @@ -455,6 +455,24 @@ kmp_json_status free_keyboard_details(keyboard_details *kbd_details) return JSON_OK; } +void free_info(gpointer data) { + kmp_info *info = (kmp_info *)data; + if (info->name) + g_free(info->name); + if (info->version) + g_free(info->version); + if (info->copyright) + g_free(info->copyright); + if (info->author_desc) + g_free(info->author_desc); + if (info->author_url) + g_free(info->author_url); + if (info->website_desc) + g_free(info->website_desc); + if (info->website_url) + g_free(info->website_url); +} + kmp_json_status free_kmp_details(kmp_details * details) { g_assert(details != NULL); @@ -462,15 +480,9 @@ kmp_json_status free_kmp_details(kmp_details * details) g_free(details->system.keymanDeveloperVersion); g_free(details->options.readmeFile); g_free(details->options.graphicFile); - g_free(details->info.name); - g_free(details->info.version); - g_free(details->info.copyright); - g_free(details->info.author_desc); - g_free(details->info.author_url); - g_free(details->info.website_desc); - g_free(details->info.website_url); + free_info(&details->info); if (details->keyboards != NULL) { - g_list_free_full(details->keyboards, (GDestroyNotify)free_keyboard); + g_list_free_full(details->keyboards, (GDestroyNotify)free_keyboard); } if (details->files != NULL) { g_list_free_full(details->files, (GDestroyNotify)free_fileinfo); diff --git a/linux/ibus-keyman/src/kmpdetails.h b/linux/ibus-keyman/src/kmpdetails.h index 3bcf6a97390..586f815baef 100644 --- a/linux/ibus-keyman/src/kmpdetails.h +++ b/linux/ibus-keyman/src/kmpdetails.h @@ -1,3 +1,6 @@ +#ifndef __KMPDETAILS_H__ +#define __KMPDETAILS_H__ + // kmp details from json #include @@ -75,4 +78,6 @@ kmp_json_status get_kmp_details(const gchar *kmp_dir, kmp_details *details); kmp_json_status free_kmp_details(kmp_details * details); kmp_json_status get_keyboard_details(const gchar *kmp_dir, const gchar *id, keyboard_details *details); kmp_json_status free_keyboard_details(keyboard_details * details); -kmp_json_status print_kmp_details(kmp_details * details); \ No newline at end of file +kmp_json_status print_kmp_details(kmp_details * details); + +#endif // __KMPDETAILS_H__ From f8866fea9537a4e8c680c4d0f3d639a9b6645f8c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 28 Sep 2023 21:35:51 +0700 Subject: [PATCH 090/207] chore(web): Update test keyboard again --- web/src/test/manual/web/issue9469/test9469/build/test9469.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/manual/web/issue9469/test9469/build/test9469.js b/web/src/test/manual/web/issue9469/test9469/build/test9469.js index 45ed85b5454..10e028fdd68 100644 --- a/web/src/test/manual/web/issue9469/test9469/build/test9469.js +++ b/web/src/test/manual/web/issue9469/test9469/build/test9469.js @@ -1 +1 @@ -if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":"*ZWNJGeneric*"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.138.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file +if(typeof keyman === 'undefined') {console.log('Keyboard requires KeymanWeb 10.0 or later');if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");} else {KeymanWeb.KR(new Keyboard_test9469());}function Keyboard_test9469(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_test9469";this.KN="test9469";this.KMINVER="10.0";this.KV={F:' 1em "Arial"',K102:0};this.KV.KLS={};this.KV.BK=(function(x){var e=Array.apply(null,Array(65)).map(String.prototype.valueOf,""),r=[],v,i,m=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];for(i=m.length-1;i>=0;i--)if((v=x[m[i]])||r.length)r=(v?v:e).slice().concat(r);return r})(this.KV.KLS);this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.1";this.KMBM=0x0000;this.KVKD="T_ZWNJ T_ZWNJIOS T_ZWNJANDROID";this.KVKL={"phone":{"font":"Tahoma","displayUnderlying":false,"layer":[{"id":"default","row":[{"id":"1","key":[{"id":"K_TAB","text":"*Tab*"},{"id":"T_TABLEFT","text":"*TabLeft*"},{"id":"U_0020","text":"*Sp*"},{"id":"U_00A0","text":"*NBSp*"},{"id":"U_202F","text":"*NarNBSp*"},{"id":"U_2000","text":"*EnQ*"},{"id":"U_2001","text":"*EmQ*"},{"id":"U_2002","text":"*EnSp*"},{"id":"U_2003","text":"*EmSp*"},{"id":"U_2008","text":"*PunctSp*"}]},{"id":"2","key":[{"id":"U_2009","pad":"50","text":"*ThSp*"},{"id":"U_200A","text":"*HSp*"},{"id":"U_200B","text":"*ZWSp*"},{"id":"U_200E","text":"*LTRM*"},{"id":"U_200F","text":"*RTLM*"},{"id":"U_00AD","text":"*SH*"},{"id":"T_0009","text":"*HTab*"},{"id":"U_200F","text":"*RTLM*"},{"id":"T_RTLBKSP","sp":"1","text":this._v>13?"*RTLBkSp*":"*BkSp*"},{"id":"T_new_338","sp":"1","text":this._v>13?"*LTRBkSp*":"*BkSp*"}]},{"id":"3","key":[{"nextlayer":"shift","id":"K_SHIFT","sp":"1","text":"*Shift*","sk":[{"id":"T_new_206","text":"*Alt*"},{"id":"T_new_205","text":"*Ctrl*"},{"id":"T_new_207","text":"*Caps*"},{"id":"T_new_208","text":"*ABC*"},{"id":"T_new_209","text":"*abc*"},{"id":"T_new_210","text":"*Symbol*"},{"id":"T_new_211","text":"*AltGr*"},{"id":"T_new_212","text":"*LAlt*"},{"id":"T_new_258","text":"*RAlt*"},{"id":"T_new_259","text":"*LCtrl*"},{"id":"T_new_260","text":"*RCtrl*"},{"id":"T_new_261","text":"*LAltCtrlShift*"},{"id":"T_new_262","text":"*RAltCtrlShift*"},{"id":"T_new_263","text":"*AltShift*"},{"id":"T_new_264","text":"*CtrlShift*"},{"id":"T_new_265","text":"*AltCtrlShift*"},{"id":"T_new_266","text":"*LAltShift*"},{"id":"T_new_267","text":"*RAltShift*"},{"id":"T_new_268","text":"*LCtrlShift*"},{"id":"T_new_269","text":"*RCtrlShift*"},{"id":"T_new_292","text":this._v>13?"*ShiftLock*":"*Shift*"},{"id":"T_new_293","text":this._v>13?"*ShiftedLock*":"*Shifted*"}]},{"id":"T_ZWNJ","text":"*ZWNJGeneric*"},{"id":"T_ZWNJIOS","text":this._v>13?"*ZWNJiOS*":"<|>"},{"id":"T_ZWNJANDROID","text":this._v>13?"*ZWNJAndroid*":"<|>"},{"id":"U_200D","text":"*ZWJ*"},{"id":"U_2060","text":"*WJ*"},{"id":"U_034F","text":"*CGJ*"},{"id":"K_M","text":"m"},{"id":"K_PERIOD","text":".","sk":[{"id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"id":"K_COLON","text":";"}]},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"590","id":"K_SPACE"},{"id":"K_ROPT","text":"*Hide*"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*","sk":[{"id":"T_new_270","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"T_new_295","text":this._v>13?"*LTREnter*":"*Enter*"}]}]}]},{"id":"shift","row":[{"id":"1","key":[{"id":"K_Q","text":"*Alt*"},{"id":"K_W","text":"*Ctrl*"},{"id":"K_E","text":"*Caps*"},{"id":"K_R","text":"*ABC*"},{"id":"K_T","text":"*abc*"},{"id":"K_Y","text":"*123*"},{"id":"K_U","text":"*Symbol*"},{"id":"K_I","text":"*Currency*"},{"id":"K_O","text":"*Shifted*"},{"id":"K_P","text":"*AltGr*"}]},{"id":"2","key":[{"id":"K_A","pad":"50","text":"*LAlt*"},{"id":"K_S","text":"*RAlt*"},{"id":"K_D","text":"*LCtrl*"},{"id":"K_F","text":"*RCtrl*"},{"id":"K_G","text":"*LAltCtrl*"},{"id":"K_H","text":"*RAltCtrl*"},{"id":"K_J","text":"*LAltCtrlShift*"},{"id":"K_K","text":"*RAltCtrlShift*"},{"id":"K_L","text":"*AltShift*"},{"id":"T_new_232","text":"*CtrlShift*"}]},{"id":"3","key":[{"nextlayer":"default","id":"K_SHIFT","sp":"2","text":"*Shifted*"},{"id":"K_Z","text":"*AltCtrlShift*"},{"id":"K_X","text":"*LAltShift*"},{"id":"K_C","text":"*RAltShift*"},{"id":"K_V","text":"*RAltShift*"},{"id":"K_B","text":"*RCtrlShift*"},{"id":"K_N","text":this._v>13?"*RTLEnter*":"*Enter*"},{"id":"K_M","text":this._v>13?"*LTREnter*":"*Enter*"},{"layer":"default","id":"K_PERIOD","text":".","sk":[{"layer":"default","id":"K_COMMA","text":","},{"layer":"shift","id":"K_1","text":"!"},{"layer":"shift","id":"K_SLASH","text":"?"},{"layer":"default","id":"K_QUOTE","text":"'"},{"layer":"shift","id":"K_QUOTE","text":"\""},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"layer":"shift","id":"K_COLON","text":":"},{"layer":"default","id":"K_COLON","text":";"}]},{"id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"numeric","width":"150","id":"K_NUMLOCK","sp":"1","text":"*123*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]},{"id":"numeric","row":[{"id":"1","key":[{"id":"K_1","text":"1"},{"id":"K_2","text":"2"},{"id":"K_3","text":"3"},{"id":"K_4","text":"4"},{"id":"K_5","text":"5"},{"id":"K_6","text":"6"},{"id":"K_7","text":"7"},{"id":"K_8","text":"8"},{"id":"K_9","text":"9"},{"id":"K_0","text":"0"}]},{"id":"2","key":[{"layer":"shift","id":"K_4","pad":"50","text":"$"},{"layer":"shift","id":"K_2","text":"@"},{"layer":"shift","id":"K_3","text":"#"},{"layer":"shift","id":"K_5","text":"%"},{"layer":"shift","id":"K_7","text":"&"},{"layer":"shift","id":"K_HYPHEN","text":"_"},{"layer":"default","id":"K_EQUAL","text":"="},{"layer":"shift","id":"K_BKSLASH","text":"|"},{"layer":"default","id":"K_BKSLASH","text":"\\"},{"width":"10","sp":"10"}]},{"id":"3","key":[{"id":"K_LBRKT","pad":"110","text":"[","sk":[{"id":"U_00AB","text":"\u00AB"},{"layer":"shift","id":"K_COMMA","text":"<"},{"layer":"shift","id":"K_LBRKT","text":"{"}]},{"layer":"shift","id":"K_9","text":"("},{"layer":"shift","id":"K_0","text":")"},{"id":"K_RBRKT","text":"]","sk":[{"id":"U_00BB","text":"\u00BB"},{"layer":"shift","id":"K_PERIOD","text":">"},{"layer":"shift","id":"K_RBRKT","text":"}"}]},{"layer":"shift","id":"K_EQUAL","text":"+"},{"id":"K_HYPHEN","text":"-"},{"layer":"shift","id":"K_8","text":"*"},{"id":"K_SLASH","text":"\/"},{"width":"100","id":"K_BKSP","sp":"1","text":"*BkSp*"}]},{"id":"4","key":[{"nextlayer":"default","width":"150","id":"K_LOWER","sp":"1","text":"*abc*"},{"width":"120","id":"K_LOPT","sp":"1","text":"*Menu*"},{"width":"610","id":"K_SPACE"},{"width":"150","id":"K_ENTER","sp":"1","text":"*Enter*"}]}]}]}};this.KVER="16.0.141.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;if(k.KKM(e,16384,256)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,257)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}else if(k.KKM(e,16384,258)) {if(1){r=m=1;k.KDC(0,t);k.KO(-1,t,"‌");}}return r;};} \ No newline at end of file From ffd428da21b2854349992ac4c63eccc881fff64a Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 28 Sep 2023 21:36:00 +0700 Subject: [PATCH 091/207] chore(android): Update test keyboard again --- .../app/src/main/assets/test9469.kmp | Bin 4417 -> 4421 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp b/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp index f09531b5651019e83d56cc684f04cdb8f88feae7..8390d5dee317ad8a78b8ee4d33805019846f9907 100644 GIT binary patch delta 2073 zcmZvdcT|&E8pc0BBtU=!14`3O5Ev8bMMyx5A`(cbDntPZ9VwwiK?3=Ztc#3FFAnyy zNE3(($?^*?+nXcn>s{({JJR6{MZI8@+@gY~2Zxll9a@{sWTd5v z?<(KqL$6=N=#oR&uk3Oob$^sII`04#k&2XRj$8A4`|hUbUq`s^*bsrAR{-_=?io5T zUvk$1J{^;)EM>b-DiS6fb~!VQQ`SQ-CNspUSB_+dcUn5RRuLchq4v;FlRpTuFUFf! zwp<3}NJZudTsZF8W9QR#GqX|MXu4vQIU)@J zNC*Hx0RW(dB+zUutZa-Tqmw@7yG4585nXO2;B6F&ETJwApx*hsMayyyA8I0C)S}uu zA$e<=dk9~hOX@=>N+IMXm^>aYAUoqZR~jLH68RNa^$O%ORX=~OOW$Is(0aW8I$dP9 z(BIqB`ylTU7L&SIk3%~;3FAh`HNLp|EOogDG&AYyxklJ^+*#hZ&0mUj{DE6>5)dw_ z=i2=JBG-*`Pg$H^gIh7BHQsuWhEWD(P9;d>Bn8|cMDxI~ill;T&xtE!S5%>UF~8S8 zw2a+pq2e`&WvPzt9aTSGP^0w9Oi<*v&qHDr<~gYOdW%*0$*Y`4>Q#EEZAPI@F=k`> zU*9)c@N@ZC$&5*{5+ho)mgjYs7}HeC9=qhhU+`l4Wu+1Ok8P|O!W@o7 zW0X?YOqPyf%}v%D#neDtglOVDm$kZPxjZtf_1|6h8{dsx=Og=gD|pUrtGA=y$A&fr z<0_g%%F#vMBromLjeIyMkEyJYaR5}NFpu85VwSj^YYiqZ z>D_#v={r^Iqk)OP0Jvt^5Mq-SSbf6h>AJPx+FAyE{_*eCYpQ}Tpw>P)@I~KLOlEN4 z{Pu63krr0u=Ro~Hk|xHx(C63B1StSGv|$Iu4J#yuL{(W!YZF5CmKr{eT6 zie^o>#orJk`YmMvth)hrNrI?SUdR3p(Hy)}ESjPi%D(F{SzZ@YcSe~tf=^C@&HE1! zoQ;f(^Yqh$o1A2Z@O0+h1vt{f37W*KI^@hWjP4i#-#hS$6wNPNM3|sGK#awmKl)pq zl@k*L@>!H?zRZF2E%|pQh=H05+N!(bW*vJ~n{js(ku(b(w^G7T#KS%hLwH$rid~1y zVZoCuz@1|dmGV)ellxOZpYy#p+O1gj(>FV>* zboZ}ety16EZ!))KnFOJw01#N*0I#qCJwAjI{4eGnJ+536ShMb|ic4c&X?ev@4Fu!% zJF2Qqa~*LrQK~Ks9(f7&fhkdHg--J;KBO5}4$t-{Jss|w=;2PS&dZ;Eb^1(dAzKYm zg3f;C;FnPgQ9}-PLhd?_!KV3g#j~k2&W@piBRB#*4759ftfH8WPx-6+PRk1K-)@vi zL|zHbM8|AFKOe+r=wVKnTH4;~pCZ4U$T}@4gGfH5%MG62#?V=L!dE5gOqfqb zZwtQY)ott`FF@<^61cW8)1{;lz`x@&zgETkTYS!_7|CyD=|3lZJ_=@v+dOSLP)!a1 zqILdk_6_tK3%Rim%YK&vrxX51It!*P#mujSY5Dv=%WP`=sq$eXOujSF_bT7j{Xb>@ R_V8%_PPoq2QkBh{e*oj8p(_9Y delta 2079 zcmZuy3pCU1AOCGkOEN?^*JyLiy;1aLb6vA6w~ee(mP^#wl%j@S-B2-?Od6G z268gQ6K7657PAD$mA<_}hXoF*`48OYx&<{un-am6>1X3Xr6og%zt+h0b5P4Fi-TN5 z>>ewsu4!s;RTPfAkHmJ*_!$sDJkx5TuE3MrF zd$r23T7l?OnXF58+pK?z{O#?#+3NEppJx|B?N2%ARiUB}QZl}(v<2v+h0i1x495H( zS%NjZ{W|f28(($E*wVJa)Vo?Td$}PoyFT#jB!cGlHw^r1U5U(y%maLcLbQEr6nFA! z=5YT;R)NU>|rob2J3j;CWrm8YUVK+?NWe?*19E^#)< zuQ?XLg>K?azMCHQ9w=k`OlEU7`Cynr)#Pxu89#wd)T_R)*OoW@3bZ|;rE+xK4GaJ} zPykQ>0Dw-R(RWxQcbFcDj2&j8j(DL|lRd68Zs_Xzg~rU|(~p`&s@)$d+VZ|QOCA|` zP!`kkJhP=f15wGXHAH8dI-&7O5L79IR3}fGTO(Gk60aV!VPX~zuJa$;lTQul+%;Pm z7cxQ_@oIA}$aLA=7-#I_RuDD7+ax$lTxi7y)+ffPq<8LIwnL7IG@jBF%@RCpZq9I+ zSNRY)RO?QaYs-sRR;)S^)7VEerm7z&x$@rTV@UthBJN>Gy=zk_+lW{v{eX5lDWtG( z`@$AGH^~u(iF~0h;_Zg1R=>`F?2n!E+j~zC-65$gs^ro#{}zc}nva~Jfhd<#x5vH` zKL3LKE`|jevzspVUSm;>1IhD+b+mIhk&4|^n^AP5QB!?G1L}iG>8d)$-6#HC8Zk)+ z&^7Z$l7Q5cz9%khCEDq28jM@V6E2P}g`Kp2rSqRdH-TAjQ*xSYZO&ZveXqhTNi4$+ zp4J0V^68y+>d%Ulv0j%l%l+Pyns)obqZ6AO#L+LQlUgr0l&4!sxGFT0s;UdDIeBIxp0CKap+hi#Wa*2c{=0e-e=}Y z&a;wBVf`QPl$Kp$V~`0>&dP3?5G*GbkuAO;7}Z0I?e;o?wj|nQe;(X1b+w1r!JiqK z8C@U#+DN*iJQA(E+2{@Sx13c4OP;cIyEH)YF&L%m3U?lN2vqH@#yNIsJ&?8Ws zxmGYH#`og^`QW=8xSJj(y0v~to>k1bx;1&;D#}&#@pfrKS5N%Tyt`4=F{U1s=o{?S zz0fPFYbHGAfwlF`$V`%hB&3AdqD@dV0_K@K zHh2b%=$K@<=ZcHmmu}Jx!{Wnj1s1{$x$D^xQ>gwU-#8U0Xmrmjx}u}yX2)bXO?{J~ z@e0N3N4;Y&gX`y=1`q8g3ilAF`h;KH)h*?W`4*Xr!wFdtMRYi@G%r@5N}{OsfN5daw}`EQd*m za0*TT_9d-s4!HoYFu&$%mmD_%cGf9?KYUk9fNwu}(GG4QY+h?n?zUR9#xt~?(Stgt z8fF6B@83R>VzP?dfYD2L=8 z5O5N(XZrcx=jir{o43iKh-fwQ{GiU*4r+VSnQQY4f9A~SRqWsgLJUKam$opQQ)d=o z5;;5;1Xcz9CzH&FFsq+3IRP_Wy`q`_c625}$>Lwv3elKkrS&^qAip&CaN6&g> Date: Mon, 18 Sep 2023 11:48:41 +0200 Subject: [PATCH 092/207] =?UTF-8?q?refactor(linux):=20Add=20more=20tests?= =?UTF-8?q?=20for=20keymanutil.c=20=F0=9F=8F=98=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- linux/ibus-keyman/src/keymanutil.c | 325 ++++----- linux/ibus-keyman/src/keymanutil_internal.h | 39 ++ linux/ibus-keyman/src/test/keymanutil_tests.c | 641 +++++++++++++++++- linux/ibus-keyman/src/test/meson.build | 4 +- linux/ibus-keyman/src/test/run-tests.sh | 2 +- linux/ibus-keyman/src/test/setup-tests.sh | 2 +- .../src/test/{ => testdata}/kmp.json | 534 +++++++-------- linux/ibus-keyman/src/test/testdata/kmp1.json | 123 ++++ linux/ibus-keyman/src/test/testdata/kmp2.json | 127 ++++ 9 files changed, 1340 insertions(+), 457 deletions(-) create mode 100644 linux/ibus-keyman/src/keymanutil_internal.h rename linux/ibus-keyman/src/test/{ => testdata}/kmp.json (95%) create mode 100644 linux/ibus-keyman/src/test/testdata/kmp1.json create mode 100644 linux/ibus-keyman/src/test/testdata/kmp2.json diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 9eb42c173b2..4e2608f246b 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -58,15 +58,16 @@ #include #include "bcp47util.h" -#include "keymanutil.h" #include "kmpdetails.h" #include "keyman-version.h" +#include "keymanutil.h" +#include "keymanutil_internal.h" #define N_(text) text // change to keyman_get_kmpdirs_fromdir // returns list of directories with kmp.json -GList * keyman_get_kmpdirs_fromdir( GList *keyboard_list, const gchar * path) +GList * keyman_get_kmpdirs_fromdir(GList *kmpdir_list, const gchar * path) { DIR *dir = opendir(path); @@ -80,13 +81,13 @@ GList * keyman_get_kmpdirs_fromdir( GList *keyboard_list, const gchar * path) if (S_ISDIR(filestat.st_mode)) { if(g_strcmp0(file->d_name, ".") != 0 && g_strcmp0(file->d_name, "..") != 0) - keyboard_list = keyman_get_kmpdirs_fromdir(keyboard_list, absfn); + kmpdir_list = keyman_get_kmpdirs_fromdir(kmpdir_list, absfn); } // Looking for kmp.json else if (S_ISREG(filestat.st_mode) && g_strcmp0(file->d_name, "kmp.json") == 0) { g_message("adding kmp path %s", path); - keyboard_list=g_list_append(keyboard_list, g_strdup(path)); + kmpdir_list=g_list_append(kmpdir_list, g_strdup(path)); } g_free(absfn); @@ -94,7 +95,7 @@ GList * keyman_get_kmpdirs_fromdir( GList *keyboard_list, const gchar * path) } closedir(dir); } - return keyboard_list; + return kmpdir_list; } gchar * keyman_get_icon_file(const gchar *kmx_file) @@ -115,7 +116,7 @@ gchar * keyman_get_icon_file(const gchar *kmx_file) return full_path_to_icon_file; } -static IBusEngineDesc * +IBusEngineDesc * ibus_keyman_engine_desc_new (gchar * file_name, gchar *name, gchar *description, @@ -152,127 +153,157 @@ ibus_keyman_engine_desc_new (gchar * file_name, return engine_desc; } -GList * -ibus_keyman_add_engines(GList * engines, GList * kmpdir_list) -{ - GList *p, *k, *l, *e; - - for (p=kmpdir_list; p != NULL; p = p->next) { - gchar * kmp_dir = (gchar *) p->data; - - kmp_details *details = g_new0(kmp_details, 1); - get_kmp_details(kmp_dir, details); - - for (k=details->keyboards; k != NULL; k = k->next) { - kmp_keyboard *keyboard = (kmp_keyboard *) k->data; - gboolean alreadyexists = FALSE; - - for (e=engines; e != NULL && alreadyexists == FALSE; e = e->next) { - IBusEngineDesc *engine_desc = (IBusEngineDesc *) e->data; - const gchar *version = ibus_engine_desc_get_version(engine_desc); - const gchar *engine_name = ibus_engine_desc_get_name(engine_desc); - gchar *kmx_file = g_path_get_basename(engine_name); - if (g_strcmp0(kmx_file, keyboard->kmx_file) == 0 && g_strcmp0(version, keyboard->version) >= 0) { - alreadyexists = TRUE; - g_debug("keyboard %s already exists at version %s which is newer or same as %s", kmx_file, version, keyboard->version); - } - g_free(kmx_file); - } +IBusEngineDesc * +get_engine_for_language( + kmp_keyboard *keyboard, + kmp_info *info, + keyboard_details *kbd_details, + gchar *kmp_dir, + gchar *lang_id, + gchar *lang_name) { + IBusEngineDesc* engine_desc = NULL; + if (!lang_id || !strlen(lang_id)) + return engine_desc; - if (!alreadyexists) { - gchar *abs_kmx = g_strjoin("/", kmp_dir, keyboard->kmx_file, NULL); - gchar *json_file = g_strjoin(".", keyboard->id, "json", NULL); - keyboard_details *kbd_details = g_new0(keyboard_details, 1); - get_keyboard_details(kmp_dir, json_file, kbd_details); - g_free(json_file); - - if (keyboard->languages != NULL) { - for (l=keyboard->languages; l != NULL; l = l->next) { - kmp_language *language = (kmp_language *) l->data; - if (language->id != NULL) { - int capacity = 255; - gchar *name_with_lang = NULL; - gchar *minimized_tag = g_new0(gchar, capacity); - int result = bcp47_minimize(language->id, minimized_tag, capacity); - if (result < 0) { - g_strlcpy(minimized_tag, language->id, capacity); - } - - gchar *lang_code = g_new0(gchar, capacity); - if (!bcp47_get_language_code(minimized_tag, lang_code, capacity)) { - g_strlcpy(lang_code, minimized_tag, capacity); - } - - // If ibus doesn't know about the language then append the - // language name to the keyboard name - if (language->name != NULL) { - gchar *ibus_lang = ibus_get_untranslated_language_name(lang_code); - g_debug("%s: untranslated ibus language for %s: %s", __FUNCTION__, minimized_tag, ibus_lang); - if (g_strcmp0(ibus_lang, "Other") == 0) { - name_with_lang = g_strjoin(" - ", keyboard->name, language->name, NULL); - } - g_free(ibus_lang); - } - - gchar *id_with_lang = g_strjoin(":", minimized_tag, abs_kmx, NULL); - - g_message("adding engine %s", id_with_lang); - engines = g_list_append( - engines, - ibus_keyman_engine_desc_new( - id_with_lang, // lang:kmx full path - name_with_lang ? name_with_lang : keyboard->name, // longname - kbd_details->description, // description - details->info.copyright, // copyright if available - lang_code, // language, most are ignored by ibus except major languages - kbd_details->license, // license - details->info.author_desc, // author name only, not email - keyman_get_icon_file(abs_kmx), // icon full path - "us", // layout defaulting to us (en-US) - keyboard->version)); - g_free(lang_code); - g_free(minimized_tag); - g_free(id_with_lang); - g_free(name_with_lang); - } - } - } - else { - g_message("adding engine %s", abs_kmx); - engines = g_list_append (engines, - ibus_keyman_engine_desc_new (abs_kmx, // kmx full path - keyboard->name, // longname - kbd_details->description, // description - details->info.copyright, // copyright if available - NULL, // language, most are ignored by ibus except major languages - kbd_details->license, // license - details->info.author_desc, // author name only, not email - keyman_get_icon_file(abs_kmx), // icon full path - "us", // layout defaulting to us (en-US) - keyboard->version)); - } - free_keyboard_details(kbd_details); - g_free(kbd_details); - g_free(abs_kmx); - } + int capacity = 255; + gchar *name_with_lang = NULL; + gchar *minimized_tag = g_new0(gchar, capacity); + int result = bcp47_minimize(lang_id, minimized_tag, capacity); + if (result < 0) { + g_strlcpy(minimized_tag, lang_id, capacity); + } + + gchar *lang_code = g_new0(gchar, capacity); + if (!bcp47_get_language_code(minimized_tag, lang_code, capacity)) { + g_strlcpy(lang_code, minimized_tag, capacity); + } + + // If ibus doesn't know about the language then append the + // language name to the keyboard name + if (lang_name != NULL) { + gchar *ibus_lang = ibus_get_untranslated_language_name(lang_code); + g_debug("%s: untranslated ibus language for %s: %s", __FUNCTION__, minimized_tag, ibus_lang); + if (g_strcmp0(ibus_lang, "Other") == 0) { + name_with_lang = g_strjoin(" - ", keyboard->name, lang_name, NULL); + } + g_free(ibus_lang); + } + + gchar *abs_kmx = g_strjoin("/", kmp_dir, keyboard->kmx_file, NULL); + gchar *id_with_lang = g_strjoin(":", minimized_tag, abs_kmx, NULL); + + g_message("adding engine %s", id_with_lang); + engine_desc = ibus_keyman_engine_desc_new( + id_with_lang, // lang:kmx full path + name_with_lang ? name_with_lang : keyboard->name, // longname + kbd_details->description, // description + info->copyright, // copyright if available + lang_code, // language, most are ignored by ibus except major languages + kbd_details->license, // license + info->author_desc, // author name only, not email + keyman_get_icon_file(abs_kmx), // icon full path + "us", // layout defaulting to us (en-US) + keyboard->version); + g_free(abs_kmx); + g_free(lang_code); + g_free(minimized_tag); + g_free(id_with_lang); + g_free(name_with_lang); + return engine_desc; +} + +// Add a keyboard (ibus engine) to the list of engines +void +keyman_add_keyboard(gpointer data, gpointer user_data) { + kmp_keyboard *keyboard = (kmp_keyboard *)data; + add_keyboard_data *kb_data = (add_keyboard_data *)user_data; + gboolean alreadyexists = FALSE; + + for (GList *e = kb_data->engines_list; e != NULL && alreadyexists == FALSE; e = e->next) { + IBusEngineDesc *engine_desc = (IBusEngineDesc *)e->data; + const gchar *version = ibus_engine_desc_get_version(engine_desc); + const gchar *engine_name = ibus_engine_desc_get_name(engine_desc); + gchar *kmx_file = g_path_get_basename(engine_name); + // If we already have an engine for this keyboard (in a different area), we + // don't want to add it again since we wouldn't add anything new + // if it's the same version + // TODO: fix version comparison (#9593) + if (g_strcmp0(kmx_file, keyboard->kmx_file) == 0 && g_strcmp0(version, keyboard->version) >= 0) { + alreadyexists = TRUE; + g_debug("keyboard %s already exists at version %s which is newer or same as %s", kmx_file, version, keyboard->version); + } + g_free(kmx_file); + } + + if (!alreadyexists) { + gchar *json_file = g_strjoin(".", keyboard->id, "json", NULL); + keyboard_details *kbd_details = g_new0(keyboard_details, 1); + get_keyboard_details(kb_data->kmp_dir, json_file, kbd_details); + g_free(json_file); + + if (keyboard->languages != NULL) { + for (GList *l = keyboard->languages; l != NULL; l = l->next) { + kmp_language *language = (kmp_language *)l->data; + IBusEngineDesc *engine_desc = + get_engine_for_language(keyboard, kb_data->info, kbd_details, kb_data->kmp_dir, language->id, language->name); + if (engine_desc) { + kb_data->engines_list = g_list_append(kb_data->engines_list, engine_desc); } - free_kmp_details(details); - g_free(details); + } + } else { + gchar *abs_kmx = g_strjoin("/", kb_data->kmp_dir, keyboard->kmx_file, NULL); + g_message("adding engine %s", abs_kmx); + kb_data->engines_list = g_list_append( + kb_data->engines_list, + ibus_keyman_engine_desc_new( + abs_kmx, // kmx full path + keyboard->name, // longname + kbd_details->description, // description + kb_data->info->copyright, // copyright if available + NULL, // language, most are ignored by ibus except major languages + kbd_details->license, // license + kb_data->info->author_desc, // author name only, not email + keyman_get_icon_file(abs_kmx), // icon full path + "us", // layout defaulting to us (en-US) + keyboard->version)); + g_free(abs_kmx); } - return engines; + free_keyboard_details(kbd_details); + g_free(kbd_details); + } +} + +// Add keyboards found in {kmp_dir}/kmp.json to engines_list +void +keyman_add_keyboards_from_dir(gpointer data, gpointer user_data) { + gchar * kmp_dir = (gchar *) data; + GList ** engines_list = (GList **)user_data; + + kmp_details *details = g_new0(kmp_details, 1); + if (get_kmp_details(kmp_dir, details) == JSON_OK) { + add_keyboard_data kb_data; + kb_data.engines_list = *engines_list; + kb_data.info = &details->info; + kb_data.kmp_dir = kmp_dir; + + g_list_foreach(details->keyboards, keyman_add_keyboard, &kb_data); + *engines_list = kb_data.engines_list; + } + free_kmp_details(details); + g_free(details); } GList * ibus_keyman_list_engines (void) { GList *engines = NULL; - GList *keyboard_list; + GList *kmpdir_list; gchar *local_keyboard_path, *xdgenv; g_debug("adding from /usr/share/keyman"); - keyboard_list = keyman_get_kmpdirs_fromdir(NULL, "/usr/share/keyman"); + kmpdir_list = keyman_get_kmpdirs_fromdir(NULL, "/usr/share/keyman"); g_debug("adding from /usr/local/share/keyman"); - keyboard_list = keyman_get_kmpdirs_fromdir(keyboard_list, "/usr/local/share/keyman"); + kmpdir_list = keyman_get_kmpdirs_fromdir(kmpdir_list, "/usr/local/share/keyman"); xdgenv = getenv("XDG_DATA_HOME"); if (xdgenv != NULL){ local_keyboard_path= g_strdup_printf("%s/keyman", xdgenv); @@ -282,10 +313,10 @@ ibus_keyman_list_engines (void) local_keyboard_path= g_strdup_printf("%s/.local/share/keyman", xdgenv); } g_debug("adding from %s", local_keyboard_path); - keyboard_list = keyman_get_kmpdirs_fromdir(keyboard_list, local_keyboard_path); + kmpdir_list = keyman_get_kmpdirs_fromdir(kmpdir_list, local_keyboard_path); g_free(local_keyboard_path); - engines = ibus_keyman_add_engines(engines, keyboard_list); - g_list_free(keyboard_list); + g_list_foreach(kmpdir_list, keyman_add_keyboards_from_dir, &engines); + g_list_free(kmpdir_list); return engines; } @@ -335,7 +366,7 @@ keyman_get_options_fromdconf(gchar *package_id, g_message("keyman_get_options_fromdconf"); // Obtain keyboard options from DConf - gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, package_id, keyboard_id); + g_autofree gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, package_id, keyboard_id); GSettings *child_settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); gchar **options = NULL; if (child_settings != NULL) @@ -344,7 +375,6 @@ keyman_get_options_fromdconf(gchar *package_id, } g_object_unref(G_OBJECT(child_settings)); - g_free(path); return options; } @@ -364,7 +394,7 @@ keyman_get_options_queue_fromdconf(gchar *package_id, GQueue *queue_options = g_queue_new(); // Obtain keyboard options from DConf - gchar **options = keyman_get_options_fromdconf(package_id, keyboard_id); + g_auto(GStrv) options = keyman_get_options_fromdconf(package_id, keyboard_id); // Parse options into queue_options if (options != NULL) @@ -372,7 +402,7 @@ keyman_get_options_queue_fromdconf(gchar *package_id, int index = 0; while (options[index] != NULL) { - gchar **option_tokens = g_strsplit(options[index], "=", 2); + g_auto(GStrv) option_tokens = g_strsplit(options[index], "=", 2); if (option_tokens != NULL && option_tokens[0] != NULL && option_tokens[1] != NULL) { g_message("Keyboard Option [%d], %s=%s", index, option_tokens[0], option_tokens[1]); @@ -386,7 +416,6 @@ keyman_get_options_queue_fromdconf(gchar *package_id, } index++; } - g_strfreev(options); } return queue_options; @@ -416,47 +445,37 @@ keyman_put_options_todconf(gchar *package_id, // Obtain keyboard options from DConf gchar **options = keyman_get_options_fromdconf(package_id, keyboard_id); - gchar *needle = g_strdup_printf("%s=", option_key); + g_autofree gchar *needle = g_strdup_printf("%s=", option_key); gchar *kvp = g_strdup_printf("%s=%s", option_key, option_value); - if (options != NULL) - { - int index = 0; - gboolean option_updated = FALSE; - while (options[index] != NULL) - { - // If option_key already exists, update value with option_value - if (g_strrstr(options[index], needle) != NULL) - { - g_free(options[index]); - options[index] = kvp; - option_updated = TRUE; - break; - } - index++; - } + g_assert(options != NULL); - if (!option_updated) + int index = 0; + gboolean option_updated = FALSE; + while (options[index] != NULL) + { + // If option_key already exists, update value with option_value + if (g_strrstr(options[index], needle) != NULL) { - // Resize to add new option and null-terminate - int size = index + 2; // old size: index + 1, plus 1 new - options = g_renew(gchar*, options, size); + g_free(options[index]); options[index] = kvp; - options[index+1] = NULL; + option_updated = TRUE; + break; } + index++; } - else + + if (!option_updated) { - // we never should come here - keyman_get_options_fromdconf will create empty - // options if they don't yet exist. - // Allocate space for new option and null-terminate - options = g_new(gchar *, 2); - options[0] = kvp; - options[1] = NULL; + // Resize to add new option and null-terminate + int size = index + 2; // old size: index + 1, plus 1 new + options = g_renew(gchar*, options, size); + options[index] = kvp; + options[index+1] = NULL; } // Write to DConf - gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, package_id, keyboard_id); + g_autofree gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, package_id, keyboard_id); GSettings *child_settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); if (child_settings != NULL) { @@ -465,8 +484,6 @@ keyman_put_options_todconf(gchar *package_id, } g_object_unref(G_OBJECT(child_settings)); - g_free(path); - g_free(needle); g_strfreev(options); // kvp got assigned to options[x] and so got freed by g_strfreev() } diff --git a/linux/ibus-keyman/src/keymanutil_internal.h b/linux/ibus-keyman/src/keymanutil_internal.h new file mode 100644 index 00000000000..112f7abd16e --- /dev/null +++ b/linux/ibus-keyman/src/keymanutil_internal.h @@ -0,0 +1,39 @@ +// Internal data structures used in the implementation of keymanutil methods +// and exposed for unit testing. + +#ifndef __KEYMANUTIL_INTERNAL_H__ +#define __KEYMANUTIL_INTERNAL_H__ + +#include +#include "kmpdetails.h" + +typedef struct { + GList *engines_list; + kmp_info *info; + gchar *kmp_dir; +} add_keyboard_data; + +IBusEngineDesc *ibus_keyman_engine_desc_new( + gchar *file_name, + gchar *name, + gchar *description, + gchar *copyright, + gchar *lang, + gchar *license, + gchar *author, + gchar *icon, + gchar *layout, + gchar *version); + +IBusEngineDesc *get_engine_for_language( + kmp_keyboard *keyboard, + kmp_info *info, + keyboard_details *kbd_details, + gchar *kmp_dir, + gchar *lang_id, + gchar *lang_name); + +void keyman_add_keyboard(gpointer data, gpointer user_data); +void keyman_add_keyboards_from_dir(gpointer data, gpointer user_data); + +#endif // __KEYMANUTIL_INTERNAL_H__ diff --git a/linux/ibus-keyman/src/test/keymanutil_tests.c b/linux/ibus-keyman/src/test/keymanutil_tests.c index 80f77a72ea6..924215f77d4 100644 --- a/linux/ibus-keyman/src/test/keymanutil_tests.c +++ b/linux/ibus-keyman/src/test/keymanutil_tests.c @@ -1,59 +1,139 @@ #include #include #include +#include +#include "kmpdetails.h" #include "keymanutil.h" +#include "keymanutil_internal.h" #define TEST_FIXTURE "keymanutil-test" void delete_options_key(gchar* testname) { - gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); - GSettings *settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); + g_autofree gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); + g_autoptr(GSettings) settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); g_settings_reset(settings, KEYMAN_DCONF_OPTIONS_KEY); - g_object_unref(G_OBJECT(settings)); - g_free(path); } void set_options_key(gchar* testname, gchar** options) { - gchar *path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); - GSettings *settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); + g_autofree gchar* path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); + g_autoptr(GSettings) settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); g_settings_set_strv(settings, KEYMAN_DCONF_OPTIONS_KEY, (const gchar* const*)options); - g_object_unref(G_OBJECT(settings)); - g_free(path); } gchar** get_options_key(gchar* testname) { - gchar* path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); - GSettings* settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); + g_autofree gchar* path = g_strdup_printf("%s%s/%s/", KEYMAN_DCONF_OPTIONS_PATH, TEST_FIXTURE, testname); + g_autoptr(GSettings) settings = g_settings_new_with_path(KEYMAN_DCONF_OPTIONS_CHILD_NAME, path); gchar** result = g_settings_get_strv(settings, KEYMAN_DCONF_OPTIONS_KEY); - g_object_unref(G_OBJECT(settings)); - g_free(path); return result; } +kmp_keyboard* +_get_kmp_keyboard(gchar* version, gchar** languages) { + kmp_keyboard* keyboard = g_new0(kmp_keyboard, 1); + keyboard->name = g_strdup("Testing"); + keyboard->id = g_strdup("tst"); + keyboard->version = g_strdup(version); + keyboard->kmx_file = g_strdup("tst.kmx"); + keyboard->kvk_file = g_strdup(""); + keyboard->languages = NULL; + for (gchar* lang = *languages++; lang; lang = *languages++) { + gchar** tokens = g_strsplit(lang, ":", 2); + kmp_language* kmp_lang = g_new0(kmp_language, 1); + kmp_lang->id = g_strdup(tokens[0]); + kmp_lang->name = g_strdup(tokens[1]); + g_strfreev(tokens); + keyboard->languages = g_list_append(keyboard->languages, kmp_lang); + } + return keyboard; +} + +kmp_info* +_get_kmp_info(gchar * copyright, gchar * author_desc, gchar * author_url) { + kmp_info* info = g_new0(kmp_info, 1); + info->copyright = g_strdup(copyright); + info->author_desc = g_strdup(author_desc); + info->author_url = g_strdup(author_url); + return info; +} + +keyboard_details* +_get_keyboard_details(gchar * description, gchar * license) { + keyboard_details* details = g_new0(keyboard_details, 1); + details->id = g_strdup("tst"); + details->description = g_strdup(description); + details->license = g_strdup(license); + return details; +} + +add_keyboard_data* +_get_keyboard_data() { + add_keyboard_data* kb_data = g_new0(add_keyboard_data, 1); + kb_data->engines_list = NULL; + kb_data->info = _get_kmp_info(NULL, NULL, NULL); + kb_data->kmp_dir = "/tmp"; + return kb_data; +} + +void +free_kb_data(add_keyboard_data* kb_data) { + if (kb_data->engines_list) + g_list_free(kb_data->engines_list); + if (kb_data->info) + g_free(kb_data->info); + g_free(kb_data); +} + +// defined in kmpdetails +void free_keyboard(gpointer data); +void free_info(gpointer data); +kmp_json_status free_keyboard_details(keyboard_details* kbd_details); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(add_keyboard_data, free_kb_data) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(kmp_keyboard, free_keyboard) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(kmp_info, free_info) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(keyboard_details, free_keyboard_details) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(IBusEngineDesc, g_object_unref) + +//---------------------------------------------------------------------------------------------- +void +test_keyman_put_options_todconf__invalid() { + // Initialize + gchar* testname = "test_keyman_put_options_todconf__test_keyman_put_options_todconf__invalid"; + delete_options_key(testname); + + // Execute + keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", NULL); + + // Verify + g_auto(GStrv) options = get_options_key(testname); + g_assert_nonnull(options); + g_assert_null(options[0]); + + // Cleanup + delete_options_key(testname); +} + void test_keyman_put_options_todconf__new_key() { // Initialize gchar* testname = "test_keyman_put_options_todconf__new_key"; delete_options_key(testname); - gchar* value = g_strdup_printf("%d", g_test_rand_int()); + g_autofree gchar* value = g_strdup_printf("%d", g_test_rand_int()); // Execute keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); // Verify - gchar** options = get_options_key(testname); - gchar* expected = g_strdup_printf("new_key=%s", value); + g_auto(GStrv) options = get_options_key(testname); + g_autofree gchar* expected = g_strdup_printf("new_key=%s", value); g_assert_nonnull(options); g_assert_cmpstr(options[0], ==, expected); g_assert_null(options[1]); // Cleanup - g_free(expected); - g_free(value); - g_strfreev(options); delete_options_key(testname); } @@ -64,14 +144,14 @@ test_keyman_put_options_todconf__other_keys() { delete_options_key(testname); gchar* existingKeys[] = {"key1=val1", "key2=val2", NULL}; set_options_key(testname, existingKeys); - gchar* value = g_strdup_printf("%d", g_test_rand_int()); + g_autofree gchar* value = g_strdup_printf("%d", g_test_rand_int()); // Execute keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); // Verify - gchar** options = get_options_key(testname); - gchar* expected = g_strdup_printf("new_key=%s", value); + g_auto(GStrv) options = get_options_key(testname); + g_autofree gchar* expected = g_strdup_printf("new_key=%s", value); g_assert_nonnull(options); g_assert_cmpstr(options[0], ==, "key1=val1"); g_assert_cmpstr(options[1], ==, "key2=val2"); @@ -79,9 +159,6 @@ test_keyman_put_options_todconf__other_keys() { g_assert_null(options[3]); // Cleanup - g_free(expected); - g_free(value); - g_strfreev(options); delete_options_key(testname); } @@ -92,37 +169,537 @@ test_keyman_put_options_todconf__existing_key() { delete_options_key(testname); gchar* existingKeys[] = {"key1=val1", "new_key=val2", NULL}; set_options_key(testname, existingKeys); - gchar* value = g_strdup_printf("%d", g_test_rand_int()); + g_autofree gchar* value = g_strdup_printf("%d", g_test_rand_int()); // Execute keyman_put_options_todconf(TEST_FIXTURE, testname, "new_key", value); // Verify - gchar** options = get_options_key(testname); - gchar* expected = g_strdup_printf("new_key=%s", value); + g_auto(GStrv) options = get_options_key(testname); + g_autofree gchar* expected = g_strdup_printf("new_key=%s", value); g_assert_nonnull(options); g_assert_cmpstr(options[0], ==, "key1=val1"); g_assert_cmpstr(options[1], ==, expected); g_assert_null(options[2]); // Cleanup - g_free(expected); - g_free(value); - g_strfreev(options); delete_options_key(testname); } -int -main(int argc, char* argv[]) { +//---------------------------------------------------------------------------------------------- +void +test_ibus_keyman_engine_desc_new__all_set() { + // Execute + g_autoptr(IBusEngineDesc) desc = ibus_keyman_engine_desc_new("name", "longname", "description", "copyright", "lang", "license", "author", "icon", "layout", "version"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "name"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "longname"); + g_assert_cmpstr(ibus_engine_desc_get_description(desc), ==, "description\ncopyright"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "lang"); + g_assert_cmpstr(ibus_engine_desc_get_license(desc), ==, "license"); + g_assert_cmpstr(ibus_engine_desc_get_author(desc), ==, "author"); + g_assert_cmpstr(ibus_engine_desc_get_icon(desc), ==, "icon"); + g_assert_cmpstr(ibus_engine_desc_get_layout(desc), ==, "layout"); + g_assert_cmpstr(ibus_engine_desc_get_version(desc), ==, "version"); +} + +void +test_ibus_keyman_engine_desc_new__only_description() { + // Execute + g_autoptr(IBusEngineDesc) desc = ibus_keyman_engine_desc_new( + "name", "longname", "description", NULL, "lang", "license", "author", "icon", "layout", "version"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "name"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "longname"); + g_assert_cmpstr(ibus_engine_desc_get_description(desc), ==, "description\n(null)"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "lang"); + g_assert_cmpstr(ibus_engine_desc_get_license(desc), ==, "license"); + g_assert_cmpstr(ibus_engine_desc_get_author(desc), ==, "author"); + g_assert_cmpstr(ibus_engine_desc_get_icon(desc), ==, "icon"); + g_assert_cmpstr(ibus_engine_desc_get_layout(desc), ==, "layout"); + g_assert_cmpstr(ibus_engine_desc_get_version(desc), ==, "version"); +} + +void +test_ibus_keyman_engine_desc_new__only_copyright() { + // Execute + g_autoptr(IBusEngineDesc) desc = ibus_keyman_engine_desc_new( + "name", "longname", NULL, "copyright", "lang", "license", "author", "icon", "layout", "version"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "name"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "longname"); + g_assert_cmpstr(ibus_engine_desc_get_description(desc), ==, "copyright"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "lang"); + g_assert_cmpstr(ibus_engine_desc_get_license(desc), ==, "license"); + g_assert_cmpstr(ibus_engine_desc_get_author(desc), ==, "author"); + g_assert_cmpstr(ibus_engine_desc_get_icon(desc), ==, "icon"); + g_assert_cmpstr(ibus_engine_desc_get_layout(desc), ==, "layout"); + g_assert_cmpstr(ibus_engine_desc_get_version(desc), ==, "version"); +} + +void +test_ibus_keyman_engine_desc_new__no_language_license_author_version() { + // Execute + g_autoptr(IBusEngineDesc) desc = ibus_keyman_engine_desc_new( + "name", "longname", "description", "copyright", NULL, NULL, NULL, "icon", "layout", NULL); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "name"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "longname"); + g_assert_cmpstr(ibus_engine_desc_get_description(desc), ==, "description\ncopyright"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "other"); + g_assert_cmpstr(ibus_engine_desc_get_license(desc), ==, ""); + g_assert_cmpstr(ibus_engine_desc_get_author(desc), ==, ""); + g_assert_cmpstr(ibus_engine_desc_get_icon(desc), ==, "icon"); + g_assert_cmpstr(ibus_engine_desc_get_layout(desc), ==, "layout"); + g_assert_cmpstr(ibus_engine_desc_get_version(desc), ==, ""); +} + +//---------------------------------------------------------------------------------------------- +void +test_get_engine_for_language__null_language() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", NULL, NULL); + + // Verify + g_assert_null(desc); +} + +void +test_get_engine_for_language__empty_language() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "", ""); + + // Verify + g_assert_null(desc); +} + +void +test_get_engine_for_language__one_language() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "en", "English"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "Testing"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "en"); +} + +void +test_get_engine_for_language__one_unknown_language() { + // Initialize + gchar* languages[] = {"foo:Foo", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "foo", "Foo"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "foo:/tmp/tst.kmx"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "Testing - Foo"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "foo"); +} + +void +test_get_engine_for_language__different_languages() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "foo", "Foo"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "foo:/tmp/tst.kmx"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "Testing - Foo"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "foo"); +} + +void +test_get_engine_for_language__no_kbd_language() { + // Initialize + gchar* languages[] = {NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(kmp_info) info = _get_kmp_info("Copyright by me", "My Author", "myauthor@example.com"); + g_autoptr(keyboard_details) details = _get_keyboard_details("my description", "MIT"); + + // Execute + g_autoptr(IBusEngineDesc) desc = get_engine_for_language(keyboard, info, details, "/tmp", "en", "English"); + + // Verify + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_cmpstr(ibus_engine_desc_get_longname(desc), ==, "Testing"); + g_assert_cmpstr(ibus_engine_desc_get_language(desc), ==, "en"); +} + +//---------------------------------------------------------------------------------------------- +void +test_keyman_add_keyboard__no_language() { + // Initialize + gchar* languages[] = {NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + g_assert_nonnull(kb_data->engines_list->data); + IBusEngineDesc* desc = (IBusEngineDesc*)kb_data->engines_list->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "/tmp/tst.kmx"); + g_assert_null(kb_data->engines_list->next); +} + +void +test_keyman_add_keyboard__one_language() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + g_assert_nonnull(kb_data->engines_list->data); + IBusEngineDesc* desc = (IBusEngineDesc*)kb_data->engines_list->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_null(kb_data->engines_list->next); +} + +void +test_keyman_add_keyboard__two_languages() { + // Initialize + gchar* languages[] = {"en:English", "fr:French", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + g_assert_nonnull(kb_data->engines_list->data); + IBusEngineDesc* desc = (IBusEngineDesc*)kb_data->engines_list->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "en:/tmp/tst.kmx"); + g_assert_nonnull(kb_data->engines_list->next); + g_assert_nonnull(kb_data->engines_list->next->data); + desc = (IBusEngineDesc*)kb_data->engines_list->next->data; + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, "fr:/tmp/tst.kmx"); + g_assert_null(kb_data->engines_list->next->next); +} + +void +test_keyman_add_keyboard__prev_engine_adding_same_version() { + // Initialize + gchar* languages[] = {"en:English", NULL}; + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.0", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + IBusEngineDesc* desc = ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); + kb_data->engines_list = g_list_append(kb_data->engines_list, desc); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + GList* list = kb_data->engines_list; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/usr/share/keyman/tst.kmx"); + g_assert_null(list->next); +} + +void +test_keyman_add_keyboard__prev_engine_adding_newer_version() { + // Initialize + gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.1", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + IBusEngineDesc* desc = + ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); + kb_data->engines_list = g_list_append(kb_data->engines_list, desc); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + GList* list = kb_data->engines_list; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/usr/share/keyman/tst.kmx"); + g_assert_nonnull(list->next); + list = list->next; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/tmp/tst.kmx"); + g_assert_nonnull(list->next); + list = list->next; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "fr:/tmp/tst.kmx"); + g_assert_null(list->next); +} + +void +test_keyman_add_keyboard__prev_engine_adding_newer_version_9593() { + // This tests bug #9593: We add keyboard version 1.10 while 1.9 is already in + // the list. + + // Initialize + gchar* languages[] = {"en:English", "fr:French", NULL}; // New version adds French + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("1.10", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + IBusEngineDesc* desc = + ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.9"); + kb_data->engines_list = g_list_append(kb_data->engines_list, desc); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + GList* list = kb_data->engines_list; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/usr/share/keyman/tst.kmx"); + g_assert_nonnull(list->next); + list = list->next; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/tmp/tst.kmx"); + g_assert_nonnull(list->next); + list = list->next; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "fr:/tmp/tst.kmx"); + g_assert_null(list->next); +} + +void +test_keyman_add_keyboard__prev_engine_adding_older_version() { + // Initialize + gchar* languages[] = {"en:English", "fr:French", NULL}; // Old version has additional French + g_autoptr(kmp_keyboard) keyboard = _get_kmp_keyboard("0.9", languages); + g_autoptr(add_keyboard_data) kb_data = _get_keyboard_data(); + IBusEngineDesc* desc = + ibus_keyman_engine_desc_new("en:/usr/share/keyman/tst.kmx", "Testing", NULL, NULL, "en", NULL, NULL, "", "us", "1.0"); + kb_data->engines_list = g_list_append(kb_data->engines_list, desc); + + // Execute + keyman_add_keyboard(keyboard, kb_data); + + // Verify + GList* list = kb_data->engines_list; + g_assert_nonnull(list->data); + g_assert_cmpstr(ibus_engine_desc_get_name((IBusEngineDesc*)list->data), ==, "en:/usr/share/keyman/tst.kmx"); + g_assert_null(list->next); +} + +//---------------------------------------------------------------------------------------------- +gchar* testdata_dir; + +gboolean +delete_directory(gchar* path) { + g_autoptr(GFile) file = g_file_new_for_path(path); + if (g_file_test(path, G_FILE_TEST_IS_DIR)) { + g_autoptr(GFileEnumerator) enumerator = NULL; + + enumerator = g_file_enumerate_children(file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); + + while (enumerator != NULL) { + GFile* child; + + if (!g_file_enumerator_iterate(enumerator, NULL, &child, NULL, NULL)) + return FALSE; + if (child == NULL) + break; + if (!delete_directory(g_file_get_path(child))) + return FALSE; + } + } + + return g_file_delete(file, NULL, NULL); +} + +void clear_kmpdir(gchar* dir) { + delete_directory(dir); + g_free(dir); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, clear_kmpdir); + +void copy_test_data(gchar* kmp_dir, gchar* name) { + g_autofree gchar* oldfile = g_strdup_printf("%s/%s", testdata_dir, name); + g_autoptr(GFile) source = g_file_new_for_path(oldfile); + g_autofree gchar* newfile = g_strdup_printf("%s/kmp.json", kmp_dir); + g_autoptr(GFile) dest = g_file_new_for_path(newfile); + g_file_copy(source, dest, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL); +} + +void +test_keyman_add_keyboards_from_dir__no_kmpjson() { + // Initialize + g_autolist(GList) keyboards = NULL; + g_autoptr(gchar) kmp_dir = g_dir_make_tmp(NULL, NULL); + + // Execute + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + + // Verify + g_assert_cmpint(g_list_length(keyboards), ==, 0); +} + +void +test_keyman_add_keyboards_from_dir__one_dir() { + // Initialize + g_autoptr(GList) keyboards = NULL; + g_autoptr(gchar) kmp_dir = g_dir_make_tmp(NULL, NULL); + copy_test_data(kmp_dir, "kmp1.json"); + + // Execute + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + + // Verify + g_assert_cmpint(g_list_length(keyboards), ==, 1); + + IBusEngineDesc* desc = IBUS_ENGINE_DESC(keyboards->data); + g_autofree gchar* expected_name = g_strdup_printf("bza:%s/test1.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name); +} + +void +test_keyman_add_keyboards_from_dir__two_langs() { + // Initialize + g_autoptr(GList) keyboards = NULL; + g_autoptr(gchar) kmp_dir = g_dir_make_tmp(NULL, NULL); + copy_test_data(kmp_dir, "kmp2.json"); + + // Execute + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + + // Verify + g_assert_cmpint(g_list_length(keyboards), ==, 2); + + IBusEngineDesc* desc = IBUS_ENGINE_DESC(keyboards->data); + g_autofree gchar* expected_name1 = g_strdup_printf("bmf-Latn:%s/test2.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name1); + desc = IBUS_ENGINE_DESC(keyboards->next->data); + g_autofree gchar* expected_name2 = g_strdup_printf("bun-Latn:%s/test2.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name2); +} + +void +test_keyman_add_keyboards_from_dir__prev_keyboards() { + // Initialize + g_autoptr(GList) keyboards = NULL; + g_autoptr(gchar) kmp_dir = g_dir_make_tmp(NULL, NULL); + copy_test_data(kmp_dir, "kmp2.json"); + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + copy_test_data(kmp_dir, "kmp1.json"); + + // Execute + keyman_add_keyboards_from_dir(kmp_dir, &keyboards); + + // Verify + g_assert_cmpint(g_list_length(keyboards), ==, 3); + + IBusEngineDesc* desc = IBUS_ENGINE_DESC(keyboards->data); + g_autofree gchar* expected_name1 = g_strdup_printf("bmf-Latn:%s/test2.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name1); + desc = IBUS_ENGINE_DESC(keyboards->next->data); + g_autofree gchar* expected_name2 = g_strdup_printf("bun-Latn:%s/test2.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name2); + desc = IBUS_ENGINE_DESC(keyboards->next->next->data); + g_autofree gchar* expected_name = g_strdup_printf("bza:%s/test1.kmx", kmp_dir); + g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name); +} + +//---------------------------------------------------------------------------------------------- +void +print_usage() { + printf( + "Usage: %s --testdata \n\n", + g_get_prgname()); + printf("Arguments:\n"); + printf("\t--testdata \tThe directory containing test kmp.json files for the tests.\n\n"); +} + +int main(int argc, char* argv[]) { gtk_init(&argc, &argv); g_test_init(&argc, &argv, NULL); g_test_set_nonfatal_assertions(); + if (argc < 3 || strcmp(argv[1], "--testdata") != 0) { + print_usage(); + return 1; + } + + testdata_dir = argv[2]; + + if (!g_file_test(testdata_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { + printf("ERROR: Testdata directory %s does not exist\n\n", testdata_dir); + print_usage(); + return 2; + } + // Add tests + g_test_add_func("/keymanutil/keyman_put_options_todconf/invalid", test_keyman_put_options_todconf__invalid); g_test_add_func("/keymanutil/keyman_put_options_todconf/new_key", test_keyman_put_options_todconf__new_key); g_test_add_func("/keymanutil/keyman_put_options_todconf/other_keys", test_keyman_put_options_todconf__other_keys); g_test_add_func("/keymanutil/keyman_put_options_todconf/existing_key", test_keyman_put_options_todconf__existing_key); + g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/all_set", test_ibus_keyman_engine_desc_new__all_set); + g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/only_description", test_ibus_keyman_engine_desc_new__only_description); + g_test_add_func("/keymanutil/ibus_keyman_engine_desc_new/only_copyright", test_ibus_keyman_engine_desc_new__only_copyright); + g_test_add_func( + "/keymanutil/ibus_keyman_engine_desc_new/no_language_license_author_version", + test_ibus_keyman_engine_desc_new__no_language_license_author_version); + + g_test_add_func("/keymanutil/get_engine_for_language/null_language", test_get_engine_for_language__null_language); + g_test_add_func("/keymanutil/get_engine_for_language/empty_language", test_get_engine_for_language__empty_language); + g_test_add_func("/keymanutil/get_engine_for_language/one_language", test_get_engine_for_language__one_language); + g_test_add_func("/keymanutil/get_engine_for_language/one_unknown_language", test_get_engine_for_language__one_unknown_language); + g_test_add_func("/keymanutil/get_engine_for_language/different_languages", test_get_engine_for_language__different_languages); + g_test_add_func("/keymanutil/get_engine_for_language/no_kbd_language", test_get_engine_for_language__no_kbd_language); + + g_test_add_func("/keymanutil/keyman_add_keyboard/no_language", test_keyman_add_keyboard__no_language); + g_test_add_func("/keymanutil/keyman_add_keyboard/one_language", test_keyman_add_keyboard__one_language); + g_test_add_func("/keymanutil/keyman_add_keyboard/two_languages", test_keyman_add_keyboard__two_languages); + g_test_add_func( + "/keymanutil/keyman_add_keyboard/prev_engine_adding_same_version", + test_keyman_add_keyboard__prev_engine_adding_same_version); + g_test_add_func( + "/keymanutil/keyman_add_keyboard/prev_engine_adding_newer_version", + test_keyman_add_keyboard__prev_engine_adding_newer_version); + // #9593 + // g_test_add_func( + // "/keymanutil/keyman_add_keyboard/prev_engine_adding_newer_version_versioncompare", + // test_keyman_add_keyboard__prev_engine_adding_newer_version_9593); + g_test_add_func( + "/keymanutil/keyman_add_keyboard/prev_engine_adding_older_version", + test_keyman_add_keyboard__prev_engine_adding_older_version); + + g_test_add_func("/keymanutil/keyman_add_keyboards_from_dir/no_kmpjson", test_keyman_add_keyboards_from_dir__no_kmpjson); + g_test_add_func("/keymanutil/keyman_add_keyboards_from_dir/one_dir", test_keyman_add_keyboards_from_dir__one_dir); + g_test_add_func("/keymanutil/keyman_add_keyboards_from_dir/two_langs", test_keyman_add_keyboards_from_dir__two_langs); + g_test_add_func("/keymanutil/keyman_add_keyboards_from_dir/prev_keyboards", test_keyman_add_keyboards_from_dir__prev_keyboards); + // Run tests int retVal = g_test_run(); diff --git a/linux/ibus-keyman/src/test/meson.build b/linux/ibus-keyman/src/test/meson.build index 3c8c1044c0d..02631e27f79 100644 --- a/linux/ibus-keyman/src/test/meson.build +++ b/linux/ibus-keyman/src/test/meson.build @@ -84,7 +84,7 @@ test( test( 'keymanutil-tests', run_src_test, - args: [ '--tap', '-k', '--env', env_file, '--', keymanutil_tests], + args: [ '--tap', '-k', '--env', env_file, '--', keymanutil_tests, '--testdata', meson.current_source_dir() / 'testdata'], env: test_env, priority: -2, is_parallel: false, @@ -103,7 +103,7 @@ test( test( 'print-kmp-test', run_src_test, - args: [ '--', print_kmp_test, meson.current_source_dir() / 'kmp.json' ], + args: [ '--', print_kmp_test, meson.current_source_dir() / 'testdata/kmp.json' ], env: test_env, priority: -2, protocol: 'exitcode', diff --git a/linux/ibus-keyman/src/test/run-tests.sh b/linux/ibus-keyman/src/test/run-tests.sh index efeb747ddc7..ae005fcbd6b 100755 --- a/linux/ibus-keyman/src/test/run-tests.sh +++ b/linux/ibus-keyman/src/test/run-tests.sh @@ -58,4 +58,4 @@ glib-compile-schemas "$SCHEMA_DIR" export GSETTINGS_BACKEND=memory -"${G_TEST_BUILDDIR:-.}/keymanutil-tests" "$@" +"${G_TEST_BUILDDIR:-.}/keymanutil-tests" "$@" --testdata "${SRCDIR}/src/test/testdata" diff --git a/linux/ibus-keyman/src/test/setup-tests.sh b/linux/ibus-keyman/src/test/setup-tests.sh index 07e7d5462b3..79bb765f509 100755 --- a/linux/ibus-keyman/src/test/setup-tests.sh +++ b/linux/ibus-keyman/src/test/setup-tests.sh @@ -3,4 +3,4 @@ set -eu . "$(dirname "$0")/../../tests/scripts/test-helper.inc.sh" -setup_display_server_only "$1" "$2" "$3" "$4" +setup_display_server_only "${1:-x11}" "${2:-/tmp/env-src-test.txt}" "${3:-/tmp/ibus-keyman-src-test-pids}" "${4:-/tmp/ibus-keyman-src-test-pids.pids}" diff --git a/linux/ibus-keyman/src/test/kmp.json b/linux/ibus-keyman/src/test/testdata/kmp.json similarity index 95% rename from linux/ibus-keyman/src/test/kmp.json rename to linux/ibus-keyman/src/test/testdata/kmp.json index dc659d324e7..d2ee4fa9e13 100644 --- a/linux/ibus-keyman/src/test/kmp.json +++ b/linux/ibus-keyman/src/test/testdata/kmp.json @@ -1,267 +1,267 @@ -{ - "system": { - "keymanDeveloperVersion": "10.0.1202.0", - "fileVersion": "7.0" - }, - "options": { - "readmeFile": "readme.htm" - }, - "info": { - "version": { - "description": "1.6.1" - }, - "name": { - "description": "LIBTRALO" - }, - "copyright": { - "description": "\u00A9 2008-2018 SIL International" - }, - "author": { - "description": "support@keyman.com", - "url": "mailto:support@keyman.com" - } - }, - "files": [ - { - "name": "libtralo.kmx", - "description": "Keyboard LIBTRALO" - }, - { - "name": "readme.htm", - "description": "File readme.htm" - }, - { - "name": "libtralo.png", - "description": "File libtralo.png" - }, - { - "name": "welcome.htm", - "description": "File welcome.htm" - }, - { - "name": "Charis_SIL_README.txt", - "description": "File Charis_SIL_README.txt" - }, - { - "name": "CharisSIL-B.ttf", - "description": "Font Charis SIL Bold" - }, - { - "name": "CharisSIL-BI.ttf", - "description": "Font Charis SIL Bold Italic" - }, - { - "name": "CharisSIL-I.ttf", - "description": "Font Charis SIL Italic" - }, - { - "name": "CharisSIL-R.ttf", - "description": "Font Charis SIL" - }, - { - "name": "FONTLOG.txt", - "description": "File FONTLOG.txt" - }, - { - "name": "OFL.txt", - "description": "File OFL.txt" - }, - { - "name": "OFL-FAQ.txt", - "description": "File OFL-FAQ.txt" - }, - { - "name": "CharisSILDan-Bold.ttf", - "description": "Font Charis SIL Dan Bold" - }, - { - "name": "CharisSILDan-BoldItalic.ttf", - "description": "Font Charis SIL Dan Bold Italic" - }, - { - "name": "CharisSILDan-Italic.ttf", - "description": "Font Charis SIL Dan Italic" - }, - { - "name": "CharisSILDan-Regular.ttf", - "description": "Font Charis SIL Dan" - }, - { - "name": "libtralo.js", - "description": "File libtralo.js" - }, - { - "name": "libtralo.kvk", - "description": "File libtralo.kvk" - }, - { - "name": "CharisSILDan_FONTLOG.txt", - "description": "File CharisSILDan_FONTLOG.txt" - }, - { - "name": "kmp.inf", - "description": "Package information" - }, - { - "name": "kmp.json", - "description": "Package information (JSON)" - } - ], - "keyboards": [ - { - "name": "LIBTRALO", - "id": "libtralo", - "version": "1.6.1", - "languages": [ - { - "name": "Bandi", - "id": "bza-Latn" - }, - { - "name": "Bassa", - "id": "bsq-Latn" - }, - { - "name": "Glio-Oubi", - "id": "oub-Latn" - }, - { - "name": "Gola", - "id": "gol-Latn" - }, - { - "name": "Northern Grebo", - "id": "gbo-Latn" - }, - { - "name": "Southern Kisi", - "id": "kss-Latn" - }, - { - "name": "Klao", - "id": "klu-Latn" - }, - { - "name": "Liberia Kpelle (Latin)", - "id": "xpe-Latn" - }, - { - "name": "Eastern Krahn", - "id": "kqo-Latn" - }, - { - "name": "Western Krahn", - "id": "krw-Latn" - }, - { - "name": "Kuwaa", - "id": "blh-Latn" - }, - { - "name": "Loma (Liberia) (Latin)", - "id": "lom-Latn" - }, - { - "name": "Mano", - "id": "mev-Latn" - }, - { - "name": "Manya", - "id": "mzj-Latn" - }, - { - "name": "Sapo", - "id": "krn-Latn" - }, - { - "name": "Bullom So", - "id": "buy-Latn" - }, - { - "name": "Kono (Sierra Leone)", - "id": "kno-Latn" - }, - { - "name": "Krio", - "id": "kri-Latn" - }, - { - "name": "Kuranko", - "id": "knk-Latn" - }, - { - "name": "West-Central Limba", - "id": "lia-Latn" - }, - { - "name": "Loko", - "id": "lok-Latn" - }, - { - "name": "Mende (Sierra Leone)", - "id": "men" - }, - { - "name": "Timne", - "id": "tem" - }, - { - "name": "Vai (Latin)", - "id": "vai-Latn" - }, - { - "name": "Dewoin (Latin)", - "id": "dee-Latn" - }, - { - "name": "Dan (Western) (Latin)", - "id": "dnj-Latn-LR" - }, - { - "name": "Gbii (Latin)", - "id": "ggb-Latn" - }, - { - "name": "Glaro-Twabo (Latin)", - "id": "glr-Latn" - }, - { - "name": "Grebo (Latin)", - "id": "grb-Latn" - }, - { - "name": "Barclayville Grebo (Latin)", - "id": "gry-Latn" - }, - { - "name": "Central Grebo (Latin)", - "id": "grv-Latn" - }, - { - "name": "Gboloo Grebo (Latin)", - "id": "gec-Latn" - }, - { - "name": "Southern Grebo (Latin)", - "id": "grj-Latn" - }, - { - "name": "Kpelle (Latin)", - "id": "kpe-Latn" - }, - { - "name": "Tajuasohn (Latin)", - "id": "tja-Latn" - }, - { - "name": "Bom-Kim (Latin)", - "id": "bmf-Latn" - }, - { - "name": "Sherbro (Latin)", - "id": "bun-Latn" - } - ] - } - ] -} +{ + "system": { + "keymanDeveloperVersion": "10.0.1202.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "readme.htm" + }, + "info": { + "version": { + "description": "1.6.1" + }, + "name": { + "description": "LIBTRALO" + }, + "copyright": { + "description": "\u00A9 2008-2018 SIL International" + }, + "author": { + "description": "support@keyman.com", + "url": "mailto:support@keyman.com" + } + }, + "files": [ + { + "name": "libtralo.kmx", + "description": "Keyboard LIBTRALO" + }, + { + "name": "readme.htm", + "description": "File readme.htm" + }, + { + "name": "libtralo.png", + "description": "File libtralo.png" + }, + { + "name": "welcome.htm", + "description": "File welcome.htm" + }, + { + "name": "Charis_SIL_README.txt", + "description": "File Charis_SIL_README.txt" + }, + { + "name": "CharisSIL-B.ttf", + "description": "Font Charis SIL Bold" + }, + { + "name": "CharisSIL-BI.ttf", + "description": "Font Charis SIL Bold Italic" + }, + { + "name": "CharisSIL-I.ttf", + "description": "Font Charis SIL Italic" + }, + { + "name": "CharisSIL-R.ttf", + "description": "Font Charis SIL" + }, + { + "name": "FONTLOG.txt", + "description": "File FONTLOG.txt" + }, + { + "name": "OFL.txt", + "description": "File OFL.txt" + }, + { + "name": "OFL-FAQ.txt", + "description": "File OFL-FAQ.txt" + }, + { + "name": "CharisSILDan-Bold.ttf", + "description": "Font Charis SIL Dan Bold" + }, + { + "name": "CharisSILDan-BoldItalic.ttf", + "description": "Font Charis SIL Dan Bold Italic" + }, + { + "name": "CharisSILDan-Italic.ttf", + "description": "Font Charis SIL Dan Italic" + }, + { + "name": "CharisSILDan-Regular.ttf", + "description": "Font Charis SIL Dan" + }, + { + "name": "libtralo.js", + "description": "File libtralo.js" + }, + { + "name": "libtralo.kvk", + "description": "File libtralo.kvk" + }, + { + "name": "CharisSILDan_FONTLOG.txt", + "description": "File CharisSILDan_FONTLOG.txt" + }, + { + "name": "kmp.inf", + "description": "Package information" + }, + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + { + "name": "LIBTRALO", + "id": "libtralo", + "version": "1.6.1", + "languages": [ + { + "name": "Bandi", + "id": "bza-Latn" + }, + { + "name": "Bassa", + "id": "bsq-Latn" + }, + { + "name": "Glio-Oubi", + "id": "oub-Latn" + }, + { + "name": "Gola", + "id": "gol-Latn" + }, + { + "name": "Northern Grebo", + "id": "gbo-Latn" + }, + { + "name": "Southern Kisi", + "id": "kss-Latn" + }, + { + "name": "Klao", + "id": "klu-Latn" + }, + { + "name": "Liberia Kpelle (Latin)", + "id": "xpe-Latn" + }, + { + "name": "Eastern Krahn", + "id": "kqo-Latn" + }, + { + "name": "Western Krahn", + "id": "krw-Latn" + }, + { + "name": "Kuwaa", + "id": "blh-Latn" + }, + { + "name": "Loma (Liberia) (Latin)", + "id": "lom-Latn" + }, + { + "name": "Mano", + "id": "mev-Latn" + }, + { + "name": "Manya", + "id": "mzj-Latn" + }, + { + "name": "Sapo", + "id": "krn-Latn" + }, + { + "name": "Bullom So", + "id": "buy-Latn" + }, + { + "name": "Kono (Sierra Leone)", + "id": "kno-Latn" + }, + { + "name": "Krio", + "id": "kri-Latn" + }, + { + "name": "Kuranko", + "id": "knk-Latn" + }, + { + "name": "West-Central Limba", + "id": "lia-Latn" + }, + { + "name": "Loko", + "id": "lok-Latn" + }, + { + "name": "Mende (Sierra Leone)", + "id": "men" + }, + { + "name": "Timne", + "id": "tem" + }, + { + "name": "Vai (Latin)", + "id": "vai-Latn" + }, + { + "name": "Dewoin (Latin)", + "id": "dee-Latn" + }, + { + "name": "Dan (Western) (Latin)", + "id": "dnj-Latn-LR" + }, + { + "name": "Gbii (Latin)", + "id": "ggb-Latn" + }, + { + "name": "Glaro-Twabo (Latin)", + "id": "glr-Latn" + }, + { + "name": "Grebo (Latin)", + "id": "grb-Latn" + }, + { + "name": "Barclayville Grebo (Latin)", + "id": "gry-Latn" + }, + { + "name": "Central Grebo (Latin)", + "id": "grv-Latn" + }, + { + "name": "Gboloo Grebo (Latin)", + "id": "gec-Latn" + }, + { + "name": "Southern Grebo (Latin)", + "id": "grj-Latn" + }, + { + "name": "Kpelle (Latin)", + "id": "kpe-Latn" + }, + { + "name": "Tajuasohn (Latin)", + "id": "tja-Latn" + }, + { + "name": "Bom-Kim (Latin)", + "id": "bmf-Latn" + }, + { + "name": "Sherbro (Latin)", + "id": "bun-Latn" + } + ] + } + ] +} diff --git a/linux/ibus-keyman/src/test/testdata/kmp1.json b/linux/ibus-keyman/src/test/testdata/kmp1.json new file mode 100644 index 00000000000..5d9954c330e --- /dev/null +++ b/linux/ibus-keyman/src/test/testdata/kmp1.json @@ -0,0 +1,123 @@ +{ + "system": { + "keymanDeveloperVersion": "10.0.1202.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "readme.htm" + }, + "info": { + "version": { + "description": "1.6.1" + }, + "name": { + "description": "LIBTRALO" + }, + "copyright": { + "description": "\u00A9 2008-2018 SIL International" + }, + "author": { + "description": "support@keyman.com", + "url": "mailto:support@keyman.com" + } + }, + "files": [ + { + "name": "libtralo.kmx", + "description": "Keyboard LIBTRALO" + }, + { + "name": "readme.htm", + "description": "File readme.htm" + }, + { + "name": "libtralo.png", + "description": "File libtralo.png" + }, + { + "name": "welcome.htm", + "description": "File welcome.htm" + }, + { + "name": "Charis_SIL_README.txt", + "description": "File Charis_SIL_README.txt" + }, + { + "name": "CharisSIL-B.ttf", + "description": "Font Charis SIL Bold" + }, + { + "name": "CharisSIL-BI.ttf", + "description": "Font Charis SIL Bold Italic" + }, + { + "name": "CharisSIL-I.ttf", + "description": "Font Charis SIL Italic" + }, + { + "name": "CharisSIL-R.ttf", + "description": "Font Charis SIL" + }, + { + "name": "FONTLOG.txt", + "description": "File FONTLOG.txt" + }, + { + "name": "OFL.txt", + "description": "File OFL.txt" + }, + { + "name": "OFL-FAQ.txt", + "description": "File OFL-FAQ.txt" + }, + { + "name": "CharisSILDan-Bold.ttf", + "description": "Font Charis SIL Dan Bold" + }, + { + "name": "CharisSILDan-BoldItalic.ttf", + "description": "Font Charis SIL Dan Bold Italic" + }, + { + "name": "CharisSILDan-Italic.ttf", + "description": "Font Charis SIL Dan Italic" + }, + { + "name": "CharisSILDan-Regular.ttf", + "description": "Font Charis SIL Dan" + }, + { + "name": "libtralo.js", + "description": "File libtralo.js" + }, + { + "name": "libtralo.kvk", + "description": "File libtralo.kvk" + }, + { + "name": "CharisSILDan_FONTLOG.txt", + "description": "File CharisSILDan_FONTLOG.txt" + }, + { + "name": "kmp.inf", + "description": "Package information" + }, + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + { + "name": "TEST1", + "id": "test1", + "version": "1.6.1", + "languages": [ + { + "name": "Bandi", + "id": "bza-Latn" + } + ] + } + ] +} diff --git a/linux/ibus-keyman/src/test/testdata/kmp2.json b/linux/ibus-keyman/src/test/testdata/kmp2.json new file mode 100644 index 00000000000..5e889091f72 --- /dev/null +++ b/linux/ibus-keyman/src/test/testdata/kmp2.json @@ -0,0 +1,127 @@ +{ + "system": { + "keymanDeveloperVersion": "10.0.1202.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "readme.htm" + }, + "info": { + "version": { + "description": "1.6.1" + }, + "name": { + "description": "LIBTRALO" + }, + "copyright": { + "description": "\u00A9 2008-2018 SIL International" + }, + "author": { + "description": "support@keyman.com", + "url": "mailto:support@keyman.com" + } + }, + "files": [ + { + "name": "libtralo.kmx", + "description": "Keyboard LIBTRALO" + }, + { + "name": "readme.htm", + "description": "File readme.htm" + }, + { + "name": "libtralo.png", + "description": "File libtralo.png" + }, + { + "name": "welcome.htm", + "description": "File welcome.htm" + }, + { + "name": "Charis_SIL_README.txt", + "description": "File Charis_SIL_README.txt" + }, + { + "name": "CharisSIL-B.ttf", + "description": "Font Charis SIL Bold" + }, + { + "name": "CharisSIL-BI.ttf", + "description": "Font Charis SIL Bold Italic" + }, + { + "name": "CharisSIL-I.ttf", + "description": "Font Charis SIL Italic" + }, + { + "name": "CharisSIL-R.ttf", + "description": "Font Charis SIL" + }, + { + "name": "FONTLOG.txt", + "description": "File FONTLOG.txt" + }, + { + "name": "OFL.txt", + "description": "File OFL.txt" + }, + { + "name": "OFL-FAQ.txt", + "description": "File OFL-FAQ.txt" + }, + { + "name": "CharisSILDan-Bold.ttf", + "description": "Font Charis SIL Dan Bold" + }, + { + "name": "CharisSILDan-BoldItalic.ttf", + "description": "Font Charis SIL Dan Bold Italic" + }, + { + "name": "CharisSILDan-Italic.ttf", + "description": "Font Charis SIL Dan Italic" + }, + { + "name": "CharisSILDan-Regular.ttf", + "description": "Font Charis SIL Dan" + }, + { + "name": "libtralo.js", + "description": "File libtralo.js" + }, + { + "name": "libtralo.kvk", + "description": "File libtralo.kvk" + }, + { + "name": "CharisSILDan_FONTLOG.txt", + "description": "File CharisSILDan_FONTLOG.txt" + }, + { + "name": "kmp.inf", + "description": "Package information" + }, + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + { + "name": "TEST2", + "id": "test2", + "version": "1.6.1", + "languages": [ + { + "name": "Bom-Kim (Latin)", + "id": "bmf-Latn" + }, + { + "name": "Sherbro (Latin)", + "id": "bun-Latn" + } + ] + } + ] +} From 1236ded7cd1c6bb8fa5fed1b57b26e950d428f11 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 28 Sep 2023 12:50:25 -0500 Subject: [PATCH 093/207] =?UTF-8?q?chore(resources):=20ldml=20update=20=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For: #9403 - unicode-org/cldr:2aa2275158c29e8e41c7f0b67c8fb786a453b89c --- .../techpreview/3.0/fr-t-k0-azerty.xml | 16 ------- .../ldml-keyboards/techpreview/cldr_info.json | 6 +-- .../techpreview/dtd/ldmlKeyboard3.dtd | 13 +----- .../techpreview/dtd/ldmlKeyboard3.xsd | 21 --------- .../techpreview/import/scanCodes-implied.xml | 6 +-- .../techpreview/ldml-keyboard3.schema.json | 43 ------------------- 6 files changed, 7 insertions(+), 98 deletions(-) diff --git a/resources/standards-data/ldml-keyboards/techpreview/3.0/fr-t-k0-azerty.xml b/resources/standards-data/ldml-keyboards/techpreview/3.0/fr-t-k0-azerty.xml index 4ccedc062f3..190700d496a 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/3.0/fr-t-k0-azerty.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/3.0/fr-t-k0-azerty.xml @@ -23,22 +23,6 @@ - - - - - - - - - - - - - - - - - + @@ -77,17 +77,6 @@ Please view the subcommittee page for the most recent information. - - - - - - - - - - - diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.xsd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.xsd index f3a7b8bed8e..47f299571bf 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.xsd +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.xsd @@ -25,7 +25,6 @@ Note: DTD @-annotations are not currently converted to .xsd. For full CLDR file - @@ -148,26 +147,6 @@ Note: DTD @-annotations are not currently converted to .xsd. For full CLDR file - - - - - - - - - - - - - - - - - - - - diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml index 23ee29d2cbc..e1ba3309d64 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml +++ b/resources/standards-data/ldml-keyboards/techpreview/import/scanCodes-implied.xml @@ -35,12 +35,12 @@ - +
    - - + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard3.schema.json b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard3.schema.json index c5d13311ef8..6d6ba9d2a9d 100644 --- a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard3.schema.json +++ b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard3.schema.json @@ -635,46 +635,6 @@ } }, "type": "object" - }, - "vkey": { - "additionalProperties": false, - "properties": { - "from": { - "type": "string" - }, - "to": { - "type": "string" - } - }, - "required": [ - "from", - "to" - ], - "type": "object" - }, - "vkeys": { - "additionalProperties": false, - "properties": { - "import": { - "items": { - "$ref": "#/definitions/import" - }, - "type": "array" - }, - "special": { - "items": { - "$ref": "#/definitions/special" - }, - "type": "array" - }, - "vkey": { - "items": { - "$ref": "#/definitions/vkey" - }, - "type": "array" - } - }, - "type": "object" } }, "properties": { @@ -740,9 +700,6 @@ }, "version": { "$ref": "#/definitions/version" - }, - "vkeys": { - "$ref": "#/definitions/vkeys" } }, "required": [ From bdef1c48d3ebe01e53f222edf8ae7573e14d7d1c Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 28 Sep 2023 13:03:44 -0500 Subject: [PATCH 094/207] =?UTF-8?q?fix(common):=20update=20keyman=20for=20?= =?UTF-8?q?removal=20of=20vkeys=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dropped in upstream CLDR-17093 See #7135 --- .../src/kmx/kmx-plus-builder/build-vkey.ts | 43 ------------ .../kmx/kmx-plus-builder/kmx-plus-builder.ts | 5 -- common/web/types/src/kmx/kmx-plus.ts | 12 ---- .../src/ldml-keyboard/ldml-keyboard-xml.ts | 10 --- core/include/ldml/keyboardprocessor_ldml.h | 4 -- core/include/ldml/keyboardprocessor_ldml.ts | 17 +---- core/src/kmx/kmx_plus.cpp | 22 +----- core/src/kmx/kmx_plus.h | 27 +------- .../src/kmc-ldml/src/compiler/compiler.ts | 2 - .../src/kmc-ldml/src/compiler/messages.ts | 18 +---- developer/src/kmc-ldml/src/compiler/vkey.ts | 69 ------------------- .../src/kmc-ldml/test/fixtures/basic.txt | 12 ---- .../src/kmc-ldml/test/fixtures/basic.xml | 5 -- .../sections/vkey/invalid-from-vkey.xml | 18 ----- .../sections/vkey/invalid-repeated-vkey.xml | 16 ----- .../sections/vkey/invalid-to-vkey.xml | 15 ---- .../test/fixtures/sections/vkey/minimal.xml | 17 ----- .../test/fixtures/sections/vkey/redundant.xml | 15 ---- .../fixtures/sections/vkey/same-target.xml | 16 ----- developer/src/kmc-ldml/test/test-vkey.ts | 62 ----------------- 20 files changed, 5 insertions(+), 400 deletions(-) delete mode 100644 common/web/types/src/kmx/kmx-plus-builder/build-vkey.ts delete mode 100644 developer/src/kmc-ldml/src/compiler/vkey.ts delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-from-vkey.xml delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-repeated-vkey.xml delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-to-vkey.xml delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/minimal.xml delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/redundant.xml delete mode 100644 developer/src/kmc-ldml/test/fixtures/sections/vkey/same-target.xml delete mode 100644 developer/src/kmc-ldml/test/test-vkey.ts diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-vkey.ts b/common/web/types/src/kmx/kmx-plus-builder/build-vkey.ts deleted file mode 100644 index 0a4863beabd..00000000000 --- a/common/web/types/src/kmx/kmx-plus-builder/build-vkey.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { constants } from '@keymanapp/ldml-keyboard-constants'; -import { KMXPlusData } from "../kmx-plus.js"; -import { BUILDER_SECTION } from './builder-section.js'; - -/* ------------------------------------------------------------------ - * vkey section - ------------------------------------------------------------------ */ - -/** - * Builder for the 'vkey' section - */ -interface BUILDER_VKEY_ITEM { - vkey: number; - target: number; -}; - -export interface BUILDER_VKEY extends BUILDER_SECTION { - count: number; - items: BUILDER_VKEY_ITEM[]; -}; - -export function build_vkey(kmxplus: KMXPlusData): BUILDER_VKEY { - if(!kmxplus.vkey.vkeys.length) { - return null; - } - - let vkey: BUILDER_VKEY = { - ident: constants.hex_section_id(constants.section.vkey), - size: constants.length_vkey + constants.length_vkey_item * kmxplus.vkey.vkeys.length, - _offset: 0, - count: kmxplus.vkey.vkeys.length, - items: [] - }; - - for(let item of kmxplus.vkey.vkeys) { - vkey.items.push({ - vkey: item.vkey, - target: item.target - }); - } - - return vkey; -} diff --git a/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts b/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts index 3cc58c9fe75..e77c60fbe7e 100644 --- a/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts +++ b/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts @@ -15,7 +15,6 @@ import { BUILDER_STRS, build_strs } from './build-strs.js'; import { BUILDER_TRAN, build_tran } from './build-tran.js'; import { BUILDER_USET, build_uset } from './build-uset.js'; import { BUILDER_VARS, build_vars } from './build-vars.js'; -import { BUILDER_VKEY, build_vkey } from './build-vkey.js'; type BUILDER_BKSP = BUILDER_TRAN; // type BUILDER_FINL = BUILDER_TRAN; @@ -36,7 +35,6 @@ type SectionBuilders = { tran?: BUILDER_TRAN; uset?: BUILDER_USET; vars?: BUILDER_VARS; - vkey?: BUILDER_VKEY; }; export default class KMXPlusBuilder { @@ -73,7 +71,6 @@ export default class KMXPlusBuilder { this.emitSection(file, this.file.COMP_PLUS_TRAN, this.sect.tran); this.emitSection(file, this.file.COMP_PLUS_USET, this.sect.uset); this.emitSection(file, this.file.COMP_PLUS_VARS, this.sect.vars); - this.emitSection(file, this.file.COMP_PLUS_VKEY, this.sect.vkey); return file; } @@ -100,7 +97,6 @@ export default class KMXPlusBuilder { this.sect.tran = build_tran(this.file.kmxplus.tran, this.sect.strs, this.sect.elem); this.sect.uset = build_uset(this.file.kmxplus, this.sect.strs); this.sect.vars = build_vars(this.file.kmxplus, this.sect.strs, this.sect.elem, this.sect.list); - this.sect.vkey = build_vkey(this.file.kmxplus); // Finalize the sect (index) section @@ -138,7 +134,6 @@ export default class KMXPlusBuilder { offset = this.finalize_sect_item(this.sect.tran, offset); offset = this.finalize_sect_item(this.sect.uset, offset); offset = this.finalize_sect_item(this.sect.vars, offset); - offset = this.finalize_sect_item(this.sect.vkey, offset); this.sect.sect.total = offset; } diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts index eba1324b857..1c022a78034 100644 --- a/common/web/types/src/kmx/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -404,17 +404,6 @@ export class Bksp extends Tran { } }; -// 'vkey' - -export class VkeyItem { - vkey: number; - target: number; -} - -export class Vkey extends Section { - vkeys: VkeyItem[] = []; -}; - // 'disp' export class DispItem { to: StrsItem; @@ -569,7 +558,6 @@ export interface KMXPlusData { tran?: Tran; uset?: Uset; // uset is ignored in-memory vars?: Vars; - vkey?: Vkey; }; export class KMXPlusFile extends KMXFile { diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts index 5f954c1605d..83720312dd8 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -27,7 +27,6 @@ export interface LKKeyboard { forms?: LKForms; displays?: LKDisplays; layers?: LKLayers[]; - vkeys?: LKVkeys; variables?: LKVariables; transforms?: LKTransforms[]; }; @@ -129,15 +128,6 @@ export interface LKRow { keys?: string; }; -export interface LKVkeys { - vkey?: LKVkey[]; -}; - -export interface LKVkey { - from?: string; - to?: string; -}; - export interface LKVariables { string?: LKString[]; set?: LKSet[]; diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyboardprocessor_ldml.h index 4c629ae77b9..641382a4ff1 100644 --- a/core/include/ldml/keyboardprocessor_ldml.h +++ b/core/include/ldml/keyboardprocessor_ldml.h @@ -92,8 +92,6 @@ #define LDML_LENGTH_USET_USET 0xC #define LDML_LENGTH_VARS 0x10 #define LDML_LENGTH_VARS_ITEM 0x10 -#define LDML_LENGTH_VKEY 0xC -#define LDML_LENGTH_VKEY_ITEM 0x8 #define LDML_MARKER_ANY_INDEX 0xD7FF #define LDML_MARKER_CODE 0x8 #define LDML_MARKER_MAX_COUNT 0xD7FE @@ -130,8 +128,6 @@ #define LDML_SECTIONNAME_USET "uset" #define LDML_SECTIONID_VARS 0x73726176 /* "vars" */ #define LDML_SECTIONNAME_VARS "vars" -#define LDML_SECTIONID_VKEY 0x79656B76 /* "vkey" */ -#define LDML_SECTIONNAME_VKEY "vkey" #define LDML_TRAN_FLAGS_ERROR 0x1 #define LDML_TRAN_GROUP_TYPE_REORDER 0x1 #define LDML_TRAN_GROUP_TYPE_TRANSFORM 0x0 diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyboardprocessor_ldml.ts index d19784b110c..90882024b39 100644 --- a/core/include/ldml/keyboardprocessor_ldml.ts +++ b/core/include/ldml/keyboardprocessor_ldml.ts @@ -37,8 +37,7 @@ export type SectionIdent = 'strs' | 'tran' | 'uset' | - 'vars' | - 'vkey'; + 'vars'; type SectionMap = { @@ -508,19 +507,6 @@ class Constants { */ readonly tran_group_type_reorder = 1; - /* ------------------------------------------------------------------ - * vkey section - ------------------------------------------------------------------ */ - - /** - * Minimum length of the 'vkey' section not including variable parts - */ - readonly length_vkey = 12; - /** - * Length of each item in the 'vkey' section variable part - */ - readonly length_vkey_item = 8; - /* ------------------------------------------------------------------ * vars section * ------------------------------------------------------------------ */ @@ -585,7 +571,6 @@ class Constants { tran: 'tran', uset: 'uset', vars: 'vars', - vkey: 'vkey', }; /** diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp index b20b826cc2e..950febfa9b4 100644 --- a/core/src/kmx/kmx_plus.cpp +++ b/core/src/kmx/kmx_plus.cpp @@ -215,25 +215,6 @@ COMP_KMXPLUS_META::valid(KMX_DWORD _kmn_unused(length)) const { return true; } -bool -COMP_KMXPLUS_VKEY::valid(KMX_DWORD _kmn_unused(length)) const { - DebugLog("vkey: count 0x%X\n", count); - if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { - DebugLog("header.size < expected size"); - assert(false); - return false; - } - for (KMX_DWORD i = 0; i < count; i++) { - DebugLog("vkey #0x%X: 0x%X->0x%X", i, entries[i].vkey, entries[i].target); - if (entries[i].vkey > 0xFF || entries[i].target > 0xFF) { - DebugLog("vkey source or target out of range [0x00…0xFF]"); - assert(false); - return false; - } - } - return true; -} - bool COMP_KMXPLUS_DISP::valid(KMX_DWORD _kmn_unused(length)) const { DebugLog("disp: count 0x%X\n", count); @@ -1183,7 +1164,7 @@ COMP_KMXPLUS_USET_Helper::getRange(KMX_DWORD i) const { kmx_plus::kmx_plus(const COMP_KEYBOARD *keyboard, size_t length) : bksp(nullptr), disp(nullptr), elem(nullptr), key2(nullptr), layr(nullptr), list(nullptr), loca(nullptr), meta(nullptr), - sect(nullptr), strs(nullptr), tran(nullptr), vars(nullptr), vkey(nullptr), valid(false) { + sect(nullptr), strs(nullptr), tran(nullptr), vars(nullptr), valid(false) { DebugLog("kmx_plus: Got a COMP_KEYBOARD at %p\n", keyboard); #if !KMXPLUS_DEBUG_LOAD DebugLog("Note: define KMXPLUS_DEBUG_LOAD=1 at compile time for more verbosity in loading"); @@ -1232,7 +1213,6 @@ kmx_plus::kmx_plus(const COMP_KEYBOARD *keyboard, size_t length) tran = section_from_sect(sect); uset = section_from_sect(sect); vars = section_from_sect(sect); - vkey = section_from_sect(sect); // Initialize the helper objects for sections with dynamic parts. // Note: all of these setters will be passed 'nullptr' diff --git a/core/src/kmx/kmx_plus.h b/core/src/kmx/kmx_plus.h index b75fcf2bd04..ec269668f8f 100644 --- a/core/src/kmx/kmx_plus.h +++ b/core/src/kmx/kmx_plus.h @@ -348,7 +348,7 @@ struct COMP_KMXPLUS_BKSP : public COMP_KMXPLUS_TRAN { /* ------------------------------------------------------------------ - * vkey section + * vars section ------------------------------------------------------------------ */ struct COMP_KMXPLUS_VARS_ITEM { @@ -376,30 +376,6 @@ static_assert(sizeof(struct COMP_KMXPLUS_VARS) % 0x4 == 0, "Structs prior to var static_assert(sizeof(struct COMP_KMXPLUS_VARS) == LDML_LENGTH_VARS, "mismatched size of section vars"); static_assert(sizeof(struct COMP_KMXPLUS_VARS_ITEM) == LDML_LENGTH_VARS_ITEM, "mismatched size of vars item"); -/* ------------------------------------------------------------------ - * vkey section - ------------------------------------------------------------------ */ - -struct COMP_KMXPLUS_VKEY_ENTRY { - KMX_DWORD_unaligned vkey; - KMX_DWORD_unaligned target; -}; - -struct COMP_KMXPLUS_VKEY { - static const KMXPLUS_IDENT IDENT = LDML_SECTIONID_VKEY; - COMP_KMXPLUS_HEADER header; - KMX_DWORD_unaligned count; - COMP_KMXPLUS_VKEY_ENTRY entries[]; - /** - * @brief True if section is valid. - */ - bool valid(KMX_DWORD length) const; -}; - -static_assert(sizeof(struct COMP_KMXPLUS_VKEY) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); -static_assert(sizeof(struct COMP_KMXPLUS_VKEY) == LDML_LENGTH_VKEY, "mismatched size of section vkey"); - - /* ------------------------------------------------------------------ * disp section ------------------------------------------------------------------ */ @@ -787,7 +763,6 @@ class kmx_plus { const COMP_KMXPLUS_TRAN *tran; const COMP_KMXPLUS_USET *uset; const COMP_KMXPLUS_VARS *vars; - const COMP_KMXPLUS_VKEY *vkey; inline bool is_valid() { return valid; } COMP_KMXPLUS_BKSP_Helper bkspHelper; COMP_KMXPLUS_KEYS_Helper key2Helper; diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index ad0340ed820..c29aa9fa938 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -8,7 +8,6 @@ import { LayrCompiler } from './layr.js'; import { LocaCompiler } from './loca.js'; import { MetaCompiler } from './meta.js'; import { NameCompiler } from './name.js'; -import { VkeyCompiler } from './vkey.js'; import { VarsCompiler } from './vars.js'; import { StrsCompiler, ElemCompiler, ListCompiler, UsetCompiler } from './empty-compiler.js'; @@ -38,7 +37,6 @@ export const SECTION_COMPILERS = [ MetaCompiler, NameCompiler, TranCompiler, - VkeyCompiler, ]; export class LdmlKeyboardCompiler { diff --git a/developer/src/kmc-ldml/src/compiler/messages.ts b/developer/src/kmc-ldml/src/compiler/messages.ts index 5bc3fba784f..28783d0f85f 100644 --- a/developer/src/kmc-ldml/src/compiler/messages.ts +++ b/developer/src/kmc-ldml/src/compiler/messages.ts @@ -1,6 +1,6 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types"; -const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler; +// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevError = CompilerErrorSeverity.Error | CompilerErrorNamespace.LdmlKeyboardCompiler; @@ -35,21 +35,7 @@ export class CompilerMessages { m(this.HINT_LocaleIsNotMinimalAndClean, `Locale '${o.sourceLocale}' is not minimal or correctly formatted and should be '${o.locale}'`); static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008; - static Hint_VkeyIsNotValid = (o:{vkey: string}) => - m(this.HINT_VkeyIsNotValid, `Virtual key '${o.vkey}' is not found in the CLDR VKey Enum table.`); - static HINT_VkeyIsNotValid = SevHint | 0x0009; - - static Hint_VkeyIsRedundant = (o:{vkey: string}) => - m(this.HINT_VkeyIsRedundant, `Virtual key '${o.vkey}' is mapped to itself, which is redundant.`); - static HINT_VkeyIsRedundant = SevHint | 0x000A; - - static Error_VkeyIsRepeated = (o:{vkey: string}) => - m(this.ERROR_VkeyIsRepeated, `Virtual key '${o.vkey}' has more than one vkey entry.`); - static ERROR_VkeyIsRepeated = SevError | 0x000B; - - static Info_MultipleVkeysHaveSameTarget = (o:{vkey: string}) => - m(this.INFO_MultipleVkeysHaveSameTarget, `Target virtual key '${o.vkey}' has multiple source mappings, which may be an error.`); - static INFO_MultipleVkeysHaveSameTarget = SevInfo | 0x000C; + // gap: 0x0009 … 0x000C were for vkeys static Error_InvalidVersion = (o:{version: string}) => m(this.ERROR_InvalidVersion, `Version number '${o.version}' must be a semantic version format string.`); diff --git a/developer/src/kmc-ldml/src/compiler/vkey.ts b/developer/src/kmc-ldml/src/compiler/vkey.ts deleted file mode 100644 index 1c4c69403be..00000000000 --- a/developer/src/kmc-ldml/src/compiler/vkey.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { constants } from "@keymanapp/ldml-keyboard-constants"; -import { KMXPlus, Constants } from '@keymanapp/common-types'; -import { CompilerMessages } from "./messages.js"; -import { SectionCompiler } from "./section-compiler.js"; - -import Vkey = KMXPlus.Vkey; -import LdmlVkeyNames = Constants.LdmlVkeyNames; - -export class VkeyCompiler extends SectionCompiler { - - public get id() { - return constants.section.vkey; - } - - public validate(): boolean { - let valid = true; - if(this.keyboard3.vkeys) { - let from: string[] = [], to: string[] = []; - - this.keyboard3.vkeys.vkey.forEach(vk => { - if(LdmlVkeyNames[vk.from] === undefined) { - // TODO-LDML: When we do #7135 this may need to change back to an error. - this.callbacks.reportMessage(CompilerMessages.Hint_VkeyIsNotValid({vkey: vk.from})); - return; - } - - if(LdmlVkeyNames[vk.to] === undefined) { - // TODO-LDML: When we do #7135 this may need to change back to an error. - this.callbacks.reportMessage(CompilerMessages.Hint_VkeyIsNotValid({vkey: vk.to})); - return; - } - - if(vk.from == vk.to) { - this.callbacks.reportMessage(CompilerMessages.Hint_VkeyIsRedundant({vkey: vk.from})); - } - - if(from.find(svk => svk == vk.from)) { - this.callbacks.reportMessage(CompilerMessages.Error_VkeyIsRepeated({vkey: vk.from})); - valid = false; - } - from.push(vk.from); - - if(to.find(svk => svk == vk.to)) { - this.callbacks.reportMessage(CompilerMessages.Info_MultipleVkeysHaveSameTarget({vkey: vk.to})); - } - to.push(vk.to); - }); - } - return valid; - } - - public compile(): Vkey { - let result = new Vkey(); - if(!this.keyboard3.vkeys) { - /* c8 ignore next 2 */ - return result; // not hit due to boxing - } - - result.vkeys = this.keyboard3.vkeys?.vkey.map(vk => { - return { - vkey: LdmlVkeyNames[vk.from], - target: LdmlVkeyNames[vk.to] - }; - }); - // Sort according to vkey binary order, per C7043 - result.vkeys.sort((a,b) => a.vkey - b.vkey); - return result; - } -} diff --git a/developer/src/kmc-ldml/test/fixtures/basic.txt b/developer/src/kmc-ldml/test/fixtures/basic.txt index 74280b3ecf8..9282894633f 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic.txt @@ -137,9 +137,6 @@ block(sectitems) 76 61 72 73 diff(sect,vars) - 76 6b 65 79 - diff(sect,vkey) - block(endsect) # ---------------------------------------------------------------------------------------------------- @@ -579,13 +576,4 @@ block(vars) # struct COMP_KMXPLUS_VARS { 00 00 00 00 # KMXPLUS_ELEM elem block(varsEnd) -block(vkey) # struct COMP_KMXPLUS_VKEY { - 76 6b 65 79 # KMX_DWORD header.ident; // 0000 Section name - vkey - sizeof(vkey) # KMX_DWORD header.size; // 0004 Section length - 02 00 00 00 # KMX_DWORD count; // 0008 Number of vkey maps - - 41 00 00 00 51 00 00 00 # KMX_DWORD vkey; KMX_DWORD target; // K_A => K_Q - 51 00 00 00 41 00 00 00 # KMX_DWORD vkey; KMX_DWORD target; // K_Q => K_A - # }; - block(eof) # end of file diff --git a/developer/src/kmc-ldml/test/fixtures/basic.xml b/developer/src/kmc-ldml/test/fixtures/basic.xml index 9c45f5e07fc..144bcb49247 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic.xml +++ b/developer/src/kmc-ldml/test/fixtures/basic.xml @@ -14,11 +14,6 @@ - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-from-vkey.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-from-vkey.xml deleted file mode 100644 index 9ed1d1b63f1..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-from-vkey.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-repeated-vkey.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-repeated-vkey.xml deleted file mode 100644 index 36deceb4d86..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-repeated-vkey.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-to-vkey.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-to-vkey.xml deleted file mode 100644 index 58b573adc24..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/invalid-to-vkey.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/minimal.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/minimal.xml deleted file mode 100644 index 4af6dd30320..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/minimal.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/redundant.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/redundant.xml deleted file mode 100644 index 612d12c0146..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/redundant.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/fixtures/sections/vkey/same-target.xml b/developer/src/kmc-ldml/test/fixtures/sections/vkey/same-target.xml deleted file mode 100644 index 9dd34420eb3..00000000000 --- a/developer/src/kmc-ldml/test/fixtures/sections/vkey/same-target.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/developer/src/kmc-ldml/test/test-vkey.ts b/developer/src/kmc-ldml/test/test-vkey.ts deleted file mode 100644 index dec24125e2a..00000000000 --- a/developer/src/kmc-ldml/test/test-vkey.ts +++ /dev/null @@ -1,62 +0,0 @@ -import 'mocha'; -import { assert } from 'chai'; -import { VkeyCompiler } from '../src/compiler/vkey.js'; -import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; -import { KMXPlus, Constants } from '@keymanapp/common-types'; -import { CompilerMessages } from '../src/compiler/messages.js'; - -import Vkey = KMXPlus.Vkey; -import USVirtualKeyCodes = Constants.USVirtualKeyCodes; - -describe('vkey compiler', function () { - this.slow(500); // 0.5 sec -- json schema validation takes a while - - it('should compile minimal vkey data', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/minimal.xml', compilerTestCallbacks) as Vkey; - assert.equal(compilerTestCallbacks.messages.length, 0); - - assert.equal(vkey.vkeys.length, 4); - // Note, final order is sorted by `vkey` member - assert.deepEqual(vkey.vkeys[0], {vkey: USVirtualKeyCodes.K_A, target: USVirtualKeyCodes.K_Q}); - assert.deepEqual(vkey.vkeys[1], {vkey: USVirtualKeyCodes.K_Q, target: USVirtualKeyCodes.K_A}); - assert.deepEqual(vkey.vkeys[2], {vkey: USVirtualKeyCodes.K_W, target: USVirtualKeyCodes.K_Z}); - assert.deepEqual(vkey.vkeys[3], {vkey: USVirtualKeyCodes.K_Z, target: USVirtualKeyCodes.K_W}); - }); - - it('should hint on redundant data', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/redundant.xml', compilerTestCallbacks) as Vkey; - assert.isNotNull(vkey); - assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_VkeyIsRedundant({vkey: 'A'})); - }); - - it('should report an info message if same target found', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/same-target.xml', compilerTestCallbacks) as Vkey; - assert.isNotNull(vkey); - assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Info_MultipleVkeysHaveSameTarget({vkey: 'Q'})); - }); - - it('should hint on invalid "from" vkey', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-from-vkey.xml', compilerTestCallbacks) as Vkey; - assert.isNotNull(vkey); - assert.equal(compilerTestCallbacks.messages.length, 2); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_VkeyIsNotValid({vkey: 'q'})); - assert.deepEqual(compilerTestCallbacks.messages[1], CompilerMessages.Hint_VkeyIsNotValid({vkey: 'HYFEN'})); - }); - - it('should hint on invalid "to" vkey', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-to-vkey.xml', compilerTestCallbacks) as Vkey; - assert.isNotNull(vkey); - assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_VkeyIsNotValid({vkey: 'A-ACUTE'})); - }); - - it('should error on repeated vkeys', async function() { - let vkey = await loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-repeated-vkey.xml', compilerTestCallbacks) as Vkey; - assert.isNull(vkey); - assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyIsRepeated({vkey: 'A'})); - }); -}); - From 5fa99f53144dfa678f97823aa28da538e47aebc1 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 28 Sep 2023 13:32:02 -0500 Subject: [PATCH 095/207] =?UTF-8?q?fix(common):=20update=20keyman=20for=20?= =?UTF-8?q?removal=20of=20vkeys=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dropped in upstream CLDR-17093 - missed changes See #7135 --- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 1 - core/tests/unit/ldml/keyboards/k_020_fr.xml | 18 ------------------ 2 files changed, 19 deletions(-) diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 60c77acc223..66f8fdbe560 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -61,7 +61,6 @@ export class LDMLKeyboardXMLSourceFileReader { boxXmlArray(source?.keyboard3, 'layers'); boxXmlArray(source?.keyboard3?.displays, 'display'); boxXmlArray(source?.keyboard3?.names, 'name'); - boxXmlArray(source?.keyboard3?.vkeys, 'vkey'); boxXmlArray(source?.keyboard3?.keys, 'key'); boxXmlArray(source?.keyboard3?.keys, 'flicks'); boxXmlArray(source?.keyboard3?.locales, 'locale'); diff --git a/core/tests/unit/ldml/keyboards/k_020_fr.xml b/core/tests/unit/ldml/keyboards/k_020_fr.xml index 843ad80ede4..42b7e1826fe 100644 --- a/core/tests/unit/ldml/keyboards/k_020_fr.xml +++ b/core/tests/unit/ldml/keyboards/k_020_fr.xml @@ -17,24 +17,6 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-zzz-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-zzz-form.xml new file mode 100644 index 00000000000..0ab10f3d032 --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-zzz-form.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + +
    + + + +
    + + + + + +
    diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-us-form.xml similarity index 75% rename from developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml rename to developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-us-form.xml index f7122d10a8f..d3a0573b21d 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/layr/hint-custom-form.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-us-form.xml @@ -7,19 +7,20 @@ - + +
    - + +
    - - + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-zzz-form.xml similarity index 74% rename from developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml rename to developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-zzz-form.xml index 5aa93783dee..502b254135b 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/layr/error-custom-form.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/warn-custom-zzz-form.xml @@ -7,18 +7,19 @@ - + +
    - +
    - + - + diff --git a/developer/src/kmc-ldml/test/test-keys.ts b/developer/src/kmc-ldml/test/test-keys.ts index 9429b339791..fb36f9a96f6 100644 --- a/developer/src/kmc-ldml/test/test-keys.ts +++ b/developer/src/kmc-ldml/test/test-keys.ts @@ -241,6 +241,90 @@ describe('keys.kmap', function () { CompilerMessages.Error_MissingFlicks({flicks:'an-undefined-flick-id',id:'Q'}), ] }, + { + subpath: 'sections/layr/invalid-invalid-form.xml', + errors: [CompilerMessages.Error_InvalidHardware({ + form: 'holographic', + }),], + }, + { + // warning on custom form + subpath: 'sections/layr/warn-custom-us-form.xml', + warnings: [ + CompilerMessages.Warn_CustomForm({id: "us"}), + ], + callback: (sect, subpath, callbacks) => { + const keys = sect as Keys; + assert.isNotNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 3); + assert.sameDeepMembers(keys.kmap, [ + { + vkey: K.K_K, + key: 'one', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_E, + key: 'two', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_Y, + key: 'three', + mod: constants.keys_mod_none, + }, + ]); + }, + }, + { + // warning on a custom unknown form - but no error! + subpath: 'sections/layr/warn-custom-zzz-form.xml', + warnings: [ + CompilerMessages.Warn_CustomForm({id: "zzz"}), + ], + callback: (sect, subpath, callbacks) => { + const keys = sect as Keys; + assert.isNotNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 3); + assert.sameDeepMembers(keys.kmap, [ + { + vkey: K.K_K, + key: 'one', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_E, + key: 'two', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_Y, + key: 'three', + mod: constants.keys_mod_none, + }, + ]); + }, + }, + { + subpath: 'sections/layr/error-custom-us-form.xml', + warnings: [ + CompilerMessages.Warn_CustomForm({id: "us"}), + ], + errors: [ + CompilerMessages.Error_InvalidScanCode({ form: "us", codes: ['ff'] }), + ], + }, + { + subpath: 'sections/layr/error-custom-zzz-form.xml', + warnings: [ + CompilerMessages.Warn_CustomForm({id: "zzz"}), + ], + errors: [ + CompilerMessages.Error_InvalidScanCode({ form: "zzz", codes: ['ff'] }), + ], + }, ]); it('should reject layouts with too many hardware rows', async function() { diff --git a/developer/src/kmc-ldml/test/test-layr.ts b/developer/src/kmc-ldml/test/test-layr.ts index 86f16ff27bf..126935c33b5 100644 --- a/developer/src/kmc-ldml/test/test-layr.ts +++ b/developer/src/kmc-ldml/test/test-layr.ts @@ -32,7 +32,7 @@ describe('layr', function () { const list0 = layr.lists[0]; assert.ok(list0); assert.equal(list0.layers.length, 1); - assert.equal(list0.hardware, constants.layr_list_hardware_us); + assert.equal(list0.hardware.value, 'us'); const layer0 = list0.layers[0]; assert.ok(layer0); assert.equal(layer0.rows.length, 1); @@ -51,7 +51,7 @@ describe('layr', function () { const layr = sect; assert.equal(layr.lists?.length, 2); - const listHardware = layr.lists.find(v => v.hardware === constants.layr_list_hardware_iso); + const listHardware = layr.lists.find(v => v.hardware.value === 'iso'); assert.ok(listHardware); assert.equal(listHardware.minDeviceWidth, 0); assert.equal(listHardware.layers.length, 2); @@ -73,7 +73,7 @@ describe('layr', function () { assert.equal(hardware1row0.keys.length, 2); allKeysOk(hardware1row0,'q w', 'hardware1row0'); - const listTouch = layr.lists.find(v => v.hardware === constants.layr_list_hardware_touch); + const listTouch = layr.lists.find(v => v.hardware.value === constants.layr_list_hardware_touch); assert.ok(listTouch); assert.equal(listTouch.minDeviceWidth, 300); assert.equal(listTouch.layers.length, 1); @@ -105,33 +105,10 @@ describe('layr', function () { subpath: 'sections/layr/invalid-multi-hardware.xml', errors: [CompilerMessages.Error_ExcessHardware({ form: 'iso' })], }, - { - subpath: 'sections/layr/invalid-invalid-form.xml', - errors: [CompilerMessages.Error_InvalidHardware({ - form: 'holographic', - }),], - }, { // missing layer element subpath: 'sections/layr/invalid-missing-layer.xml', errors: [CompilerMessages.Error_MustBeAtLeastOneLayerElement()], }, - { - // warning on custom form - subpath: 'sections/layr/hint-custom-form.xml', - warnings: [ - CompilerMessages.Warn_UnsupportedCustomForm({id: "us"}), - ], - }, - { - // error on unknown form - subpath: 'sections/layr/error-custom-form.xml', - warnings: [ - CompilerMessages.Warn_UnsupportedCustomForm({id: "zzz"}), - ], - errors: [CompilerMessages.Error_InvalidHardware({ - form: 'zzz', - }),], - }, ]); }); From 8db7a219819b1700fff3065946a4186ef6a8b147 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 14:23:12 +1100 Subject: [PATCH 107/207] chore: Apply suggestions from code review Co-authored-by: Eberhard Beilharz --- docs/build/old/core-desktop-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/old/core-desktop-notes.md b/docs/build/old/core-desktop-notes.md index db72bd4f642..9d22c7bd9c3 100644 --- a/docs/build/old/core-desktop-notes.md +++ b/docs/build/old/core-desktop-notes.md @@ -96,7 +96,7 @@ You may also need `kmc` - see below. * Add emcc to PATH (probably upstream/enscripten) -You may also need the `kmc` - see below. +You may also need `kmc` - see below. #### kmc - All Linux platforms From 452b9107c36a0b4dd0c37840fe3fbd930b793f91 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 14:23:47 +1100 Subject: [PATCH 108/207] chore: Apply suggestions from code review --- docs/build/old/core-desktop-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/old/core-desktop-notes.md b/docs/build/old/core-desktop-notes.md index 9d22c7bd9c3..f4c41231043 100644 --- a/docs/build/old/core-desktop-notes.md +++ b/docs/build/old/core-desktop-notes.md @@ -127,7 +127,7 @@ npm i -g @keymanapp/kmc If you want to rebuild keyboards for tests, you'll also need node: ```bash -# TODO: node install +brew install node ``` And you will also need to install `kmc`: From fafedbbd72e6f128c7bbe3ddeacf61260d636bd1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 14:24:07 +1100 Subject: [PATCH 109/207] chore: Apply suggestions from code review --- developer/src/test/auto/kmcomp/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/test/auto/kmcomp/Makefile b/developer/src/test/auto/kmcomp/Makefile index 2221cd78574..cbbeb5c8a15 100644 --- a/developer/src/test/auto/kmcomp/Makefile +++ b/developer/src/test/auto/kmcomp/Makefile @@ -1,7 +1,7 @@ # # Test that kmc passes various compile tests # -# Uses kmc.exe rather than attempting to run a separate test +# Uses kmc.js (via kmc.cmd) rather than attempting to run a separate test # !include ..\..\..\Defines.mak From c168ef01cb5b4fd9476cf65537931276c65b4796 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 12:36:54 +0700 Subject: [PATCH 110/207] feat(developer): show an INFO message when warnings have failed a build Fixes #9610. If a build fails when "treat warnings as errors" is on, but there are only warnings and no error messages, it can be confusing for the developer. Adds the following `INFO_WarningsHaveFailedBuild` message: `The build failed because option "treat warnings as errors" is enabled and there are one or more warnings.` --- developer/src/kmc/src/commands/build.ts | 3 +++ .../src/messages/infrastructureMessages.ts | 4 ++++ .../kmc/test/test-infrastructureMessages.ts | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 1047dd8208c..62fbb95031c 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -129,6 +129,9 @@ async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, o : InfrastructureMessages.Info_FileBuiltSuccessfully({filename:buildFilename}) ); } else { + if(!callbacks.hasFailureMessage(false)) { // false == check only for error+fatal messages + callbacks.reportMessage(InfrastructureMessages.Info_WarningsHaveFailedBuild()); + } callbacks.reportMessage(builder instanceof BuildProject ? InfrastructureMessages.Info_ProjectNotBuiltSuccessfully({filename:buildFilename}) : InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename:buildFilename}) diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index ee828dae751..25032152a50 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -71,5 +71,9 @@ export class InfrastructureMessages { static Error_NotAProjectFile = (o:{filename:string}) => m(this.ERROR_NotAProjectFile, `File ${o.filename} must have a .kpj extension to be treated as a project.`); static ERROR_NotAProjectFile = SevError | 0x000F; + + static Info_WarningsHaveFailedBuild = () => m(this.INFO_WarningsHaveFailedBuild, + `The build failed because option "treat warnings as errors" is enabled and there are one or more warnings.`); + static INFO_WarningsHaveFailedBuild = SevInfo | 0x0010; } diff --git a/developer/src/kmc/test/test-infrastructureMessages.ts b/developer/src/kmc/test/test-infrastructureMessages.ts index a9b55d075df..fde352f3dd3 100644 --- a/developer/src/kmc/test/test-infrastructureMessages.ts +++ b/developer/src/kmc/test/test-infrastructureMessages.ts @@ -6,6 +6,7 @@ import { makePathToFixture } from './helpers/index.js'; import { NodeCompilerCallbacks } from '../src/util/NodeCompilerCallbacks.js'; import { CompilerErrorNamespace } from '@keymanapp/common-types'; import { unitTestEndpoints } from '../src/commands/build.js'; +import { KmnCompilerMessages } from '@keymanapp/kmc-kmn'; describe('InfrastructureMessages', function () { it('should have a valid InfrastructureMessages object', function() { @@ -65,4 +66,22 @@ describe('InfrastructureMessages', function () { assert.isTrue(ncb.hasMessage(InfrastructureMessages.HINT_FilenameHasDifferingCase), `HINT_FilenameHasDifferingCase not generated, instead got: `+JSON.stringify(ncb.messages,null,2)); }); + + // INFO_WarningsHaveFailedBuild + + it('should generate INFO_WarningsHaveFailedBuild if only warnings failed the build', async function() { + // NOTE: we can probably re-use this format for most other message tests in the future + const ncb = new NodeCompilerCallbacks({logLevel: 'silent'}); + const filename = makePathToFixture('compiler-warnings-as-errors', 'keyboard.kmn'); + const expectedMessages = [ + InfrastructureMessages.INFO_BuildingFile, + KmnCompilerMessages.WARN_HeaderStatementIsDeprecated, + InfrastructureMessages.INFO_WarningsHaveFailedBuild, + InfrastructureMessages.INFO_FileNotBuiltSuccessfully + ]; + await unitTestEndpoints.build(filename, ncb, {compilerWarningsAsErrors: true}); + assert.deepEqual(ncb.messages.map(m => m.code), expectedMessages, + `actual callbacks.messages:\n${JSON.stringify(ncb.messages,null,2)}\n\n`+ + `did not match expected:\n${JSON.stringify(expectedMessages,null,2)}\n\n`); + }); }); From d4fb7cd23429b9d5bf538c4224e9a1c59c6457df Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 12:48:10 +0700 Subject: [PATCH 111/207] chore(developer): reduce duplicate words warning to hint Fixes #9597. --- developer/src/kmc-model/src/build-trie.ts | 12 +++++++----- developer/src/kmc-model/src/model-compiler-errors.ts | 12 ++++++------ developer/src/kmc-model/test/test-parse-wordlist.ts | 8 ++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/developer/src/kmc-model/src/build-trie.ts b/developer/src/kmc-model/src/build-trie.ts index 5f5ae99f85d..7a9b8873f4f 100644 --- a/developer/src/kmc-model/src/build-trie.ts +++ b/developer/src/kmc-model/src/build-trie.ts @@ -112,8 +112,10 @@ function _parseWordList(wordlist: WordList, source: WordListSource): void { wordform = wordform.normalize('NFC'); if (original !== wordform) { - // Mixed normalization forms are yucky! Warn about it. - callbacks.reportMessage(ModelCompilerMessages.Warn_MixedNormalizationForms({wordform: wordform})); + // Mixed normalization forms are yucky! Hint about it, because it may + // result in unexpected counts where multiple normalization forms for one + // word + callbacks.reportMessage(ModelCompilerMessages.Hint_MixedNormalizationForms({wordform: wordform})); } wordform = wordform.trim() @@ -129,9 +131,9 @@ function _parseWordList(wordlist: WordList, source: WordListSource): void { } if (wordsSeenInThisFile.has(wordform)) { - // The same word seen across multiple files is fine, - // but a word seen multiple times in one file is a problem! - callbacks.reportMessage(ModelCompilerMessages.Warn_DuplicateWordInSameFile({wordform: wordform})); + // The same word seen across multiple files is fine, but a word seen + // multiple times in one file may be a problem + callbacks.reportMessage(ModelCompilerMessages.Hint_DuplicateWordInSameFile({wordform: wordform})); } wordsSeenInThisFile.add(wordform); diff --git a/developer/src/kmc-model/src/model-compiler-errors.ts b/developer/src/kmc-model/src/model-compiler-errors.ts index beb3398caac..5d112eb96b6 100644 --- a/developer/src/kmc-model/src/model-compiler-errors.ts +++ b/developer/src/kmc-model/src/model-compiler-errors.ts @@ -2,8 +2,8 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerM const Namespace = CompilerErrorNamespace.ModelCompiler; // const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; -const SevWarn = CompilerErrorSeverity.Warn | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; +// const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -24,13 +24,13 @@ export class ModelCompilerMessages { static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error'); static FATAL_UnexpectedException = SevFatal | 0x0001; - static Warn_MixedNormalizationForms = (o:{wordform: string}) => m(this.WARN_MixedNormalizationForms, + static Hint_MixedNormalizationForms = (o:{wordform: string}) => m(this.HINT_MixedNormalizationForms, `“${o.wordform}” is not in Unicode NFC. Automatically converting to NFC.`); - static WARN_MixedNormalizationForms = SevWarn | 0x0002; + static HINT_MixedNormalizationForms = SevHint | 0x0002; - static Warn_DuplicateWordInSameFile = (o:{wordform: string}) => m(this.WARN_DuplicateWordInSameFile, + static Hint_DuplicateWordInSameFile = (o:{wordform: string}) => m(this.HINT_DuplicateWordInSameFile, `duplicate word “${o.wordform}” found in same file; summing counts`); - static WARN_DuplicateWordInSameFile = SevWarn | 0x0003; + static HINT_DuplicateWordInSameFile = SevHint | 0x0003; static Error_UnimplementedModelFormat = (o:{format: string}) => m(this.ERROR_UnimplementedModelFormat, `Unimplemented model format: ${o.format}`); diff --git a/developer/src/kmc-model/test/test-parse-wordlist.ts b/developer/src/kmc-model/test/test-parse-wordlist.ts index ab610f5e802..93269e243d1 100644 --- a/developer/src/kmc-model/test/test-parse-wordlist.ts +++ b/developer/src/kmc-model/test/test-parse-wordlist.ts @@ -108,9 +108,9 @@ describe('parsing a word list', function () { assert.lengthOf(testCallbacks.messages, 4); // hello has been seen multiple times: - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.WARN_DuplicateWordInSameFile)); + assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); // helló and hello + U+0301 have both been seen: - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.WARN_MixedNormalizationForms)); + assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); // Let's parse another file: @@ -119,9 +119,9 @@ describe('parsing a word list', function () { parseWordListFromContents(repeatedWords, "hello\u0301\t5\n"); assert.lengthOf(testCallbacks.messages, 1); // hello + U+0301 (NFD) has been seen, but... - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.WARN_MixedNormalizationForms)); + assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); // BUT! We have not seen a duplicate **within the same file** - assert.isFalse(testCallbacks.hasMessage(ModelCompilerMessages.WARN_DuplicateWordInSameFile)); + assert.isFalse(testCallbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); assert.deepEqual(repeatedWords, { hello: expected['hello'], From 7adc9b5d955961c6cf3e6901923ab118e72adba7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 15:30:07 +0700 Subject: [PATCH 112/207] feat(developer): support `store(&version) '17.0'` Fixes #9541. Also adds version 16.0 support to the kmcmplib compiler constants, and unit tests for both versions. Does not add any support for automatic version feature detection, because that forces an inverted dependency on the touch layout compilation phase (done in kmc-kmw), which would be a significant refactor. This may be something we need to support in the future. --- common/include/kmx_file.h | 3 +- common/web/types/src/kmx/kmx.ts | 3 +- common/windows/cpp/include/legacy_kmx_file.h | 3 +- .../delphi/keyboards/kmxfileconsts.pas | 3 +- .../test/fixtures/features/version_160.kmn | 8 ++++ .../test/fixtures/features/version_170.kmn | 8 ++++ developer/src/kmc-kmn/test/test-features.ts | 44 +++++++++++++++++++ developer/src/kmcmplib/src/Compiler.cpp | 2 + .../KeymanEngine4Mac/KME/KMBinaryFileFormat.h | 3 +- 9 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 developer/src/kmc-kmn/test/fixtures/features/version_160.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/features/version_170.kmn create mode 100644 developer/src/kmc-kmn/test/test-features.ts diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index b689ba685ad..243e5e0d638 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -68,9 +68,10 @@ namespace kmx { #define VERSION_150 0x00000F00 #define VERSION_160 0x00001000 +#define VERSION_170 0x00001100 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_160 +#define VERSION_MAX VERSION_170 // // Backspace types diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts index 8a2c3e47aaa..6102a69cf55 100644 --- a/common/web/types/src/kmx/kmx.ts +++ b/common/web/types/src/kmx/kmx.ts @@ -144,9 +144,10 @@ export class KMXFile { public static readonly VERSION_150 = 0x00000F00; public static readonly VERSION_160 = 0x00001000; + public static readonly VERSION_170 = 0x00001100; public static readonly VERSION_MIN = this.VERSION_50; - public static readonly VERSION_MAX = this.VERSION_160; + public static readonly VERSION_MAX = this.VERSION_170; // // Backspace types diff --git a/common/windows/cpp/include/legacy_kmx_file.h b/common/windows/cpp/include/legacy_kmx_file.h index 6be4b3b9861..9b8f6a59fb1 100644 --- a/common/windows/cpp/include/legacy_kmx_file.h +++ b/common/windows/cpp/include/legacy_kmx_file.h @@ -74,8 +74,9 @@ #define VERSION_140 0x00000E00 #define VERSION_150 0x00000F00 #define VERSION_160 0x00001000 +#define VERSION_170 0x00001100 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_160 +#define VERSION_MAX VERSION_170 /* Special flag for WM_CHAR/WM_KEY???/WM_SYSKEY???: says that key has been diff --git a/common/windows/delphi/keyboards/kmxfileconsts.pas b/common/windows/delphi/keyboards/kmxfileconsts.pas index e08feb65641..966dada8827 100644 --- a/common/windows/delphi/keyboards/kmxfileconsts.pas +++ b/common/windows/delphi/keyboards/kmxfileconsts.pas @@ -99,9 +99,10 @@ interface VERSION_140 = $00000E00; VERSION_150 = $00000F00; VERSION_160 = $00001000; + VERSION_170 = $00001100; VERSION_MIN = VERSION_50; - VERSION_MAX = VERSION_160; + VERSION_MAX = VERSION_170; VERSION_MASK_MINOR = $00FF; VERSION_MASK_MAJOR = $FF00; diff --git a/developer/src/kmc-kmn/test/fixtures/features/version_160.kmn b/developer/src/kmc-kmn/test/fixtures/features/version_160.kmn new file mode 100644 index 00000000000..43ba17e6af7 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/features/version_160.kmn @@ -0,0 +1,8 @@ +c Description: Verifies that kmcmplib can compile a v16.0 keyboard + +store(&NAME) 'version_160' +store(&VERSION) '16.0' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-kmn/test/fixtures/features/version_170.kmn b/developer/src/kmc-kmn/test/fixtures/features/version_170.kmn new file mode 100644 index 00000000000..b9779d0734b --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/features/version_170.kmn @@ -0,0 +1,8 @@ +c Description: Verifies that kmcmplib can compile a v17.0 keyboard + +store(&NAME) 'version_170' +store(&VERSION) '17.0' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-kmn/test/test-features.ts b/developer/src/kmc-kmn/test/test-features.ts new file mode 100644 index 00000000000..aa3b050a9fd --- /dev/null +++ b/developer/src/kmc-kmn/test/test-features.ts @@ -0,0 +1,44 @@ +import 'mocha'; +import { assert } from 'chai'; +import { KmnCompiler } from '../src/main.js'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { KMX, KmxFileReader } from '@keymanapp/common-types'; + +describe('Keyboard compiler features', async function() { + let compiler: KmnCompiler = null; + let callbacks: TestCompilerCallbacks = null; + + this.beforeAll(async function() { + compiler = new KmnCompiler(); + callbacks = new TestCompilerCallbacks(); + assert(await compiler.init(callbacks)); + assert(compiler.verifyInitialized()); + }); + + beforeEach(function() { + callbacks.clear(); + }); + + // Test each Keyman file version target + + const versions = [ + // TODO(lowpri): we should add a test for each version + also test automatic feature detection + ['16.0', '160', KMX.KMXFile.VERSION_160], + ['17.0', '170', KMX.KMXFile.VERSION_170], + ]; + + for(const v of versions) { + it(`should build a version ${v[0]} keyboard`, function() { + const fixtureName = makePathToFixture('features', `version_${v[1]}.kmn`); + + const result = compiler.runCompiler(fixtureName, `version_${v[1]}.kmx`, {saveDebug: true}); + if(result === null) callbacks.printMessages(); + assert.isNotNull(result); + + const reader = new KmxFileReader(); + const keyboard = reader.read(result.kmx.data); + assert.equal(keyboard.fileVersion, v[2]); + }); + } +}); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 02990bc3248..b09b9d60e4e 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -1029,6 +1029,8 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE else if (u16ncmp(p, u"10.0", 4) == 0) fk->version = VERSION_100; else if (u16ncmp(p, u"14.0", 4) == 0) fk->version = VERSION_140; // Adds support for #917 -- context() with notany() for KeymanWeb else if (u16ncmp(p, u"15.0", 4) == 0) fk->version = VERSION_150; // Adds support for U_xxxx_yyyy #2858 + else if (u16ncmp(p, u"16.0", 4) == 0) fk->version = VERSION_160; // KMXPlus + else if (u16ncmp(p, u"17.0", 4) == 0) fk->version = VERSION_170; // Flicks and gestures else return CERR_InvalidVersion; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h index 97907c67c1f..d91f9b4f40e 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h @@ -45,8 +45,9 @@ struct COMP_KEYBOARD { #define VERSION_140 0x00000E00 #define VERSION_150 0x00000F00 #define VERSION_160 0x00001000 +#define VERSION_170 0x00001100 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_160 +#define VERSION_MAX VERSION_170 struct COMP_GROUP { DWORD dpName; // string (only debug) From a2fa1f918edfa4997a36dd466042d3bbdf20860a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 2 Oct 2023 15:55:00 +0700 Subject: [PATCH 113/207] feat(developer): issue hint if package includes keyboard source files Fixes #9325. Also tweaks test for error_package_must_contain_a_model_or_a_keyboard as the fixture had a .kmn, triggering the new hint, which was unhelpful in this case. --- .../src/kmc-package/src/compiler/messages.ts | 6 ++- .../src/compiler/package-validation.ts | 24 +++++++++++- ...age_must_contain_a_model_or_a_keyboard.kps | 9 ++--- .../test/fixtures/invalid/example.txt | 1 + ...t_source_file_should_not_be_in_package.kps | 38 +++++++++++++++++++ .../src/kmc-package/test/test-messages.ts | 9 ++++- 6 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 developer/src/kmc-package/test/fixtures/invalid/example.txt create mode 100644 developer/src/kmc-package/test/fixtures/invalid/hint_source_file_should_not_be_in_package.kps diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index c230c697723..86f088ab691 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.PackageCompiler; const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -116,5 +116,9 @@ export class CompilerMessages { `The keyboard ${o.id} should have at least one language specified.`); static WARN_KeyboardShouldHaveAtLeastOneLanguage = SevWarn | 0x001B; + static Hint_PackageContainsSourceFile = (o:{filename:string}) => m(this.HINT_PackageContainsSourceFile, + `The source file ${o.filename} should not be included in the package; instead include the compiled result.`); + static HINT_PackageContainsSourceFile = SevHint | 0x001C; + } diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts index 94e180a465e..f0463e8f532 100644 --- a/developer/src/kmc-package/src/compiler/package-validation.ts +++ b/developer/src/kmc-package/src/compiler/package-validation.ts @@ -1,4 +1,4 @@ -import { KmpJsonFile, CompilerCallbacks, CompilerOptions } from '@keymanapp/common-types'; +import { KmpJsonFile, CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types'; import { CompilerMessages } from './messages.js'; import { keymanEngineForWindowsFiles, keymanForWindowsInstallerFiles, keymanForWindowsRedistFiles } from './redist-files.js'; @@ -167,15 +167,35 @@ export class PackageValidation { } private checkIfContentFileIsDangerous(file: KmpJsonFile.KmpJsonFileContentFile): boolean { - let filename = this.callbacks.path.basename(file.name).toLowerCase(); + const filename = this.callbacks.path.basename(file.name).toLowerCase(); + + // # Test for inclusion of redistributable files + if(keymanForWindowsInstallerFiles.includes(filename) || keymanForWindowsRedistFiles.includes(filename) || keymanEngineForWindowsFiles.includes(filename)) { this.callbacks.reportMessage(CompilerMessages.Warn_RedistFileShouldNotBeInPackage({filename})); } + + // # Test for inclusion of .doc or .docx files + if(filename.match(/\.doc(x?)$/)) { this.callbacks.reportMessage(CompilerMessages.Warn_DocFileDangerous({filename})); } + + // # Test for inclusion of keyboard source files + // + // We treat this as a hint, because it's not a dangerous problem, just + // something that suggests that perhaps they are trying to distribute the + // wrong files. + // + // Note: we allow .xml in the package because there are other files + // which may be valid, not just LDML keyboards + const fileType = KeymanFileTypes.sourceTypeFromFilename(file.name); + if(fileType !== null && fileType !== KeymanFileTypes.Source.LdmlKeyboard) { + this.callbacks.reportMessage(CompilerMessages.Hint_PackageContainsSourceFile({filename: file.name})); + } + return true; } diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_package_must_contain_a_model_or_a_keyboard.kps b/developer/src/kmc-package/test/fixtures/invalid/error_package_must_contain_a_model_or_a_keyboard.kps index 335201a6e5b..51b3e3f363d 100644 --- a/developer/src/kmc-package/test/fixtures/invalid/error_package_must_contain_a_model_or_a_keyboard.kps +++ b/developer/src/kmc-package/test/fixtures/invalid/error_package_must_contain_a_model_or_a_keyboard.kps @@ -10,12 +10,11 @@ - - khmer_angkor.kmn - Keyboard Khmer Angkor + + example.txt + Example text file 0 - .kmn + .txt diff --git a/developer/src/kmc-package/test/fixtures/invalid/example.txt b/developer/src/kmc-package/test/fixtures/invalid/example.txt new file mode 100644 index 00000000000..0f8c58ac580 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/example.txt @@ -0,0 +1 @@ +This is a sample text file \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/invalid/hint_source_file_should_not_be_in_package.kps b/developer/src/kmc-package/test/fixtures/invalid/hint_source_file_should_not_be_in_package.kps new file mode 100644 index 00000000000..8de72f2a829 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_source_file_should_not_be_in_package.kps @@ -0,0 +1,38 @@ + + + + 15.0.266.0 + 7.0 + + + SENĆOŦEN (Saanich Dialect) Keyboard + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + basic.kmn + Keyboard Source Basic + 0 + .kmn + + + + + Basic + basic + 1.0 + + Khmer + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index e1d463dc4f2..477fc8f7009 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -182,7 +182,7 @@ describe('CompilerMessages', function () { // ERROR_PackageMustContainAPackageOrAKeyboard - it('should generate ERROR_PackageMustContainAModelOrAKeyboard if package contains a .doc file', async function() { + it('should generate ERROR_PackageMustContainAModelOrAKeyboard if package contains no keyboard or model', async function() { testForMessage(this, ['invalid', 'error_package_must_contain_a_model_or_a_keyboard.kps'], CompilerMessages.ERROR_PackageMustContainAModelOrAKeyboard); }); @@ -201,4 +201,11 @@ describe('CompilerMessages', function () { CompilerMessages.WARN_KeyboardShouldHaveAtLeastOneLanguage); }); + // HINT_PackageContainsSourceFile + + it('should generate HINT_PackageContainsSourceFile if package contains a source file', async function() { + testForMessage(this, ['invalid', 'hint_source_file_should_not_be_in_package.kps'], + CompilerMessages.HINT_PackageContainsSourceFile); + }); + }); From 1b8516652b282676300cede7629688b323fe4d58 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Oct 2023 09:23:48 -0500 Subject: [PATCH 114/207] Actualizar virtual-key-constants.ts Co-authored-by: Marc Durdin --- common/web/types/src/consts/virtual-key-constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 5624afb5729..447c26e2c2d 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -199,9 +199,9 @@ export const CLDRScanToUSVirtualKeyCodes = { 0x39: k.K_SPACE, - 0x56: k.K_oE2, // << Dup + 0x56: k.K_oE2, // << Same as 0x7D; found on iso, abnt2 0x73: k.k_oC1, - 0x7D: k.K_oE2, // << Dup + 0x7D: k.K_oE2, // << Same as 0x56; found on jis }; export type KeyMap = number[][]; From 8200f84418005ca63528979faa8acac257a07a17 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Oct 2023 12:30:36 -0500 Subject: [PATCH 115/207] =?UTF-8?q?feat(developer):=20scancode=20and=20for?= =?UTF-8?q?ms=20=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix casing of k_oC1 - allow vkey 226 to be doubly defined - drop LdmlVkeyNames table and test, not needed anymore (was for vkey #7135) Fixes: #9403 --- .../types/src/consts/virtual-key-constants.ts | 62 +--------------- .../test-ldml-keyboard-xml-reader.ts | 6 +- .../test/test-virtual-key-constants.ts | 70 ------------------- 3 files changed, 6 insertions(+), 132 deletions(-) delete mode 100644 developer/src/kmc-ldml/test/test-virtual-key-constants.ts diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 447c26e2c2d..798dd95eade 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -122,8 +122,8 @@ export const USVirtualKeyCodes = { */ K_oE2:226, K_OE2:226, - k_oC1:193, // ISO B11, ABNT-2 key to left of right shift, not on US keyboard - k_OC1:193, + K_oC1:193, // ISO B11, ABNT-2 key to left of right shift, not on US keyboard + K_OC1:193, 'K_?C1':193, 'k_?C1':193, K_oDF:0xDF, @@ -200,7 +200,7 @@ export const CLDRScanToUSVirtualKeyCodes = { 0x39: k.K_SPACE, 0x56: k.K_oE2, // << Same as 0x7D; found on iso, abnt2 - 0x73: k.k_oC1, + 0x73: k.K_oC1, 0x7D: k.K_oE2, // << Same as 0x56; found on jis }; @@ -230,59 +230,3 @@ export function CLDRScanToVkey(scan: number, badScans?: Set): number { } } - -/** - * Maps LDML VKey Names from CLDR VKey Enum in TR35 to Keyman virtual key codes - */ -export const LdmlVkeyNames: Record = { - 'SPACE': k.K_SPACE, // 0x20, // A03 - '0': k.K_0, // 0x30, // E10 - '1': k.K_1, // 0x31, // E01 - '2': k.K_2, // 0x32, // E02 - '3': k.K_3, // 0x33, // E03 - '4': k.K_4, // 0x34, // E04 - '5': k.K_5, // 0x35, // E05 - '6': k.K_6, // 0x36, // E06 - '7': k.K_7, // 0x37, // E07 - '8': k.K_8, // 0x38, // E08 - '9': k.K_9, // 0x39, // E09 - 'A': k.K_A, // 0x41, // C01 - 'B': k.K_B, // 0x42, // B05 - 'C': k.K_C, // 0x43, // B03 - 'D': k.K_D, // 0x44, // C03 - 'E': k.K_E, // 0x45, // D03 - 'F': k.K_F, // 0x46, // C04 - 'G': k.K_G, // 0x47, // C05 - 'H': k.K_H, // 0x48, // C06 - 'I': k.K_I, // 0x49, // D08 - 'J': k.K_J, // 0x4A, // C07 - 'K': k.K_K, // 0x4B, // C08 - 'L': k.K_L, // 0x4C, // C09 - 'M': k.K_M, // 0x4D, // B07 - 'N': k.K_N, // 0x4E, // B06 - 'O': k.K_O, // 0x4F, // D09 - 'P': k.K_P, // 0x50, // D10 - 'Q': k.K_Q, // 0x51, // D01 - 'R': k.K_R, // 0x52, // D04 - 'S': k.K_S, // 0x53, // C02 - 'T': k.K_T, // 0x54, // D05 - 'U': k.K_U, // 0x55, // D07 - 'V': k.K_V, // 0x56, // B05 - 'W': k.K_W, // 0x57, // D02 - 'X': k.K_X, // 0x58, // B02 - 'Y': k.K_Y, // 0x59, // D06 - 'Z': k.K_Z, // 0x5A, // B01 - 'SEMICOLON': k.K_COLON, // 0xBA, // C10 - 'EQUAL': k.K_EQUAL, // 0xBB, // E12 - 'COMMA': k.K_COMMA, // 0xBC, // B08 - 'HYPHEN': k.K_HYPHEN, // 0xBD, // E11 - 'PERIOD': k.K_PERIOD, // 0xBE, // B09 - 'SLASH': k.K_SLASH, // 0xBF, // B10 - 'GRAVE': k.K_BKQUOTE, // 0xC0, // E00 - 'LBRACKET': k.K_LBRKT, // 0xDB, // D11 - 'BACKSLASH': k.K_BKSLASH, // 0xDC, // D13 - 'RBRACKET': k.K_RBRKT, // 0xDD, // D12 - 'QUOTE': k.K_QUOTE, // 0xDE, // C11 - 'LESS-THAN': k.K_oE2, // 0xE2, // B00 102nd key on European layouts, right of left shift. - 'ABNT2': k.k_oC1, // 0xC1, // B11 Extra key, left of right-shift, ABNT2 -}; diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index 8816ffce235..252f3e43190 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -158,14 +158,14 @@ describe('check scan code routines', () => { assert.equal(badScans.size, 1); assert.sameDeepMembers(Array.from(badScans.values()), [0xFF]); }); - it.skip('CLDRScanToUSVirtualKeyCodes should be 1:1', () => { + it('CLDRScanToUSVirtualKeyCodes should be 1:1', () => { const vkeyToScan = new Map(); // check all scancodes for (let scan = 0; scan <= 0xFF; scan++) { const vkey = CLDRScanToVkey(scan); - if (vkey !== undefined) { + if (vkey !== undefined && vkey !== 226) { assert.isFalse(vkeyToScan.has(vkey), - `vkey ${vkey} mapped from more than one scancode: ${Number(vkeyToScan.get(vkey)).toString(16)} and ${Number(scan).toString(16)}`); + `vkey ${vkey} (other than 226) mapped from more than one scancode: ${Number(vkeyToScan.get(vkey)).toString(16)} and ${Number(scan).toString(16)}`); vkeyToScan.set(vkey, scan); } } diff --git a/developer/src/kmc-ldml/test/test-virtual-key-constants.ts b/developer/src/kmc-ldml/test/test-virtual-key-constants.ts deleted file mode 100644 index 3ad5d3662cc..00000000000 --- a/developer/src/kmc-ldml/test/test-virtual-key-constants.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { expect } from 'chai'; -import { Constants } from '@keymanapp/common-types'; - -import LdmlVkeyNames = Constants.LdmlVkeyNames; - -describe('virtual key constants', function () { - - it('should map the CLDR VKey Enum values to the right constants', function () { - - // These are copied-and-pasted from the table in TR35 - // We want to check that our constants match the ones in that table! - const CLDRVKeyEnumValues: Record = { - 'SPACE': 0x20, - '0': 0x30, - '1': 0x31, - '2': 0x32, - '3': 0x33, - '4': 0x34, - '5': 0x35, - '6': 0x36, - '7': 0x37, - '8': 0x38, - '9': 0x39, - 'A': 0x41, - 'B': 0x42, - 'C': 0x43, - 'D': 0x44, - 'E': 0x45, - 'F': 0x46, - 'G': 0x47, - 'H': 0x48, - 'I': 0x49, - 'J': 0x4A, - 'K': 0x4B, - 'L': 0x4C, - 'M': 0x4D, - 'N': 0x4E, - 'O': 0x4F, - 'P': 0x50, - 'Q': 0x51, - 'R': 0x52, - 'S': 0x53, - 'T': 0x54, - 'U': 0x55, - 'V': 0x56, - 'W': 0x57, - 'X': 0x58, - 'Y': 0x59, - 'Z': 0x5A, - 'SEMICOLON': 0xBA, - 'EQUAL': 0xBB, - 'COMMA': 0xBC, - 'HYPHEN': 0xBD, - 'PERIOD': 0xBE, - 'SLASH': 0xBF, - 'GRAVE': 0xC0, - 'LBRACKET': 0xDB, - 'BACKSLASH': 0xDC, - 'RBRACKET': 0xDD, - 'QUOTE': 0xDE, - 'LESS-THAN': 0xE2, - 'ABNT2': 0xC1 - }; - - const keys = Object.keys(CLDRVKeyEnumValues); - for (let key of keys) { - expect(CLDRVKeyEnumValues[key]).to.be.equal(LdmlVkeyNames[key]); - } - }); -}); From c33172a146d0920b6999bd8049e524d3e4c1a6b0 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 2 Oct 2023 14:08:15 -0400 Subject: [PATCH 116/207] auto: increment master version to 17.0.185 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 277c080391a..b23206e773e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 17.0.184 alpha 2023-10-02 + +* fix(web): fixes toolbar refocus timing after a keyboard change (#9618) + ## 17.0.183 alpha 2023-09-29 * feat(web): browser-KMW support for default subkeys (#9496) diff --git a/VERSION.md b/VERSION.md index 8d2ab4f96c1..10aedf6d36f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.184 \ No newline at end of file +17.0.185 \ No newline at end of file From 2b2661bb1bea711a13454ad568d00aefeddc8e4a Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Oct 2023 18:00:48 -0500 Subject: [PATCH 117/207] =?UTF-8?q?feat(developer):=20scancode=20and=20for?= =?UTF-8?q?ms=20=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixes to the special case test Fixes: #9403 --- .../types/src/consts/virtual-key-constants.ts | 1 + .../test-ldml-keyboard-xml-reader.ts | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts index 798dd95eade..3347660f015 100644 --- a/common/web/types/src/consts/virtual-key-constants.ts +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -202,6 +202,7 @@ export const CLDRScanToUSVirtualKeyCodes = { 0x56: k.K_oE2, // << Same as 0x7D; found on iso, abnt2 0x73: k.K_oC1, 0x7D: k.K_oE2, // << Same as 0x56; found on jis + }; export type KeyMap = number[][]; diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index 252f3e43190..43429676a0a 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -3,7 +3,7 @@ import 'mocha'; import {assert} from 'chai'; import { CommonTypesMessages } from '../../src/util/common-events.js'; import { testReaderCases } from '../helpers/reader-callback-test.js'; -import { CLDRScanToVkey, CLDRScanToKeyMap } from '../../src/consts/virtual-key-constants.js'; +import { CLDRScanToVkey, CLDRScanToKeyMap, USVirtualKeyCodes } from '../../src/consts/virtual-key-constants.js'; function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) { return keys.filter(({id}) => ids.indexOf(id) !== -1); @@ -163,11 +163,20 @@ describe('check scan code routines', () => { // check all scancodes for (let scan = 0; scan <= 0xFF; scan++) { const vkey = CLDRScanToVkey(scan); - if (vkey !== undefined && vkey !== 226) { + if (vkey === undefined) { + // not mapped, which is OK + continue; + } + if (scan === 0x56 || scan === 0x7D) { + // These both can map to this scancode + assert.equal(vkey, USVirtualKeyCodes.K_oE2); + } else { + // don't check those exceptions assert.isFalse(vkeyToScan.has(vkey), - `vkey ${vkey} (other than 226) mapped from more than one scancode: ${Number(vkeyToScan.get(vkey)).toString(16)} and ${Number(scan).toString(16)}`); - vkeyToScan.set(vkey, scan); + `vkey ${vkey} mapped from more than one scancode: ${Number(vkeyToScan.get(vkey)).toString(16)} and ${Number(scan).toString(16)}`); } + // do make sure nothing else maps to that vkey + vkeyToScan.set(vkey, scan); } }); }); From 886c64d1c3d241082734a78640db06587acfc8e6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 3 Oct 2023 13:29:34 +0700 Subject: [PATCH 118/207] chore(developer): add test for checkFilenameConventions == false or unset Fixes #9317. --- .../kmc-package/test/fixtures/invalid/my_file.PDF | Bin 0 -> 54 bytes developer/src/kmc-package/test/test-messages.ts | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 developer/src/kmc-package/test/fixtures/invalid/my_file.PDF diff --git a/developer/src/kmc-package/test/fixtures/invalid/my_file.PDF b/developer/src/kmc-package/test/fixtures/invalid/my_file.PDF new file mode 100644 index 0000000000000000000000000000000000000000..8f8a5ee09e0ed32de70f7196baee6c0ab39aa4b4 GIT binary patch literal 54 zcmdP!%P&c_Qpn3MQAkt>aB)*e%gjmDQOGJSE>XzLOU@}xNlj5mECxyzC*~I9q=Mx& E0p;Zq9{>OV literal 0 HcmV?d00001 diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index e1d463dc4f2..7dca440cee0 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -128,6 +128,13 @@ describe('CompilerMessages', function () { CompilerMessages.WARN_FileInPackageDoesNotFollowFilenameConventions, {checkFilenameConventions: true}); }); + // Test the inverse -- no warning generated if checkFilenameConventions is false + + it('should not generate WARN_FileInPackageDoesNotFollowFilenameConventions if content filename has wrong conventions but checkFilenameConventions is false', async function() { + testForMessage(this, ['invalid', 'warn_file_in_package_does_not_follow_filename_conventions.kps'], null, {checkFilenameConventions: false}); + testForMessage(this, ['invalid', 'warn_file_in_package_does_not_follow_filename_conventions_2.kps'], null, {checkFilenameConventions: false}); + }); + // ERROR_PackageNameCannotBeBlank it('should generate ERROR_PackageNameCannotBeBlank if package info has empty name', async function() { From c66e8c1e8f967cb286028c8d62488b55403caa15 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 3 Oct 2023 13:56:21 +0700 Subject: [PATCH 119/207] chore(developer): switch on code coverage reporting for kmc Fixes #8661. --- developer/src/kmc/build.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index e7c809419ef..137098d6c42 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -74,9 +74,10 @@ fi if builder_start_action test; then eslint . tsc --build test/ - mocha - # TODO: enable c8 (disabled because no coverage at present) - # && c8 --reporter=lcov --reporter=text mocha + readonly C8_THRESHOLD=35 + c8 --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha + builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." + builder_echo warning "Please increase threshold in build.sh as test coverage improves." builder_finish_action success test fi From 1b9c6fba51a0a00d1848f94c13031d510ec82f76 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 3 Oct 2023 15:28:22 +0700 Subject: [PATCH 120/207] feat(developer): warn if .kps includes a .js which is not touch-capable Fixes #9324. As .js keyboards in packages are only used by the iOS and Android apps, it's kinda helpful if the keyboard itself includes a touch layout. While Keyman will fall back to a desktop vk layout if a touch layout is not present, this is less than ideal -- but that's why this is setup as a hint and not a warning. --- .../src/compiler/kmx-keyboard-metadata.ts | 3 +- .../src/kmc-package/src/compiler/messages.ts | 5 ++- .../package-keyboard-target-validator.ts | 16 ++++++-- .../src/compiler/web-keyboard-metadata.ts | 6 ++- ...t_js_keyboard_file_has_no_touch_targets.js | 1 + ..._js_keyboard_file_has_no_touch_targets.kmn | 7 ++++ ..._js_keyboard_file_has_no_touch_targets.kps | 40 +++++++++++++++++++ .../src/kmc-package/test/test-messages.ts | 11 +++++ 8 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.js create mode 100644 developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kmn create mode 100644 developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kps diff --git a/developer/src/kmc-package/src/compiler/kmx-keyboard-metadata.ts b/developer/src/kmc-package/src/compiler/kmx-keyboard-metadata.ts index 1fbefd7e940..75ada3a8acc 100644 --- a/developer/src/kmc-package/src/compiler/kmx-keyboard-metadata.ts +++ b/developer/src/kmc-package/src/compiler/kmx-keyboard-metadata.ts @@ -8,7 +8,8 @@ export function getCompiledKmxKeyboardMetadata(kmx: KMX.KEYBOARD): WebKeyboardMe keyboardName: getStoreFromKmx(kmx, KMX.KMXFile.TSS_NAME), keyboardVersion: getStoreFromKmx(kmx, KMX.KMXFile.TSS_KEYBOARDVERSION), minKeymanVersion: ((kmx.fileVersion & 0xFF00) >> 8).toString() + '.' + (kmx.fileVersion & 0xFF).toString(), - targets: getStoreFromKmx(kmx, KMX.KMXFile.TSS_TARGETS) ?? 'windows' + targets: getStoreFromKmx(kmx, KMX.KMXFile.TSS_TARGETS) ?? 'windows', + hasTouchLayout: !!getStoreFromKmx(kmx, KMX.KMXFile.TSS_LAYOUTFILE) }; } diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index c230c697723..79bae2d5b9f 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.PackageCompiler; const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -116,5 +116,8 @@ export class CompilerMessages { `The keyboard ${o.id} should have at least one language specified.`); static WARN_KeyboardShouldHaveAtLeastOneLanguage = SevWarn | 0x001B; + static Hint_JsKeyboardFileHasNoTouchTargets = (o:{id:string}) => m(this.HINT_JsKeyboardFileHasNoTouchTargets, + `The keyboard ${o.id} has been included for touch platforms, but does not include a touch layout.`); + static HINT_JsKeyboardFileHasNoTouchTargets = SevHint | 0x001C; } diff --git a/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts b/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts index a79ec8f00a7..96cb23686e9 100644 --- a/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts +++ b/developer/src/kmc-package/src/compiler/package-keyboard-target-validator.ts @@ -8,9 +8,14 @@ export class PackageKeyboardTargetValidator { public verifyAllTargets(kmp: KmpJsonFile.KmpJsonFile, metadata: KeyboardMetadataCollection): void { for(let keyboard of Object.keys(metadata)) { + let hasJS = true; + if(metadata[keyboard].data.targets) { - // get the targets from the .kmx - this.verifyTargets(metadata[keyboard].keyboard, metadata[keyboard].data.targets, kmp); + // get the targets from the .kmx (only the .kmx has the targets data) + hasJS = this.verifyTargets(metadata[keyboard].keyboard, metadata[keyboard].data.targets, kmp); + } + if(hasJS && !metadata[keyboard].data.hasTouchLayout) { + this.callbacks.reportMessage(CompilerMessages.Hint_JsKeyboardFileHasNoTouchTargets({id: metadata[keyboard].keyboard.id})); } } } @@ -23,7 +28,7 @@ export class PackageKeyboardTargetValidator { keyboard: KmpJsonFile.KmpJsonFileKeyboard, targetsText: string, kmp: KmpJsonFile.KmpJsonFile - ): void { + ): boolean { // Note, if we have gotten this far, we've already located and loaded a // .kmx, so no need to verify that the package includes a .kmx @@ -34,7 +39,12 @@ export class PackageKeyboardTargetValidator { if(!kmp.files.find(file => this.callbacks.path.basename(file.name, '.js') == keyboard.id)) { // .js version of the keyboard is not found, warn this.callbacks.reportMessage(CompilerMessages.Warn_JsKeyboardFileIsMissing({id: keyboard.id})); + return false; } + // A js file is included and targeted + return true; } + // js is not targeted + return false; } } \ No newline at end of file diff --git a/developer/src/kmc-package/src/compiler/web-keyboard-metadata.ts b/developer/src/kmc-package/src/compiler/web-keyboard-metadata.ts index 5800163ff95..eed412df1e2 100644 --- a/developer/src/kmc-package/src/compiler/web-keyboard-metadata.ts +++ b/developer/src/kmc-package/src/compiler/web-keyboard-metadata.ts @@ -6,6 +6,7 @@ export interface WebKeyboardMetadata { isRtl: boolean; isMnemonic: boolean; targets?: string; + hasTouchLayout: boolean; }; /** @@ -24,12 +25,14 @@ export function getCompiledWebKeyboardMetadata(js: string): WebKeyboardMetadata const minverRegex = /this.KMINVER\s*=\s*([''"])(.*?)\1/; const rtlRegex = /this.KRTL\s*=\s*(.*?)\s*;/; const mnemonicRegex = /this.KM\s*=\s*(.*?)\s*;/; + const touchLayoutRegex = /this.KVKL\s*=\s*{/; const name = nameRegex.exec(js); const kbver = kbverRegex.exec(js); const minver = minverRegex.exec(js); const rtl = rtlRegex.exec(js); const mnemonic = mnemonicRegex.exec(js); + const touchLayout = touchLayoutRegex.exec(js); const SKeymanVersion70 = '7.0'; @@ -38,6 +41,7 @@ export function getCompiledWebKeyboardMetadata(js: string): WebKeyboardMetadata keyboardVersion: kbver ? kbver[2] : null, minKeymanVersion: minver ? minver[2] : SKeymanVersion70, isRtl: !!(rtl && rtl[1].match(/^(1|true)$/)), - isMnemonic: !!(mnemonic && mnemonic[1].match(/^(1|true)$/)) + isMnemonic: !!(mnemonic && mnemonic[1].match(/^(1|true)$/)), + hasTouchLayout: !!touchLayout }; } diff --git a/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.js b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.js new file mode 100644 index 00000000000..5a6fff14f71 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.js @@ -0,0 +1 @@ +KeymanWeb.KR(new Keyboard_hint_js_keyboard_file_has_no_touch_targets());function Keyboard_hint_js_keyboard_file_has_no_touch_targets(){this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;this.KI="Keyboard_hint_js_keyboard_file_has_no_touch_targets";this.KN="hint_js_keyboard_file_has_no_touch_targets";this.KMINVER="9.0";this.KV=null;this.KDU=0;this.KH='';this.KM=0;this.KBVER="1.0";this.KMBM=0x0;this.KVER="17.0.184.0";this.KVS=[];this.gs=function(t,e) {return this.g0(t,e);};this.gs=function(t,e) {return this.g0(t,e);};this.g0=function(t,e) {var k=KeymanWeb,r=0,m=0;return r;};} \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kmn b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kmn new file mode 100644 index 00000000000..f5919a61c73 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kmn @@ -0,0 +1,7 @@ +store(&name) 'hint_js_keyboard_file_has_no_touch_targets' +store(&keyboardversion) '1.0' +store(&targets) 'web' + +begin unicode > use(main) + +group(main) using keys \ No newline at end of file diff --git a/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kps b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kps new file mode 100644 index 00000000000..0a24a29deb4 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_js_keyboard_file_has_no_touch_targets.kps @@ -0,0 +1,40 @@ + + + + 15.0.266.0 + 7.0 + + + + + + + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + + + + hint_js_keyboard_file_has_no_touch_targets.js + Keyboard + 0 + .js + + + + + hint_js_keyboard_file_has_no_touch_targets + hint_js_keyboard_file_has_no_touch_targets + 1.0 + + Central Khmer (Khmer, Cambodia) + + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index e1d463dc4f2..95bb6adfd95 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -201,4 +201,15 @@ describe('CompilerMessages', function () { CompilerMessages.WARN_KeyboardShouldHaveAtLeastOneLanguage); }); + // HINT_JsKeyboardFileHasNoTouchTargets + + it('should generate HINT_JsKeyboardFileHasNoTouchTargets if keyboard has no touch targets', async function() { + testForMessage(this, ['invalid', 'hint_js_keyboard_file_has_no_touch_targets.kps'], + CompilerMessages.HINT_JsKeyboardFileHasNoTouchTargets); + }); + + it('should not generate HINT_JsKeyboardFileHasNoTouchTargets if keyboard has a touch target', async function() { + testForMessage(this, ['khmer_angkor', 'source', 'khmer_angkor.kps'], null); + }); + }); From 4c206e50a786b53a2b68b26d946505a163ebe6e2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 3 Oct 2023 15:48:43 +0700 Subject: [PATCH 121/207] fix(core): clean cached ICU in core Fixes #9636. --- core/commands.inc.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/commands.inc.sh b/core/commands.inc.sh index 6916b61a9d9..bff1e90f2dc 100644 --- a/core/commands.inc.sh +++ b/core/commands.inc.sh @@ -11,7 +11,19 @@ do_clean() { # clean: note build/ will be left, but build// should be gone local target=$1 builder_start_action clean:$target || return 0 + rm -rf "$MESON_PATH" + + # Removes ICU cached components so it will be re-downloaded and built. Note: + # we could use `git clean`, but this clarifies exactly what is deleted, and + # but we try not to use git commands in build scripts, to maintain clear + # responsibility + rm -rf \ + "$THIS_SCRIPT_PATH/subprojects/packagecache" \ + "$THIS_SCRIPT_PATH/subprojects/icu" \ + "$THIS_SCRIPT_PATH/subprojects/*.tgz" \ + "$THIS_SCRIPT_PATH/subprojects/*.zip" + builder_finish_action success clean:$target } From 311c7675eeb581042f6ac2d9a726d21061f9ddf4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 00:34:57 +1100 Subject: [PATCH 122/207] chore: Update developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas --- .../Keyman.Developer.System.Project.kmnProjectFileAction.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas index e490bd7821e..ce952d979ee 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas @@ -46,8 +46,6 @@ function TkmnProjectFileAction.Clean: Boolean; begin FJS := TKeyboardUtils.GetKeymanWebCompiledFileName(FileName); CleanFile(FJS); // keyboard-x.y.js -// CleanFile(ChangeFileExt(FJS, '') + '_load.js'); // keyboard-x.y_load.js -// CleanFile(ChangeFileExt(FJS, '.json'), True); // keyboard-x.y_load.js end; Result := True; From 64e7606290b78fb7efad2554230fc6102f382a1b Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 3 Oct 2023 14:03:03 -0400 Subject: [PATCH 123/207] auto: increment master version to 17.0.186 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b23206e773e..afc72e7a17c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 17.0.185 alpha 2023-10-03 + +* chore(web): Add non-printing characters to the OSK (#9547) +* feat(developer): support `store(&version) '17.0'` (#9656) +* chore(developer): add test for `checkFilenameConventions == false` or unset (#9661) +* feat(developer): ldml scan codes support (#9615) + ## 17.0.184 alpha 2023-10-02 * fix(web): fixes toolbar refocus timing after a keyboard change (#9618) diff --git a/VERSION.md b/VERSION.md index 10aedf6d36f..e7118d25e17 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.185 \ No newline at end of file +17.0.186 \ No newline at end of file From 2fa8a09672eb7ccedb0e1c91121d08b8a345c504 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 12:44:56 +1100 Subject: [PATCH 124/207] chore: Update core/commands.inc.sh Co-authored-by: Steven R. Loomis --- core/commands.inc.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/core/commands.inc.sh b/core/commands.inc.sh index bff1e90f2dc..2d3bccc4c08 100644 --- a/core/commands.inc.sh +++ b/core/commands.inc.sh @@ -19,7 +19,6 @@ do_clean() { # but we try not to use git commands in build scripts, to maintain clear # responsibility rm -rf \ - "$THIS_SCRIPT_PATH/subprojects/packagecache" \ "$THIS_SCRIPT_PATH/subprojects/icu" \ "$THIS_SCRIPT_PATH/subprojects/*.tgz" \ "$THIS_SCRIPT_PATH/subprojects/*.zip" From 8e23b11dfcb357e82f37d1b04dbcf14a3b9e745d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 09:38:48 +0700 Subject: [PATCH 125/207] chore(developer): convert Server to ES Modules Fixes #9672. Also rewrote build.sh as a builder script in order to simplify some of the build changes. Makes /web a dependency of Keyman Developer Server, which is a significant improvement, but we need to consider if we want to use web's debug build as we did in the past. --- developer/src/server/build-addins.inc.sh | 2 +- developer/src/server/build.sh | 203 +++++++----------- developer/src/server/package.json | 12 +- developer/src/server/postbuild.sh | 25 --- developer/src/server/src/config.ts | 2 +- developer/src/server/src/data.ts | 4 +- developer/src/server/src/environment.ts | 4 +- .../src/handlers/api/debugobject/get.ts | 6 +- .../src/handlers/api/debugobject/register.ts | 12 +- .../handlers/api/debugobject/unregister.ts | 8 +- .../server/src/handlers/api/font/register.ts | 6 +- .../src/handlers/api/keyboard/register.ts | 6 +- .../src/handlers/api/package/register.ts | 6 +- .../server/src/handlers/inc/keyboards-css.ts | 6 +- .../server/src/handlers/inc/keyboards-js.ts | 4 +- .../server/src/handlers/inc/packages-json.ts | 6 +- developer/src/server/src/index.ts | 26 +-- developer/src/server/src/ngrok.d.ts | 1 + developer/src/server/src/routes.ts | 40 ++-- developer/src/server/src/tray.ts | 4 +- developer/src/server/src/version-data.test.ts | 2 +- developer/src/server/src/win32-tray.ts | 13 +- .../src/server/src/win32/console/index.d.ts | 5 - .../src/server/src/win32/console/index.js | 7 - .../src/server/src/win32/console/index.ts | 6 + .../src/win32/trayicon/{index.js => index.ts} | 23 +- developer/src/server/tsconfig.json | 9 +- 27 files changed, 185 insertions(+), 263 deletions(-) delete mode 100755 developer/src/server/postbuild.sh create mode 100644 developer/src/server/src/ngrok.d.ts delete mode 100644 developer/src/server/src/win32/console/index.d.ts delete mode 100644 developer/src/server/src/win32/console/index.js create mode 100644 developer/src/server/src/win32/console/index.ts rename developer/src/server/src/win32/trayicon/{index.js => index.ts} (68%) diff --git a/developer/src/server/build-addins.inc.sh b/developer/src/server/build-addins.inc.sh index cce1e9ae6f0..3643ebffcdf 100644 --- a/developer/src/server/build-addins.inc.sh +++ b/developer/src/server/build-addins.inc.sh @@ -11,7 +11,7 @@ isNodeX64() { # [[ $(file -b "$(which node)" | grep x86-64) ]] && echo 1 || echo 0 } -build_addins() { +do_build_addins() { local NODEX64=$(isNodeX64) local ARCH=x64 local TRAYICON_TARGET=addon.x64.node diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index c735e26fecb..e609635b114 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -1,10 +1,4 @@ #!/usr/bin/env bash -# -# Compiles Keyman Developer Server for deployment -# - -# Exit on command failure and when using unset variables: -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -16,121 +10,64 @@ EX_USAGE=64 . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" . "$KEYMAN_ROOT/resources/build/jq.inc.sh" -pushd "$(dirname "$THIS_SCRIPT")" - -# Build the main script. -build () { - npm run build || builder_die "Could not build top-level JavaScript file." -} - -display_usage ( ) { - echo "Usage: $0 [--production|--watch] [--test|--tdd]" - echo " $0 --help" - echo - echo " --help displays this screen and exits" - echo " --production, -p builds production release in /developer/bin/server/" - echo " --skip-package-install, -S don't run npm install (not valid with --production)" - echo " --test, -t runs unit tests after building" - #echo " --watch, -w builds dev server in watch mode" - echo " --tdd runs unit tests WITHOUT building" - echo " --no-build-addins don't build/copy Win32 addins" - echo " --no-build-kmw don't build KeymanWeb" - echo " --no-copy-kmw don't copy KeymanWeb" +cd "$THIS_SCRIPT_PATH" + +builder_describe "Build Keyman Developer Server" \ + @/common/web/keyman-version \ + @/web \ + clean \ + configure \ + build \ + test \ + "installer Prepare for Keyman Developer installer" \ + "publish Publish to NPM" \ + ":server Keyman Developer Server main program" \ + ":addins Windows addins for GUI integration" + +builder_describe_outputs \ + configure:server /node_modules \ + configure:addins /node_modules \ + build:server /developer/src/server/build/src/index.js \ + build:addins /developer/src/server/build/src/win32/console/index.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +function clean_server() { + rm -rf "$THIS_SCRIPT_PATH/build" + rm -rf "$THIS_SCRIPT_PATH/node_modules" + rm -rf "$THIS_SCRIPT_PATH/tsconfig.tsbuildinfo" + rm -rf "$THIS_SCRIPT_PATH/src/site/resource" + + # No longer in use, cleanup from previous versions: + rm -rf "$THIS_SCRIPT_PATH/dist" } -################################ Main script ################################ - -run_tests=0 -production=0 -build=1 -install_dependencies=1 -build_keymanweb=1 -copy_keymanweb=1 -build_addins=1 - -# Process command-line arguments -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - --help|-h|-?) - display_usage - exit - ;; - --production|-p) - production=1 - ;; - --skip-package-install|-S) - install_dependencies=0 - ;; - --no-build-kmw) - build_keymanweb=0 - ;; - --no-build-addins) - build_addins=0 - ;; - --no-copy-kmw) - copy_keymanweb=0 - ;; - --test) - run_tests=1 - ;; - --tdd) - run_tests=1 - build=0 - install_dependencies=0 - ;; - *) - echo "$0: invalid option: $key" - display_usage - exit $EX_USAGE - esac - shift # past the processed argument -done - -# ---------------------------------------- -# Install dependencies -# ---------------------------------------- - -# Check if Node.JS/npm is installed. -type npm >/dev/null ||\ - builder_die "Build environment setup error detected! Please ensure Node.js is installed!" - -if (( install_dependencies )) ; then +function configure_server() { verify_npm_setup # See https://github.com/bubenshchykov/ngrok/issues/254, https://github.com/bubenshchykov/ngrok/pull/255 # TODO: this is horrible; is there a way we can avoid this? rm -f "$KEYMAN_ROOT"/node_modules/ngrok/bin/ngrok.exe -fi - -# ---------------------------------------- -# Rebuild and bundle addins -# ---------------------------------------- +} -if (( build_addins )); then - . ./build-addins.inc.sh +function build_addins() { + # Rebuild and bundle addins + source ./build-addins.inc.sh # If we have an x64 version of node installed if [[ $(isNodeX64) ]]; then - build_addins + do_build_addins fi # Build with the Keyman Developer x86 version of node - PATH="$KEYMAN_ROOT/developer/src/inst/node/dist:$PATH" build_addins -fi - -# ---------------------------------------- -# Build and bundle KeymanWeb -# ---------------------------------------- - -if (( build_keymanweb )); then - pushd "$KEYMAN_ROOT/web/" - ./build.sh build --debug - popd -fi + PATH="$KEYMAN_ROOT/developer/src/inst/node/dist:$PATH" do_build_addins +} -if (( copy_keymanweb )); then - WEB_SRC="$KEYMAN_ROOT/web/build/publish/debug" - DST="$(dirname "$THIS_SCRIPT")/src/site/resource" +function build_server() { + # Copy keymanweb build artifacts + local WEB_SRC="$KEYMAN_ROOT/web/build/publish/debug" + local DST="$THIS_SCRIPT_PATH/src/site/resource" rm -rf "$DST" mkdir -p "$DST/osk" @@ -140,33 +77,26 @@ if (( copy_keymanweb )); then cp -R "$WEB_SRC/ui/"* "$DST/ui/" cp "$KEYMAN_ROOT/web/LICENSE" "$DST/" cp "$KEYMAN_ROOT/web/README.md" "$DST/" -fi - -# ---------------------------------------- -# Build the project -# ---------------------------------------- -npm run build || builder_die "Compilation failed." -echo "Typescript compilation successful." + # Build server + tsc --build -# ---------------------------------------- -# Unit tests -# ---------------------------------------- + # Post build + mkdir -p "$THIS_SCRIPT_PATH/build/src/site/" + mkdir -p "$THIS_SCRIPT_PATH/build/src/win32/" + cp -r "$THIS_SCRIPT_PATH/src/site/"** "$THIS_SCRIPT_PATH/build/src/site/" + cp -r "$THIS_SCRIPT_PATH/src/win32/"** "$THIS_SCRIPT_PATH/build/src/win32/" -if (( run_tests )); then - npm test || builder_die "Tests failed" -fi - -# ---------------------------------------- -# Deploy to dist/ -# ---------------------------------------- + replaceVersionStrings "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js.in" "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js" + rm "$THIS_SCRIPT_PATH/build/src/site/lib/sentry/init.js.in" +} -if (( production )) ; then +function installer_server() { # We need to build in a tmp folder so that npm doesn't get confused by our # monorepo setup, and so we can copy the relevant node_modules in, because # we'll need them in order to build the deployable version. - PRODBUILDTEMP=`mktemp -d` + local PRODBUILDTEMP=`mktemp -d` echo "Preparing in $PRODBUILDTEMP" # Remove @keymanapp devDependencies because they won't install outside the # monorepo context @@ -180,14 +110,27 @@ if (( production )) ; then rm -f node_modules/ngrok/bin/ngrok.exe popd - # @keymanapp/keyman-version is required in dist now but we need to copy it in manually + # @keymanapp/keyman-version is required in build/ now but we need to copy it in manually mkdir -p "$PRODBUILDTEMP/node_modules/@keymanapp/" cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/keyman-version/" "$PRODBUILDTEMP/node_modules/@keymanapp/" # We'll build in the $KEYMAN_ROOT/developer/bin/server/ folder rm -rf "$KEYMAN_ROOT/developer/bin/server/" - mkdir -p "$KEYMAN_ROOT/developer/bin/server/dist/" - cp -R dist/* "$KEYMAN_ROOT/developer/bin/server/dist/" + mkdir -p "$KEYMAN_ROOT/developer/bin/server/build/" + cp -R build/* "$KEYMAN_ROOT/developer/bin/server/build/" cp -R "$PRODBUILDTEMP"/* "$KEYMAN_ROOT/developer/bin/server/" rm -rf "$PRODBUILDTEMP" -fi +} +builder_run_action clean:server clean_server +builder_run_action configure:server configure_server +builder_run_action build:server build_server +builder_run_action build:addins build_addins +builder_run_action test:server mocha +# builder_run_action test:addins # no op +builder_run_action installer:server installer_server + +# TODO: consider 'watch' +# function watch_server() { +# tsc-watch --onSuccess "node --inspect ." --onFailure "node --inspect ." +# } +# builder_run_action watch:server watch_server diff --git a/developer/src/server/package.json b/developer/src/server/package.json index dd779db1a4d..6d23e11bfb7 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -1,13 +1,11 @@ { "name": "@keymanapp/developer-server", "description": "Keyman Developer backend server", - "main": "dist/index.js", + "main": "build/src/index.js", + "type": "module", "scripts": { - "build": "tsc -b", - "postbuild": "npx gosh ./postbuild.sh", - "prod": "node .", - "test": "mocha", - "watch": "npm run postbuild && tsc-watch --onSuccess \"node --inspect .\" --onFailure \"node --inspect .\"" + "build": "npx gosh ./build.sh build", + "test": "npx gosh ./build.sh test" }, "license": "MIT", "dependencies": { @@ -24,8 +22,8 @@ "node-windows-trayicon": "keymanapp/node-windows-trayicon#keyman-16.0" }, "devDependencies": { - "@keymanapp/resources-gosh": "*", "@keymanapp/keyman-version": "*", + "@keymanapp/resources-gosh": "*", "@types/chai": "^4.3.0", "@types/express": "^4.17.13", "@types/mocha": "^9.1.0", diff --git a/developer/src/server/postbuild.sh b/developer/src/server/postbuild.sh deleted file mode 100755 index e4b90e7e50b..00000000000 --- a/developer/src/server/postbuild.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# -# postbuild script run by npm during/after a build -# - -# Exit on command failure and when using unset variables: -set -eu - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" - -LOCAL_ROOT="$(dirname "$THIS_SCRIPT")" - -mkdir -p "$LOCAL_ROOT/dist/site/" -mkdir -p "$LOCAL_ROOT/dist/win32/" -cp -r "$LOCAL_ROOT/src/site/"** "$LOCAL_ROOT/dist/site/" -cp -r "$LOCAL_ROOT/src/win32/"** "$LOCAL_ROOT/dist/win32/" - -replaceVersionStrings "$LOCAL_ROOT/dist/site/lib/sentry/init.js.in" "$LOCAL_ROOT/dist/site/lib/sentry/init.js" -rm "$LOCAL_ROOT/dist/site/lib/sentry/init.js.in" diff --git a/developer/src/server/src/config.ts b/developer/src/server/src/config.ts index 5c67cd8c7e5..6197cde05aa 100644 --- a/developer/src/server/src/config.ts +++ b/developer/src/server/src/config.ts @@ -1,4 +1,4 @@ -import fs = require('fs'); +import * as fs from 'fs'; export class Configuration { public readonly appDataPath: string; diff --git a/developer/src/server/src/data.ts b/developer/src/server/src/data.ts index fcc750495d7..58763f07013 100644 --- a/developer/src/server/src/data.ts +++ b/developer/src/server/src/data.ts @@ -1,5 +1,5 @@ -import fs = require('fs'); -import { configuration } from './config'; +import * as fs from 'fs'; +import { configuration } from './config.js'; export interface DebugObject { id: string; diff --git a/developer/src/server/src/environment.ts b/developer/src/server/src/environment.ts index be359f81d32..f3e05660245 100644 --- a/developer/src/server/src/environment.ts +++ b/developer/src/server/src/environment.ts @@ -1,5 +1,5 @@ -import { extractVersionData } from './version-data'; +import { extractVersionData } from './version-data.js'; // TODO: environment should be just KEYMAN_VERSION -const KEYMAN_VERSION = require("@keymanapp/keyman-version").KEYMAN_VERSION; +import KEYMAN_VERSION from "@keymanapp/keyman-version"; export const environment = extractVersionData(KEYMAN_VERSION.VERSION_WITH_TAG); diff --git a/developer/src/server/src/handlers/api/debugobject/get.ts b/developer/src/server/src/handlers/api/debugobject/get.ts index 3da2896d556..a701ca3a8f3 100644 --- a/developer/src/server/src/handlers/api/debugobject/get.ts +++ b/developer/src/server/src/handlers/api/debugobject/get.ts @@ -1,6 +1,6 @@ -import chalk = require('chalk'); -import express = require('express'); -import { DebugObject, isValidId, simplifyId } from "../../../data"; +import * as express from 'express'; +import chalk from 'chalk'; +import { DebugObject, isValidId, simplifyId } from "../../../data.js"; export default function apiGet (data: { [id: string]: DebugObject }, req: express.Request, res: express.Response, next: express.NextFunction) { let id = req.query['id'] as string; diff --git a/developer/src/server/src/handlers/api/debugobject/register.ts b/developer/src/server/src/handlers/api/debugobject/register.ts index 9b52b577d88..a1f7a72cd4c 100644 --- a/developer/src/server/src/handlers/api/debugobject/register.ts +++ b/developer/src/server/src/handlers/api/debugobject/register.ts @@ -1,9 +1,9 @@ -import express = require('express'); -import { DebugObject, isValidId, simplifyId } from "../../../data"; -import fs = require('fs'); -import crypto = require('crypto'); -import { configuration } from '../../../config'; -import chalk = require('chalk'); +import * as express from 'express'; +import { DebugObject, isValidId, simplifyId } from "../../../data.js"; +import * as fs from 'fs'; +import * as crypto from 'crypto'; +import { configuration } from '../../../config.js'; +import chalk from 'chalk'; // We allow only 12 objects of each type in the cache const MAX_OBJECTS = 12; diff --git a/developer/src/server/src/handlers/api/debugobject/unregister.ts b/developer/src/server/src/handlers/api/debugobject/unregister.ts index 6473c2edbde..59a846d135d 100644 --- a/developer/src/server/src/handlers/api/debugobject/unregister.ts +++ b/developer/src/server/src/handlers/api/debugobject/unregister.ts @@ -1,7 +1,7 @@ -import express = require('express'); -import { DebugObject, isValidId, simplifyId } from "../../../data"; -import fs = require('fs'); -import chalk = require('chalk'); +import * as express from 'express'; +import { DebugObject, isValidId, simplifyId } from "../../../data.js"; +import * as fs from 'fs'; +import chalk from 'chalk'; export default function apiUnregister (root:{ [id: string]: O }, req: express.Request, res: express.Response, next: express.NextFunction) { let id = req.body['id']; diff --git a/developer/src/server/src/handlers/api/font/register.ts b/developer/src/server/src/handlers/api/font/register.ts index 14d60e8e12f..8da3646a63f 100644 --- a/developer/src/server/src/handlers/api/font/register.ts +++ b/developer/src/server/src/handlers/api/font/register.ts @@ -1,6 +1,6 @@ -import chalk = require('chalk'); -import express = require('express'); -import { data, DebugFont, simplifyId } from "../../../data"; +import chalk from 'chalk'; +import * as express from 'express'; +import { data, DebugFont, simplifyId } from "../../../data.js"; export default function apiKeyboardRegister (req: express.Request, res: express.Response, next: express.NextFunction) { const id = simplifyId(req.body['id']); diff --git a/developer/src/server/src/handlers/api/keyboard/register.ts b/developer/src/server/src/handlers/api/keyboard/register.ts index 02bdd3dde71..bb89d7b4c30 100644 --- a/developer/src/server/src/handlers/api/keyboard/register.ts +++ b/developer/src/server/src/handlers/api/keyboard/register.ts @@ -1,6 +1,6 @@ -import chalk = require('chalk'); -import express = require('express'); -import { data, DebugKeyboard } from "../../../data"; +import chalk from 'chalk'; +import * as express from 'express'; +import { data, DebugKeyboard } from "../../../data.js"; export default function apiKeyboardRegister (req: express.Request, res: express.Response, next: express.NextFunction) { const keyboard: DebugKeyboard = data.keyboards[req.body['id']]; diff --git a/developer/src/server/src/handlers/api/package/register.ts b/developer/src/server/src/handlers/api/package/register.ts index c21340c7fe7..cf6e2388468 100644 --- a/developer/src/server/src/handlers/api/package/register.ts +++ b/developer/src/server/src/handlers/api/package/register.ts @@ -1,6 +1,6 @@ -import chalk = require('chalk'); -import express = require('express'); -import { data, DebugPackage } from "../../../data"; +import chalk from 'chalk'; +import * as express from 'express'; +import { data, DebugPackage } from "../../../data.js"; export default function apiPackageRegister (req: express.Request, res: express.Response, next: express.NextFunction) { const kmp: DebugPackage = data.packages[req.body['id']]; diff --git a/developer/src/server/src/handlers/inc/keyboards-css.ts b/developer/src/server/src/handlers/inc/keyboards-css.ts index 9302fd11648..ed4cc7e15b8 100644 --- a/developer/src/server/src/handlers/inc/keyboards-css.ts +++ b/developer/src/server/src/handlers/inc/keyboards-css.ts @@ -1,6 +1,6 @@ -import express = require('express'); -import path = require('path'); -import { data, SiteData } from "../../data"; +import * as express from 'express'; +import * as path from 'path'; +import { data, SiteData } from "../../data.js"; export default function handleIncKeyboardsCss (req: express.Request, res: express.Response) { let headers = {"Content-Type": "text/css"}; diff --git a/developer/src/server/src/handlers/inc/keyboards-js.ts b/developer/src/server/src/handlers/inc/keyboards-js.ts index ee09fa0f377..3c42702bf95 100644 --- a/developer/src/server/src/handlers/inc/keyboards-js.ts +++ b/developer/src/server/src/handlers/inc/keyboards-js.ts @@ -1,5 +1,5 @@ -import express = require('express'); -import { SiteData, data } from "../../data"; +import * as express from 'express'; +import { SiteData, data } from "../../data.js"; export default function handleIncKeyboardsJs (req: express.Request, res: express.Response) { let headers = {"Content-Type": "application/javascript"}; diff --git a/developer/src/server/src/handlers/inc/packages-json.ts b/developer/src/server/src/handlers/inc/packages-json.ts index a27fb00615d..44449a5ef66 100644 --- a/developer/src/server/src/handlers/inc/packages-json.ts +++ b/developer/src/server/src/handlers/inc/packages-json.ts @@ -1,6 +1,6 @@ -import express = require('express'); -import { data } from "../../data"; -import { environment } from '../../environment'; +import * as express from 'express'; +import { data } from "../../data.js"; +import { environment } from '../../environment.js'; export default function handleIncPackagesJson (req: express.Request, res: express.Response) { const packages = Object.keys(data.packages).map(id => { return { id: id, filename: id+'.kmp', name: data.packages[id].name} }); diff --git a/developer/src/server/src/index.ts b/developer/src/server/src/index.ts index a6d109f1bad..a2975545730 100644 --- a/developer/src/server/src/index.ts +++ b/developer/src/server/src/index.ts @@ -1,21 +1,21 @@ -import { environment } from './environment'; +import { environment } from './environment.js'; -const Sentry = require("@sentry/node"); +import * as Sentry from '@sentry/node'; Sentry.init({ dsn: 'https://39b25a09410349a58fe12aaf721565af@o1005580.ingest.sentry.io/5983519', // Keyman Developer environment: environment.versionEnvironment, release: environment.versionGitTag }); -import express = require('express'); -import ws = require('ws'); -import os = require('os'); -import multer = require('multer'); -import fs = require('fs'); -import setupRoutes from './routes'; -import { configuration } from './config'; -import tray from './tray'; -import chalk = require('chalk'); +import express from 'express'; +import * as ws from 'ws'; +import * as os from 'os'; +import multer from 'multer'; +import * as fs from 'fs'; +import setupRoutes from './routes.js'; +import { configuration } from './config.js'; +import tray from './tray.js'; +import chalk from 'chalk'; const options = { ngrokLog: false, // Set this to true if you need to see ngrok logs in the console @@ -58,7 +58,7 @@ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 102 /* Websockets */ -const wsServer = new ws.Server({ noServer: true }); +const wsServer = new ws.WebSocketServer({ noServer: true }); wsServer.on('connection', socket => { socket.on('message', (message) => { console.debug('wsServer.socket.onmessage: '+message.toString()); @@ -87,7 +87,7 @@ server.on('upgrade', (request, socket, head) => { configuration.ngrokEndpoint = ''; if(configuration.useNgrok && os.platform() == 'win32' && fs.existsSync(configuration.ngrokBinPath)) { - const ngrok = require('ngrok'); + const ngrok: any = await import('ngrok'); (async function() { configuration.ngrokEndpoint = await ngrok.connect({ proto: 'http', diff --git a/developer/src/server/src/ngrok.d.ts b/developer/src/server/src/ngrok.d.ts new file mode 100644 index 00000000000..61f00d7a0b3 --- /dev/null +++ b/developer/src/server/src/ngrok.d.ts @@ -0,0 +1 @@ +declare module 'ngrok'; \ No newline at end of file diff --git a/developer/src/server/src/routes.ts b/developer/src/server/src/routes.ts index fdd2e421aad..20c09f838f7 100644 --- a/developer/src/server/src/routes.ts +++ b/developer/src/server/src/routes.ts @@ -1,21 +1,21 @@ -import express = require('express'); -import multer = require('multer'); -import ws = require('ws'); -import handleIncKeyboardsJs from './handlers/inc/keyboards-js'; -import { data, DebugFont, DebugKeyboard, DebugModel, DebugObject, DebugPackage, isValidId } from './data'; -import apiGet from './handlers/api/debugobject/get'; -import apiRegister, { apiRegisterFile } from './handlers/api/debugobject/register'; -import apiKeyboardRegister from './handlers/api/keyboard/register'; -import apiFontRegister from './handlers/api/font/register'; -import apiUnregister from './handlers/api/debugobject/unregister'; -import handleIncPackagesJson from './handlers/inc/packages-json'; -import apiPackageRegister from './handlers/api/package/register'; -import handleIncKeyboardsCss from './handlers/inc/keyboards-css'; -import { Environment } from './version-data'; -import { configuration } from './config'; -import chalk = require('chalk'); - -export default function setupRoutes(app: express.Express, upload: multer.Multer, wsServer: ws.Server, environment: Environment ) { +import * as express from 'express'; +import * as ws from 'ws'; +import * as multer from 'multer'; +import handleIncKeyboardsJs from './handlers/inc/keyboards-js.js'; +import { data, DebugFont, DebugKeyboard, DebugModel, DebugObject, DebugPackage, isValidId } from './data.js'; +import apiGet from './handlers/api/debugobject/get.js'; +import apiRegister, { apiRegisterFile } from './handlers/api/debugobject/register.js'; +import apiKeyboardRegister from './handlers/api/keyboard/register.js'; +import apiFontRegister from './handlers/api/font/register.js'; +import apiUnregister from './handlers/api/debugobject/unregister.js'; +import handleIncPackagesJson from './handlers/inc/packages-json.js'; +import apiPackageRegister from './handlers/api/package/register.js'; +import handleIncKeyboardsCss from './handlers/inc/keyboards-css.js'; +import { Environment } from './version-data.js'; +import { configuration } from './config.js'; +import chalk from 'chalk'; + +export default function setupRoutes(app: express.Express, upload: multer.Multer, wsServer: ws.WebSocketServer, environment: Environment ) { /* Middleware - JSON and logging */ @@ -49,7 +49,7 @@ export default function setupRoutes(app: express.Express, upload: multer.Multer, /* All routes */ - app.use('/', express.static('dist/site')); + app.use('/', express.static('build/src/site')); app.post('/upload', localhostOnly, upload.single('file'), (req, res, next) => { const name = req.file.originalname; @@ -170,7 +170,7 @@ export default function setupRoutes(app: express.Express, upload: multer.Multer, /* Utility functions */ -function notifyClients(wsServer: ws.Server, res: express.Request, req: express.Response, next: express.NextFunction) { +function notifyClients(wsServer: ws.WebSocketServer, res: express.Request, req: express.Response, next: express.NextFunction) { wsServer.clients.forEach(c => { c.send('refresh', (err) => { if(err) console.error(chalk.red('Websocket send error '+err.message)); diff --git a/developer/src/server/src/tray.ts b/developer/src/server/src/tray.ts index 0c9c3bf2f65..780d9c792c4 100644 --- a/developer/src/server/src/tray.ts +++ b/developer/src/server/src/tray.ts @@ -1,4 +1,4 @@ -import os = require('os'); +import * as os from 'os'; class TrayStub { public start(localPort: number, ngrokAddress: string) {} @@ -9,7 +9,7 @@ class TrayStub { let tray = new TrayStub(); if(os.platform() == 'win32') { - const Win32Tray = require('./win32-tray'); + const { Win32Tray } = await import('./win32-tray.js'); tray = new Win32Tray(); } diff --git a/developer/src/server/src/version-data.test.ts b/developer/src/server/src/version-data.test.ts index 5f33932b3bd..9f7ebd385f9 100644 --- a/developer/src/server/src/version-data.test.ts +++ b/developer/src/server/src/version-data.test.ts @@ -1,7 +1,7 @@ import {assert} from 'chai'; import 'mocha'; -import { extractVersionData } from './version-data'; +import { extractVersionData } from './version-data.js'; describe('extractVersionData', function() { diff --git a/developer/src/server/src/win32-tray.ts b/developer/src/server/src/win32-tray.ts index b17c775f00c..02ef0a4db80 100644 --- a/developer/src/server/src/win32-tray.ts +++ b/developer/src/server/src/win32-tray.ts @@ -1,14 +1,15 @@ -import path = require("path"); +import * as path from "path"; +import * as url from 'url'; // TODO: this is a Windows-only tray icon. There are a number of // cross-platform solutions but none of them are wonderful. We // should replace this when we find a decent one. -const WindowsTrayicon = require("./win32/trayicon"); -const WindowsConsole = require("./win32/console"); -const open = require("open"); +import WindowsTrayicon from "./win32/trayicon/index.js"; +import WindowsConsole from "./win32/console/index.js"; +import open from 'open'; -module.exports = class Win32Tray { +export class Win32Tray { private myTrayApp: any; public restart(localPort: number, ngrokAddress: string) { @@ -55,7 +56,7 @@ module.exports = class Win32Tray { this.myTrayApp = new WindowsTrayicon({ title: "Keyman Developer Server", - icon: path.resolve(__dirname, "site", "favicon.ico"), + icon: path.resolve(url.fileURLToPath(new URL('.', import.meta.url)), "site", "favicon.ico"), menu: menu }); diff --git a/developer/src/server/src/win32/console/index.d.ts b/developer/src/server/src/win32/console/index.d.ts deleted file mode 100644 index ac9e647fa40..00000000000 --- a/developer/src/server/src/win32/console/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "node-hide-console-window" { - function hideConsole(): void - - function showConsole(): void -} \ No newline at end of file diff --git a/developer/src/server/src/win32/console/index.js b/developer/src/server/src/win32/console/index.js deleted file mode 100644 index 26026657a0b..00000000000 --- a/developer/src/server/src/win32/console/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const os = require('os'); -const {showConsole, hideConsole} = require(os.arch() == 'ia32' ? "./node-hide-console-window" : "./node-hide-console-window.x64"); - -module.exports = { - showConsole, - hideConsole -} \ No newline at end of file diff --git a/developer/src/server/src/win32/console/index.ts b/developer/src/server/src/win32/console/index.ts new file mode 100644 index 00000000000..1bee9ada877 --- /dev/null +++ b/developer/src/server/src/win32/console/index.ts @@ -0,0 +1,6 @@ +import * as os from 'os'; +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const {showConsole, hideConsole} = require(os.arch() == 'ia32' ? "./node-hide-console-window" : "./node-hide-console-window.x64"); + +export default { showConsole, hideConsole }; diff --git a/developer/src/server/src/win32/trayicon/index.js b/developer/src/server/src/win32/trayicon/index.ts similarity index 68% rename from developer/src/server/src/win32/trayicon/index.js rename to developer/src/server/src/win32/trayicon/index.ts index 1e50cef3da0..6b313af262f 100644 --- a/developer/src/server/src/win32/trayicon/index.js +++ b/developer/src/server/src/win32/trayicon/index.ts @@ -1,8 +1,17 @@ -const os = require('os'); +import * as os from 'os'; +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); const { CTrayIconContainer } = require(os.arch() == 'ia32' ? "./addon" : "./addon.x64"); class WindowsTrayicon { - constructor(options) { + __itemCallbacks: any[]; + __icon: any; + __trayTitle: any; + __menuItems: any; + __nativeTray: any; + + + constructor(options: any) { this.__itemCallbacks = []; this.__icon = options.icon; this.__trayTitle = options.title || ""; @@ -12,7 +21,7 @@ class WindowsTrayicon { for (const item of this.__menuItems) { this.__nativeTray.AddMenuItem(item.id, item.caption); } - this.__nativeTray.OnMenuItem((id) => { + this.__nativeTray.OnMenuItem((id: any) => { for (const cb of this.__itemCallbacks) { cb(id); } @@ -23,15 +32,15 @@ class WindowsTrayicon { } this.__nativeTray.Start(); } - item(cb) { + item(cb: any) { if ("function" === typeof cb) { this.__itemCallbacks.push(cb); } } - balloon(title, text, timeout = 5000) { + balloon(title: any, text: any, timeout = 5000) { return new Promise((resolve) => { this.__nativeTray.ShowBalloon(title, text, timeout, () => { - resolve(); + resolve(null); }) }); } @@ -40,4 +49,4 @@ class WindowsTrayicon { } } -module.exports = WindowsTrayicon; +export default WindowsTrayicon; diff --git a/developer/src/server/tsconfig.json b/developer/src/server/tsconfig.json index 280b535dd9e..701a97af6c6 100644 --- a/developer/src/server/tsconfig.json +++ b/developer/src/server/tsconfig.json @@ -1,13 +1,13 @@ { - "extends": "../../../tsconfig-base.json", + "extends": "../../../tsconfig.esm-base.json", "compilerOptions": { - "module": "commonjs", "target": "es2022", - "moduleResolution": "node", "sourceMap": true, - "outDir": "dist/", + "outDir": "build/src/", "rootDir": "src/", + "baseUrl": ".", + "declaration": true, "alwaysStrict": true, "noImplicitThis": true, @@ -16,6 +16,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "noUnusedLocals": true, + "allowJs": true, "lib": ["es2022"] }, "include": [ From 81960a649606bc4dd380e86cc9d3fce9a0548b43 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 09:50:05 +0700 Subject: [PATCH 126/207] chore(developer): move server from cjs to esm tsconfig --- tsconfig.cjs.json | 2 -- tsconfig.esm.json | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 3dff61f8b64..2b8030ac766 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -9,8 +9,6 @@ { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json" }, - { "path": "./developer/src/server/tsconfig.json" }, - { "path": "./resources/build/version/tsconfig.json" }, { "path": "./resources/build/version/tsconfig.production.json" }, // { "path": "./web/bulk_rendering/tsconfig.json" }, diff --git a/tsconfig.esm.json b/tsconfig.esm.json index e0f72d8f8d9..c58550431a8 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -26,6 +26,7 @@ { "path": "./developer/src/kmc-model-info/tsconfig.json" }, { "path": "./developer/src/kmc-package/test/tsconfig.json" }, { "path": "./developer/src/kmc-package/tsconfig.json" }, + { "path": "./developer/src/server/tsconfig.json" }, { "path": "./common/web/keyman-version" }, { "path": "./common/web/types/" }, From e7ac526c69610b918d9d6bfa09785da9ad24b981 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 11:50:03 +0700 Subject: [PATCH 127/207] chore(developer): fixup Makefile for server --- developer/src/server/Makefile | 2 +- developer/src/server/build.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/developer/src/server/Makefile b/developer/src/server/Makefile index 0a580ea3d6a..eef73908049 100644 --- a/developer/src/server/Makefile +++ b/developer/src/server/Makefile @@ -5,7 +5,7 @@ !include ..\Defines.mak build: .virtual - $(GIT_BASH_FOR_KEYMAN) build.sh --production --test + $(GIT_BASH_FOR_KEYMAN) build.sh configure build test installer clean: .virtual #TODO: move to build.sh `clean` target diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index e609635b114..44cdbe1ca73 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -24,6 +24,10 @@ builder_describe "Build Keyman Developer Server" \ ":server Keyman Developer Server main program" \ ":addins Windows addins for GUI integration" +builder_describe_internal_dependency \ + publish:server build:server \ + publish:server build:addins + builder_describe_outputs \ configure:server /node_modules \ configure:addins /node_modules \ From f69f28bbd579c1b6b532df764491508dcef97b9b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 11:04:36 +0700 Subject: [PATCH 128/207] chore(common): convert hextobin to ES Modules Fixes #9675. --- common/tools/hextobin/build.sh | 26 ++++---------------------- common/tools/hextobin/hextobin.ts | 5 +---- common/tools/hextobin/package.json | 10 ++-------- common/tools/hextobin/tsconfig.json | 24 +++++++----------------- tsconfig.esm.json | 1 + 5 files changed, 15 insertions(+), 51 deletions(-) diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh index 702e6610f01..48c8542510c 100755 --- a/common/tools/hextobin/build.sh +++ b/common/tools/hextobin/build.sh @@ -1,11 +1,4 @@ #!/usr/bin/env bash -# -# Builds hextobin.js -# - -# Exit on command failure and when using unset variables: -set -eu - ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -23,20 +16,9 @@ builder_describe "Build hextobin" clean configure build builder_describe_outputs \ configure /node_modules \ build build/index.js -builder_parse "$@" -if builder_start_action clean; then - npm run clean - builder_finish_action success clean -fi - -if builder_start_action configure; then - verify_npm_setup - builder_finish_action success configure -fi - -if builder_start_action build; then - npm run build - builder_finish_action success build -fi +builder_parse "$@" +builder_run_action clean rm -rf build/ node_modules/ +builder_run_action configure verify_npm_setup +builder_run_action build tsc --build diff --git a/common/tools/hextobin/hextobin.ts b/common/tools/hextobin/hextobin.ts index 503b18d4b1a..e53e08ddbcf 100644 --- a/common/tools/hextobin/hextobin.ts +++ b/common/tools/hextobin/hextobin.ts @@ -1,7 +1,6 @@ #!/usr/bin/env node -import * as fs from 'fs'; -import * as rd from 'readline'; import * as program from 'commander'; +import hextobin from './index.js'; let inputFilename: string = ""; let outputFilename: string = ""; @@ -43,6 +42,4 @@ function exitDueToUsageError(message: string): never { return process.exit(64); // SysExits.EX_USAGE } -import hextobin from './index'; - hextobin(inputFilename, outputFilename, {silent: false}); diff --git a/common/tools/hextobin/package.json b/common/tools/hextobin/package.json index 155f8297597..2ebb0e9f4e1 100644 --- a/common/tools/hextobin/package.json +++ b/common/tools/hextobin/package.json @@ -1,6 +1,8 @@ { "name": "@keymanapp/hextobin", "description": "hextobin conversion tool", + "type": "module", + "private": true, "keywords": [ "keyman", "hex", @@ -14,14 +16,6 @@ "bin": { "hextobin": "build/hextobin.js" }, - "scripts": { - "clean": "tsc -b --clean", - "build": "tsc -b" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" - }, "devDependencies": { "@types/node": "^18.7.18" } diff --git a/common/tools/hextobin/tsconfig.json b/common/tools/hextobin/tsconfig.json index e32831fe737..db0a65ae9db 100644 --- a/common/tools/hextobin/tsconfig.json +++ b/common/tools/hextobin/tsconfig.json @@ -1,19 +1,9 @@ { - "extends": "../../../tsconfig-base.json", - "compilerOptions": { - "composite": true, - "declaration": true, - "target": "ES2020", - "module": "CommonJS", - "moduleResolution": "node", - "rootDir": ".", - "outDir": "build/", - }, - "exclude": [ - "node_modules" - ], - "files": [ - "index.ts", - "hextobin.ts" - ] + "extends": "../../../tsconfig.esm-base.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build/" + }, + "exclude": ["node_modules"], + "files": ["index.ts", "hextobin.ts"] } diff --git a/tsconfig.esm.json b/tsconfig.esm.json index c58550431a8..47c0ef92e9e 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -47,5 +47,6 @@ // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, { "path": "./common/web/lm-message-types/" }, { "path": "./common/web/lm-worker/" }, + { "path": "./common/tools/hextobin/" } ] } \ No newline at end of file From fbd9a4b5e899923e8e734c8e50ee6518711b7b4b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 11:46:49 +0700 Subject: [PATCH 129/207] chore(common): convert resources/build/version to ES Modules Fixes #9677. --- common/tools/hextobin/build.sh | 2 +- package-lock.json | 149 ++++++------------ resources/build/increment-version.sh | 4 +- resources/build/version/.gitignore | 1 + resources/build/version/build.sh | 24 +++ resources/build/version/package.json | 11 +- resources/build/version/src/fixupHistory.ts | 7 +- resources/build/version/src/index.ts | 15 +- resources/build/version/src/reportHistory.ts | 6 +- resources/build/version/tsconfig.json | 7 +- .../build/version/tsconfig.production.json | 25 --- tsconfig.esm.json | 4 +- 12 files changed, 106 insertions(+), 149 deletions(-) create mode 100755 resources/build/version/build.sh delete mode 100644 resources/build/version/tsconfig.production.json diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh index 48c8542510c..8df4f9b83a3 100755 --- a/common/tools/hextobin/build.sh +++ b/common/tools/hextobin/build.sh @@ -15,7 +15,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe "Build hextobin" clean configure build builder_describe_outputs \ configure /node_modules \ - build build/index.js + build /common/tools/hextobin/build/index.js builder_parse "$@" diff --git a/package-lock.json b/package-lock.json index 405d2154cfb..dea220c34da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3830,6 +3830,21 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.26", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", + "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.9.2", "license": "MIT", @@ -4753,6 +4768,7 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5148,6 +5164,7 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5884,7 +5901,6 @@ }, "node_modules/escalade": { "version": "3.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9573,6 +9589,7 @@ }, "node_modules/p-try": { "version": "2.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9603,6 +9620,7 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9930,6 +9948,7 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/requirejs": { @@ -10170,6 +10189,7 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "devOptional": true, "license": "ISC" }, "node_modules/set-immediate-shim": { @@ -11172,6 +11192,7 @@ }, "node_modules/which-module": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { @@ -11231,7 +11252,6 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11247,7 +11267,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11255,7 +11274,6 @@ }, "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11263,7 +11281,6 @@ }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "4.2.2", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11276,7 +11293,6 @@ }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.0" @@ -11337,7 +11353,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -11497,16 +11512,14 @@ "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", "typescript": "^4.9.5", - "yargs": "^15.1.0" + "yargs": "^17.7.2" }, "devDependencies": { "@types/node": "^13.7.0", "@types/semver": "^7.1.0", + "@types/yargs": "^17.0.26", "semver": "^7.5.2", "ts-node": "^10.9.1" - }, - "engines": { - "node": ">=16.0" } }, "resources/build/version/node_modules/@types/node": { @@ -11516,74 +11529,37 @@ }, "resources/build/version/node_modules/ansi-regex": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } }, "resources/build/version/node_modules/cliui": { - "version": "6.0.0", - "license": "ISC", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "resources/build/version/node_modules/find-up": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "resources/build/version/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/locate-path": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/p-limit": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "resources/build/version/node_modules/p-locate": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { "node": ">=8" } }, "resources/build/version/node_modules/string-width": { "version": "4.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11595,7 +11571,8 @@ }, "resources/build/version/node_modules/strip-ansi": { "version": "6.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -11603,51 +11580,29 @@ "node": ">=8" } }, - "resources/build/version/node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/y18n": { - "version": "4.0.3", - "license": "ISC" - }, "resources/build/version/node_modules/yargs": { - "version": "15.4.1", - "license": "MIT", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "resources/build/version/node_modules/yargs-parser": { - "version": "18.1.3", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=6" + "node": ">=12" } }, "resources/gosh": { diff --git a/resources/build/increment-version.sh b/resources/build/increment-version.sh index 445459da161..0f5e2d0608f 100755 --- a/resources/build/increment-version.sh +++ b/resources/build/increment-version.sh @@ -88,9 +88,7 @@ echo "increment-version.sh: building resources/build/version" pushd "$KEYMAN_ROOT" npm ci -pushd "$KEYMAN_ROOT/resources/build/version" -npm run build:ts -popd +"$KEYMAN_ROOT/resources/build/version/build.sh" echo "increment-version.sh: running resources/build/version" pushd "$KEYMAN_ROOT" diff --git a/resources/build/version/.gitignore b/resources/build/version/.gitignore index 687e3299fe0..3e42060cc69 100644 --- a/resources/build/version/.gitignore +++ b/resources/build/version/.gitignore @@ -47,3 +47,4 @@ coverage # Build output dist lib +build diff --git a/resources/build/version/build.sh b/resources/build/version/build.sh new file mode 100755 index 00000000000..64892803bc8 --- /dev/null +++ b/resources/build/version/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +# This script runs from its own folder +cd "$THIS_SCRIPT_PATH" + +################################ Main script ################################ + +builder_describe "Build version tooling" clean configure build +builder_describe_outputs \ + configure /resources/build/version/node_modules \ + build /resources/build/version/build/src/index.js + +builder_parse "$@" + +builder_run_action clean rm -rf build/ node_modules/ dist/ lib/ +builder_run_action configure verify_npm_setup +builder_run_action build tsc --build diff --git a/resources/build/version/package.json b/resources/build/version/package.json index 93911e79701..57264f02e44 100644 --- a/resources/build/version/package.json +++ b/resources/build/version/package.json @@ -1,25 +1,24 @@ { + "description": "Automatically updates HISTORY.md based on pull requests", + "type": "module", "dependencies": { "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", "typescript": "^4.9.5", - "yargs": "^15.1.0" + "yargs": "^17.7.2" }, - "description": "Automatically updates HISTORY.md based on pull requests", "devDependencies": { "@types/node": "^13.7.0", "@types/semver": "^7.1.0", + "@types/yargs": "^17.0.26", "semver": "^7.5.2", "ts-node": "^10.9.1" }, - "engines": { - "node": ">=16.0" - }, "files": [ "src" ], "license": "MIT", - "main": "lib/index.js", + "main": "build/src/index.js", "name": "@keymanapp/auto-history-action", "private": true, "scripts": { diff --git a/resources/build/version/src/fixupHistory.ts b/resources/build/version/src/fixupHistory.ts index 2145fa94656..9f48f4851dd 100644 --- a/resources/build/version/src/fixupHistory.ts +++ b/resources/build/version/src/fixupHistory.ts @@ -5,7 +5,7 @@ import { import { GitHub } from '@actions/github'; import { readFileSync, writeFileSync } from 'fs'; import { gt } from 'semver'; -import { reportHistory } from './reportHistory'; +import { reportHistory } from './reportHistory.js'; interface PRInformation { title: string; @@ -42,7 +42,8 @@ const splicePullsIntoHistory = async (pulls: PRInformation[]): Promise<{count: n const versionMatch = new RegExp(`^## ${version} ${tier}`, 'i'); let historyChunks: { heading: string[]; newer: string[]; current: string[]; older: string[] } = {heading:[], newer:[], current:[], older:[]}; - let state = "heading"; + type ChunkType = keyof typeof historyChunks; + let state: ChunkType = "heading"; for(const line of history) { if(line.match(versionMatch)) { // We are in the correct section of history @@ -90,7 +91,7 @@ const splicePullsIntoHistory = async (pulls: PRInformation[]): Promise<{count: n let found = false; // Look for the PR in any other chunk -- can be found with merges of master into PR or chained PRs - for(const chunk of ['newer','current','older']) { + for(const chunk of ['newer','current','older'] as ChunkType[]) { for(const line of historyChunks[chunk]) { if(line.match(pullNumberRe)) { found = true; diff --git a/resources/build/version/src/index.ts b/resources/build/version/src/index.ts index 3d5f896650d..69776231c62 100644 --- a/resources/build/version/src/index.ts +++ b/resources/build/version/src/index.ts @@ -1,13 +1,14 @@ import { info as logInfo } from '@actions/core'; import { GitHub } from '@actions/github'; -import { sendCommentToPullRequestAndRelatedIssues, fixupHistory } from './fixupHistory'; -import { incrementVersion } from './incrementVersion'; -const yargs = require('yargs'); +import { sendCommentToPullRequestAndRelatedIssues, fixupHistory } from './fixupHistory.js'; +import { incrementVersion } from './incrementVersion.js'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; import { readFileSync } from 'fs'; -import { reportHistory } from './reportHistory'; +import { reportHistory } from './reportHistory.js'; -const argv = yargs +const argv = await yargs(hideBin(process.argv)) .command(['history'], 'Fixes up HISTORY.md with pull request data') .command(['version'], 'Increments the current patch version in VERSION.md') .command(['report-history'], 'Print list of outstanding PRs waiting for the next build') @@ -81,10 +82,10 @@ const main = async (): Promise => { if(argv._.includes('report-history')) { let pulls = await reportHistory(octokit, argv.base, argv.force, argv['github-pr'], argv.from, argv.to); - let versions = {}; + let versions: {[index:string]:any} = {}; pulls.forEach((item) => { if(typeof item.version == 'string') { - if(typeof versions[item.version] == 'undefined') { + if(typeof (versions)[item.version] == 'undefined') { versions[item.version] = {data: item.tag_data, pulls: []}; } // We want to invert the order of the pulls as we go to diff --git a/resources/build/version/src/reportHistory.ts b/resources/build/version/src/reportHistory.ts index bf726063339..2fae1ab628e 100644 --- a/resources/build/version/src/reportHistory.ts +++ b/resources/build/version/src/reportHistory.ts @@ -2,8 +2,8 @@ import { warning as logWarning, info as logInfo } from '@actions/core'; import { GitHub } from '@actions/github'; -import { findLastHistoryPR, getAssociatedPR} from './graphql/queries'; -import { spawnChild } from './util/spawnAwait'; +import { findLastHistoryPR, getAssociatedPR} from './graphql/queries.js'; +import { spawnChild } from './util/spawnAwait.js'; const getPullRequestInformation = async ( octokit: GitHub, base: string @@ -67,7 +67,7 @@ const getAssociatedPRInformation = async ( } }: any = response; - const node = nodes.find(node => node.state == 'MERGED'); + const node = nodes.find((node:any) => node.state == 'MERGED'); return node ? { title: node.title, number: node.number } : undefined; }; diff --git a/resources/build/version/tsconfig.json b/resources/build/version/tsconfig.json index 655da5e8afc..9f537c799da 100644 --- a/resources/build/version/tsconfig.json +++ b/resources/build/version/tsconfig.json @@ -1,7 +1,8 @@ { + "extends": "../../../tsconfig.esm-base.json", "compilerOptions": { - "rootDir": "." + "rootDir": ".", + "outDir": "build/", }, - "extends": "./tsconfig.production.json", - "include": ["./**/*"] + "include": ["./**/*.ts"] } diff --git a/resources/build/version/tsconfig.production.json b/resources/build/version/tsconfig.production.json deleted file mode 100644 index 2189a3042d4..00000000000 --- a/resources/build/version/tsconfig.production.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "alwaysStrict": true, - "charset": "utf-8", - "declaration": true, - "declarationMap": true, - "downlevelIteration": true, - "forceConsistentCasingInFileNames": true, - "lib": ["es2017"], - "module": "commonjs", - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "outDir": "lib", - "pretty": true, - "rootDir": "src", - "sourceMap": true, - "strict": true, - "strictNullChecks": true, - "target": "es2017" - }, - "files": ["./src/index.ts"] -} diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 47c0ef92e9e..0252447c63b 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -47,6 +47,8 @@ // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, { "path": "./common/web/lm-message-types/" }, { "path": "./common/web/lm-worker/" }, - { "path": "./common/tools/hextobin/" } + { "path": "./common/tools/hextobin/" }, + + { "path": "./resources/build/version/" }, ] } \ No newline at end of file From f432cab85193ce5ace9c65ab06d328e0f7263854 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 12:06:46 +0700 Subject: [PATCH 130/207] chore(common): keyman-version now generates only es module --- common/web/keyman-version/build-bundler.js | 22 --------- common/web/keyman-version/build.sh | 53 ++++------------------ common/web/keyman-version/package.json | 13 +----- common/web/keyman-version/tsconfig.json | 10 +--- package-lock.json | 7 --- 5 files changed, 13 insertions(+), 92 deletions(-) delete mode 100644 common/web/keyman-version/build-bundler.js diff --git a/common/web/keyman-version/build-bundler.js b/common/web/keyman-version/build-bundler.js deleted file mode 100644 index a5f26a44df9..00000000000 --- a/common/web/keyman-version/build-bundler.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Bundles @keymanapp/keyman-version as a single-file CommonJS module for components - * still in need of it. - */ - -import esbuild from 'esbuild'; - -// Bundles to a compact ESModule -esbuild.buildSync({ - entryPoints: ['build/version.inc.js'], - bundle: true, - sourcemap: true, - //minify: true, // No need to minify a module. - //keepNames: true, - format: "cjs", - // Sets 'common/web' as a root folder for module resolution; - // this allows the keyman-version import to resolve. - nodePaths: ['..'], - outfile: "build/version.inc.cjs", - tsconfig: 'tsconfig.json', - target: "es5" -}); \ No newline at end of file diff --git a/common/web/keyman-version/build.sh b/common/web/keyman-version/build.sh index 939709732c2..28dd2972111 100755 --- a/common/web/keyman-version/build.sh +++ b/common/web/keyman-version/build.sh @@ -1,11 +1,4 @@ #!/usr/bin/env bash -# -# Builds the include script for the current Keyman version. -# - -# Exit on command failure and when using unset variables: -set -eu - ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -13,6 +6,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" @@ -24,9 +18,7 @@ builder_describe "Build the include script for current Keyman version" \ clean \ build \ "pack build a local .tgz pack for testing" \ - "publish publish to npm" \ - test \ - --dry-run + "publish publish to npm" builder_describe_outputs \ configure "/node_modules" \ @@ -34,21 +26,7 @@ builder_describe_outputs \ builder_parse "$@" - -if builder_start_action configure; then - verify_npm_setup - builder_finish_action success configure -fi - -if builder_start_action clean; then - npm run clean - rm -f ./version.inc.ts - rm -f ./keyman-version.mts - rm -rf build - builder_finish_action success clean -fi - -if builder_start_action build; then +function do_build() { # Generate version.inc.ts echo " // Generated by common/web/keyman-version/build.sh @@ -72,22 +50,11 @@ export class KEYMAN_VERSION { export default KEYMAN_VERSION; " > ./version.inc.ts - tsc -b $builder_verbose - # kmlmc (the lexical model compiler) relies on a Node-based import, but after some of the earlier - # ES-modularization work, our main output's an ES module. Fortunately, esbuild can provide an easy stopgap. - - # Generates a CommonJS variant (in case other modules still need it). - node ./build-bundler.js - - builder_finish_action success build -fi + tsc --build $builder_verbose +} -if builder_start_action publish; then - . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" - builder_publish_to_npm - builder_finish_action success publish -elif builder_start_action pack; then - . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" - builder_publish_to_pack - builder_finish_action success pack -fi +builder_run_action clean rm -rf version.inc.ts keyman-version.mts build/ +builder_run_action configure verify_npm_setup +builder_run_action build do_build +builder_run_action publish builder_publish_to_npm +builder_run_action pack builder_publish_to_pack diff --git a/common/web/keyman-version/package.json b/common/web/keyman-version/package.json index 0ec9c1322be..ded749528c4 100644 --- a/common/web/keyman-version/package.json +++ b/common/web/keyman-version/package.json @@ -1,26 +1,15 @@ { "name": "@keymanapp/keyman-version", "description": "Keyman global version data", - "main": "./build/version.inc.cjs", "exports": { - ".": { - "import": "./build/version.inc.js", - "require": "./build/version.inc.cjs" - }, - "./build/version.inc.cjs": "./build/version.inc.cjs" + ".": "./build/version.inc.js" }, "files": [ "/build/" ], - "scripts": { - "build": "echo 'Building @keymanapp/keyman-version' && tsc -b", - "clean": "tsc -b --clean", - "tsc": "tsc" - }, "license": "MIT", "type": "module", "devDependencies": { - "@types/node": "^20.4.1", "typescript": "^4.9.5" } } diff --git a/common/web/keyman-version/tsconfig.json b/common/web/keyman-version/tsconfig.json index 8618c6dd922..03478f60212 100644 --- a/common/web/keyman-version/tsconfig.json +++ b/common/web/keyman-version/tsconfig.json @@ -1,17 +1,11 @@ { - "extends": "../../../tsconfig-base.json", + "extends": "../../../tsconfig.esm-base.json", "compilerOptions": { - "allowJs": false, - "declaration": true, - "module": "es6", - "sourceMap": true, "outDir": "./build", - "lib": ["es6"], - "target": "es5", - "downlevelIteration": true, "rootDir": "." }, + "include": [ "version.inc.ts" ] diff --git a/package-lock.json b/package-lock.json index dea220c34da..10b7dea4232 100644 --- a/package-lock.json +++ b/package-lock.json @@ -271,16 +271,9 @@ "name": "@keymanapp/keyman-version", "license": "MIT", "devDependencies": { - "@types/node": "^20.4.1", "typescript": "^4.9.5" } }, - "common/web/keyman-version/node_modules/@types/node": { - "version": "20.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz", - "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==", - "dev": true - }, "common/web/lm-message-types": { "name": "@keymanapp/lm-message-types", "license": "MIT", From 253dea0ba66f2bb03bdde4787ea6972689813d20 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 12:21:10 +0700 Subject: [PATCH 131/207] chore(common): fixup Commander call --- common/tools/hextobin/hextobin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/tools/hextobin/hextobin.ts b/common/tools/hextobin/hextobin.ts index e53e08ddbcf..6bf56c79533 100644 --- a/common/tools/hextobin/hextobin.ts +++ b/common/tools/hextobin/hextobin.ts @@ -1,10 +1,11 @@ #!/usr/bin/env node -import * as program from 'commander'; +import { Command } from 'commander'; import hextobin from './index.js'; let inputFilename: string = ""; let outputFilename: string = ""; +const program = new Command(); program .description( `Will convert the input file which is a hex dump to the output file. Hex dump can contain From 8c28b96c9f40cc92d05b8b2d8a1b9e6bc41b5d96 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 11:46:49 +0700 Subject: [PATCH 132/207] chore(common): convert resources/build/version to ES Modules Fixes #9677. --- common/tools/hextobin/build.sh | 2 +- package-lock.json | 149 ++++++------------ resources/build/increment-version.sh | 4 +- resources/build/version/.gitignore | 1 + resources/build/version/build.sh | 24 +++ resources/build/version/package.json | 11 +- resources/build/version/src/fixupHistory.ts | 7 +- resources/build/version/src/index.ts | 15 +- resources/build/version/src/reportHistory.ts | 6 +- resources/build/version/tsconfig.json | 7 +- .../build/version/tsconfig.production.json | 25 --- tsconfig.esm.json | 4 +- 12 files changed, 106 insertions(+), 149 deletions(-) create mode 100755 resources/build/version/build.sh delete mode 100644 resources/build/version/tsconfig.production.json diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh index 48c8542510c..8df4f9b83a3 100755 --- a/common/tools/hextobin/build.sh +++ b/common/tools/hextobin/build.sh @@ -15,7 +15,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe "Build hextobin" clean configure build builder_describe_outputs \ configure /node_modules \ - build build/index.js + build /common/tools/hextobin/build/index.js builder_parse "$@" diff --git a/package-lock.json b/package-lock.json index 405d2154cfb..dea220c34da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3830,6 +3830,21 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.26", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", + "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.9.2", "license": "MIT", @@ -4753,6 +4768,7 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5148,6 +5164,7 @@ }, "node_modules/decamelize": { "version": "1.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5884,7 +5901,6 @@ }, "node_modules/escalade": { "version": "3.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9573,6 +9589,7 @@ }, "node_modules/p-try": { "version": "2.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9603,6 +9620,7 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9930,6 +9948,7 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/requirejs": { @@ -10170,6 +10189,7 @@ }, "node_modules/set-blocking": { "version": "2.0.0", + "devOptional": true, "license": "ISC" }, "node_modules/set-immediate-shim": { @@ -11172,6 +11192,7 @@ }, "node_modules/which-module": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { @@ -11231,7 +11252,6 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11247,7 +11267,6 @@ }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11255,7 +11274,6 @@ }, "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11263,7 +11281,6 @@ }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "4.2.2", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -11276,7 +11293,6 @@ }, "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.0", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.0" @@ -11337,7 +11353,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -11497,16 +11512,14 @@ "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", "typescript": "^4.9.5", - "yargs": "^15.1.0" + "yargs": "^17.7.2" }, "devDependencies": { "@types/node": "^13.7.0", "@types/semver": "^7.1.0", + "@types/yargs": "^17.0.26", "semver": "^7.5.2", "ts-node": "^10.9.1" - }, - "engines": { - "node": ">=16.0" } }, "resources/build/version/node_modules/@types/node": { @@ -11516,74 +11529,37 @@ }, "resources/build/version/node_modules/ansi-regex": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "engines": { "node": ">=8" } }, "resources/build/version/node_modules/cliui": { - "version": "6.0.0", - "license": "ISC", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "resources/build/version/node_modules/find-up": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "resources/build/version/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/locate-path": { - "version": "5.0.0", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/p-limit": { - "version": "2.3.0", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "resources/build/version/node_modules/p-locate": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { "node": ">=8" } }, "resources/build/version/node_modules/string-width": { "version": "4.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11595,7 +11571,8 @@ }, "resources/build/version/node_modules/strip-ansi": { "version": "6.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -11603,51 +11580,29 @@ "node": ">=8" } }, - "resources/build/version/node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/y18n": { - "version": "4.0.3", - "license": "ISC" - }, "resources/build/version/node_modules/yargs": { - "version": "15.4.1", - "license": "MIT", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" } }, "resources/build/version/node_modules/yargs-parser": { - "version": "18.1.3", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=6" + "node": ">=12" } }, "resources/gosh": { diff --git a/resources/build/increment-version.sh b/resources/build/increment-version.sh index 445459da161..0f5e2d0608f 100755 --- a/resources/build/increment-version.sh +++ b/resources/build/increment-version.sh @@ -88,9 +88,7 @@ echo "increment-version.sh: building resources/build/version" pushd "$KEYMAN_ROOT" npm ci -pushd "$KEYMAN_ROOT/resources/build/version" -npm run build:ts -popd +"$KEYMAN_ROOT/resources/build/version/build.sh" echo "increment-version.sh: running resources/build/version" pushd "$KEYMAN_ROOT" diff --git a/resources/build/version/.gitignore b/resources/build/version/.gitignore index 687e3299fe0..3e42060cc69 100644 --- a/resources/build/version/.gitignore +++ b/resources/build/version/.gitignore @@ -47,3 +47,4 @@ coverage # Build output dist lib +build diff --git a/resources/build/version/build.sh b/resources/build/version/build.sh new file mode 100755 index 00000000000..64892803bc8 --- /dev/null +++ b/resources/build/version/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +# This script runs from its own folder +cd "$THIS_SCRIPT_PATH" + +################################ Main script ################################ + +builder_describe "Build version tooling" clean configure build +builder_describe_outputs \ + configure /resources/build/version/node_modules \ + build /resources/build/version/build/src/index.js + +builder_parse "$@" + +builder_run_action clean rm -rf build/ node_modules/ dist/ lib/ +builder_run_action configure verify_npm_setup +builder_run_action build tsc --build diff --git a/resources/build/version/package.json b/resources/build/version/package.json index 93911e79701..57264f02e44 100644 --- a/resources/build/version/package.json +++ b/resources/build/version/package.json @@ -1,25 +1,24 @@ { + "description": "Automatically updates HISTORY.md based on pull requests", + "type": "module", "dependencies": { "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", "typescript": "^4.9.5", - "yargs": "^15.1.0" + "yargs": "^17.7.2" }, - "description": "Automatically updates HISTORY.md based on pull requests", "devDependencies": { "@types/node": "^13.7.0", "@types/semver": "^7.1.0", + "@types/yargs": "^17.0.26", "semver": "^7.5.2", "ts-node": "^10.9.1" }, - "engines": { - "node": ">=16.0" - }, "files": [ "src" ], "license": "MIT", - "main": "lib/index.js", + "main": "build/src/index.js", "name": "@keymanapp/auto-history-action", "private": true, "scripts": { diff --git a/resources/build/version/src/fixupHistory.ts b/resources/build/version/src/fixupHistory.ts index 2145fa94656..9f48f4851dd 100644 --- a/resources/build/version/src/fixupHistory.ts +++ b/resources/build/version/src/fixupHistory.ts @@ -5,7 +5,7 @@ import { import { GitHub } from '@actions/github'; import { readFileSync, writeFileSync } from 'fs'; import { gt } from 'semver'; -import { reportHistory } from './reportHistory'; +import { reportHistory } from './reportHistory.js'; interface PRInformation { title: string; @@ -42,7 +42,8 @@ const splicePullsIntoHistory = async (pulls: PRInformation[]): Promise<{count: n const versionMatch = new RegExp(`^## ${version} ${tier}`, 'i'); let historyChunks: { heading: string[]; newer: string[]; current: string[]; older: string[] } = {heading:[], newer:[], current:[], older:[]}; - let state = "heading"; + type ChunkType = keyof typeof historyChunks; + let state: ChunkType = "heading"; for(const line of history) { if(line.match(versionMatch)) { // We are in the correct section of history @@ -90,7 +91,7 @@ const splicePullsIntoHistory = async (pulls: PRInformation[]): Promise<{count: n let found = false; // Look for the PR in any other chunk -- can be found with merges of master into PR or chained PRs - for(const chunk of ['newer','current','older']) { + for(const chunk of ['newer','current','older'] as ChunkType[]) { for(const line of historyChunks[chunk]) { if(line.match(pullNumberRe)) { found = true; diff --git a/resources/build/version/src/index.ts b/resources/build/version/src/index.ts index 3d5f896650d..69776231c62 100644 --- a/resources/build/version/src/index.ts +++ b/resources/build/version/src/index.ts @@ -1,13 +1,14 @@ import { info as logInfo } from '@actions/core'; import { GitHub } from '@actions/github'; -import { sendCommentToPullRequestAndRelatedIssues, fixupHistory } from './fixupHistory'; -import { incrementVersion } from './incrementVersion'; -const yargs = require('yargs'); +import { sendCommentToPullRequestAndRelatedIssues, fixupHistory } from './fixupHistory.js'; +import { incrementVersion } from './incrementVersion.js'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; import { readFileSync } from 'fs'; -import { reportHistory } from './reportHistory'; +import { reportHistory } from './reportHistory.js'; -const argv = yargs +const argv = await yargs(hideBin(process.argv)) .command(['history'], 'Fixes up HISTORY.md with pull request data') .command(['version'], 'Increments the current patch version in VERSION.md') .command(['report-history'], 'Print list of outstanding PRs waiting for the next build') @@ -81,10 +82,10 @@ const main = async (): Promise => { if(argv._.includes('report-history')) { let pulls = await reportHistory(octokit, argv.base, argv.force, argv['github-pr'], argv.from, argv.to); - let versions = {}; + let versions: {[index:string]:any} = {}; pulls.forEach((item) => { if(typeof item.version == 'string') { - if(typeof versions[item.version] == 'undefined') { + if(typeof (versions)[item.version] == 'undefined') { versions[item.version] = {data: item.tag_data, pulls: []}; } // We want to invert the order of the pulls as we go to diff --git a/resources/build/version/src/reportHistory.ts b/resources/build/version/src/reportHistory.ts index bf726063339..2fae1ab628e 100644 --- a/resources/build/version/src/reportHistory.ts +++ b/resources/build/version/src/reportHistory.ts @@ -2,8 +2,8 @@ import { warning as logWarning, info as logInfo } from '@actions/core'; import { GitHub } from '@actions/github'; -import { findLastHistoryPR, getAssociatedPR} from './graphql/queries'; -import { spawnChild } from './util/spawnAwait'; +import { findLastHistoryPR, getAssociatedPR} from './graphql/queries.js'; +import { spawnChild } from './util/spawnAwait.js'; const getPullRequestInformation = async ( octokit: GitHub, base: string @@ -67,7 +67,7 @@ const getAssociatedPRInformation = async ( } }: any = response; - const node = nodes.find(node => node.state == 'MERGED'); + const node = nodes.find((node:any) => node.state == 'MERGED'); return node ? { title: node.title, number: node.number } : undefined; }; diff --git a/resources/build/version/tsconfig.json b/resources/build/version/tsconfig.json index 655da5e8afc..9f537c799da 100644 --- a/resources/build/version/tsconfig.json +++ b/resources/build/version/tsconfig.json @@ -1,7 +1,8 @@ { + "extends": "../../../tsconfig.esm-base.json", "compilerOptions": { - "rootDir": "." + "rootDir": ".", + "outDir": "build/", }, - "extends": "./tsconfig.production.json", - "include": ["./**/*"] + "include": ["./**/*.ts"] } diff --git a/resources/build/version/tsconfig.production.json b/resources/build/version/tsconfig.production.json deleted file mode 100644 index 2189a3042d4..00000000000 --- a/resources/build/version/tsconfig.production.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "alwaysStrict": true, - "charset": "utf-8", - "declaration": true, - "declarationMap": true, - "downlevelIteration": true, - "forceConsistentCasingInFileNames": true, - "lib": ["es2017"], - "module": "commonjs", - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "outDir": "lib", - "pretty": true, - "rootDir": "src", - "sourceMap": true, - "strict": true, - "strictNullChecks": true, - "target": "es2017" - }, - "files": ["./src/index.ts"] -} diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 47c0ef92e9e..0252447c63b 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -47,6 +47,8 @@ // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, { "path": "./common/web/lm-message-types/" }, { "path": "./common/web/lm-worker/" }, - { "path": "./common/tools/hextobin/" } + { "path": "./common/tools/hextobin/" }, + + { "path": "./resources/build/version/" }, ] } \ No newline at end of file From 11e5cd3a47b90d4bab0c0bddbf77a14bbe4bf174 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 12:07:19 +0700 Subject: [PATCH 133/207] chore: update root tsconfig --- tsconfig.cjs.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 2b8030ac766..701d06ce3b2 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -8,9 +8,5 @@ { "path": "./common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json" }, - - { "path": "./resources/build/version/tsconfig.json" }, - { "path": "./resources/build/version/tsconfig.production.json" }, - // { "path": "./web/bulk_rendering/tsconfig.json" }, ] } \ No newline at end of file From d4e4992714b5f91d55dc52a88775bf8932309fb5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 12:55:51 +0700 Subject: [PATCH 134/207] chore(common): cleanup final Typescript non-ESM metadata Fixes #9262. --- common/models/templates/package.json | 4 +- common/models/wordbreakers/package.json | 3 +- common/predictive-text/testing/index.html | 28 -------- .../one-stage-embedded-webworker/.gitignore | 2 - .../one-stage-embedded-webworker/build.sh | 54 -------------- .../one-stage-embedded-webworker/index.html | 42 ----------- .../one-stage-embedded-webworker/main.ts | 52 -------------- .../tsconfig.json | 13 ---- .../post-blob-webworker/canaryWorker.js | 5 -- .../testing/post-blob-webworker/index.html | 72 ------------------- .../testing/simple-webworker/canaryWorker.js | 9 --- .../testing/simple-webworker/index.html | 55 -------------- .../two-stage-embedded-webworker/.gitignore | 2 - .../two-stage-embedded-webworker/build.sh | 68 ------------------ .../two-stage-embedded-webworker/index.html | 42 ----------- .../two-stage-embedded-webworker/main.ts | 30 -------- .../tsconfig.json | 13 ---- .../worker/canaryWorker.ts | 9 --- .../worker/tsconfig.json | 11 --- common/tools/hextobin/tsconfig.json | 2 +- .../sourcemap-path-remapper/tsconfig.json | 3 +- common/web/es-bundling/tsconfig.json | 2 +- common/web/keyman-version/tsconfig.json | 2 +- common/web/lm-message-types/tsconfig.json | 2 +- common/web/sentry-manager/src/tsconfig.json | 3 +- common/web/types/test/tsconfig.json | 2 +- common/web/types/tsconfig.json | 2 +- core/include/ldml/tsconfig.build.json | 2 +- core/include/ldml/tsconfig.json | 4 +- .../src/common/web/test-helpers/tsconfig.json | 2 +- developer/src/kmc-analyze/tsconfig.json | 2 +- .../src/kmc-keyboard-info/test/tsconfig.json | 1 - developer/src/kmc-kmn/tsconfig.json | 2 +- developer/src/kmc-ldml/tsconfig.json | 2 +- .../src/kmc-model-info/test/tsconfig.json | 1 - developer/src/kmc-model/test/tsconfig.json | 1 - developer/src/kmc/tsconfig.kmc-base.json | 2 +- developer/src/server/tsconfig.json | 2 +- resources/build/version/tsconfig.json | 2 +- tsconfig-base.json => tsconfig.base.json | 15 ++++ tsconfig.cjs.json | 12 ---- tsconfig.esm-base.json | 24 ------- tsconfig.esm.json => tsconfig.json | 44 +++++------- web/tsconfig.base.json | 3 +- 44 files changed, 53 insertions(+), 600 deletions(-) delete mode 100644 common/predictive-text/testing/index.html delete mode 100644 common/predictive-text/testing/one-stage-embedded-webworker/.gitignore delete mode 100755 common/predictive-text/testing/one-stage-embedded-webworker/build.sh delete mode 100644 common/predictive-text/testing/one-stage-embedded-webworker/index.html delete mode 100644 common/predictive-text/testing/one-stage-embedded-webworker/main.ts delete mode 100644 common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json delete mode 100644 common/predictive-text/testing/post-blob-webworker/canaryWorker.js delete mode 100644 common/predictive-text/testing/post-blob-webworker/index.html delete mode 100644 common/predictive-text/testing/simple-webworker/canaryWorker.js delete mode 100644 common/predictive-text/testing/simple-webworker/index.html delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/.gitignore delete mode 100755 common/predictive-text/testing/two-stage-embedded-webworker/build.sh delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/index.html delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/main.ts delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/worker/canaryWorker.ts delete mode 100644 common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json rename tsconfig-base.json => tsconfig.base.json (65%) delete mode 100644 tsconfig.cjs.json delete mode 100644 tsconfig.esm-base.json rename tsconfig.esm.json => tsconfig.json (87%) diff --git a/common/models/templates/package.json b/common/models/templates/package.json index 0bec0fc88fa..33f89cca706 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -21,9 +21,7 @@ "exports": { ".": "./build/obj/index.js", "./lib": { - "types": "./build/lib/index.d.ts", - "import": "./build/lib/index.mjs", - "require": "./build/lib/index.cjs" + "types": "./build/lib/index.d.ts" }, "./obj/*.js": "./build/obj/*.js" }, diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index bd8d0157fbd..7a03dc9e8fb 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -19,8 +19,7 @@ "exports": { ".": "./build/obj/index.js", "./lib": { - "import": "./build/lib/index.mjs", - "require": "./build/lib/index.cjs" + "types": "./build/lib/index.d.ts" }, "./obj/*.js": "./build/obj/*.js" }, diff --git a/common/predictive-text/testing/index.html b/common/predictive-text/testing/index.html deleted file mode 100644 index 8a00094b701..00000000000 --- a/common/predictive-text/testing/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - LMLayer Testing - - - -

    Language-Modeling Layer module testing

    -

    A simple basic WebWorker.

    Designed as a baseline functionality test we can run against various platforms as a first-stage canary of sorts. -

    An embedded WebWorker prototype.

    A prototype for directly embedding a WebWorker's code within a "master" script. -

    A better embedded WebWorker prototype.

    A prototype for two-stage compilation of a master/slave main-script/WebWorker pair. -

    Sending code via a Blob URI

    A prototype for sending code over to a WebWorker via a Blob URI reference. - - diff --git a/common/predictive-text/testing/one-stage-embedded-webworker/.gitignore b/common/predictive-text/testing/one-stage-embedded-webworker/.gitignore deleted file mode 100644 index df6361deda9..00000000000 --- a/common/predictive-text/testing/one-stage-embedded-webworker/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -main.js -main.js.map \ No newline at end of file diff --git a/common/predictive-text/testing/one-stage-embedded-webworker/build.sh b/common/predictive-text/testing/one-stage-embedded-webworker/build.sh deleted file mode 100755 index f1f727b8c03..00000000000 --- a/common/predictive-text/testing/one-stage-embedded-webworker/build.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash -# -# Compiles the Language Modeling Layer for common use in predictive text and autocorrective applications. -# Designed for optimal compatibility with the Keyman Suite. -# - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -display_usage ( ) { - echo "build.sh [-clean]" - echo - echo " -clean to erase pre-existing build products before a re-build" -} - -SOURCE="testing/one-stage-embedded-webworker" - -echo "Node.js + dependencies check" -npm install --no-optional - -if [ $? -ne 0 ]; then - builder_die "Build environment setup error detected! Please ensure Node.js is installed!" -fi - -# A nice, extensible method for -clean operations. Add to this as necessary. -clean ( ) { - rm -rf "./*.js" - if [ $? -ne 0 ]; then - builder_die "Failed to erase the prior build." - fi -} - -# Process command-line arguments -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - -clean) - clean - ;; - esac - shift # past the processed argument -done - -npm run tsc -- -p $SOURCE/tsconfig.json - -if [ $? -ne 0 ]; then - builder_die "Compilation failed." -fi - -echo "Typescript compilation successful." \ No newline at end of file diff --git a/common/predictive-text/testing/one-stage-embedded-webworker/index.html b/common/predictive-text/testing/one-stage-embedded-webworker/index.html deleted file mode 100644 index 244f4aa2833..00000000000 --- a/common/predictive-text/testing/one-stage-embedded-webworker/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - Embedded WebWorker test - - - - - - - - - -

    LM Layer Testing - One-Stage Embedded WebWorkers

    -

    This page serves as a prototype for embedding a WebWorker's code within its "master" script - across the TypeScript transpilation boundary.

    -
    - - - -

    Return to testing home page

    - - diff --git a/common/predictive-text/testing/one-stage-embedded-webworker/main.ts b/common/predictive-text/testing/one-stage-embedded-webworker/main.ts deleted file mode 100644 index 5d15ae88b58..00000000000 --- a/common/predictive-text/testing/one-stage-embedded-webworker/main.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Useful for passing class constructors -type Workable = { - new (...args: any[]): T; - // Requires a static implementation. - onmessage(e: any): void; -}; - -class A { - a(x: number, y: number):number { - return x + y; - } -} - -var WorkerGlobals = { - counter: 0, - a: A -} - -class WorkerCore { - static WorkerGlobals = WorkerGlobals; - //static counter: number; - - static onmessage(e: any) { - WorkerGlobals.counter++; - - console.log("Message received from main page: ", e.data); - - // Forces TypeScript to interpret this line as plain JavaScript, as it uses a non-Worker definition. - // @ts-ignore - postMessage(WorkerGlobals.counter); - } -} - -function createWorkerFromClasses(globals: object, fn: Workable): Worker { - var sep = ";\n"; - let glb = "var WorkerGlobals = " + JSON.stringify(globals); - let wc = "var onmessage = " + fn.onmessage.toString(); - var blob = new Blob([glb, sep, wc], { type: 'text/javascript' }); - let url = URL.createObjectURL(blob); - - return new Worker(url); -} - -var canaryWorker = createWorkerFromClasses(WorkerGlobals, WorkerCore); - -canaryWorker.onmessage = function(e) { - var counter = e.data; // Number of times the WebWorker has been messaged. - console.log("Received message from the WebWorker: " + e.data); - - var txtFeedback = document.getElementById("txtFeedback"); - txtFeedback.value = counter + " click(s)"; -} diff --git a/common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json b/common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json deleted file mode 100644 index f1e9be86003..00000000000 --- a/common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "allowJs": false, - "module": "none", - "outDir": "./", - "inlineSources": true, - "sourceMap": true, - "target": "es5" - }, - "files" : [ - "main.ts" - ] -} diff --git a/common/predictive-text/testing/post-blob-webworker/canaryWorker.js b/common/predictive-text/testing/post-blob-webworker/canaryWorker.js deleted file mode 100644 index 5c69deace8e..00000000000 --- a/common/predictive-text/testing/post-blob-webworker/canaryWorker.js +++ /dev/null @@ -1,5 +0,0 @@ -// receive a message and execute its uri -onmessage = function (e) { - let uri = e.data.uri; - importScripts(uri); -}; diff --git a/common/predictive-text/testing/post-blob-webworker/index.html b/common/predictive-text/testing/post-blob-webworker/index.html deleted file mode 100644 index a04b6be377b..00000000000 --- a/common/predictive-text/testing/post-blob-webworker/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - Blob URI test - - - - - - - - - -

    LM Layer Testing - Sending a function to a WebWorker via a Blob URI test

    -

    This page sends a function over to the Worker via a Blob URI, and - expects the function to reply back. -

    -
    - - - -

    Return to testing home page

    - - diff --git a/common/predictive-text/testing/simple-webworker/canaryWorker.js b/common/predictive-text/testing/simple-webworker/canaryWorker.js deleted file mode 100644 index cf8674d3304..00000000000 --- a/common/predictive-text/testing/simple-webworker/canaryWorker.js +++ /dev/null @@ -1,9 +0,0 @@ -var counter = 0; - -onmessage = function(e) { - counter++; - - console.log("Message received from main page: ", e.data); - - postMessage(counter); -} \ No newline at end of file diff --git a/common/predictive-text/testing/simple-webworker/index.html b/common/predictive-text/testing/simple-webworker/index.html deleted file mode 100644 index adbcd30d3e6..00000000000 --- a/common/predictive-text/testing/simple-webworker/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - Basic WebWorker test - - - - - - - - - -

    LM Layer Testing - basic WebWorker canary

    -

    This page uses a simple WebWorker useful for ensuring functionality on various platforms.

    -
    - - - -

    Return to testing home page

    - - diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/.gitignore b/common/predictive-text/testing/two-stage-embedded-webworker/.gitignore deleted file mode 100644 index 20efd1ec834..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -**/*.js -**/*.js.map \ No newline at end of file diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/build.sh b/common/predictive-text/testing/two-stage-embedded-webworker/build.sh deleted file mode 100755 index dd1854ec00b..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/build.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash -# -# Compiles the Language Modeling Layer for common use in predictive text and autocorrective applications. -# Designed for optimal compatibility with the Keyman Suite. -# - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -display_usage ( ) { - echo "build.sh [-clean]" - echo - echo " -clean to erase pre-existing build products before a re-build" -} - -SOURCE="testing/two-stage-embedded-webworker" - -COMPILED_WORKER="worker.js" -EMBEDDED_WORKER="embedded_worker.js" - -echo "Node.js + dependencies check" -npm install --no-optional - -if [ $? -ne 0 ]; then - builder_die "Build environment setup error detected! Please ensure Node.js is installed!" -fi - -# A nice, extensible method for -clean operations. Add to this as necessary. -clean ( ) { - rm -rf "./*.js" - if [ $? -ne 0 ]; then - builder_die "Failed to erase the prior build." - fi -} - -# Process command-line arguments -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - -clean) - clean - ;; - esac - shift # past the processed argument -done - -npm run tsc -- -p $SOURCE/worker/tsconfig.json -if [ $? -ne 0 ]; then - builder_die "Worker compilation failed." -fi - -rm $EMBEDDED_WORKER &> /dev/null - -echo "var LMLayerWorker = function() {" >> $EMBEDDED_WORKER -cat $COMPILED_WORKER >> $EMBEDDED_WORKER -echo "" >> $EMBEDDED_WORKER -echo "}" >> $EMBEDDED_WORKER - -npm run tsc -- -p $SOURCE/tsconfig.json -if [ $? -ne 0 ]; then - builder_die "Final compilation failed." -fi - -echo "Typescript compilation successful." \ No newline at end of file diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/index.html b/common/predictive-text/testing/two-stage-embedded-webworker/index.html deleted file mode 100644 index 244f4aa2833..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - Embedded WebWorker test - - - - - - - - - -

    LM Layer Testing - One-Stage Embedded WebWorkers

    -

    This page serves as a prototype for embedding a WebWorker's code within its "master" script - across the TypeScript transpilation boundary.

    -
    - - - -

    Return to testing home page

    - - diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/main.ts b/common/predictive-text/testing/two-stage-embedded-webworker/main.ts deleted file mode 100644 index 9eb276d3794..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/main.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Provides the final source for our compiled WebWorker within a wrapping function. -/// - -function createWorker(fn: Function): Worker { - var str_fn = fn.toString(); - - // We now unwrap our WebWorker from its function. - var str_lines = str_fn.split("\n"); - var blob_lines = [] - - for(var i=1; i < str_lines.length -1; i++) { - blob_lines.push(str_lines[i] + "\n"); - } - // Unwrapping complete. - - var blob = new Blob(blob_lines, { type: 'text/javascript' }); - let url = URL.createObjectURL(blob); - - return new Worker(url); -} - -var canaryWorker = createWorker(LMLayerWorker); - -canaryWorker.onmessage = function(e) { - var counter = e.data; // Number of times the WebWorker has been messaged. - console.log("Received message from the WebWorker: " + e.data); - - var txtFeedback = document.getElementById("txtFeedback"); - txtFeedback.value = counter + " click(s)"; -} diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json b/common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json deleted file mode 100644 index a25930a70e6..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "module": "none", - "outFile": "./main.js", - "inlineSources": true, - "inlineSourceMap": true, - "target": "es5" - }, - "files" : [ - "main.ts" - ] -} diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/worker/canaryWorker.ts b/common/predictive-text/testing/two-stage-embedded-webworker/worker/canaryWorker.ts deleted file mode 100644 index cf8674d3304..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/worker/canaryWorker.ts +++ /dev/null @@ -1,9 +0,0 @@ -var counter = 0; - -onmessage = function(e) { - counter++; - - console.log("Message received from main page: ", e.data); - - postMessage(counter); -} \ No newline at end of file diff --git a/common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json b/common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json deleted file mode 100644 index ca8006ba440..00000000000 --- a/common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "allowJs": false, - "module": "none", - "outFile": "../worker.js", - "inlineSources": true, - "sourceMap": true, - "lib": ["webworker", "es6"], - "target": "es5" - } -} diff --git a/common/tools/hextobin/tsconfig.json b/common/tools/hextobin/tsconfig.json index db0a65ae9db..55e3f66308e 100644 --- a/common/tools/hextobin/tsconfig.json +++ b/common/tools/hextobin/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", "outDir": "build/" diff --git a/common/tools/sourcemap-path-remapper/tsconfig.json b/common/tools/sourcemap-path-remapper/tsconfig.json index f9b531b311a..72809897bd7 100644 --- a/common/tools/sourcemap-path-remapper/tsconfig.json +++ b/common/tools/sourcemap-path-remapper/tsconfig.json @@ -1,12 +1,11 @@ { - "extends": "../../../tsconfig-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "allowJs": false, "allowSyntheticDefaultImports": true, "declaration": true, "module": "es6", - "moduleResolution": "node", "inlineSourceMap": true, "inlineSources": true, "sourceRoot": "/common/tools/sourcemap-path-remapper/src", diff --git a/common/web/es-bundling/tsconfig.json b/common/web/es-bundling/tsconfig.json index d2ab738acfb..a5149b51c6f 100644 --- a/common/web/es-bundling/tsconfig.json +++ b/common/web/es-bundling/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "baseUrl": "./", "outDir": "build/", diff --git a/common/web/keyman-version/tsconfig.json b/common/web/keyman-version/tsconfig.json index 03478f60212..0712c03f751 100644 --- a/common/web/keyman-version/tsconfig.json +++ b/common/web/keyman-version/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "./build", diff --git a/common/web/lm-message-types/tsconfig.json b/common/web/lm-message-types/tsconfig.json index 124def350d2..f9d427f0bfc 100644 --- a/common/web/lm-message-types/tsconfig.json +++ b/common/web/lm-message-types/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "declaration": true, "module": "none", diff --git a/common/web/sentry-manager/src/tsconfig.json b/common/web/sentry-manager/src/tsconfig.json index 490c315dc90..f42eaea73bd 100644 --- a/common/web/sentry-manager/src/tsconfig.json +++ b/common/web/sentry-manager/src/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig-base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, @@ -7,7 +7,6 @@ "inlineSources": true, "lib": ["es6", "dom"], "module": "es6", - "moduleResolution": "Node16", "outDir": "../build/obj", "rootDir": "./", "sourceMap": true, diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json index c522129c99a..ee468c7fbd6 100644 --- a/common/web/types/test/tsconfig.json +++ b/common/web/types/test/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.esm-base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json index 5059030c0f8..730244e67c9 100644 --- a/common/web/types/tsconfig.json +++ b/common/web/types/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "build/src/", diff --git a/core/include/ldml/tsconfig.build.json b/core/include/ldml/tsconfig.build.json index 17c809a857a..fd0f003272c 100644 --- a/core/include/ldml/tsconfig.build.json +++ b/core/include/ldml/tsconfig.build.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "composite": true, "declaration": true, diff --git a/core/include/ldml/tsconfig.json b/core/include/ldml/tsconfig.json index b89c96a0181..fa4135b0212 100644 --- a/core/include/ldml/tsconfig.json +++ b/core/include/ldml/tsconfig.json @@ -1,10 +1,8 @@ { - "extends": "../../../tsconfig-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "composite": true, "declaration": true, - "module": "es2020", - "moduleResolution": "node", "rootDir": ".", "outDir": "build/", }, diff --git a/developer/src/common/web/test-helpers/tsconfig.json b/developer/src/common/web/test-helpers/tsconfig.json index 47646be3a61..d7089c703f2 100644 --- a/developer/src/common/web/test-helpers/tsconfig.json +++ b/developer/src/common/web/test-helpers/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../../tsconfig.esm-base.json", + "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", diff --git a/developer/src/kmc-analyze/tsconfig.json b/developer/src/kmc-analyze/tsconfig.json index 3a9149e1213..987abd3cc3c 100644 --- a/developer/src/kmc-analyze/tsconfig.json +++ b/developer/src/kmc-analyze/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "build/src/", diff --git a/developer/src/kmc-keyboard-info/test/tsconfig.json b/developer/src/kmc-keyboard-info/test/tsconfig.json index bf96b7b1509..6d48ad864d2 100644 --- a/developer/src/kmc-keyboard-info/test/tsconfig.json +++ b/developer/src/kmc-keyboard-info/test/tsconfig.json @@ -6,7 +6,6 @@ "rootDirs": ["./", "../src/"], "outDir": "../build/test", "esModuleInterop": true, - "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { diff --git a/developer/src/kmc-kmn/tsconfig.json b/developer/src/kmc-kmn/tsconfig.json index 04683902eae..4210b750af0 100644 --- a/developer/src/kmc-kmn/tsconfig.json +++ b/developer/src/kmc-kmn/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "build/src/", diff --git a/developer/src/kmc-ldml/tsconfig.json b/developer/src/kmc-ldml/tsconfig.json index c9ec649b40f..fdcf5fd82bb 100644 --- a/developer/src/kmc-ldml/tsconfig.json +++ b/developer/src/kmc-ldml/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "build/src/", diff --git a/developer/src/kmc-model-info/test/tsconfig.json b/developer/src/kmc-model-info/test/tsconfig.json index bf96b7b1509..6d48ad864d2 100644 --- a/developer/src/kmc-model-info/test/tsconfig.json +++ b/developer/src/kmc-model-info/test/tsconfig.json @@ -6,7 +6,6 @@ "rootDirs": ["./", "../src/"], "outDir": "../build/test", "esModuleInterop": true, - "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { diff --git a/developer/src/kmc-model/test/tsconfig.json b/developer/src/kmc-model/test/tsconfig.json index 3902bed57f0..0917ea186ae 100644 --- a/developer/src/kmc-model/test/tsconfig.json +++ b/developer/src/kmc-model/test/tsconfig.json @@ -6,7 +6,6 @@ "rootDirs": ["./", "../src/"], "outDir": "../build/test", "esModuleInterop": true, - "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { diff --git a/developer/src/kmc/tsconfig.kmc-base.json b/developer/src/kmc/tsconfig.kmc-base.json index f8cff50b3fa..6956b6e05db 100644 --- a/developer/src/kmc/tsconfig.kmc-base.json +++ b/developer/src/kmc/tsconfig.kmc-base.json @@ -1,3 +1,3 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", } diff --git a/developer/src/server/tsconfig.json b/developer/src/server/tsconfig.json index 701a97af6c6..e5adde95784 100644 --- a/developer/src/server/tsconfig.json +++ b/developer/src/server/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "target": "es2022", diff --git a/resources/build/version/tsconfig.json b/resources/build/version/tsconfig.json index 9f537c799da..2d99762ca9b 100644 --- a/resources/build/version/tsconfig.json +++ b/resources/build/version/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.esm-base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", "outDir": "build/", diff --git a/tsconfig-base.json b/tsconfig.base.json similarity index 65% rename from tsconfig-base.json rename to tsconfig.base.json index 8e3eafb6cf4..2c56fd70794 100644 --- a/tsconfig-base.json +++ b/tsconfig.base.json @@ -1,5 +1,18 @@ { "compilerOptions": { + "module": "ES2022", + "target": "es2022", + "moduleResolution": "node16", + "forceConsistentCasingInFileNames": true, + "sourceMap": true, + "alwaysStrict": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "noUnusedLocals": true, + "rootDir": ".", // TODO: move all compiler options here @@ -9,7 +22,9 @@ "declarationMap": true, "baseUrl": ".", + "paths": { + "@keymanapp/common-types": ["./common/web/types/src/main"], "@keymanapp/input-processor": ["./common/web/input-processor/src"], "@keymanapp/keyboard-processor": ["./common/web/keyboard-processor/src"], "@keymanapp/keyman": ["./web" ], diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json deleted file mode 100644 index 701d06ce3b2..00000000000 --- a/tsconfig.cjs.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - // Lists only CommonJS or 'none' modules; as we move modules from cjs/none to - // esm, we should move them from here into tsconfig.esm.json. Eventually, when - // we have only ES modules, we'll delete this file. - "files": [], - "include": [], - "references": [ - { "path": "./common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json" }, - { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json" }, - { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json" }, - ] -} \ No newline at end of file diff --git a/tsconfig.esm-base.json b/tsconfig.esm-base.json deleted file mode 100644 index d17d505e5c2..00000000000 --- a/tsconfig.esm-base.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "./tsconfig-base.json", - - "compilerOptions": { - "module": "ES2022", - "target": "es2022", - "moduleResolution": "Node16", - "forceConsistentCasingInFileNames": true, - "sourceMap": true, - "alwaysStrict": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "noImplicitAny": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "noUnusedLocals": true, - - "paths": { - "@keymanapp/keyman-version": ["./common/web/keyman-version/keyman-version.mts"], - "@keymanapp/common-types": ["./common/web/types/src/main"], - // "@keymanapp/": ["core/include/ldml/ldml-keyboard-constants"], - }, - }, -} diff --git a/tsconfig.esm.json b/tsconfig.json similarity index 87% rename from tsconfig.esm.json rename to tsconfig.json index 0252447c63b..497bcf7344b 100644 --- a/tsconfig.esm.json +++ b/tsconfig.json @@ -1,19 +1,28 @@ { - // Lists only ES modules; as we move modules from cjs/none to esm, we should - // move them from tsconfig.cjs.json to here. Eventually, when we have only ES - // modules, we can rename this to tsconfig.json. - "files": [], - "include": [], "references": [ { "path": "./core/include/ldml/tsconfig.json" }, - //{ "path": "./developer/src/kmc/test/tsconfig.json" }, + { "path": "./common/models/templates/tsconfig.json" }, + { "path": "./common/models/types/tsconfig.json" }, + { "path": "./common/models/wordbreakers/tsconfig.json" }, + { "path": "./common/predictive-text/tsconfig.json" }, + { "path": "./common/tools/hextobin/" }, + { "path": "./common/web/input-processor/tsconfig.json" }, + { "path": "./common/web/keyboard-processor/tsconfig.json" }, + { "path": "./common/web/keyman-version" }, + { "path": "./common/web/lm-message-types/" }, + { "path": "./common/web/lm-worker/" }, + { "path": "./common/web/recorder/tsconfig.json" }, + { "path": "./common/web/sentry-manager/src/tsconfig.json" }, + { "path": "./common/web/types/" }, + { "path": "./common/web/utils/tsconfig.json" }, + { "path": "./developer/src/common/web/test-helpers/tsconfig.json" }, { "path": "./developer/src/kmc/tsconfig.json" }, - - // { "path": "./developer/src/kmc-analyze/test/tsconfig.json" }, + { "path": "./developer/src/kmc/test/tsconfig.json" }, { "path": "./developer/src/kmc-analyze/tsconfig.json" }, + // { "path": "./developer/src/kmc-analyze/test/tsconfig.json" }, { "path": "./developer/src/kmc-kmn/test/tsconfig.json" }, { "path": "./developer/src/kmc-kmn/tsconfig.json" }, { "path": "./developer/src/kmc-keyboard-info/test/tsconfig.json" }, @@ -28,27 +37,10 @@ { "path": "./developer/src/kmc-package/tsconfig.json" }, { "path": "./developer/src/server/tsconfig.json" }, - { "path": "./common/web/keyman-version" }, - { "path": "./common/web/types/" }, - - { "path": "./common/web/input-processor/tsconfig.json" }, - { "path": "./common/web/keyboard-processor/tsconfig.json" }, - { "path": "./common/web/recorder/tsconfig.json" }, - { "path": "./common/web/sentry-manager/src/tsconfig.json" }, - { "path": "./common/web/utils/tsconfig.json" }, - - { "path": "./common/models/templates/tsconfig.json" }, - { "path": "./common/models/types/tsconfig.json" }, - { "path": "./common/models/wordbreakers/tsconfig.json" }, - { "path": "./common/predictive-text/tsconfig.json" }, + { "path": "./resources/build/version/" }, { "path": "./web/src/tsconfig.all.json" }, // { "path": "./web/tools/recorder/tsconfig.json" }, // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, - { "path": "./common/web/lm-message-types/" }, - { "path": "./common/web/lm-worker/" }, - { "path": "./common/tools/hextobin/" }, - - { "path": "./resources/build/version/" }, ] } \ No newline at end of file diff --git a/web/tsconfig.base.json b/web/tsconfig.base.json index a2b78b7b15f..fa60ca26e6d 100644 --- a/web/tsconfig.base.json +++ b/web/tsconfig.base.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig-base.json", + "extends": "../tsconfig.base.json", "compilerOptions": { // Primary settings - the version of ES6 we can target in TS, our downcompile target, @@ -7,7 +7,6 @@ "allowSyntheticDefaultImports": true, "lib": ["es6"], "module": "es6", - "moduleResolution": "Node16", "target": "es5", // Other settings - declaration files, sourcemapping, and other miscellaneous bits. From 75a0977cdbbfd55fcfd3cd6dff4b5f604dc20270 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 14:17:59 +0700 Subject: [PATCH 135/207] chore(developer): fix server tests --- developer/src/server/build.sh | 10 +++++++++- developer/src/server/package.json | 4 ++-- developer/src/server/test/tsconfig.json | 16 ++++++++++++++++ .../server/{src => test}/version-data.test.ts | 4 +--- package-lock.json | 9 +++++---- 5 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 developer/src/server/test/tsconfig.json rename developer/src/server/{src => test}/version-data.test.ts (97%) diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 44cdbe1ca73..7e47d4f663d 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -125,11 +125,19 @@ function installer_server() { cp -R "$PRODBUILDTEMP"/* "$KEYMAN_ROOT/developer/bin/server/" rm -rf "$PRODBUILDTEMP" } + +function test_server() { + # eslint . + tsc --build test + # c8 --reporter=lcov --reporter=text + mocha +} + builder_run_action clean:server clean_server builder_run_action configure:server configure_server builder_run_action build:server build_server builder_run_action build:addins build_addins -builder_run_action test:server mocha +builder_run_action test:server test_server # builder_run_action test:addins # no op builder_run_action installer:server installer_server diff --git a/developer/src/server/package.json b/developer/src/server/package.json index 6d23e11bfb7..fbba3454395 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -28,7 +28,7 @@ "@types/express": "^4.17.13", "@types/mocha": "^9.1.0", "@types/multer": "^1.4.7", - "@types/node": "^17.0.0", + "@types/node": "^20.4.1", "@types/ws": "^8.2.2", "chai": "^4.3.4", "copyfiles": "^2.4.1", @@ -39,6 +39,6 @@ }, "mocha": { "require": "ts-node/register", - "spec": "**/*.test.ts" + "spec": "build/**/*.test.js" } } diff --git a/developer/src/server/test/tsconfig.json b/developer/src/server/test/tsconfig.json new file mode 100644 index 00000000000..e25eb9fb3f4 --- /dev/null +++ b/developer/src/server/test/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.esm-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "baseUrl": ".", + }, + "include": [ + "**/*.test.ts" + ], + "references": [ + { "path": "../" }, + ] +} \ No newline at end of file diff --git a/developer/src/server/src/version-data.test.ts b/developer/src/server/test/version-data.test.ts similarity index 97% rename from developer/src/server/src/version-data.test.ts rename to developer/src/server/test/version-data.test.ts index 9f7ebd385f9..74762b7d7f8 100644 --- a/developer/src/server/src/version-data.test.ts +++ b/developer/src/server/test/version-data.test.ts @@ -1,8 +1,6 @@ import {assert} from 'chai'; import 'mocha'; - -import { extractVersionData } from './version-data.js'; - +import { extractVersionData } from '../src/version-data.js'; describe('extractVersionData', function() { it('should parse version strings', function() { diff --git a/package-lock.json b/package-lock.json index 405d2154cfb..75cab78dd29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2654,7 +2654,7 @@ "@types/express": "^4.17.13", "@types/mocha": "^9.1.0", "@types/multer": "^1.4.7", - "@types/node": "^17.0.0", + "@types/node": "^20.4.1", "@types/ws": "^8.2.2", "chai": "^4.3.4", "copyfiles": "^2.4.1", @@ -2674,9 +2674,10 @@ "license": "MIT" }, "developer/src/server/node_modules/@types/node": { - "version": "17.0.45", - "dev": true, - "license": "MIT" + "version": "20.8.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", + "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", + "dev": true }, "developer/src/server/node_modules/busboy": { "version": "1.6.0", From 39699f75d5db883673176d70b89664a600182713 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 14:24:17 +0700 Subject: [PATCH 136/207] chore(common): restore es5 target for keyman-version --- common/web/keyman-version/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/common/web/keyman-version/tsconfig.json b/common/web/keyman-version/tsconfig.json index 03478f60212..9f4f718745d 100644 --- a/common/web/keyman-version/tsconfig.json +++ b/common/web/keyman-version/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./build", + "target": "es5", "rootDir": "." }, From 27dfef024d11f076c4cb3bce72fe0e227fcac57f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 14:41:31 +0700 Subject: [PATCH 137/207] chore(common): restore (unused) test target for keyman-version --- common/web/keyman-version/build.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/web/keyman-version/build.sh b/common/web/keyman-version/build.sh index 28dd2972111..d4a07e4c762 100755 --- a/common/web/keyman-version/build.sh +++ b/common/web/keyman-version/build.sh @@ -18,7 +18,8 @@ builder_describe "Build the include script for current Keyman version" \ clean \ build \ "pack build a local .tgz pack for testing" \ - "publish publish to npm" + "publish publish to npm" \ + test builder_describe_outputs \ configure "/node_modules" \ From 33b6b5abc26444d278d00405b1efb58dc0655ef0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 15:16:34 +0700 Subject: [PATCH 138/207] chore: update tsconfig --- developer/src/server/test/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/server/test/tsconfig.json b/developer/src/server/test/tsconfig.json index e25eb9fb3f4..fa377b0fde1 100644 --- a/developer/src/server/test/tsconfig.json +++ b/developer/src/server/test/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig.esm-base.json", + "extends": "../../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", From f832b9ff3d7c5852f0076e30953745950e223b2f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 15:25:01 +0700 Subject: [PATCH 139/207] chore(developer): remove temp non-esm module in kmc-model --- .../sourcemap-path-remapper/tsconfig.json | 1 - .../kmc-model/test/test-join-word-breaker.ts | 2 +- .../test/test-override-script-defaults.ts | 2 +- .../src/kmc-model/test/wordbreakers/README.md | 3 - .../src/kmc-model/test/wordbreakers/data.ts | 1776 ----------------- .../wordbreakers/default-wordbreaker-esm.ts | 383 ---- 6 files changed, 2 insertions(+), 2165 deletions(-) delete mode 100644 developer/src/kmc-model/test/wordbreakers/README.md delete mode 100644 developer/src/kmc-model/test/wordbreakers/data.ts delete mode 100644 developer/src/kmc-model/test/wordbreakers/default-wordbreaker-esm.ts diff --git a/common/tools/sourcemap-path-remapper/tsconfig.json b/common/tools/sourcemap-path-remapper/tsconfig.json index 72809897bd7..6bf1a23b8d2 100644 --- a/common/tools/sourcemap-path-remapper/tsconfig.json +++ b/common/tools/sourcemap-path-remapper/tsconfig.json @@ -5,7 +5,6 @@ "allowJs": false, "allowSyntheticDefaultImports": true, "declaration": true, - "module": "es6", "inlineSourceMap": true, "inlineSources": true, "sourceRoot": "/common/tools/sourcemap-path-remapper/src", diff --git a/developer/src/kmc-model/test/test-join-word-breaker.ts b/developer/src/kmc-model/test/test-join-word-breaker.ts index d06bc7a6efd..b08411cb561 100644 --- a/developer/src/kmc-model/test/test-join-word-breaker.ts +++ b/developer/src/kmc-model/test/test-join-word-breaker.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import defaultWordBreaker from './wordbreakers/default-wordbreaker-esm.js'; +import defaultWordBreaker from '@keymanapp/models-wordbreakers'; import {decorateWithJoin} from '../src/join-word-breaker-decorator.js'; describe('The join word breaker decorator', function () { diff --git a/developer/src/kmc-model/test/test-override-script-defaults.ts b/developer/src/kmc-model/test/test-override-script-defaults.ts index 909f52c2981..a0500320fd9 100644 --- a/developer/src/kmc-model/test/test-override-script-defaults.ts +++ b/developer/src/kmc-model/test/test-override-script-defaults.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import defaultWordBreaker from './wordbreakers/default-wordbreaker-esm.js'; +import defaultWordBreaker from '@keymanapp/models-wordbreakers'; import {decorateWithScriptOverrides} from '../src/script-overrides-decorator.js'; const THIN_SPACE = "\u2009"; diff --git a/developer/src/kmc-model/test/wordbreakers/README.md b/developer/src/kmc-model/test/wordbreakers/README.md deleted file mode 100644 index d204b51c6a3..00000000000 --- a/developer/src/kmc-model/test/wordbreakers/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Wordbreakers ES Module format - -TODO: once we move common/models/wordbreakers to ESM, eliminate this. diff --git a/developer/src/kmc-model/test/wordbreakers/data.ts b/developer/src/kmc-model/test/wordbreakers/data.ts deleted file mode 100644 index 5622bf5c037..00000000000 --- a/developer/src/kmc-model/test/wordbreakers/data.ts +++ /dev/null @@ -1,1776 +0,0 @@ -// TEMP: esm version of /common/models/wordbreakers/default/data.ts -// Hand updated version of automatically generated file. -/** - * Valid values for a word break property. - */ -export enum WordBreakProperty { - Other, - LF, - Newline, - CR, - WSegSpace, - Double_Quote, - Single_Quote, - MidNum, - MidNumLet, - Numeric, - MidLetter, - ALetter, - ExtendNumLet, - Format, - Extend, - Hebrew_Letter, - ZWJ, - Katakana, - Regional_Indicator, - sot, - eot -}; - -/** - * Constants for indexing values in WORD_BREAK_PROPERTY. - */ -export enum I { - Start = 0, - Value = 1 -} - -export const WORD_BREAK_PROPERTY: [number, WordBreakProperty][] = [ - [/*start*/ 0x0, WordBreakProperty.Other], - [/*start*/ 0xA, WordBreakProperty.LF], - [/*start*/ 0xB, WordBreakProperty.Newline], - [/*start*/ 0xD, WordBreakProperty.CR], - [/*start*/ 0xE, WordBreakProperty.Other], - [/*start*/ 0x20, WordBreakProperty.WSegSpace], - [/*start*/ 0x21, WordBreakProperty.Other], - [/*start*/ 0x22, WordBreakProperty.Double_Quote], - [/*start*/ 0x23, WordBreakProperty.Other], - [/*start*/ 0x27, WordBreakProperty.Single_Quote], - [/*start*/ 0x28, WordBreakProperty.Other], - [/*start*/ 0x2C, WordBreakProperty.MidNum], - [/*start*/ 0x2D, WordBreakProperty.Other], - [/*start*/ 0x2E, WordBreakProperty.MidNumLet], - [/*start*/ 0x2F, WordBreakProperty.Other], - [/*start*/ 0x30, WordBreakProperty.Numeric], - [/*start*/ 0x3A, WordBreakProperty.MidLetter], - [/*start*/ 0x3B, WordBreakProperty.MidNum], - [/*start*/ 0x3C, WordBreakProperty.Other], - [/*start*/ 0x41, WordBreakProperty.ALetter], - [/*start*/ 0x5B, WordBreakProperty.Other], - [/*start*/ 0x5F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x60, WordBreakProperty.Other], - [/*start*/ 0x61, WordBreakProperty.ALetter], - [/*start*/ 0x7B, WordBreakProperty.Other], - [/*start*/ 0x85, WordBreakProperty.Newline], - [/*start*/ 0x86, WordBreakProperty.Other], - [/*start*/ 0xAA, WordBreakProperty.ALetter], - [/*start*/ 0xAB, WordBreakProperty.Other], - [/*start*/ 0xAD, WordBreakProperty.Format], - [/*start*/ 0xAE, WordBreakProperty.Other], - [/*start*/ 0xB5, WordBreakProperty.ALetter], - [/*start*/ 0xB6, WordBreakProperty.Other], - [/*start*/ 0xB7, WordBreakProperty.MidLetter], - [/*start*/ 0xB8, WordBreakProperty.Other], - [/*start*/ 0xBA, WordBreakProperty.ALetter], - [/*start*/ 0xBB, WordBreakProperty.Other], - [/*start*/ 0xC0, WordBreakProperty.ALetter], - [/*start*/ 0xD7, WordBreakProperty.Other], - [/*start*/ 0xD8, WordBreakProperty.ALetter], - [/*start*/ 0xF7, WordBreakProperty.Other], - [/*start*/ 0xF8, WordBreakProperty.ALetter], - [/*start*/ 0x2D8, WordBreakProperty.Other], - [/*start*/ 0x2DE, WordBreakProperty.ALetter], - [/*start*/ 0x300, WordBreakProperty.Extend], - [/*start*/ 0x370, WordBreakProperty.ALetter], - [/*start*/ 0x375, WordBreakProperty.Other], - [/*start*/ 0x376, WordBreakProperty.ALetter], - [/*start*/ 0x378, WordBreakProperty.Other], - [/*start*/ 0x37A, WordBreakProperty.ALetter], - [/*start*/ 0x37E, WordBreakProperty.MidNum], - [/*start*/ 0x37F, WordBreakProperty.ALetter], - [/*start*/ 0x380, WordBreakProperty.Other], - [/*start*/ 0x386, WordBreakProperty.ALetter], - [/*start*/ 0x387, WordBreakProperty.MidLetter], - [/*start*/ 0x388, WordBreakProperty.ALetter], - [/*start*/ 0x38B, WordBreakProperty.Other], - [/*start*/ 0x38C, WordBreakProperty.ALetter], - [/*start*/ 0x38D, WordBreakProperty.Other], - [/*start*/ 0x38E, WordBreakProperty.ALetter], - [/*start*/ 0x3A2, WordBreakProperty.Other], - [/*start*/ 0x3A3, WordBreakProperty.ALetter], - [/*start*/ 0x3F6, WordBreakProperty.Other], - [/*start*/ 0x3F7, WordBreakProperty.ALetter], - [/*start*/ 0x482, WordBreakProperty.Other], - [/*start*/ 0x483, WordBreakProperty.Extend], - [/*start*/ 0x48A, WordBreakProperty.ALetter], - [/*start*/ 0x530, WordBreakProperty.Other], - [/*start*/ 0x531, WordBreakProperty.ALetter], - [/*start*/ 0x557, WordBreakProperty.Other], - [/*start*/ 0x559, WordBreakProperty.ALetter], - [/*start*/ 0x55D, WordBreakProperty.Other], - [/*start*/ 0x55E, WordBreakProperty.ALetter], - [/*start*/ 0x55F, WordBreakProperty.MidLetter], - [/*start*/ 0x560, WordBreakProperty.ALetter], - [/*start*/ 0x589, WordBreakProperty.MidNum], - [/*start*/ 0x58A, WordBreakProperty.ALetter], - [/*start*/ 0x58B, WordBreakProperty.Other], - [/*start*/ 0x591, WordBreakProperty.Extend], - [/*start*/ 0x5BE, WordBreakProperty.Other], - [/*start*/ 0x5BF, WordBreakProperty.Extend], - [/*start*/ 0x5C0, WordBreakProperty.Other], - [/*start*/ 0x5C1, WordBreakProperty.Extend], - [/*start*/ 0x5C3, WordBreakProperty.Other], - [/*start*/ 0x5C4, WordBreakProperty.Extend], - [/*start*/ 0x5C6, WordBreakProperty.Other], - [/*start*/ 0x5C7, WordBreakProperty.Extend], - [/*start*/ 0x5C8, WordBreakProperty.Other], - [/*start*/ 0x5D0, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0x5EB, WordBreakProperty.Other], - [/*start*/ 0x5EF, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0x5F3, WordBreakProperty.ALetter], - [/*start*/ 0x5F4, WordBreakProperty.MidLetter], - [/*start*/ 0x5F5, WordBreakProperty.Other], - [/*start*/ 0x600, WordBreakProperty.Format], - [/*start*/ 0x606, WordBreakProperty.Other], - [/*start*/ 0x60C, WordBreakProperty.MidNum], - [/*start*/ 0x60E, WordBreakProperty.Other], - [/*start*/ 0x610, WordBreakProperty.Extend], - [/*start*/ 0x61B, WordBreakProperty.Other], - [/*start*/ 0x61C, WordBreakProperty.Format], - [/*start*/ 0x61D, WordBreakProperty.Other], - [/*start*/ 0x620, WordBreakProperty.ALetter], - [/*start*/ 0x64B, WordBreakProperty.Extend], - [/*start*/ 0x660, WordBreakProperty.Numeric], - [/*start*/ 0x66A, WordBreakProperty.Other], - [/*start*/ 0x66B, WordBreakProperty.Numeric], - [/*start*/ 0x66C, WordBreakProperty.MidNum], - [/*start*/ 0x66D, WordBreakProperty.Other], - [/*start*/ 0x66E, WordBreakProperty.ALetter], - [/*start*/ 0x670, WordBreakProperty.Extend], - [/*start*/ 0x671, WordBreakProperty.ALetter], - [/*start*/ 0x6D4, WordBreakProperty.Other], - [/*start*/ 0x6D5, WordBreakProperty.ALetter], - [/*start*/ 0x6D6, WordBreakProperty.Extend], - [/*start*/ 0x6DD, WordBreakProperty.Format], - [/*start*/ 0x6DE, WordBreakProperty.Other], - [/*start*/ 0x6DF, WordBreakProperty.Extend], - [/*start*/ 0x6E5, WordBreakProperty.ALetter], - [/*start*/ 0x6E7, WordBreakProperty.Extend], - [/*start*/ 0x6E9, WordBreakProperty.Other], - [/*start*/ 0x6EA, WordBreakProperty.Extend], - [/*start*/ 0x6EE, WordBreakProperty.ALetter], - [/*start*/ 0x6F0, WordBreakProperty.Numeric], - [/*start*/ 0x6FA, WordBreakProperty.ALetter], - [/*start*/ 0x6FD, WordBreakProperty.Other], - [/*start*/ 0x6FF, WordBreakProperty.ALetter], - [/*start*/ 0x700, WordBreakProperty.Other], - [/*start*/ 0x70F, WordBreakProperty.Format], - [/*start*/ 0x710, WordBreakProperty.ALetter], - [/*start*/ 0x711, WordBreakProperty.Extend], - [/*start*/ 0x712, WordBreakProperty.ALetter], - [/*start*/ 0x730, WordBreakProperty.Extend], - [/*start*/ 0x74B, WordBreakProperty.Other], - [/*start*/ 0x74D, WordBreakProperty.ALetter], - [/*start*/ 0x7A6, WordBreakProperty.Extend], - [/*start*/ 0x7B1, WordBreakProperty.ALetter], - [/*start*/ 0x7B2, WordBreakProperty.Other], - [/*start*/ 0x7C0, WordBreakProperty.Numeric], - [/*start*/ 0x7CA, WordBreakProperty.ALetter], - [/*start*/ 0x7EB, WordBreakProperty.Extend], - [/*start*/ 0x7F4, WordBreakProperty.ALetter], - [/*start*/ 0x7F6, WordBreakProperty.Other], - [/*start*/ 0x7F8, WordBreakProperty.MidNum], - [/*start*/ 0x7F9, WordBreakProperty.Other], - [/*start*/ 0x7FA, WordBreakProperty.ALetter], - [/*start*/ 0x7FB, WordBreakProperty.Other], - [/*start*/ 0x7FD, WordBreakProperty.Extend], - [/*start*/ 0x7FE, WordBreakProperty.Other], - [/*start*/ 0x800, WordBreakProperty.ALetter], - [/*start*/ 0x816, WordBreakProperty.Extend], - [/*start*/ 0x81A, WordBreakProperty.ALetter], - [/*start*/ 0x81B, WordBreakProperty.Extend], - [/*start*/ 0x824, WordBreakProperty.ALetter], - [/*start*/ 0x825, WordBreakProperty.Extend], - [/*start*/ 0x828, WordBreakProperty.ALetter], - [/*start*/ 0x829, WordBreakProperty.Extend], - [/*start*/ 0x82E, WordBreakProperty.Other], - [/*start*/ 0x840, WordBreakProperty.ALetter], - [/*start*/ 0x859, WordBreakProperty.Extend], - [/*start*/ 0x85C, WordBreakProperty.Other], - [/*start*/ 0x860, WordBreakProperty.ALetter], - [/*start*/ 0x86B, WordBreakProperty.Other], - [/*start*/ 0x8A0, WordBreakProperty.ALetter], - [/*start*/ 0x8B5, WordBreakProperty.Other], - [/*start*/ 0x8B6, WordBreakProperty.ALetter], - [/*start*/ 0x8C8, WordBreakProperty.Other], - [/*start*/ 0x8D3, WordBreakProperty.Extend], - [/*start*/ 0x8E2, WordBreakProperty.Format], - [/*start*/ 0x8E3, WordBreakProperty.Extend], - [/*start*/ 0x904, WordBreakProperty.ALetter], - [/*start*/ 0x93A, WordBreakProperty.Extend], - [/*start*/ 0x93D, WordBreakProperty.ALetter], - [/*start*/ 0x93E, WordBreakProperty.Extend], - [/*start*/ 0x950, WordBreakProperty.ALetter], - [/*start*/ 0x951, WordBreakProperty.Extend], - [/*start*/ 0x958, WordBreakProperty.ALetter], - [/*start*/ 0x962, WordBreakProperty.Extend], - [/*start*/ 0x964, WordBreakProperty.Other], - [/*start*/ 0x966, WordBreakProperty.Numeric], - [/*start*/ 0x970, WordBreakProperty.Other], - [/*start*/ 0x971, WordBreakProperty.ALetter], - [/*start*/ 0x981, WordBreakProperty.Extend], - [/*start*/ 0x984, WordBreakProperty.Other], - [/*start*/ 0x985, WordBreakProperty.ALetter], - [/*start*/ 0x98D, WordBreakProperty.Other], - [/*start*/ 0x98F, WordBreakProperty.ALetter], - [/*start*/ 0x991, WordBreakProperty.Other], - [/*start*/ 0x993, WordBreakProperty.ALetter], - [/*start*/ 0x9A9, WordBreakProperty.Other], - [/*start*/ 0x9AA, WordBreakProperty.ALetter], - [/*start*/ 0x9B1, WordBreakProperty.Other], - [/*start*/ 0x9B2, WordBreakProperty.ALetter], - [/*start*/ 0x9B3, WordBreakProperty.Other], - [/*start*/ 0x9B6, WordBreakProperty.ALetter], - [/*start*/ 0x9BA, WordBreakProperty.Other], - [/*start*/ 0x9BC, WordBreakProperty.Extend], - [/*start*/ 0x9BD, WordBreakProperty.ALetter], - [/*start*/ 0x9BE, WordBreakProperty.Extend], - [/*start*/ 0x9C5, WordBreakProperty.Other], - [/*start*/ 0x9C7, WordBreakProperty.Extend], - [/*start*/ 0x9C9, WordBreakProperty.Other], - [/*start*/ 0x9CB, WordBreakProperty.Extend], - [/*start*/ 0x9CE, WordBreakProperty.ALetter], - [/*start*/ 0x9CF, WordBreakProperty.Other], - [/*start*/ 0x9D7, WordBreakProperty.Extend], - [/*start*/ 0x9D8, WordBreakProperty.Other], - [/*start*/ 0x9DC, WordBreakProperty.ALetter], - [/*start*/ 0x9DE, WordBreakProperty.Other], - [/*start*/ 0x9DF, WordBreakProperty.ALetter], - [/*start*/ 0x9E2, WordBreakProperty.Extend], - [/*start*/ 0x9E4, WordBreakProperty.Other], - [/*start*/ 0x9E6, WordBreakProperty.Numeric], - [/*start*/ 0x9F0, WordBreakProperty.ALetter], - [/*start*/ 0x9F2, WordBreakProperty.Other], - [/*start*/ 0x9FC, WordBreakProperty.ALetter], - [/*start*/ 0x9FD, WordBreakProperty.Other], - [/*start*/ 0x9FE, WordBreakProperty.Extend], - [/*start*/ 0x9FF, WordBreakProperty.Other], - [/*start*/ 0xA01, WordBreakProperty.Extend], - [/*start*/ 0xA04, WordBreakProperty.Other], - [/*start*/ 0xA05, WordBreakProperty.ALetter], - [/*start*/ 0xA0B, WordBreakProperty.Other], - [/*start*/ 0xA0F, WordBreakProperty.ALetter], - [/*start*/ 0xA11, WordBreakProperty.Other], - [/*start*/ 0xA13, WordBreakProperty.ALetter], - [/*start*/ 0xA29, WordBreakProperty.Other], - [/*start*/ 0xA2A, WordBreakProperty.ALetter], - [/*start*/ 0xA31, WordBreakProperty.Other], - [/*start*/ 0xA32, WordBreakProperty.ALetter], - [/*start*/ 0xA34, WordBreakProperty.Other], - [/*start*/ 0xA35, WordBreakProperty.ALetter], - [/*start*/ 0xA37, WordBreakProperty.Other], - [/*start*/ 0xA38, WordBreakProperty.ALetter], - [/*start*/ 0xA3A, WordBreakProperty.Other], - [/*start*/ 0xA3C, WordBreakProperty.Extend], - [/*start*/ 0xA3D, WordBreakProperty.Other], - [/*start*/ 0xA3E, WordBreakProperty.Extend], - [/*start*/ 0xA43, WordBreakProperty.Other], - [/*start*/ 0xA47, WordBreakProperty.Extend], - [/*start*/ 0xA49, WordBreakProperty.Other], - [/*start*/ 0xA4B, WordBreakProperty.Extend], - [/*start*/ 0xA4E, WordBreakProperty.Other], - [/*start*/ 0xA51, WordBreakProperty.Extend], - [/*start*/ 0xA52, WordBreakProperty.Other], - [/*start*/ 0xA59, WordBreakProperty.ALetter], - [/*start*/ 0xA5D, WordBreakProperty.Other], - [/*start*/ 0xA5E, WordBreakProperty.ALetter], - [/*start*/ 0xA5F, WordBreakProperty.Other], - [/*start*/ 0xA66, WordBreakProperty.Numeric], - [/*start*/ 0xA70, WordBreakProperty.Extend], - [/*start*/ 0xA72, WordBreakProperty.ALetter], - [/*start*/ 0xA75, WordBreakProperty.Extend], - [/*start*/ 0xA76, WordBreakProperty.Other], - [/*start*/ 0xA81, WordBreakProperty.Extend], - [/*start*/ 0xA84, WordBreakProperty.Other], - [/*start*/ 0xA85, WordBreakProperty.ALetter], - [/*start*/ 0xA8E, WordBreakProperty.Other], - [/*start*/ 0xA8F, WordBreakProperty.ALetter], - [/*start*/ 0xA92, WordBreakProperty.Other], - [/*start*/ 0xA93, WordBreakProperty.ALetter], - [/*start*/ 0xAA9, WordBreakProperty.Other], - [/*start*/ 0xAAA, WordBreakProperty.ALetter], - [/*start*/ 0xAB1, WordBreakProperty.Other], - [/*start*/ 0xAB2, WordBreakProperty.ALetter], - [/*start*/ 0xAB4, WordBreakProperty.Other], - [/*start*/ 0xAB5, WordBreakProperty.ALetter], - [/*start*/ 0xABA, WordBreakProperty.Other], - [/*start*/ 0xABC, WordBreakProperty.Extend], - [/*start*/ 0xABD, WordBreakProperty.ALetter], - [/*start*/ 0xABE, WordBreakProperty.Extend], - [/*start*/ 0xAC6, WordBreakProperty.Other], - [/*start*/ 0xAC7, WordBreakProperty.Extend], - [/*start*/ 0xACA, WordBreakProperty.Other], - [/*start*/ 0xACB, WordBreakProperty.Extend], - [/*start*/ 0xACE, WordBreakProperty.Other], - [/*start*/ 0xAD0, WordBreakProperty.ALetter], - [/*start*/ 0xAD1, WordBreakProperty.Other], - [/*start*/ 0xAE0, WordBreakProperty.ALetter], - [/*start*/ 0xAE2, WordBreakProperty.Extend], - [/*start*/ 0xAE4, WordBreakProperty.Other], - [/*start*/ 0xAE6, WordBreakProperty.Numeric], - [/*start*/ 0xAF0, WordBreakProperty.Other], - [/*start*/ 0xAF9, WordBreakProperty.ALetter], - [/*start*/ 0xAFA, WordBreakProperty.Extend], - [/*start*/ 0xB00, WordBreakProperty.Other], - [/*start*/ 0xB01, WordBreakProperty.Extend], - [/*start*/ 0xB04, WordBreakProperty.Other], - [/*start*/ 0xB05, WordBreakProperty.ALetter], - [/*start*/ 0xB0D, WordBreakProperty.Other], - [/*start*/ 0xB0F, WordBreakProperty.ALetter], - [/*start*/ 0xB11, WordBreakProperty.Other], - [/*start*/ 0xB13, WordBreakProperty.ALetter], - [/*start*/ 0xB29, WordBreakProperty.Other], - [/*start*/ 0xB2A, WordBreakProperty.ALetter], - [/*start*/ 0xB31, WordBreakProperty.Other], - [/*start*/ 0xB32, WordBreakProperty.ALetter], - [/*start*/ 0xB34, WordBreakProperty.Other], - [/*start*/ 0xB35, WordBreakProperty.ALetter], - [/*start*/ 0xB3A, WordBreakProperty.Other], - [/*start*/ 0xB3C, WordBreakProperty.Extend], - [/*start*/ 0xB3D, WordBreakProperty.ALetter], - [/*start*/ 0xB3E, WordBreakProperty.Extend], - [/*start*/ 0xB45, WordBreakProperty.Other], - [/*start*/ 0xB47, WordBreakProperty.Extend], - [/*start*/ 0xB49, WordBreakProperty.Other], - [/*start*/ 0xB4B, WordBreakProperty.Extend], - [/*start*/ 0xB4E, WordBreakProperty.Other], - [/*start*/ 0xB55, WordBreakProperty.Extend], - [/*start*/ 0xB58, WordBreakProperty.Other], - [/*start*/ 0xB5C, WordBreakProperty.ALetter], - [/*start*/ 0xB5E, WordBreakProperty.Other], - [/*start*/ 0xB5F, WordBreakProperty.ALetter], - [/*start*/ 0xB62, WordBreakProperty.Extend], - [/*start*/ 0xB64, WordBreakProperty.Other], - [/*start*/ 0xB66, WordBreakProperty.Numeric], - [/*start*/ 0xB70, WordBreakProperty.Other], - [/*start*/ 0xB71, WordBreakProperty.ALetter], - [/*start*/ 0xB72, WordBreakProperty.Other], - [/*start*/ 0xB82, WordBreakProperty.Extend], - [/*start*/ 0xB83, WordBreakProperty.ALetter], - [/*start*/ 0xB84, WordBreakProperty.Other], - [/*start*/ 0xB85, WordBreakProperty.ALetter], - [/*start*/ 0xB8B, WordBreakProperty.Other], - [/*start*/ 0xB8E, WordBreakProperty.ALetter], - [/*start*/ 0xB91, WordBreakProperty.Other], - [/*start*/ 0xB92, WordBreakProperty.ALetter], - [/*start*/ 0xB96, WordBreakProperty.Other], - [/*start*/ 0xB99, WordBreakProperty.ALetter], - [/*start*/ 0xB9B, WordBreakProperty.Other], - [/*start*/ 0xB9C, WordBreakProperty.ALetter], - [/*start*/ 0xB9D, WordBreakProperty.Other], - [/*start*/ 0xB9E, WordBreakProperty.ALetter], - [/*start*/ 0xBA0, WordBreakProperty.Other], - [/*start*/ 0xBA3, WordBreakProperty.ALetter], - [/*start*/ 0xBA5, WordBreakProperty.Other], - [/*start*/ 0xBA8, WordBreakProperty.ALetter], - [/*start*/ 0xBAB, WordBreakProperty.Other], - [/*start*/ 0xBAE, WordBreakProperty.ALetter], - [/*start*/ 0xBBA, WordBreakProperty.Other], - [/*start*/ 0xBBE, WordBreakProperty.Extend], - [/*start*/ 0xBC3, WordBreakProperty.Other], - [/*start*/ 0xBC6, WordBreakProperty.Extend], - [/*start*/ 0xBC9, WordBreakProperty.Other], - [/*start*/ 0xBCA, WordBreakProperty.Extend], - [/*start*/ 0xBCE, WordBreakProperty.Other], - [/*start*/ 0xBD0, WordBreakProperty.ALetter], - [/*start*/ 0xBD1, WordBreakProperty.Other], - [/*start*/ 0xBD7, WordBreakProperty.Extend], - [/*start*/ 0xBD8, WordBreakProperty.Other], - [/*start*/ 0xBE6, WordBreakProperty.Numeric], - [/*start*/ 0xBF0, WordBreakProperty.Other], - [/*start*/ 0xC00, WordBreakProperty.Extend], - [/*start*/ 0xC05, WordBreakProperty.ALetter], - [/*start*/ 0xC0D, WordBreakProperty.Other], - [/*start*/ 0xC0E, WordBreakProperty.ALetter], - [/*start*/ 0xC11, WordBreakProperty.Other], - [/*start*/ 0xC12, WordBreakProperty.ALetter], - [/*start*/ 0xC29, WordBreakProperty.Other], - [/*start*/ 0xC2A, WordBreakProperty.ALetter], - [/*start*/ 0xC3A, WordBreakProperty.Other], - [/*start*/ 0xC3D, WordBreakProperty.ALetter], - [/*start*/ 0xC3E, WordBreakProperty.Extend], - [/*start*/ 0xC45, WordBreakProperty.Other], - [/*start*/ 0xC46, WordBreakProperty.Extend], - [/*start*/ 0xC49, WordBreakProperty.Other], - [/*start*/ 0xC4A, WordBreakProperty.Extend], - [/*start*/ 0xC4E, WordBreakProperty.Other], - [/*start*/ 0xC55, WordBreakProperty.Extend], - [/*start*/ 0xC57, WordBreakProperty.Other], - [/*start*/ 0xC58, WordBreakProperty.ALetter], - [/*start*/ 0xC5B, WordBreakProperty.Other], - [/*start*/ 0xC60, WordBreakProperty.ALetter], - [/*start*/ 0xC62, WordBreakProperty.Extend], - [/*start*/ 0xC64, WordBreakProperty.Other], - [/*start*/ 0xC66, WordBreakProperty.Numeric], - [/*start*/ 0xC70, WordBreakProperty.Other], - [/*start*/ 0xC80, WordBreakProperty.ALetter], - [/*start*/ 0xC81, WordBreakProperty.Extend], - [/*start*/ 0xC84, WordBreakProperty.Other], - [/*start*/ 0xC85, WordBreakProperty.ALetter], - [/*start*/ 0xC8D, WordBreakProperty.Other], - [/*start*/ 0xC8E, WordBreakProperty.ALetter], - [/*start*/ 0xC91, WordBreakProperty.Other], - [/*start*/ 0xC92, WordBreakProperty.ALetter], - [/*start*/ 0xCA9, WordBreakProperty.Other], - [/*start*/ 0xCAA, WordBreakProperty.ALetter], - [/*start*/ 0xCB4, WordBreakProperty.Other], - [/*start*/ 0xCB5, WordBreakProperty.ALetter], - [/*start*/ 0xCBA, WordBreakProperty.Other], - [/*start*/ 0xCBC, WordBreakProperty.Extend], - [/*start*/ 0xCBD, WordBreakProperty.ALetter], - [/*start*/ 0xCBE, WordBreakProperty.Extend], - [/*start*/ 0xCC5, WordBreakProperty.Other], - [/*start*/ 0xCC6, WordBreakProperty.Extend], - [/*start*/ 0xCC9, WordBreakProperty.Other], - [/*start*/ 0xCCA, WordBreakProperty.Extend], - [/*start*/ 0xCCE, WordBreakProperty.Other], - [/*start*/ 0xCD5, WordBreakProperty.Extend], - [/*start*/ 0xCD7, WordBreakProperty.Other], - [/*start*/ 0xCDE, WordBreakProperty.ALetter], - [/*start*/ 0xCDF, WordBreakProperty.Other], - [/*start*/ 0xCE0, WordBreakProperty.ALetter], - [/*start*/ 0xCE2, WordBreakProperty.Extend], - [/*start*/ 0xCE4, WordBreakProperty.Other], - [/*start*/ 0xCE6, WordBreakProperty.Numeric], - [/*start*/ 0xCF0, WordBreakProperty.Other], - [/*start*/ 0xCF1, WordBreakProperty.ALetter], - [/*start*/ 0xCF3, WordBreakProperty.Other], - [/*start*/ 0xD00, WordBreakProperty.Extend], - [/*start*/ 0xD04, WordBreakProperty.ALetter], - [/*start*/ 0xD0D, WordBreakProperty.Other], - [/*start*/ 0xD0E, WordBreakProperty.ALetter], - [/*start*/ 0xD11, WordBreakProperty.Other], - [/*start*/ 0xD12, WordBreakProperty.ALetter], - [/*start*/ 0xD3B, WordBreakProperty.Extend], - [/*start*/ 0xD3D, WordBreakProperty.ALetter], - [/*start*/ 0xD3E, WordBreakProperty.Extend], - [/*start*/ 0xD45, WordBreakProperty.Other], - [/*start*/ 0xD46, WordBreakProperty.Extend], - [/*start*/ 0xD49, WordBreakProperty.Other], - [/*start*/ 0xD4A, WordBreakProperty.Extend], - [/*start*/ 0xD4E, WordBreakProperty.ALetter], - [/*start*/ 0xD4F, WordBreakProperty.Other], - [/*start*/ 0xD54, WordBreakProperty.ALetter], - [/*start*/ 0xD57, WordBreakProperty.Extend], - [/*start*/ 0xD58, WordBreakProperty.Other], - [/*start*/ 0xD5F, WordBreakProperty.ALetter], - [/*start*/ 0xD62, WordBreakProperty.Extend], - [/*start*/ 0xD64, WordBreakProperty.Other], - [/*start*/ 0xD66, WordBreakProperty.Numeric], - [/*start*/ 0xD70, WordBreakProperty.Other], - [/*start*/ 0xD7A, WordBreakProperty.ALetter], - [/*start*/ 0xD80, WordBreakProperty.Other], - [/*start*/ 0xD81, WordBreakProperty.Extend], - [/*start*/ 0xD84, WordBreakProperty.Other], - [/*start*/ 0xD85, WordBreakProperty.ALetter], - [/*start*/ 0xD97, WordBreakProperty.Other], - [/*start*/ 0xD9A, WordBreakProperty.ALetter], - [/*start*/ 0xDB2, WordBreakProperty.Other], - [/*start*/ 0xDB3, WordBreakProperty.ALetter], - [/*start*/ 0xDBC, WordBreakProperty.Other], - [/*start*/ 0xDBD, WordBreakProperty.ALetter], - [/*start*/ 0xDBE, WordBreakProperty.Other], - [/*start*/ 0xDC0, WordBreakProperty.ALetter], - [/*start*/ 0xDC7, WordBreakProperty.Other], - [/*start*/ 0xDCA, WordBreakProperty.Extend], - [/*start*/ 0xDCB, WordBreakProperty.Other], - [/*start*/ 0xDCF, WordBreakProperty.Extend], - [/*start*/ 0xDD5, WordBreakProperty.Other], - [/*start*/ 0xDD6, WordBreakProperty.Extend], - [/*start*/ 0xDD7, WordBreakProperty.Other], - [/*start*/ 0xDD8, WordBreakProperty.Extend], - [/*start*/ 0xDE0, WordBreakProperty.Other], - [/*start*/ 0xDE6, WordBreakProperty.Numeric], - [/*start*/ 0xDF0, WordBreakProperty.Other], - [/*start*/ 0xDF2, WordBreakProperty.Extend], - [/*start*/ 0xDF4, WordBreakProperty.Other], - [/*start*/ 0xE31, WordBreakProperty.Extend], - [/*start*/ 0xE32, WordBreakProperty.Other], - [/*start*/ 0xE34, WordBreakProperty.Extend], - [/*start*/ 0xE3B, WordBreakProperty.Other], - [/*start*/ 0xE47, WordBreakProperty.Extend], - [/*start*/ 0xE4F, WordBreakProperty.Other], - [/*start*/ 0xE50, WordBreakProperty.Numeric], - [/*start*/ 0xE5A, WordBreakProperty.Other], - [/*start*/ 0xEB1, WordBreakProperty.Extend], - [/*start*/ 0xEB2, WordBreakProperty.Other], - [/*start*/ 0xEB4, WordBreakProperty.Extend], - [/*start*/ 0xEBD, WordBreakProperty.Other], - [/*start*/ 0xEC8, WordBreakProperty.Extend], - [/*start*/ 0xECE, WordBreakProperty.Other], - [/*start*/ 0xED0, WordBreakProperty.Numeric], - [/*start*/ 0xEDA, WordBreakProperty.Other], - [/*start*/ 0xF00, WordBreakProperty.ALetter], - [/*start*/ 0xF01, WordBreakProperty.Other], - [/*start*/ 0xF18, WordBreakProperty.Extend], - [/*start*/ 0xF1A, WordBreakProperty.Other], - [/*start*/ 0xF20, WordBreakProperty.Numeric], - [/*start*/ 0xF2A, WordBreakProperty.Other], - [/*start*/ 0xF35, WordBreakProperty.Extend], - [/*start*/ 0xF36, WordBreakProperty.Other], - [/*start*/ 0xF37, WordBreakProperty.Extend], - [/*start*/ 0xF38, WordBreakProperty.Other], - [/*start*/ 0xF39, WordBreakProperty.Extend], - [/*start*/ 0xF3A, WordBreakProperty.Other], - [/*start*/ 0xF3E, WordBreakProperty.Extend], - [/*start*/ 0xF40, WordBreakProperty.ALetter], - [/*start*/ 0xF48, WordBreakProperty.Other], - [/*start*/ 0xF49, WordBreakProperty.ALetter], - [/*start*/ 0xF6D, WordBreakProperty.Other], - [/*start*/ 0xF71, WordBreakProperty.Extend], - [/*start*/ 0xF85, WordBreakProperty.Other], - [/*start*/ 0xF86, WordBreakProperty.Extend], - [/*start*/ 0xF88, WordBreakProperty.ALetter], - [/*start*/ 0xF8D, WordBreakProperty.Extend], - [/*start*/ 0xF98, WordBreakProperty.Other], - [/*start*/ 0xF99, WordBreakProperty.Extend], - [/*start*/ 0xFBD, WordBreakProperty.Other], - [/*start*/ 0xFC6, WordBreakProperty.Extend], - [/*start*/ 0xFC7, WordBreakProperty.Other], - [/*start*/ 0x102B, WordBreakProperty.Extend], - [/*start*/ 0x103F, WordBreakProperty.Other], - [/*start*/ 0x1040, WordBreakProperty.Numeric], - [/*start*/ 0x104A, WordBreakProperty.Other], - [/*start*/ 0x1056, WordBreakProperty.Extend], - [/*start*/ 0x105A, WordBreakProperty.Other], - [/*start*/ 0x105E, WordBreakProperty.Extend], - [/*start*/ 0x1061, WordBreakProperty.Other], - [/*start*/ 0x1062, WordBreakProperty.Extend], - [/*start*/ 0x1065, WordBreakProperty.Other], - [/*start*/ 0x1067, WordBreakProperty.Extend], - [/*start*/ 0x106E, WordBreakProperty.Other], - [/*start*/ 0x1071, WordBreakProperty.Extend], - [/*start*/ 0x1075, WordBreakProperty.Other], - [/*start*/ 0x1082, WordBreakProperty.Extend], - [/*start*/ 0x108E, WordBreakProperty.Other], - [/*start*/ 0x108F, WordBreakProperty.Extend], - [/*start*/ 0x1090, WordBreakProperty.Numeric], - [/*start*/ 0x109A, WordBreakProperty.Extend], - [/*start*/ 0x109E, WordBreakProperty.Other], - [/*start*/ 0x10A0, WordBreakProperty.ALetter], - [/*start*/ 0x10C6, WordBreakProperty.Other], - [/*start*/ 0x10C7, WordBreakProperty.ALetter], - [/*start*/ 0x10C8, WordBreakProperty.Other], - [/*start*/ 0x10CD, WordBreakProperty.ALetter], - [/*start*/ 0x10CE, WordBreakProperty.Other], - [/*start*/ 0x10D0, WordBreakProperty.ALetter], - [/*start*/ 0x10FB, WordBreakProperty.Other], - [/*start*/ 0x10FC, WordBreakProperty.ALetter], - [/*start*/ 0x1249, WordBreakProperty.Other], - [/*start*/ 0x124A, WordBreakProperty.ALetter], - [/*start*/ 0x124E, WordBreakProperty.Other], - [/*start*/ 0x1250, WordBreakProperty.ALetter], - [/*start*/ 0x1257, WordBreakProperty.Other], - [/*start*/ 0x1258, WordBreakProperty.ALetter], - [/*start*/ 0x1259, WordBreakProperty.Other], - [/*start*/ 0x125A, WordBreakProperty.ALetter], - [/*start*/ 0x125E, WordBreakProperty.Other], - [/*start*/ 0x1260, WordBreakProperty.ALetter], - [/*start*/ 0x1289, WordBreakProperty.Other], - [/*start*/ 0x128A, WordBreakProperty.ALetter], - [/*start*/ 0x128E, WordBreakProperty.Other], - [/*start*/ 0x1290, WordBreakProperty.ALetter], - [/*start*/ 0x12B1, WordBreakProperty.Other], - [/*start*/ 0x12B2, WordBreakProperty.ALetter], - [/*start*/ 0x12B6, WordBreakProperty.Other], - [/*start*/ 0x12B8, WordBreakProperty.ALetter], - [/*start*/ 0x12BF, WordBreakProperty.Other], - [/*start*/ 0x12C0, WordBreakProperty.ALetter], - [/*start*/ 0x12C1, WordBreakProperty.Other], - [/*start*/ 0x12C2, WordBreakProperty.ALetter], - [/*start*/ 0x12C6, WordBreakProperty.Other], - [/*start*/ 0x12C8, WordBreakProperty.ALetter], - [/*start*/ 0x12D7, WordBreakProperty.Other], - [/*start*/ 0x12D8, WordBreakProperty.ALetter], - [/*start*/ 0x1311, WordBreakProperty.Other], - [/*start*/ 0x1312, WordBreakProperty.ALetter], - [/*start*/ 0x1316, WordBreakProperty.Other], - [/*start*/ 0x1318, WordBreakProperty.ALetter], - [/*start*/ 0x135B, WordBreakProperty.Other], - [/*start*/ 0x135D, WordBreakProperty.Extend], - [/*start*/ 0x1360, WordBreakProperty.Other], - [/*start*/ 0x1380, WordBreakProperty.ALetter], - [/*start*/ 0x1390, WordBreakProperty.Other], - [/*start*/ 0x13A0, WordBreakProperty.ALetter], - [/*start*/ 0x13F6, WordBreakProperty.Other], - [/*start*/ 0x13F8, WordBreakProperty.ALetter], - [/*start*/ 0x13FE, WordBreakProperty.Other], - [/*start*/ 0x1401, WordBreakProperty.ALetter], - [/*start*/ 0x166D, WordBreakProperty.Other], - [/*start*/ 0x166F, WordBreakProperty.ALetter], - [/*start*/ 0x1680, WordBreakProperty.WSegSpace], - [/*start*/ 0x1681, WordBreakProperty.ALetter], - [/*start*/ 0x169B, WordBreakProperty.Other], - [/*start*/ 0x16A0, WordBreakProperty.ALetter], - [/*start*/ 0x16EB, WordBreakProperty.Other], - [/*start*/ 0x16EE, WordBreakProperty.ALetter], - [/*start*/ 0x16F9, WordBreakProperty.Other], - [/*start*/ 0x1700, WordBreakProperty.ALetter], - [/*start*/ 0x170D, WordBreakProperty.Other], - [/*start*/ 0x170E, WordBreakProperty.ALetter], - [/*start*/ 0x1712, WordBreakProperty.Extend], - [/*start*/ 0x1715, WordBreakProperty.Other], - [/*start*/ 0x1720, WordBreakProperty.ALetter], - [/*start*/ 0x1732, WordBreakProperty.Extend], - [/*start*/ 0x1735, WordBreakProperty.Other], - [/*start*/ 0x1740, WordBreakProperty.ALetter], - [/*start*/ 0x1752, WordBreakProperty.Extend], - [/*start*/ 0x1754, WordBreakProperty.Other], - [/*start*/ 0x1760, WordBreakProperty.ALetter], - [/*start*/ 0x176D, WordBreakProperty.Other], - [/*start*/ 0x176E, WordBreakProperty.ALetter], - [/*start*/ 0x1771, WordBreakProperty.Other], - [/*start*/ 0x1772, WordBreakProperty.Extend], - [/*start*/ 0x1774, WordBreakProperty.Other], - [/*start*/ 0x17B4, WordBreakProperty.Extend], - [/*start*/ 0x17D4, WordBreakProperty.Other], - [/*start*/ 0x17DD, WordBreakProperty.Extend], - [/*start*/ 0x17DE, WordBreakProperty.Other], - [/*start*/ 0x17E0, WordBreakProperty.Numeric], - [/*start*/ 0x17EA, WordBreakProperty.Other], - [/*start*/ 0x180B, WordBreakProperty.Extend], - [/*start*/ 0x180E, WordBreakProperty.Format], - [/*start*/ 0x180F, WordBreakProperty.Other], - [/*start*/ 0x1810, WordBreakProperty.Numeric], - [/*start*/ 0x181A, WordBreakProperty.Other], - [/*start*/ 0x1820, WordBreakProperty.ALetter], - [/*start*/ 0x1879, WordBreakProperty.Other], - [/*start*/ 0x1880, WordBreakProperty.ALetter], - [/*start*/ 0x1885, WordBreakProperty.Extend], - [/*start*/ 0x1887, WordBreakProperty.ALetter], - [/*start*/ 0x18A9, WordBreakProperty.Extend], - [/*start*/ 0x18AA, WordBreakProperty.ALetter], - [/*start*/ 0x18AB, WordBreakProperty.Other], - [/*start*/ 0x18B0, WordBreakProperty.ALetter], - [/*start*/ 0x18F6, WordBreakProperty.Other], - [/*start*/ 0x1900, WordBreakProperty.ALetter], - [/*start*/ 0x191F, WordBreakProperty.Other], - [/*start*/ 0x1920, WordBreakProperty.Extend], - [/*start*/ 0x192C, WordBreakProperty.Other], - [/*start*/ 0x1930, WordBreakProperty.Extend], - [/*start*/ 0x193C, WordBreakProperty.Other], - [/*start*/ 0x1946, WordBreakProperty.Numeric], - [/*start*/ 0x1950, WordBreakProperty.Other], - [/*start*/ 0x19D0, WordBreakProperty.Numeric], - [/*start*/ 0x19DA, WordBreakProperty.Other], - [/*start*/ 0x1A00, WordBreakProperty.ALetter], - [/*start*/ 0x1A17, WordBreakProperty.Extend], - [/*start*/ 0x1A1C, WordBreakProperty.Other], - [/*start*/ 0x1A55, WordBreakProperty.Extend], - [/*start*/ 0x1A5F, WordBreakProperty.Other], - [/*start*/ 0x1A60, WordBreakProperty.Extend], - [/*start*/ 0x1A7D, WordBreakProperty.Other], - [/*start*/ 0x1A7F, WordBreakProperty.Extend], - [/*start*/ 0x1A80, WordBreakProperty.Numeric], - [/*start*/ 0x1A8A, WordBreakProperty.Other], - [/*start*/ 0x1A90, WordBreakProperty.Numeric], - [/*start*/ 0x1A9A, WordBreakProperty.Other], - [/*start*/ 0x1AB0, WordBreakProperty.Extend], - [/*start*/ 0x1AC1, WordBreakProperty.Other], - [/*start*/ 0x1B00, WordBreakProperty.Extend], - [/*start*/ 0x1B05, WordBreakProperty.ALetter], - [/*start*/ 0x1B34, WordBreakProperty.Extend], - [/*start*/ 0x1B45, WordBreakProperty.ALetter], - [/*start*/ 0x1B4C, WordBreakProperty.Other], - [/*start*/ 0x1B50, WordBreakProperty.Numeric], - [/*start*/ 0x1B5A, WordBreakProperty.Other], - [/*start*/ 0x1B6B, WordBreakProperty.Extend], - [/*start*/ 0x1B74, WordBreakProperty.Other], - [/*start*/ 0x1B80, WordBreakProperty.Extend], - [/*start*/ 0x1B83, WordBreakProperty.ALetter], - [/*start*/ 0x1BA1, WordBreakProperty.Extend], - [/*start*/ 0x1BAE, WordBreakProperty.ALetter], - [/*start*/ 0x1BB0, WordBreakProperty.Numeric], - [/*start*/ 0x1BBA, WordBreakProperty.ALetter], - [/*start*/ 0x1BE6, WordBreakProperty.Extend], - [/*start*/ 0x1BF4, WordBreakProperty.Other], - [/*start*/ 0x1C00, WordBreakProperty.ALetter], - [/*start*/ 0x1C24, WordBreakProperty.Extend], - [/*start*/ 0x1C38, WordBreakProperty.Other], - [/*start*/ 0x1C40, WordBreakProperty.Numeric], - [/*start*/ 0x1C4A, WordBreakProperty.Other], - [/*start*/ 0x1C4D, WordBreakProperty.ALetter], - [/*start*/ 0x1C50, WordBreakProperty.Numeric], - [/*start*/ 0x1C5A, WordBreakProperty.ALetter], - [/*start*/ 0x1C7E, WordBreakProperty.Other], - [/*start*/ 0x1C80, WordBreakProperty.ALetter], - [/*start*/ 0x1C89, WordBreakProperty.Other], - [/*start*/ 0x1C90, WordBreakProperty.ALetter], - [/*start*/ 0x1CBB, WordBreakProperty.Other], - [/*start*/ 0x1CBD, WordBreakProperty.ALetter], - [/*start*/ 0x1CC0, WordBreakProperty.Other], - [/*start*/ 0x1CD0, WordBreakProperty.Extend], - [/*start*/ 0x1CD3, WordBreakProperty.Other], - [/*start*/ 0x1CD4, WordBreakProperty.Extend], - [/*start*/ 0x1CE9, WordBreakProperty.ALetter], - [/*start*/ 0x1CED, WordBreakProperty.Extend], - [/*start*/ 0x1CEE, WordBreakProperty.ALetter], - [/*start*/ 0x1CF4, WordBreakProperty.Extend], - [/*start*/ 0x1CF5, WordBreakProperty.ALetter], - [/*start*/ 0x1CF7, WordBreakProperty.Extend], - [/*start*/ 0x1CFA, WordBreakProperty.ALetter], - [/*start*/ 0x1CFB, WordBreakProperty.Other], - [/*start*/ 0x1D00, WordBreakProperty.ALetter], - [/*start*/ 0x1DC0, WordBreakProperty.Extend], - [/*start*/ 0x1DFA, WordBreakProperty.Other], - [/*start*/ 0x1DFB, WordBreakProperty.Extend], - [/*start*/ 0x1E00, WordBreakProperty.ALetter], - [/*start*/ 0x1F16, WordBreakProperty.Other], - [/*start*/ 0x1F18, WordBreakProperty.ALetter], - [/*start*/ 0x1F1E, WordBreakProperty.Other], - [/*start*/ 0x1F20, WordBreakProperty.ALetter], - [/*start*/ 0x1F46, WordBreakProperty.Other], - [/*start*/ 0x1F48, WordBreakProperty.ALetter], - [/*start*/ 0x1F4E, WordBreakProperty.Other], - [/*start*/ 0x1F50, WordBreakProperty.ALetter], - [/*start*/ 0x1F58, WordBreakProperty.Other], - [/*start*/ 0x1F59, WordBreakProperty.ALetter], - [/*start*/ 0x1F5A, WordBreakProperty.Other], - [/*start*/ 0x1F5B, WordBreakProperty.ALetter], - [/*start*/ 0x1F5C, WordBreakProperty.Other], - [/*start*/ 0x1F5D, WordBreakProperty.ALetter], - [/*start*/ 0x1F5E, WordBreakProperty.Other], - [/*start*/ 0x1F5F, WordBreakProperty.ALetter], - [/*start*/ 0x1F7E, WordBreakProperty.Other], - [/*start*/ 0x1F80, WordBreakProperty.ALetter], - [/*start*/ 0x1FB5, WordBreakProperty.Other], - [/*start*/ 0x1FB6, WordBreakProperty.ALetter], - [/*start*/ 0x1FBD, WordBreakProperty.Other], - [/*start*/ 0x1FBE, WordBreakProperty.ALetter], - [/*start*/ 0x1FBF, WordBreakProperty.Other], - [/*start*/ 0x1FC2, WordBreakProperty.ALetter], - [/*start*/ 0x1FC5, WordBreakProperty.Other], - [/*start*/ 0x1FC6, WordBreakProperty.ALetter], - [/*start*/ 0x1FCD, WordBreakProperty.Other], - [/*start*/ 0x1FD0, WordBreakProperty.ALetter], - [/*start*/ 0x1FD4, WordBreakProperty.Other], - [/*start*/ 0x1FD6, WordBreakProperty.ALetter], - [/*start*/ 0x1FDC, WordBreakProperty.Other], - [/*start*/ 0x1FE0, WordBreakProperty.ALetter], - [/*start*/ 0x1FED, WordBreakProperty.Other], - [/*start*/ 0x1FF2, WordBreakProperty.ALetter], - [/*start*/ 0x1FF5, WordBreakProperty.Other], - [/*start*/ 0x1FF6, WordBreakProperty.ALetter], - [/*start*/ 0x1FFD, WordBreakProperty.Other], - [/*start*/ 0x2000, WordBreakProperty.WSegSpace], - [/*start*/ 0x2007, WordBreakProperty.Other], - [/*start*/ 0x2008, WordBreakProperty.WSegSpace], - [/*start*/ 0x200B, WordBreakProperty.Other], - [/*start*/ 0x200C, WordBreakProperty.Extend], - [/*start*/ 0x200D, WordBreakProperty.ZWJ], - [/*start*/ 0x200E, WordBreakProperty.Format], - [/*start*/ 0x2010, WordBreakProperty.Other], - [/*start*/ 0x2018, WordBreakProperty.MidNumLet], - [/*start*/ 0x201A, WordBreakProperty.Other], - [/*start*/ 0x2024, WordBreakProperty.MidNumLet], - [/*start*/ 0x2025, WordBreakProperty.Other], - [/*start*/ 0x2027, WordBreakProperty.MidLetter], - [/*start*/ 0x2028, WordBreakProperty.Newline], - [/*start*/ 0x202A, WordBreakProperty.Format], - [/*start*/ 0x202F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2030, WordBreakProperty.Other], - [/*start*/ 0x203F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2041, WordBreakProperty.Other], - [/*start*/ 0x2044, WordBreakProperty.MidNum], - [/*start*/ 0x2045, WordBreakProperty.Other], - [/*start*/ 0x2054, WordBreakProperty.ExtendNumLet], - [/*start*/ 0x2055, WordBreakProperty.Other], - [/*start*/ 0x205F, WordBreakProperty.WSegSpace], - [/*start*/ 0x2060, WordBreakProperty.Format], - [/*start*/ 0x2065, WordBreakProperty.Other], - [/*start*/ 0x2066, WordBreakProperty.Format], - [/*start*/ 0x2070, WordBreakProperty.Other], - [/*start*/ 0x2071, WordBreakProperty.ALetter], - [/*start*/ 0x2072, WordBreakProperty.Other], - [/*start*/ 0x207F, WordBreakProperty.ALetter], - [/*start*/ 0x2080, WordBreakProperty.Other], - [/*start*/ 0x2090, WordBreakProperty.ALetter], - [/*start*/ 0x209D, WordBreakProperty.Other], - [/*start*/ 0x20D0, WordBreakProperty.Extend], - [/*start*/ 0x20F1, WordBreakProperty.Other], - [/*start*/ 0x2102, WordBreakProperty.ALetter], - [/*start*/ 0x2103, WordBreakProperty.Other], - [/*start*/ 0x2107, WordBreakProperty.ALetter], - [/*start*/ 0x2108, WordBreakProperty.Other], - [/*start*/ 0x210A, WordBreakProperty.ALetter], - [/*start*/ 0x2114, WordBreakProperty.Other], - [/*start*/ 0x2115, WordBreakProperty.ALetter], - [/*start*/ 0x2116, WordBreakProperty.Other], - [/*start*/ 0x2119, WordBreakProperty.ALetter], - [/*start*/ 0x211E, WordBreakProperty.Other], - [/*start*/ 0x2124, WordBreakProperty.ALetter], - [/*start*/ 0x2125, WordBreakProperty.Other], - [/*start*/ 0x2126, WordBreakProperty.ALetter], - [/*start*/ 0x2127, WordBreakProperty.Other], - [/*start*/ 0x2128, WordBreakProperty.ALetter], - [/*start*/ 0x2129, WordBreakProperty.Other], - [/*start*/ 0x212A, WordBreakProperty.ALetter], - [/*start*/ 0x212E, WordBreakProperty.Other], - [/*start*/ 0x212F, WordBreakProperty.ALetter], - [/*start*/ 0x213A, WordBreakProperty.Other], - [/*start*/ 0x213C, WordBreakProperty.ALetter], - [/*start*/ 0x2140, WordBreakProperty.Other], - [/*start*/ 0x2145, WordBreakProperty.ALetter], - [/*start*/ 0x214A, WordBreakProperty.Other], - [/*start*/ 0x214E, WordBreakProperty.ALetter], - [/*start*/ 0x214F, WordBreakProperty.Other], - [/*start*/ 0x2160, WordBreakProperty.ALetter], - [/*start*/ 0x2189, WordBreakProperty.Other], - [/*start*/ 0x24B6, WordBreakProperty.ALetter], - [/*start*/ 0x24EA, WordBreakProperty.Other], - [/*start*/ 0x2C00, WordBreakProperty.ALetter], - [/*start*/ 0x2C2F, WordBreakProperty.Other], - [/*start*/ 0x2C30, WordBreakProperty.ALetter], - [/*start*/ 0x2C5F, WordBreakProperty.Other], - [/*start*/ 0x2C60, WordBreakProperty.ALetter], - [/*start*/ 0x2CE5, WordBreakProperty.Other], - [/*start*/ 0x2CEB, WordBreakProperty.ALetter], - [/*start*/ 0x2CEF, WordBreakProperty.Extend], - [/*start*/ 0x2CF2, WordBreakProperty.ALetter], - [/*start*/ 0x2CF4, WordBreakProperty.Other], - [/*start*/ 0x2D00, WordBreakProperty.ALetter], - [/*start*/ 0x2D26, WordBreakProperty.Other], - [/*start*/ 0x2D27, WordBreakProperty.ALetter], - [/*start*/ 0x2D28, WordBreakProperty.Other], - [/*start*/ 0x2D2D, WordBreakProperty.ALetter], - [/*start*/ 0x2D2E, WordBreakProperty.Other], - [/*start*/ 0x2D30, WordBreakProperty.ALetter], - [/*start*/ 0x2D68, WordBreakProperty.Other], - [/*start*/ 0x2D6F, WordBreakProperty.ALetter], - [/*start*/ 0x2D70, WordBreakProperty.Other], - [/*start*/ 0x2D7F, WordBreakProperty.Extend], - [/*start*/ 0x2D80, WordBreakProperty.ALetter], - [/*start*/ 0x2D97, WordBreakProperty.Other], - [/*start*/ 0x2DA0, WordBreakProperty.ALetter], - [/*start*/ 0x2DA7, WordBreakProperty.Other], - [/*start*/ 0x2DA8, WordBreakProperty.ALetter], - [/*start*/ 0x2DAF, WordBreakProperty.Other], - [/*start*/ 0x2DB0, WordBreakProperty.ALetter], - [/*start*/ 0x2DB7, WordBreakProperty.Other], - [/*start*/ 0x2DB8, WordBreakProperty.ALetter], - [/*start*/ 0x2DBF, WordBreakProperty.Other], - [/*start*/ 0x2DC0, WordBreakProperty.ALetter], - [/*start*/ 0x2DC7, WordBreakProperty.Other], - [/*start*/ 0x2DC8, WordBreakProperty.ALetter], - [/*start*/ 0x2DCF, WordBreakProperty.Other], - [/*start*/ 0x2DD0, WordBreakProperty.ALetter], - [/*start*/ 0x2DD7, WordBreakProperty.Other], - [/*start*/ 0x2DD8, WordBreakProperty.ALetter], - [/*start*/ 0x2DDF, WordBreakProperty.Other], - [/*start*/ 0x2DE0, WordBreakProperty.Extend], - [/*start*/ 0x2E00, WordBreakProperty.Other], - [/*start*/ 0x2E2F, WordBreakProperty.ALetter], - [/*start*/ 0x2E30, WordBreakProperty.Other], - [/*start*/ 0x3000, WordBreakProperty.WSegSpace], - [/*start*/ 0x3001, WordBreakProperty.Other], - [/*start*/ 0x3005, WordBreakProperty.ALetter], - [/*start*/ 0x3006, WordBreakProperty.Other], - [/*start*/ 0x302A, WordBreakProperty.Extend], - [/*start*/ 0x3030, WordBreakProperty.Other], - [/*start*/ 0x3031, WordBreakProperty.Katakana], - [/*start*/ 0x3036, WordBreakProperty.Other], - [/*start*/ 0x303B, WordBreakProperty.ALetter], - [/*start*/ 0x303D, WordBreakProperty.Other], - [/*start*/ 0x3099, WordBreakProperty.Extend], - [/*start*/ 0x309B, WordBreakProperty.Katakana], - [/*start*/ 0x309D, WordBreakProperty.Other], - [/*start*/ 0x30A0, WordBreakProperty.Katakana], - [/*start*/ 0x30FB, WordBreakProperty.Other], - [/*start*/ 0x30FC, WordBreakProperty.Katakana], - [/*start*/ 0x3100, WordBreakProperty.Other], - [/*start*/ 0x3105, WordBreakProperty.ALetter], - [/*start*/ 0x3130, WordBreakProperty.Other], - [/*start*/ 0x3131, WordBreakProperty.ALetter], - [/*start*/ 0x318F, WordBreakProperty.Other], - [/*start*/ 0x31A0, WordBreakProperty.ALetter], - [/*start*/ 0x31C0, WordBreakProperty.Other], - [/*start*/ 0x31F0, WordBreakProperty.Katakana], - [/*start*/ 0x3200, WordBreakProperty.Other], - [/*start*/ 0x32D0, WordBreakProperty.Katakana], - [/*start*/ 0x32FF, WordBreakProperty.Other], - [/*start*/ 0x3300, WordBreakProperty.Katakana], - [/*start*/ 0x3358, WordBreakProperty.Other], - [/*start*/ 0xA000, WordBreakProperty.ALetter], - [/*start*/ 0xA48D, WordBreakProperty.Other], - [/*start*/ 0xA4D0, WordBreakProperty.ALetter], - [/*start*/ 0xA4FE, WordBreakProperty.Other], - [/*start*/ 0xA500, WordBreakProperty.ALetter], - [/*start*/ 0xA60D, WordBreakProperty.Other], - [/*start*/ 0xA610, WordBreakProperty.ALetter], - [/*start*/ 0xA620, WordBreakProperty.Numeric], - [/*start*/ 0xA62A, WordBreakProperty.ALetter], - [/*start*/ 0xA62C, WordBreakProperty.Other], - [/*start*/ 0xA640, WordBreakProperty.ALetter], - [/*start*/ 0xA66F, WordBreakProperty.Extend], - [/*start*/ 0xA673, WordBreakProperty.Other], - [/*start*/ 0xA674, WordBreakProperty.Extend], - [/*start*/ 0xA67E, WordBreakProperty.Other], - [/*start*/ 0xA67F, WordBreakProperty.ALetter], - [/*start*/ 0xA69E, WordBreakProperty.Extend], - [/*start*/ 0xA6A0, WordBreakProperty.ALetter], - [/*start*/ 0xA6F0, WordBreakProperty.Extend], - [/*start*/ 0xA6F2, WordBreakProperty.Other], - [/*start*/ 0xA708, WordBreakProperty.ALetter], - [/*start*/ 0xA7C0, WordBreakProperty.Other], - [/*start*/ 0xA7C2, WordBreakProperty.ALetter], - [/*start*/ 0xA7CB, WordBreakProperty.Other], - [/*start*/ 0xA7F5, WordBreakProperty.ALetter], - [/*start*/ 0xA802, WordBreakProperty.Extend], - [/*start*/ 0xA803, WordBreakProperty.ALetter], - [/*start*/ 0xA806, WordBreakProperty.Extend], - [/*start*/ 0xA807, WordBreakProperty.ALetter], - [/*start*/ 0xA80B, WordBreakProperty.Extend], - [/*start*/ 0xA80C, WordBreakProperty.ALetter], - [/*start*/ 0xA823, WordBreakProperty.Extend], - [/*start*/ 0xA828, WordBreakProperty.Other], - [/*start*/ 0xA82C, WordBreakProperty.Extend], - [/*start*/ 0xA82D, WordBreakProperty.Other], - [/*start*/ 0xA840, WordBreakProperty.ALetter], - [/*start*/ 0xA874, WordBreakProperty.Other], - [/*start*/ 0xA880, WordBreakProperty.Extend], - [/*start*/ 0xA882, WordBreakProperty.ALetter], - [/*start*/ 0xA8B4, WordBreakProperty.Extend], - [/*start*/ 0xA8C6, WordBreakProperty.Other], - [/*start*/ 0xA8D0, WordBreakProperty.Numeric], - [/*start*/ 0xA8DA, WordBreakProperty.Other], - [/*start*/ 0xA8E0, WordBreakProperty.Extend], - [/*start*/ 0xA8F2, WordBreakProperty.ALetter], - [/*start*/ 0xA8F8, WordBreakProperty.Other], - [/*start*/ 0xA8FB, WordBreakProperty.ALetter], - [/*start*/ 0xA8FC, WordBreakProperty.Other], - [/*start*/ 0xA8FD, WordBreakProperty.ALetter], - [/*start*/ 0xA8FF, WordBreakProperty.Extend], - [/*start*/ 0xA900, WordBreakProperty.Numeric], - [/*start*/ 0xA90A, WordBreakProperty.ALetter], - [/*start*/ 0xA926, WordBreakProperty.Extend], - [/*start*/ 0xA92E, WordBreakProperty.Other], - [/*start*/ 0xA930, WordBreakProperty.ALetter], - [/*start*/ 0xA947, WordBreakProperty.Extend], - [/*start*/ 0xA954, WordBreakProperty.Other], - [/*start*/ 0xA960, WordBreakProperty.ALetter], - [/*start*/ 0xA97D, WordBreakProperty.Other], - [/*start*/ 0xA980, WordBreakProperty.Extend], - [/*start*/ 0xA984, WordBreakProperty.ALetter], - [/*start*/ 0xA9B3, WordBreakProperty.Extend], - [/*start*/ 0xA9C1, WordBreakProperty.Other], - [/*start*/ 0xA9CF, WordBreakProperty.ALetter], - [/*start*/ 0xA9D0, WordBreakProperty.Numeric], - [/*start*/ 0xA9DA, WordBreakProperty.Other], - [/*start*/ 0xA9E5, WordBreakProperty.Extend], - [/*start*/ 0xA9E6, WordBreakProperty.Other], - [/*start*/ 0xA9F0, WordBreakProperty.Numeric], - [/*start*/ 0xA9FA, WordBreakProperty.Other], - [/*start*/ 0xAA00, WordBreakProperty.ALetter], - [/*start*/ 0xAA29, WordBreakProperty.Extend], - [/*start*/ 0xAA37, WordBreakProperty.Other], - [/*start*/ 0xAA40, WordBreakProperty.ALetter], - [/*start*/ 0xAA43, WordBreakProperty.Extend], - [/*start*/ 0xAA44, WordBreakProperty.ALetter], - [/*start*/ 0xAA4C, WordBreakProperty.Extend], - [/*start*/ 0xAA4E, WordBreakProperty.Other], - [/*start*/ 0xAA50, WordBreakProperty.Numeric], - [/*start*/ 0xAA5A, WordBreakProperty.Other], - [/*start*/ 0xAA7B, WordBreakProperty.Extend], - [/*start*/ 0xAA7E, WordBreakProperty.Other], - [/*start*/ 0xAAB0, WordBreakProperty.Extend], - [/*start*/ 0xAAB1, WordBreakProperty.Other], - [/*start*/ 0xAAB2, WordBreakProperty.Extend], - [/*start*/ 0xAAB5, WordBreakProperty.Other], - [/*start*/ 0xAAB7, WordBreakProperty.Extend], - [/*start*/ 0xAAB9, WordBreakProperty.Other], - [/*start*/ 0xAABE, WordBreakProperty.Extend], - [/*start*/ 0xAAC0, WordBreakProperty.Other], - [/*start*/ 0xAAC1, WordBreakProperty.Extend], - [/*start*/ 0xAAC2, WordBreakProperty.Other], - [/*start*/ 0xAAE0, WordBreakProperty.ALetter], - [/*start*/ 0xAAEB, WordBreakProperty.Extend], - [/*start*/ 0xAAF0, WordBreakProperty.Other], - [/*start*/ 0xAAF2, WordBreakProperty.ALetter], - [/*start*/ 0xAAF5, WordBreakProperty.Extend], - [/*start*/ 0xAAF7, WordBreakProperty.Other], - [/*start*/ 0xAB01, WordBreakProperty.ALetter], - [/*start*/ 0xAB07, WordBreakProperty.Other], - [/*start*/ 0xAB09, WordBreakProperty.ALetter], - [/*start*/ 0xAB0F, WordBreakProperty.Other], - [/*start*/ 0xAB11, WordBreakProperty.ALetter], - [/*start*/ 0xAB17, WordBreakProperty.Other], - [/*start*/ 0xAB20, WordBreakProperty.ALetter], - [/*start*/ 0xAB27, WordBreakProperty.Other], - [/*start*/ 0xAB28, WordBreakProperty.ALetter], - [/*start*/ 0xAB2F, WordBreakProperty.Other], - [/*start*/ 0xAB30, WordBreakProperty.ALetter], - [/*start*/ 0xAB6A, WordBreakProperty.Other], - [/*start*/ 0xAB70, WordBreakProperty.ALetter], - [/*start*/ 0xABE3, WordBreakProperty.Extend], - [/*start*/ 0xABEB, WordBreakProperty.Other], - [/*start*/ 0xABEC, WordBreakProperty.Extend], - [/*start*/ 0xABEE, WordBreakProperty.Other], - [/*start*/ 0xABF0, WordBreakProperty.Numeric], - [/*start*/ 0xABFA, WordBreakProperty.Other], - [/*start*/ 0xAC00, WordBreakProperty.ALetter], - [/*start*/ 0xD7A4, WordBreakProperty.Other], - [/*start*/ 0xD7B0, WordBreakProperty.ALetter], - [/*start*/ 0xD7C7, WordBreakProperty.Other], - [/*start*/ 0xD7CB, WordBreakProperty.ALetter], - [/*start*/ 0xD7FC, WordBreakProperty.Other], - [/*start*/ 0xFB00, WordBreakProperty.ALetter], - [/*start*/ 0xFB07, WordBreakProperty.Other], - [/*start*/ 0xFB13, WordBreakProperty.ALetter], - [/*start*/ 0xFB18, WordBreakProperty.Other], - [/*start*/ 0xFB1D, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB1E, WordBreakProperty.Extend], - [/*start*/ 0xFB1F, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB29, WordBreakProperty.Other], - [/*start*/ 0xFB2A, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB37, WordBreakProperty.Other], - [/*start*/ 0xFB38, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB3D, WordBreakProperty.Other], - [/*start*/ 0xFB3E, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB3F, WordBreakProperty.Other], - [/*start*/ 0xFB40, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB42, WordBreakProperty.Other], - [/*start*/ 0xFB43, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB45, WordBreakProperty.Other], - [/*start*/ 0xFB46, WordBreakProperty.Hebrew_Letter], - [/*start*/ 0xFB50, WordBreakProperty.ALetter], - [/*start*/ 0xFBB2, WordBreakProperty.Other], - [/*start*/ 0xFBD3, WordBreakProperty.ALetter], - [/*start*/ 0xFD3E, WordBreakProperty.Other], - [/*start*/ 0xFD50, WordBreakProperty.ALetter], - [/*start*/ 0xFD90, WordBreakProperty.Other], - [/*start*/ 0xFD92, WordBreakProperty.ALetter], - [/*start*/ 0xFDC8, WordBreakProperty.Other], - [/*start*/ 0xFDF0, WordBreakProperty.ALetter], - [/*start*/ 0xFDFC, WordBreakProperty.Other], - [/*start*/ 0xFE00, WordBreakProperty.Extend], - [/*start*/ 0xFE10, WordBreakProperty.MidNum], - [/*start*/ 0xFE11, WordBreakProperty.Other], - [/*start*/ 0xFE13, WordBreakProperty.MidLetter], - [/*start*/ 0xFE14, WordBreakProperty.MidNum], - [/*start*/ 0xFE15, WordBreakProperty.Other], - [/*start*/ 0xFE20, WordBreakProperty.Extend], - [/*start*/ 0xFE30, WordBreakProperty.Other], - [/*start*/ 0xFE33, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFE35, WordBreakProperty.Other], - [/*start*/ 0xFE4D, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFE50, WordBreakProperty.MidNum], - [/*start*/ 0xFE51, WordBreakProperty.Other], - [/*start*/ 0xFE52, WordBreakProperty.MidNumLet], - [/*start*/ 0xFE53, WordBreakProperty.Other], - [/*start*/ 0xFE54, WordBreakProperty.MidNum], - [/*start*/ 0xFE55, WordBreakProperty.MidLetter], - [/*start*/ 0xFE56, WordBreakProperty.Other], - [/*start*/ 0xFE70, WordBreakProperty.ALetter], - [/*start*/ 0xFE75, WordBreakProperty.Other], - [/*start*/ 0xFE76, WordBreakProperty.ALetter], - [/*start*/ 0xFEFD, WordBreakProperty.Other], - [/*start*/ 0xFEFF, WordBreakProperty.Format], - [/*start*/ 0xFF00, WordBreakProperty.Other], - [/*start*/ 0xFF07, WordBreakProperty.MidNumLet], - [/*start*/ 0xFF08, WordBreakProperty.Other], - [/*start*/ 0xFF0C, WordBreakProperty.MidNum], - [/*start*/ 0xFF0D, WordBreakProperty.Other], - [/*start*/ 0xFF0E, WordBreakProperty.MidNumLet], - [/*start*/ 0xFF0F, WordBreakProperty.Other], - [/*start*/ 0xFF10, WordBreakProperty.Numeric], - [/*start*/ 0xFF1A, WordBreakProperty.MidLetter], - [/*start*/ 0xFF1B, WordBreakProperty.MidNum], - [/*start*/ 0xFF1C, WordBreakProperty.Other], - [/*start*/ 0xFF21, WordBreakProperty.ALetter], - [/*start*/ 0xFF3B, WordBreakProperty.Other], - [/*start*/ 0xFF3F, WordBreakProperty.ExtendNumLet], - [/*start*/ 0xFF40, WordBreakProperty.Other], - [/*start*/ 0xFF41, WordBreakProperty.ALetter], - [/*start*/ 0xFF5B, WordBreakProperty.Other], - [/*start*/ 0xFF66, WordBreakProperty.Katakana], - [/*start*/ 0xFF9E, WordBreakProperty.Extend], - [/*start*/ 0xFFA0, WordBreakProperty.ALetter], - [/*start*/ 0xFFBF, WordBreakProperty.Other], - [/*start*/ 0xFFC2, WordBreakProperty.ALetter], - [/*start*/ 0xFFC8, WordBreakProperty.Other], - [/*start*/ 0xFFCA, WordBreakProperty.ALetter], - [/*start*/ 0xFFD0, WordBreakProperty.Other], - [/*start*/ 0xFFD2, WordBreakProperty.ALetter], - [/*start*/ 0xFFD8, WordBreakProperty.Other], - [/*start*/ 0xFFDA, WordBreakProperty.ALetter], - [/*start*/ 0xFFDD, WordBreakProperty.Other], - [/*start*/ 0xFFF9, WordBreakProperty.Format], - [/*start*/ 0xFFFC, WordBreakProperty.Other], - [/*start*/ 0x10000, WordBreakProperty.ALetter], - [/*start*/ 0x1000C, WordBreakProperty.Other], - [/*start*/ 0x1000D, WordBreakProperty.ALetter], - [/*start*/ 0x10027, WordBreakProperty.Other], - [/*start*/ 0x10028, WordBreakProperty.ALetter], - [/*start*/ 0x1003B, WordBreakProperty.Other], - [/*start*/ 0x1003C, WordBreakProperty.ALetter], - [/*start*/ 0x1003E, WordBreakProperty.Other], - [/*start*/ 0x1003F, WordBreakProperty.ALetter], - [/*start*/ 0x1004E, WordBreakProperty.Other], - [/*start*/ 0x10050, WordBreakProperty.ALetter], - [/*start*/ 0x1005E, WordBreakProperty.Other], - [/*start*/ 0x10080, WordBreakProperty.ALetter], - [/*start*/ 0x100FB, WordBreakProperty.Other], - [/*start*/ 0x10140, WordBreakProperty.ALetter], - [/*start*/ 0x10175, WordBreakProperty.Other], - [/*start*/ 0x101FD, WordBreakProperty.Extend], - [/*start*/ 0x101FE, WordBreakProperty.Other], - [/*start*/ 0x10280, WordBreakProperty.ALetter], - [/*start*/ 0x1029D, WordBreakProperty.Other], - [/*start*/ 0x102A0, WordBreakProperty.ALetter], - [/*start*/ 0x102D1, WordBreakProperty.Other], - [/*start*/ 0x102E0, WordBreakProperty.Extend], - [/*start*/ 0x102E1, WordBreakProperty.Other], - [/*start*/ 0x10300, WordBreakProperty.ALetter], - [/*start*/ 0x10320, WordBreakProperty.Other], - [/*start*/ 0x1032D, WordBreakProperty.ALetter], - [/*start*/ 0x1034B, WordBreakProperty.Other], - [/*start*/ 0x10350, WordBreakProperty.ALetter], - [/*start*/ 0x10376, WordBreakProperty.Extend], - [/*start*/ 0x1037B, WordBreakProperty.Other], - [/*start*/ 0x10380, WordBreakProperty.ALetter], - [/*start*/ 0x1039E, WordBreakProperty.Other], - [/*start*/ 0x103A0, WordBreakProperty.ALetter], - [/*start*/ 0x103C4, WordBreakProperty.Other], - [/*start*/ 0x103C8, WordBreakProperty.ALetter], - [/*start*/ 0x103D0, WordBreakProperty.Other], - [/*start*/ 0x103D1, WordBreakProperty.ALetter], - [/*start*/ 0x103D6, WordBreakProperty.Other], - [/*start*/ 0x10400, WordBreakProperty.ALetter], - [/*start*/ 0x1049E, WordBreakProperty.Other], - [/*start*/ 0x104A0, WordBreakProperty.Numeric], - [/*start*/ 0x104AA, WordBreakProperty.Other], - [/*start*/ 0x104B0, WordBreakProperty.ALetter], - [/*start*/ 0x104D4, WordBreakProperty.Other], - [/*start*/ 0x104D8, WordBreakProperty.ALetter], - [/*start*/ 0x104FC, WordBreakProperty.Other], - [/*start*/ 0x10500, WordBreakProperty.ALetter], - [/*start*/ 0x10528, WordBreakProperty.Other], - [/*start*/ 0x10530, WordBreakProperty.ALetter], - [/*start*/ 0x10564, WordBreakProperty.Other], - [/*start*/ 0x10600, WordBreakProperty.ALetter], - [/*start*/ 0x10737, WordBreakProperty.Other], - [/*start*/ 0x10740, WordBreakProperty.ALetter], - [/*start*/ 0x10756, WordBreakProperty.Other], - [/*start*/ 0x10760, WordBreakProperty.ALetter], - [/*start*/ 0x10768, WordBreakProperty.Other], - [/*start*/ 0x10800, WordBreakProperty.ALetter], - [/*start*/ 0x10806, WordBreakProperty.Other], - [/*start*/ 0x10808, WordBreakProperty.ALetter], - [/*start*/ 0x10809, WordBreakProperty.Other], - [/*start*/ 0x1080A, WordBreakProperty.ALetter], - [/*start*/ 0x10836, WordBreakProperty.Other], - [/*start*/ 0x10837, WordBreakProperty.ALetter], - [/*start*/ 0x10839, WordBreakProperty.Other], - [/*start*/ 0x1083C, WordBreakProperty.ALetter], - [/*start*/ 0x1083D, WordBreakProperty.Other], - [/*start*/ 0x1083F, WordBreakProperty.ALetter], - [/*start*/ 0x10856, WordBreakProperty.Other], - [/*start*/ 0x10860, WordBreakProperty.ALetter], - [/*start*/ 0x10877, WordBreakProperty.Other], - [/*start*/ 0x10880, WordBreakProperty.ALetter], - [/*start*/ 0x1089F, WordBreakProperty.Other], - [/*start*/ 0x108E0, WordBreakProperty.ALetter], - [/*start*/ 0x108F3, WordBreakProperty.Other], - [/*start*/ 0x108F4, WordBreakProperty.ALetter], - [/*start*/ 0x108F6, WordBreakProperty.Other], - [/*start*/ 0x10900, WordBreakProperty.ALetter], - [/*start*/ 0x10916, WordBreakProperty.Other], - [/*start*/ 0x10920, WordBreakProperty.ALetter], - [/*start*/ 0x1093A, WordBreakProperty.Other], - [/*start*/ 0x10980, WordBreakProperty.ALetter], - [/*start*/ 0x109B8, WordBreakProperty.Other], - [/*start*/ 0x109BE, WordBreakProperty.ALetter], - [/*start*/ 0x109C0, WordBreakProperty.Other], - [/*start*/ 0x10A00, WordBreakProperty.ALetter], - [/*start*/ 0x10A01, WordBreakProperty.Extend], - [/*start*/ 0x10A04, WordBreakProperty.Other], - [/*start*/ 0x10A05, WordBreakProperty.Extend], - [/*start*/ 0x10A07, WordBreakProperty.Other], - [/*start*/ 0x10A0C, WordBreakProperty.Extend], - [/*start*/ 0x10A10, WordBreakProperty.ALetter], - [/*start*/ 0x10A14, WordBreakProperty.Other], - [/*start*/ 0x10A15, WordBreakProperty.ALetter], - [/*start*/ 0x10A18, WordBreakProperty.Other], - [/*start*/ 0x10A19, WordBreakProperty.ALetter], - [/*start*/ 0x10A36, WordBreakProperty.Other], - [/*start*/ 0x10A38, WordBreakProperty.Extend], - [/*start*/ 0x10A3B, WordBreakProperty.Other], - [/*start*/ 0x10A3F, WordBreakProperty.Extend], - [/*start*/ 0x10A40, WordBreakProperty.Other], - [/*start*/ 0x10A60, WordBreakProperty.ALetter], - [/*start*/ 0x10A7D, WordBreakProperty.Other], - [/*start*/ 0x10A80, WordBreakProperty.ALetter], - [/*start*/ 0x10A9D, WordBreakProperty.Other], - [/*start*/ 0x10AC0, WordBreakProperty.ALetter], - [/*start*/ 0x10AC8, WordBreakProperty.Other], - [/*start*/ 0x10AC9, WordBreakProperty.ALetter], - [/*start*/ 0x10AE5, WordBreakProperty.Extend], - [/*start*/ 0x10AE7, WordBreakProperty.Other], - [/*start*/ 0x10B00, WordBreakProperty.ALetter], - [/*start*/ 0x10B36, WordBreakProperty.Other], - [/*start*/ 0x10B40, WordBreakProperty.ALetter], - [/*start*/ 0x10B56, WordBreakProperty.Other], - [/*start*/ 0x10B60, WordBreakProperty.ALetter], - [/*start*/ 0x10B73, WordBreakProperty.Other], - [/*start*/ 0x10B80, WordBreakProperty.ALetter], - [/*start*/ 0x10B92, WordBreakProperty.Other], - [/*start*/ 0x10C00, WordBreakProperty.ALetter], - [/*start*/ 0x10C49, WordBreakProperty.Other], - [/*start*/ 0x10C80, WordBreakProperty.ALetter], - [/*start*/ 0x10CB3, WordBreakProperty.Other], - [/*start*/ 0x10CC0, WordBreakProperty.ALetter], - [/*start*/ 0x10CF3, WordBreakProperty.Other], - [/*start*/ 0x10D00, WordBreakProperty.ALetter], - [/*start*/ 0x10D24, WordBreakProperty.Extend], - [/*start*/ 0x10D28, WordBreakProperty.Other], - [/*start*/ 0x10D30, WordBreakProperty.Numeric], - [/*start*/ 0x10D3A, WordBreakProperty.Other], - [/*start*/ 0x10E80, WordBreakProperty.ALetter], - [/*start*/ 0x10EAA, WordBreakProperty.Other], - [/*start*/ 0x10EAB, WordBreakProperty.Extend], - [/*start*/ 0x10EAD, WordBreakProperty.Other], - [/*start*/ 0x10EB0, WordBreakProperty.ALetter], - [/*start*/ 0x10EB2, WordBreakProperty.Other], - [/*start*/ 0x10F00, WordBreakProperty.ALetter], - [/*start*/ 0x10F1D, WordBreakProperty.Other], - [/*start*/ 0x10F27, WordBreakProperty.ALetter], - [/*start*/ 0x10F28, WordBreakProperty.Other], - [/*start*/ 0x10F30, WordBreakProperty.ALetter], - [/*start*/ 0x10F46, WordBreakProperty.Extend], - [/*start*/ 0x10F51, WordBreakProperty.Other], - [/*start*/ 0x10FB0, WordBreakProperty.ALetter], - [/*start*/ 0x10FC5, WordBreakProperty.Other], - [/*start*/ 0x10FE0, WordBreakProperty.ALetter], - [/*start*/ 0x10FF7, WordBreakProperty.Other], - [/*start*/ 0x11000, WordBreakProperty.Extend], - [/*start*/ 0x11003, WordBreakProperty.ALetter], - [/*start*/ 0x11038, WordBreakProperty.Extend], - [/*start*/ 0x11047, WordBreakProperty.Other], - [/*start*/ 0x11066, WordBreakProperty.Numeric], - [/*start*/ 0x11070, WordBreakProperty.Other], - [/*start*/ 0x1107F, WordBreakProperty.Extend], - [/*start*/ 0x11083, WordBreakProperty.ALetter], - [/*start*/ 0x110B0, WordBreakProperty.Extend], - [/*start*/ 0x110BB, WordBreakProperty.Other], - [/*start*/ 0x110BD, WordBreakProperty.Format], - [/*start*/ 0x110BE, WordBreakProperty.Other], - [/*start*/ 0x110CD, WordBreakProperty.Format], - [/*start*/ 0x110CE, WordBreakProperty.Other], - [/*start*/ 0x110D0, WordBreakProperty.ALetter], - [/*start*/ 0x110E9, WordBreakProperty.Other], - [/*start*/ 0x110F0, WordBreakProperty.Numeric], - [/*start*/ 0x110FA, WordBreakProperty.Other], - [/*start*/ 0x11100, WordBreakProperty.Extend], - [/*start*/ 0x11103, WordBreakProperty.ALetter], - [/*start*/ 0x11127, WordBreakProperty.Extend], - [/*start*/ 0x11135, WordBreakProperty.Other], - [/*start*/ 0x11136, WordBreakProperty.Numeric], - [/*start*/ 0x11140, WordBreakProperty.Other], - [/*start*/ 0x11144, WordBreakProperty.ALetter], - [/*start*/ 0x11145, WordBreakProperty.Extend], - [/*start*/ 0x11147, WordBreakProperty.ALetter], - [/*start*/ 0x11148, WordBreakProperty.Other], - [/*start*/ 0x11150, WordBreakProperty.ALetter], - [/*start*/ 0x11173, WordBreakProperty.Extend], - [/*start*/ 0x11174, WordBreakProperty.Other], - [/*start*/ 0x11176, WordBreakProperty.ALetter], - [/*start*/ 0x11177, WordBreakProperty.Other], - [/*start*/ 0x11180, WordBreakProperty.Extend], - [/*start*/ 0x11183, WordBreakProperty.ALetter], - [/*start*/ 0x111B3, WordBreakProperty.Extend], - [/*start*/ 0x111C1, WordBreakProperty.ALetter], - [/*start*/ 0x111C5, WordBreakProperty.Other], - [/*start*/ 0x111C9, WordBreakProperty.Extend], - [/*start*/ 0x111CD, WordBreakProperty.Other], - [/*start*/ 0x111CE, WordBreakProperty.Extend], - [/*start*/ 0x111D0, WordBreakProperty.Numeric], - [/*start*/ 0x111DA, WordBreakProperty.ALetter], - [/*start*/ 0x111DB, WordBreakProperty.Other], - [/*start*/ 0x111DC, WordBreakProperty.ALetter], - [/*start*/ 0x111DD, WordBreakProperty.Other], - [/*start*/ 0x11200, WordBreakProperty.ALetter], - [/*start*/ 0x11212, WordBreakProperty.Other], - [/*start*/ 0x11213, WordBreakProperty.ALetter], - [/*start*/ 0x1122C, WordBreakProperty.Extend], - [/*start*/ 0x11238, WordBreakProperty.Other], - [/*start*/ 0x1123E, WordBreakProperty.Extend], - [/*start*/ 0x1123F, WordBreakProperty.Other], - [/*start*/ 0x11280, WordBreakProperty.ALetter], - [/*start*/ 0x11287, WordBreakProperty.Other], - [/*start*/ 0x11288, WordBreakProperty.ALetter], - [/*start*/ 0x11289, WordBreakProperty.Other], - [/*start*/ 0x1128A, WordBreakProperty.ALetter], - [/*start*/ 0x1128E, WordBreakProperty.Other], - [/*start*/ 0x1128F, WordBreakProperty.ALetter], - [/*start*/ 0x1129E, WordBreakProperty.Other], - [/*start*/ 0x1129F, WordBreakProperty.ALetter], - [/*start*/ 0x112A9, WordBreakProperty.Other], - [/*start*/ 0x112B0, WordBreakProperty.ALetter], - [/*start*/ 0x112DF, WordBreakProperty.Extend], - [/*start*/ 0x112EB, WordBreakProperty.Other], - [/*start*/ 0x112F0, WordBreakProperty.Numeric], - [/*start*/ 0x112FA, WordBreakProperty.Other], - [/*start*/ 0x11300, WordBreakProperty.Extend], - [/*start*/ 0x11304, WordBreakProperty.Other], - [/*start*/ 0x11305, WordBreakProperty.ALetter], - [/*start*/ 0x1130D, WordBreakProperty.Other], - [/*start*/ 0x1130F, WordBreakProperty.ALetter], - [/*start*/ 0x11311, WordBreakProperty.Other], - [/*start*/ 0x11313, WordBreakProperty.ALetter], - [/*start*/ 0x11329, WordBreakProperty.Other], - [/*start*/ 0x1132A, WordBreakProperty.ALetter], - [/*start*/ 0x11331, WordBreakProperty.Other], - [/*start*/ 0x11332, WordBreakProperty.ALetter], - [/*start*/ 0x11334, WordBreakProperty.Other], - [/*start*/ 0x11335, WordBreakProperty.ALetter], - [/*start*/ 0x1133A, WordBreakProperty.Other], - [/*start*/ 0x1133B, WordBreakProperty.Extend], - [/*start*/ 0x1133D, WordBreakProperty.ALetter], - [/*start*/ 0x1133E, WordBreakProperty.Extend], - [/*start*/ 0x11345, WordBreakProperty.Other], - [/*start*/ 0x11347, WordBreakProperty.Extend], - [/*start*/ 0x11349, WordBreakProperty.Other], - [/*start*/ 0x1134B, WordBreakProperty.Extend], - [/*start*/ 0x1134E, WordBreakProperty.Other], - [/*start*/ 0x11350, WordBreakProperty.ALetter], - [/*start*/ 0x11351, WordBreakProperty.Other], - [/*start*/ 0x11357, WordBreakProperty.Extend], - [/*start*/ 0x11358, WordBreakProperty.Other], - [/*start*/ 0x1135D, WordBreakProperty.ALetter], - [/*start*/ 0x11362, WordBreakProperty.Extend], - [/*start*/ 0x11364, WordBreakProperty.Other], - [/*start*/ 0x11366, WordBreakProperty.Extend], - [/*start*/ 0x1136D, WordBreakProperty.Other], - [/*start*/ 0x11370, WordBreakProperty.Extend], - [/*start*/ 0x11375, WordBreakProperty.Other], - [/*start*/ 0x11400, WordBreakProperty.ALetter], - [/*start*/ 0x11435, WordBreakProperty.Extend], - [/*start*/ 0x11447, WordBreakProperty.ALetter], - [/*start*/ 0x1144B, WordBreakProperty.Other], - [/*start*/ 0x11450, WordBreakProperty.Numeric], - [/*start*/ 0x1145A, WordBreakProperty.Other], - [/*start*/ 0x1145E, WordBreakProperty.Extend], - [/*start*/ 0x1145F, WordBreakProperty.ALetter], - [/*start*/ 0x11462, WordBreakProperty.Other], - [/*start*/ 0x11480, WordBreakProperty.ALetter], - [/*start*/ 0x114B0, WordBreakProperty.Extend], - [/*start*/ 0x114C4, WordBreakProperty.ALetter], - [/*start*/ 0x114C6, WordBreakProperty.Other], - [/*start*/ 0x114C7, WordBreakProperty.ALetter], - [/*start*/ 0x114C8, WordBreakProperty.Other], - [/*start*/ 0x114D0, WordBreakProperty.Numeric], - [/*start*/ 0x114DA, WordBreakProperty.Other], - [/*start*/ 0x11580, WordBreakProperty.ALetter], - [/*start*/ 0x115AF, WordBreakProperty.Extend], - [/*start*/ 0x115B6, WordBreakProperty.Other], - [/*start*/ 0x115B8, WordBreakProperty.Extend], - [/*start*/ 0x115C1, WordBreakProperty.Other], - [/*start*/ 0x115D8, WordBreakProperty.ALetter], - [/*start*/ 0x115DC, WordBreakProperty.Extend], - [/*start*/ 0x115DE, WordBreakProperty.Other], - [/*start*/ 0x11600, WordBreakProperty.ALetter], - [/*start*/ 0x11630, WordBreakProperty.Extend], - [/*start*/ 0x11641, WordBreakProperty.Other], - [/*start*/ 0x11644, WordBreakProperty.ALetter], - [/*start*/ 0x11645, WordBreakProperty.Other], - [/*start*/ 0x11650, WordBreakProperty.Numeric], - [/*start*/ 0x1165A, WordBreakProperty.Other], - [/*start*/ 0x11680, WordBreakProperty.ALetter], - [/*start*/ 0x116AB, WordBreakProperty.Extend], - [/*start*/ 0x116B8, WordBreakProperty.ALetter], - [/*start*/ 0x116B9, WordBreakProperty.Other], - [/*start*/ 0x116C0, WordBreakProperty.Numeric], - [/*start*/ 0x116CA, WordBreakProperty.Other], - [/*start*/ 0x1171D, WordBreakProperty.Extend], - [/*start*/ 0x1172C, WordBreakProperty.Other], - [/*start*/ 0x11730, WordBreakProperty.Numeric], - [/*start*/ 0x1173A, WordBreakProperty.Other], - [/*start*/ 0x11800, WordBreakProperty.ALetter], - [/*start*/ 0x1182C, WordBreakProperty.Extend], - [/*start*/ 0x1183B, WordBreakProperty.Other], - [/*start*/ 0x118A0, WordBreakProperty.ALetter], - [/*start*/ 0x118E0, WordBreakProperty.Numeric], - [/*start*/ 0x118EA, WordBreakProperty.Other], - [/*start*/ 0x118FF, WordBreakProperty.ALetter], - [/*start*/ 0x11907, WordBreakProperty.Other], - [/*start*/ 0x11909, WordBreakProperty.ALetter], - [/*start*/ 0x1190A, WordBreakProperty.Other], - [/*start*/ 0x1190C, WordBreakProperty.ALetter], - [/*start*/ 0x11914, WordBreakProperty.Other], - [/*start*/ 0x11915, WordBreakProperty.ALetter], - [/*start*/ 0x11917, WordBreakProperty.Other], - [/*start*/ 0x11918, WordBreakProperty.ALetter], - [/*start*/ 0x11930, WordBreakProperty.Extend], - [/*start*/ 0x11936, WordBreakProperty.Other], - [/*start*/ 0x11937, WordBreakProperty.Extend], - [/*start*/ 0x11939, WordBreakProperty.Other], - [/*start*/ 0x1193B, WordBreakProperty.Extend], - [/*start*/ 0x1193F, WordBreakProperty.ALetter], - [/*start*/ 0x11940, WordBreakProperty.Extend], - [/*start*/ 0x11941, WordBreakProperty.ALetter], - [/*start*/ 0x11942, WordBreakProperty.Extend], - [/*start*/ 0x11944, WordBreakProperty.Other], - [/*start*/ 0x11950, WordBreakProperty.Numeric], - [/*start*/ 0x1195A, WordBreakProperty.Other], - [/*start*/ 0x119A0, WordBreakProperty.ALetter], - [/*start*/ 0x119A8, WordBreakProperty.Other], - [/*start*/ 0x119AA, WordBreakProperty.ALetter], - [/*start*/ 0x119D1, WordBreakProperty.Extend], - [/*start*/ 0x119D8, WordBreakProperty.Other], - [/*start*/ 0x119DA, WordBreakProperty.Extend], - [/*start*/ 0x119E1, WordBreakProperty.ALetter], - [/*start*/ 0x119E2, WordBreakProperty.Other], - [/*start*/ 0x119E3, WordBreakProperty.ALetter], - [/*start*/ 0x119E4, WordBreakProperty.Extend], - [/*start*/ 0x119E5, WordBreakProperty.Other], - [/*start*/ 0x11A00, WordBreakProperty.ALetter], - [/*start*/ 0x11A01, WordBreakProperty.Extend], - [/*start*/ 0x11A0B, WordBreakProperty.ALetter], - [/*start*/ 0x11A33, WordBreakProperty.Extend], - [/*start*/ 0x11A3A, WordBreakProperty.ALetter], - [/*start*/ 0x11A3B, WordBreakProperty.Extend], - [/*start*/ 0x11A3F, WordBreakProperty.Other], - [/*start*/ 0x11A47, WordBreakProperty.Extend], - [/*start*/ 0x11A48, WordBreakProperty.Other], - [/*start*/ 0x11A50, WordBreakProperty.ALetter], - [/*start*/ 0x11A51, WordBreakProperty.Extend], - [/*start*/ 0x11A5C, WordBreakProperty.ALetter], - [/*start*/ 0x11A8A, WordBreakProperty.Extend], - [/*start*/ 0x11A9A, WordBreakProperty.Other], - [/*start*/ 0x11A9D, WordBreakProperty.ALetter], - [/*start*/ 0x11A9E, WordBreakProperty.Other], - [/*start*/ 0x11AC0, WordBreakProperty.ALetter], - [/*start*/ 0x11AF9, WordBreakProperty.Other], - [/*start*/ 0x11C00, WordBreakProperty.ALetter], - [/*start*/ 0x11C09, WordBreakProperty.Other], - [/*start*/ 0x11C0A, WordBreakProperty.ALetter], - [/*start*/ 0x11C2F, WordBreakProperty.Extend], - [/*start*/ 0x11C37, WordBreakProperty.Other], - [/*start*/ 0x11C38, WordBreakProperty.Extend], - [/*start*/ 0x11C40, WordBreakProperty.ALetter], - [/*start*/ 0x11C41, WordBreakProperty.Other], - [/*start*/ 0x11C50, WordBreakProperty.Numeric], - [/*start*/ 0x11C5A, WordBreakProperty.Other], - [/*start*/ 0x11C72, WordBreakProperty.ALetter], - [/*start*/ 0x11C90, WordBreakProperty.Other], - [/*start*/ 0x11C92, WordBreakProperty.Extend], - [/*start*/ 0x11CA8, WordBreakProperty.Other], - [/*start*/ 0x11CA9, WordBreakProperty.Extend], - [/*start*/ 0x11CB7, WordBreakProperty.Other], - [/*start*/ 0x11D00, WordBreakProperty.ALetter], - [/*start*/ 0x11D07, WordBreakProperty.Other], - [/*start*/ 0x11D08, WordBreakProperty.ALetter], - [/*start*/ 0x11D0A, WordBreakProperty.Other], - [/*start*/ 0x11D0B, WordBreakProperty.ALetter], - [/*start*/ 0x11D31, WordBreakProperty.Extend], - [/*start*/ 0x11D37, WordBreakProperty.Other], - [/*start*/ 0x11D3A, WordBreakProperty.Extend], - [/*start*/ 0x11D3B, WordBreakProperty.Other], - [/*start*/ 0x11D3C, WordBreakProperty.Extend], - [/*start*/ 0x11D3E, WordBreakProperty.Other], - [/*start*/ 0x11D3F, WordBreakProperty.Extend], - [/*start*/ 0x11D46, WordBreakProperty.ALetter], - [/*start*/ 0x11D47, WordBreakProperty.Extend], - [/*start*/ 0x11D48, WordBreakProperty.Other], - [/*start*/ 0x11D50, WordBreakProperty.Numeric], - [/*start*/ 0x11D5A, WordBreakProperty.Other], - [/*start*/ 0x11D60, WordBreakProperty.ALetter], - [/*start*/ 0x11D66, WordBreakProperty.Other], - [/*start*/ 0x11D67, WordBreakProperty.ALetter], - [/*start*/ 0x11D69, WordBreakProperty.Other], - [/*start*/ 0x11D6A, WordBreakProperty.ALetter], - [/*start*/ 0x11D8A, WordBreakProperty.Extend], - [/*start*/ 0x11D8F, WordBreakProperty.Other], - [/*start*/ 0x11D90, WordBreakProperty.Extend], - [/*start*/ 0x11D92, WordBreakProperty.Other], - [/*start*/ 0x11D93, WordBreakProperty.Extend], - [/*start*/ 0x11D98, WordBreakProperty.ALetter], - [/*start*/ 0x11D99, WordBreakProperty.Other], - [/*start*/ 0x11DA0, WordBreakProperty.Numeric], - [/*start*/ 0x11DAA, WordBreakProperty.Other], - [/*start*/ 0x11EE0, WordBreakProperty.ALetter], - [/*start*/ 0x11EF3, WordBreakProperty.Extend], - [/*start*/ 0x11EF7, WordBreakProperty.Other], - [/*start*/ 0x11FB0, WordBreakProperty.ALetter], - [/*start*/ 0x11FB1, WordBreakProperty.Other], - [/*start*/ 0x12000, WordBreakProperty.ALetter], - [/*start*/ 0x1239A, WordBreakProperty.Other], - [/*start*/ 0x12400, WordBreakProperty.ALetter], - [/*start*/ 0x1246F, WordBreakProperty.Other], - [/*start*/ 0x12480, WordBreakProperty.ALetter], - [/*start*/ 0x12544, WordBreakProperty.Other], - [/*start*/ 0x13000, WordBreakProperty.ALetter], - [/*start*/ 0x1342F, WordBreakProperty.Other], - [/*start*/ 0x13430, WordBreakProperty.Format], - [/*start*/ 0x13439, WordBreakProperty.Other], - [/*start*/ 0x14400, WordBreakProperty.ALetter], - [/*start*/ 0x14647, WordBreakProperty.Other], - [/*start*/ 0x16800, WordBreakProperty.ALetter], - [/*start*/ 0x16A39, WordBreakProperty.Other], - [/*start*/ 0x16A40, WordBreakProperty.ALetter], - [/*start*/ 0x16A5F, WordBreakProperty.Other], - [/*start*/ 0x16A60, WordBreakProperty.Numeric], - [/*start*/ 0x16A6A, WordBreakProperty.Other], - [/*start*/ 0x16AD0, WordBreakProperty.ALetter], - [/*start*/ 0x16AEE, WordBreakProperty.Other], - [/*start*/ 0x16AF0, WordBreakProperty.Extend], - [/*start*/ 0x16AF5, WordBreakProperty.Other], - [/*start*/ 0x16B00, WordBreakProperty.ALetter], - [/*start*/ 0x16B30, WordBreakProperty.Extend], - [/*start*/ 0x16B37, WordBreakProperty.Other], - [/*start*/ 0x16B40, WordBreakProperty.ALetter], - [/*start*/ 0x16B44, WordBreakProperty.Other], - [/*start*/ 0x16B50, WordBreakProperty.Numeric], - [/*start*/ 0x16B5A, WordBreakProperty.Other], - [/*start*/ 0x16B63, WordBreakProperty.ALetter], - [/*start*/ 0x16B78, WordBreakProperty.Other], - [/*start*/ 0x16B7D, WordBreakProperty.ALetter], - [/*start*/ 0x16B90, WordBreakProperty.Other], - [/*start*/ 0x16E40, WordBreakProperty.ALetter], - [/*start*/ 0x16E80, WordBreakProperty.Other], - [/*start*/ 0x16F00, WordBreakProperty.ALetter], - [/*start*/ 0x16F4B, WordBreakProperty.Other], - [/*start*/ 0x16F4F, WordBreakProperty.Extend], - [/*start*/ 0x16F50, WordBreakProperty.ALetter], - [/*start*/ 0x16F51, WordBreakProperty.Extend], - [/*start*/ 0x16F88, WordBreakProperty.Other], - [/*start*/ 0x16F8F, WordBreakProperty.Extend], - [/*start*/ 0x16F93, WordBreakProperty.ALetter], - [/*start*/ 0x16FA0, WordBreakProperty.Other], - [/*start*/ 0x16FE0, WordBreakProperty.ALetter], - [/*start*/ 0x16FE2, WordBreakProperty.Other], - [/*start*/ 0x16FE3, WordBreakProperty.ALetter], - [/*start*/ 0x16FE4, WordBreakProperty.Extend], - [/*start*/ 0x16FE5, WordBreakProperty.Other], - [/*start*/ 0x16FF0, WordBreakProperty.Extend], - [/*start*/ 0x16FF2, WordBreakProperty.Other], - [/*start*/ 0x1B000, WordBreakProperty.Katakana], - [/*start*/ 0x1B001, WordBreakProperty.Other], - [/*start*/ 0x1B164, WordBreakProperty.Katakana], - [/*start*/ 0x1B168, WordBreakProperty.Other], - [/*start*/ 0x1BC00, WordBreakProperty.ALetter], - [/*start*/ 0x1BC6B, WordBreakProperty.Other], - [/*start*/ 0x1BC70, WordBreakProperty.ALetter], - [/*start*/ 0x1BC7D, WordBreakProperty.Other], - [/*start*/ 0x1BC80, WordBreakProperty.ALetter], - [/*start*/ 0x1BC89, WordBreakProperty.Other], - [/*start*/ 0x1BC90, WordBreakProperty.ALetter], - [/*start*/ 0x1BC9A, WordBreakProperty.Other], - [/*start*/ 0x1BC9D, WordBreakProperty.Extend], - [/*start*/ 0x1BC9F, WordBreakProperty.Other], - [/*start*/ 0x1BCA0, WordBreakProperty.Format], - [/*start*/ 0x1BCA4, WordBreakProperty.Other], - [/*start*/ 0x1D165, WordBreakProperty.Extend], - [/*start*/ 0x1D16A, WordBreakProperty.Other], - [/*start*/ 0x1D16D, WordBreakProperty.Extend], - [/*start*/ 0x1D173, WordBreakProperty.Format], - [/*start*/ 0x1D17B, WordBreakProperty.Extend], - [/*start*/ 0x1D183, WordBreakProperty.Other], - [/*start*/ 0x1D185, WordBreakProperty.Extend], - [/*start*/ 0x1D18C, WordBreakProperty.Other], - [/*start*/ 0x1D1AA, WordBreakProperty.Extend], - [/*start*/ 0x1D1AE, WordBreakProperty.Other], - [/*start*/ 0x1D242, WordBreakProperty.Extend], - [/*start*/ 0x1D245, WordBreakProperty.Other], - [/*start*/ 0x1D400, WordBreakProperty.ALetter], - [/*start*/ 0x1D455, WordBreakProperty.Other], - [/*start*/ 0x1D456, WordBreakProperty.ALetter], - [/*start*/ 0x1D49D, WordBreakProperty.Other], - [/*start*/ 0x1D49E, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A0, WordBreakProperty.Other], - [/*start*/ 0x1D4A2, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A3, WordBreakProperty.Other], - [/*start*/ 0x1D4A5, WordBreakProperty.ALetter], - [/*start*/ 0x1D4A7, WordBreakProperty.Other], - [/*start*/ 0x1D4A9, WordBreakProperty.ALetter], - [/*start*/ 0x1D4AD, WordBreakProperty.Other], - [/*start*/ 0x1D4AE, WordBreakProperty.ALetter], - [/*start*/ 0x1D4BA, WordBreakProperty.Other], - [/*start*/ 0x1D4BB, WordBreakProperty.ALetter], - [/*start*/ 0x1D4BC, WordBreakProperty.Other], - [/*start*/ 0x1D4BD, WordBreakProperty.ALetter], - [/*start*/ 0x1D4C4, WordBreakProperty.Other], - [/*start*/ 0x1D4C5, WordBreakProperty.ALetter], - [/*start*/ 0x1D506, WordBreakProperty.Other], - [/*start*/ 0x1D507, WordBreakProperty.ALetter], - [/*start*/ 0x1D50B, WordBreakProperty.Other], - [/*start*/ 0x1D50D, WordBreakProperty.ALetter], - [/*start*/ 0x1D515, WordBreakProperty.Other], - [/*start*/ 0x1D516, WordBreakProperty.ALetter], - [/*start*/ 0x1D51D, WordBreakProperty.Other], - [/*start*/ 0x1D51E, WordBreakProperty.ALetter], - [/*start*/ 0x1D53A, WordBreakProperty.Other], - [/*start*/ 0x1D53B, WordBreakProperty.ALetter], - [/*start*/ 0x1D53F, WordBreakProperty.Other], - [/*start*/ 0x1D540, WordBreakProperty.ALetter], - [/*start*/ 0x1D545, WordBreakProperty.Other], - [/*start*/ 0x1D546, WordBreakProperty.ALetter], - [/*start*/ 0x1D547, WordBreakProperty.Other], - [/*start*/ 0x1D54A, WordBreakProperty.ALetter], - [/*start*/ 0x1D551, WordBreakProperty.Other], - [/*start*/ 0x1D552, WordBreakProperty.ALetter], - [/*start*/ 0x1D6A6, WordBreakProperty.Other], - [/*start*/ 0x1D6A8, WordBreakProperty.ALetter], - [/*start*/ 0x1D6C1, WordBreakProperty.Other], - [/*start*/ 0x1D6C2, WordBreakProperty.ALetter], - [/*start*/ 0x1D6DB, WordBreakProperty.Other], - [/*start*/ 0x1D6DC, WordBreakProperty.ALetter], - [/*start*/ 0x1D6FB, WordBreakProperty.Other], - [/*start*/ 0x1D6FC, WordBreakProperty.ALetter], - [/*start*/ 0x1D715, WordBreakProperty.Other], - [/*start*/ 0x1D716, WordBreakProperty.ALetter], - [/*start*/ 0x1D735, WordBreakProperty.Other], - [/*start*/ 0x1D736, WordBreakProperty.ALetter], - [/*start*/ 0x1D74F, WordBreakProperty.Other], - [/*start*/ 0x1D750, WordBreakProperty.ALetter], - [/*start*/ 0x1D76F, WordBreakProperty.Other], - [/*start*/ 0x1D770, WordBreakProperty.ALetter], - [/*start*/ 0x1D789, WordBreakProperty.Other], - [/*start*/ 0x1D78A, WordBreakProperty.ALetter], - [/*start*/ 0x1D7A9, WordBreakProperty.Other], - [/*start*/ 0x1D7AA, WordBreakProperty.ALetter], - [/*start*/ 0x1D7C3, WordBreakProperty.Other], - [/*start*/ 0x1D7C4, WordBreakProperty.ALetter], - [/*start*/ 0x1D7CC, WordBreakProperty.Other], - [/*start*/ 0x1D7CE, WordBreakProperty.Numeric], - [/*start*/ 0x1D800, WordBreakProperty.Other], - [/*start*/ 0x1DA00, WordBreakProperty.Extend], - [/*start*/ 0x1DA37, WordBreakProperty.Other], - [/*start*/ 0x1DA3B, WordBreakProperty.Extend], - [/*start*/ 0x1DA6D, WordBreakProperty.Other], - [/*start*/ 0x1DA75, WordBreakProperty.Extend], - [/*start*/ 0x1DA76, WordBreakProperty.Other], - [/*start*/ 0x1DA84, WordBreakProperty.Extend], - [/*start*/ 0x1DA85, WordBreakProperty.Other], - [/*start*/ 0x1DA9B, WordBreakProperty.Extend], - [/*start*/ 0x1DAA0, WordBreakProperty.Other], - [/*start*/ 0x1DAA1, WordBreakProperty.Extend], - [/*start*/ 0x1DAB0, WordBreakProperty.Other], - [/*start*/ 0x1E000, WordBreakProperty.Extend], - [/*start*/ 0x1E007, WordBreakProperty.Other], - [/*start*/ 0x1E008, WordBreakProperty.Extend], - [/*start*/ 0x1E019, WordBreakProperty.Other], - [/*start*/ 0x1E01B, WordBreakProperty.Extend], - [/*start*/ 0x1E022, WordBreakProperty.Other], - [/*start*/ 0x1E023, WordBreakProperty.Extend], - [/*start*/ 0x1E025, WordBreakProperty.Other], - [/*start*/ 0x1E026, WordBreakProperty.Extend], - [/*start*/ 0x1E02B, WordBreakProperty.Other], - [/*start*/ 0x1E100, WordBreakProperty.ALetter], - [/*start*/ 0x1E12D, WordBreakProperty.Other], - [/*start*/ 0x1E130, WordBreakProperty.Extend], - [/*start*/ 0x1E137, WordBreakProperty.ALetter], - [/*start*/ 0x1E13E, WordBreakProperty.Other], - [/*start*/ 0x1E140, WordBreakProperty.Numeric], - [/*start*/ 0x1E14A, WordBreakProperty.Other], - [/*start*/ 0x1E14E, WordBreakProperty.ALetter], - [/*start*/ 0x1E14F, WordBreakProperty.Other], - [/*start*/ 0x1E2C0, WordBreakProperty.ALetter], - [/*start*/ 0x1E2EC, WordBreakProperty.Extend], - [/*start*/ 0x1E2F0, WordBreakProperty.Numeric], - [/*start*/ 0x1E2FA, WordBreakProperty.Other], - [/*start*/ 0x1E800, WordBreakProperty.ALetter], - [/*start*/ 0x1E8C5, WordBreakProperty.Other], - [/*start*/ 0x1E8D0, WordBreakProperty.Extend], - [/*start*/ 0x1E8D7, WordBreakProperty.Other], - [/*start*/ 0x1E900, WordBreakProperty.ALetter], - [/*start*/ 0x1E944, WordBreakProperty.Extend], - [/*start*/ 0x1E94B, WordBreakProperty.ALetter], - [/*start*/ 0x1E94C, WordBreakProperty.Other], - [/*start*/ 0x1E950, WordBreakProperty.Numeric], - [/*start*/ 0x1E95A, WordBreakProperty.Other], - [/*start*/ 0x1EE00, WordBreakProperty.ALetter], - [/*start*/ 0x1EE04, WordBreakProperty.Other], - [/*start*/ 0x1EE05, WordBreakProperty.ALetter], - [/*start*/ 0x1EE20, WordBreakProperty.Other], - [/*start*/ 0x1EE21, WordBreakProperty.ALetter], - [/*start*/ 0x1EE23, WordBreakProperty.Other], - [/*start*/ 0x1EE24, WordBreakProperty.ALetter], - [/*start*/ 0x1EE25, WordBreakProperty.Other], - [/*start*/ 0x1EE27, WordBreakProperty.ALetter], - [/*start*/ 0x1EE28, WordBreakProperty.Other], - [/*start*/ 0x1EE29, WordBreakProperty.ALetter], - [/*start*/ 0x1EE33, WordBreakProperty.Other], - [/*start*/ 0x1EE34, WordBreakProperty.ALetter], - [/*start*/ 0x1EE38, WordBreakProperty.Other], - [/*start*/ 0x1EE39, WordBreakProperty.ALetter], - [/*start*/ 0x1EE3A, WordBreakProperty.Other], - [/*start*/ 0x1EE3B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE3C, WordBreakProperty.Other], - [/*start*/ 0x1EE42, WordBreakProperty.ALetter], - [/*start*/ 0x1EE43, WordBreakProperty.Other], - [/*start*/ 0x1EE47, WordBreakProperty.ALetter], - [/*start*/ 0x1EE48, WordBreakProperty.Other], - [/*start*/ 0x1EE49, WordBreakProperty.ALetter], - [/*start*/ 0x1EE4A, WordBreakProperty.Other], - [/*start*/ 0x1EE4B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE4C, WordBreakProperty.Other], - [/*start*/ 0x1EE4D, WordBreakProperty.ALetter], - [/*start*/ 0x1EE50, WordBreakProperty.Other], - [/*start*/ 0x1EE51, WordBreakProperty.ALetter], - [/*start*/ 0x1EE53, WordBreakProperty.Other], - [/*start*/ 0x1EE54, WordBreakProperty.ALetter], - [/*start*/ 0x1EE55, WordBreakProperty.Other], - [/*start*/ 0x1EE57, WordBreakProperty.ALetter], - [/*start*/ 0x1EE58, WordBreakProperty.Other], - [/*start*/ 0x1EE59, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5A, WordBreakProperty.Other], - [/*start*/ 0x1EE5B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5C, WordBreakProperty.Other], - [/*start*/ 0x1EE5D, WordBreakProperty.ALetter], - [/*start*/ 0x1EE5E, WordBreakProperty.Other], - [/*start*/ 0x1EE5F, WordBreakProperty.ALetter], - [/*start*/ 0x1EE60, WordBreakProperty.Other], - [/*start*/ 0x1EE61, WordBreakProperty.ALetter], - [/*start*/ 0x1EE63, WordBreakProperty.Other], - [/*start*/ 0x1EE64, WordBreakProperty.ALetter], - [/*start*/ 0x1EE65, WordBreakProperty.Other], - [/*start*/ 0x1EE67, WordBreakProperty.ALetter], - [/*start*/ 0x1EE6B, WordBreakProperty.Other], - [/*start*/ 0x1EE6C, WordBreakProperty.ALetter], - [/*start*/ 0x1EE73, WordBreakProperty.Other], - [/*start*/ 0x1EE74, WordBreakProperty.ALetter], - [/*start*/ 0x1EE78, WordBreakProperty.Other], - [/*start*/ 0x1EE79, WordBreakProperty.ALetter], - [/*start*/ 0x1EE7D, WordBreakProperty.Other], - [/*start*/ 0x1EE7E, WordBreakProperty.ALetter], - [/*start*/ 0x1EE7F, WordBreakProperty.Other], - [/*start*/ 0x1EE80, WordBreakProperty.ALetter], - [/*start*/ 0x1EE8A, WordBreakProperty.Other], - [/*start*/ 0x1EE8B, WordBreakProperty.ALetter], - [/*start*/ 0x1EE9C, WordBreakProperty.Other], - [/*start*/ 0x1EEA1, WordBreakProperty.ALetter], - [/*start*/ 0x1EEA4, WordBreakProperty.Other], - [/*start*/ 0x1EEA5, WordBreakProperty.ALetter], - [/*start*/ 0x1EEAA, WordBreakProperty.Other], - [/*start*/ 0x1EEAB, WordBreakProperty.ALetter], - [/*start*/ 0x1EEBC, WordBreakProperty.Other], - [/*start*/ 0x1F130, WordBreakProperty.ALetter], - [/*start*/ 0x1F14A, WordBreakProperty.Other], - [/*start*/ 0x1F150, WordBreakProperty.ALetter], - [/*start*/ 0x1F16A, WordBreakProperty.Other], - [/*start*/ 0x1F170, WordBreakProperty.ALetter], - [/*start*/ 0x1F18A, WordBreakProperty.Other], - [/*start*/ 0x1F1E6, WordBreakProperty.Regional_Indicator], - [/*start*/ 0x1F200, WordBreakProperty.Other], - [/*start*/ 0x1F3FB, WordBreakProperty.Extend], - [/*start*/ 0x1F400, WordBreakProperty.Other], - [/*start*/ 0x1FBF0, WordBreakProperty.Numeric], - [/*start*/ 0x1FBFA, WordBreakProperty.Other], - [/*start*/ 0xE0001, WordBreakProperty.Format], - [/*start*/ 0xE0002, WordBreakProperty.Other], - [/*start*/ 0xE0020, WordBreakProperty.Extend], - [/*start*/ 0xE0080, WordBreakProperty.Other], - [/*start*/ 0xE0100, WordBreakProperty.Extend], - [/*start*/ 0xE01F0, WordBreakProperty.Other], -]; diff --git a/developer/src/kmc-model/test/wordbreakers/default-wordbreaker-esm.ts b/developer/src/kmc-model/test/wordbreakers/default-wordbreaker-esm.ts deleted file mode 100644 index 55c0e560da6..00000000000 --- a/developer/src/kmc-model/test/wordbreakers/default-wordbreaker-esm.ts +++ /dev/null @@ -1,383 +0,0 @@ -// TEMP: esm version of /common/models/wordbreakers/default/index.ts - - import { I, WORD_BREAK_PROPERTY, WordBreakProperty } from './data.js'; - - - /** - * Word breaker based on Unicode Standard Annex #29, Section 4.1: - * Default Word Boundary Specification. - * - * @see http://unicode.org/reports/tr29/#Word_Boundaries - * @see https://github.com/eddieantonio/unicode-default-word-boundary/tree/v12.0.0 - */ - export default function default_(text: string): Span[] { - let boundaries = findBoundaries(text); - if (boundaries.length == 0) { - return []; - } - - // All non-empty strings have at least TWO boundaries: at the start and at the end of - // the string. - let spans = []; - for (let i = 0; i < boundaries.length - 1; i++) { - let start = boundaries[i]; - let end = boundaries[i + 1]; - let span = new LazySpan(text, start, end); - - if (isNonSpace(span.text)) { - spans.push(span); - // Preserve a sequence-final space if it exists. Needed to signal "end of word". - } else if (i == boundaries.length - 2) { // if "we just checked the final boundary"... - // We don't want to return the whitespace itself; the correct token is simply ''. - span = new LazySpan(text, end, end); - spans.push(span); - } - } - return spans; - } - - // Utilities // - // type WordBreakProperty = data.WordBreakProperty; - // const WordBreakProperty = data.WordBreakProperty; - // type I = data.I; - // const I = data.I; - // const WORD_BREAK_PROPERTY = data.WORD_BREAK_PROPERTY; - - /** - * A span that does not cut out the substring until it absolutely has to! - */ - class LazySpan implements Span { - private _source: string; - readonly start: number; - readonly end: number; - constructor(source: string, start: number, end: number) { - this._source = source; - this.start = start; - this.end = end; - } - - get text(): string { - return this._source.substring(this.start, this.end); - } - - get length(): number { - return this.end - this.start; - } - } - - /** - * Returns true when the chunk does not solely consist of whitespace. - * - * @param chunk a chunk of text. Starts and ends at word boundaries. - */ - function isNonSpace(chunk: string): boolean { - return !Array.from(chunk).map(property).every(wb => ( - wb === WordBreakProperty.CR || - wb === WordBreakProperty.LF || - wb === WordBreakProperty.Newline || - wb === WordBreakProperty.WSegSpace - )); - } - - /** - * Yields a series of string indices where a word break should - * occur. That is, there should be a break BEFORE each string - * index yielded by this generator. - * - * @param text Text to find word boundaries in. - */ - function findBoundaries(text: string): number[] { - // WB1 and WB2: no boundaries if given an empty string. - if (text.length === 0) { - // There are no boundaries in an empty string! - return []; - } - - // This algorithm works by maintaining a sliding window of four SCALAR VALUES. - // - // - Scalar values? JavaScript strings are NOT actually a string of - // Unicode code points; some characters are made up of TWO - // JavaScript indices. e.g., - // "💩".length === 2; - // "💩"[0] === '\uD83D'; - // "💩"[1] === '\uDCA9'; - // - // These characters that are represented by TWO indices are - // called "surrogate pairs". Since we don't want to be in the - // "middle" of a character, make sure we're always advancing - // by scalar values, and NOT indices. That means, we sometimes - // need to advance by TWO indices, not just one. - // - Four values? Some rules look at what's to the left of - // left, and some look at what's to the right of right. So - // keep track of this! - - let boundaries = []; - - let rightPos: number; - let lookaheadPos = 0; // lookahead, one scalar value to the right of right. - // Before the start of the string is also the start of the string. - let lookbehind: WordBreakProperty; - let left = WordBreakProperty.sot; - let right = WordBreakProperty.sot; - let lookahead = wordbreakPropertyAt(0); - // Count RIs to make sure we're not splitting emoji flags: - let nConsecutiveRegionalIndicators = 0; - - do { - // Shift all positions, one scalar value to the right. - rightPos = lookaheadPos; - lookaheadPos = positionAfter(lookaheadPos); - // Shift all properties, one scalar value to the right. - [lookbehind, left, right, lookahead] = - [left, right, lookahead, wordbreakPropertyAt(lookaheadPos)]; - - // Break at the start and end of text, unless the text is empty. - // WB1: Break at start of text... - if (left === WordBreakProperty.sot) { - boundaries.push(rightPos); - continue; - } - // WB2: Break at the end of text... - if (right === WordBreakProperty.eot) { - boundaries.push(rightPos); - break; // Reached the end of the string. We're done! - } - // WB3: Do not break within CRLF: - if (left === WordBreakProperty.CR && right === WordBreakProperty.LF) - continue; - // WB3b: Otherwise, break after... - if (left === WordBreakProperty.Newline || - left === WordBreakProperty.CR || - left === WordBreakProperty.LF) { - boundaries.push(rightPos); - continue; - } - // WB3a: ...and before newlines - if (right === WordBreakProperty.Newline || - right === WordBreakProperty.CR || - right === WordBreakProperty.LF) { - boundaries.push(rightPos); - continue; - } - - // TODO: WB3c is not implemented, due to its complex, error-prone - // implementation, requiring a ginormous regexp, and the fact that - // the only thing it does is prevent big emoji sequences from being - // split up, like 🧚🏼‍♂️ - // https://www.unicode.org/Public/emoji/12.0/emoji-zwj-sequences.txt - - // WB3d: Keep horizontal whitespace together - if (left === WordBreakProperty.WSegSpace && right == WordBreakProperty.WSegSpace) - continue; - - // WB4: Ignore format and extend characters - // This is to keep grapheme clusters together! - // See: Section 6.2: https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules - // N.B.: The rule about "except after sot, CR, LF, and - // Newline" already been by WB1, WB2, WB3a, and WB3b above. - while (right === WordBreakProperty.Format || - right === WordBreakProperty.Extend || - right === WordBreakProperty.ZWJ) { - // Continue advancing in the string, as if these - // characters do not exist. DO NOT update left and - // lookbehind however! - [rightPos, lookaheadPos] = [lookaheadPos, positionAfter(lookaheadPos)]; - [right, lookahead] = [lookahead, wordbreakPropertyAt(lookaheadPos)]; - } - // In ignoring the characters in the previous loop, we could - // have fallen off the end of the string, so end the loop - // prematurely if that happens! - if (right === WordBreakProperty.eot) { - boundaries.push(rightPos); - break; - } - // WB4 (continued): Lookahead must ALSO ignore these format, - // extend, ZWJ characters! - while (lookahead === WordBreakProperty.Format || - lookahead === WordBreakProperty.Extend || - lookahead === WordBreakProperty.ZWJ) { - // Continue advancing in the string, as if these - // characters do not exist. DO NOT update left and right, - // however! - lookaheadPos = positionAfter(lookaheadPos); - lookahead = wordbreakPropertyAt(lookaheadPos); - } - - // WB5: Do not break between most letters. - if (isAHLetter(left) && isAHLetter(right)) - continue; - // Do not break across certain punctuation - // WB6: (Don't break before apostrophes in contractions) - if (isAHLetter(left) && isAHLetter(lookahead) && - (right === WordBreakProperty.MidLetter || isMidNumLetQ(right))) - continue; - // WB7: (Don't break after apostrophes in contractions) - if (isAHLetter(lookbehind) && isAHLetter(right) && - (left === WordBreakProperty.MidLetter || isMidNumLetQ(left))) - continue; - // WB7a - if (left === WordBreakProperty.Hebrew_Letter && right === WordBreakProperty.Single_Quote) - continue; - // WB7b - if (left === WordBreakProperty.Hebrew_Letter && right === WordBreakProperty.Double_Quote && - lookahead === WordBreakProperty.Hebrew_Letter) - continue; - // WB7c - if (lookbehind === WordBreakProperty.Hebrew_Letter && left === WordBreakProperty.Double_Quote && - right === WordBreakProperty.Hebrew_Letter) - continue; - // Do not break within sequences of digits, or digits adjacent to letters. - // e.g., "3a" or "A3" - // WB8 - if (left === WordBreakProperty.Numeric && right === WordBreakProperty.Numeric) - continue; - // WB9 - if (isAHLetter(left) && right === WordBreakProperty.Numeric) - continue; - // WB10 - if (left === WordBreakProperty.Numeric && isAHLetter(right)) - continue; - // Do not break within sequences, such as 3.2, 3,456.789 - // WB11 - if (lookbehind === WordBreakProperty.Numeric && right === WordBreakProperty.Numeric && - (left === WordBreakProperty.MidNum || isMidNumLetQ(left))) - continue; - // WB12 - if (left === WordBreakProperty.Numeric && lookahead === WordBreakProperty.Numeric && - (right === WordBreakProperty.MidNum || isMidNumLetQ(right))) - continue; - // WB13: Do not break between Katakana - if (left === WordBreakProperty.Katakana && right === WordBreakProperty.Katakana) - continue; - // Do not break from extenders (e.g., U+202F NARROW NO-BREAK SPACE) - // WB13a - if ((isAHLetter(left) || - left === WordBreakProperty.Numeric || - left === WordBreakProperty.Katakana || - left === WordBreakProperty.ExtendNumLet) && - right === WordBreakProperty.ExtendNumLet) - continue; - // WB13b - if ((isAHLetter(right) || - right === WordBreakProperty.Numeric || - right === WordBreakProperty.Katakana) && left === WordBreakProperty.ExtendNumLet) - continue; - - // WB15 & WB16: - // Do not break within emoji flag sequences. That is, do not break between - // regional indicator (RI) symbols if there is an odd number of RI - // characters before the break point. - if (right === WordBreakProperty.Regional_Indicator) { - // Emoji flags are actually composed of TWO scalar values, each being a - // "regional indicator". These indicators correspond to Latin letters. Put - // two of them together, and they spell out an ISO 3166-1-alpha-2 country - // code. Since these always come in pairs, NEVER split the pairs! So, if - // we happen to be inside the middle of an odd numbered of - // Regional_Indicators, DON'T SPLIT IT! - nConsecutiveRegionalIndicators += 1; - if ((nConsecutiveRegionalIndicators % 2) == 1) { - continue; - } - } else { - nConsecutiveRegionalIndicators = 0; - } - // WB999: Otherwise, break EVERYWHERE (including around ideographs) - boundaries.push(rightPos); - } while (rightPos < text.length); - - return boundaries; - - ///// Internal utility functions ///// - - /** - * Returns the position of the start of the next scalar value. This jumps - * over surrogate pairs. - * - * If asked for the character AFTER the end of the string, this always - * returns the length of the string. - */ - function positionAfter(pos: number): number { - if (pos >= text.length) { - return text.length; - } else if (isStartOfSurrogatePair(text[pos])) { - return pos + 2; - } - return pos + 1; - } - - /** - * Return the value of the Word_Break property at the given string index. - * @param pos position in the text. - */ - function wordbreakPropertyAt(pos: number) { - if (pos < 0) { - return WordBreakProperty.sot; // Always "start of string" before the string starts! - } else if (pos >= text.length) { - return WordBreakProperty.eot; // Always "end of string" after the string ends! - } else if (isStartOfSurrogatePair(text[pos])) { - // Surrogate pairs the next TWO items from the string! - return property(text[pos] + text[pos + 1]); - } - return property(text[pos]); - } - - // Word_Break rule macros - // See: https://unicode.org/reports/tr29/#WB_Rule_Macros - function isAHLetter(prop: WordBreakProperty): boolean { - return prop === WordBreakProperty.ALetter || - prop === WordBreakProperty.Hebrew_Letter; - } - - function isMidNumLetQ(prop: WordBreakProperty): boolean { - return prop === WordBreakProperty.MidNumLet || - prop === WordBreakProperty.Single_Quote; - } - } - - function isStartOfSurrogatePair(character: string) { - let codeUnit = character.charCodeAt(0); - return codeUnit >= 0xD800 && codeUnit <= 0xDBFF; - } - - /** - * Return the Word_Break property value for a character. - * Note that - * @param character a scalar value - */ - function property(character: string): WordBreakProperty { - // This MUST be a scalar value. - // TODO: remove dependence on character.codepointAt()? - let codepoint = character.codePointAt(0) as number; - return searchForProperty(codepoint, 0, WORD_BREAK_PROPERTY.length - 1); - } - - /** - * Binary search for the word break property of a given CODE POINT. - * - * The auto-generated data.ts master array defines a **character range** - * lookup table. If a character's codepoint is equal to or greater than - * the I.Start value for an entry and exclusively less than the next entry, - * it falls in the first entry's range bucket and is classified accordingly - * by this method. - */ - function searchForProperty(codePoint: number, left: number, right: number): WordBreakProperty { - // All items that are not found in the array are assigned the 'Other' property. - if (right < left) { - return WordBreakProperty.Other; - } - - let midpoint = left + ~~((right - left) / 2); - let candidate = WORD_BREAK_PROPERTY[midpoint]; - - let nextRange = WORD_BREAK_PROPERTY[midpoint + 1]; - let startOfNextRange = nextRange ? nextRange[I.Start] : Infinity; - - if (codePoint < candidate[I.Start]) { - return searchForProperty(codePoint, left, midpoint - 1); - } else if (codePoint >= startOfNextRange) { - return searchForProperty(codePoint, midpoint + 1, right); - } - - // We found it! - return candidate[I.Value]; - } From 383678a00241228073074e290be2bc620b51dae0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 4 Oct 2023 17:34:43 +0700 Subject: [PATCH 140/207] chore(developer): cleanup hextobin call --- developer/src/kmc-ldml/test/test-compiler-e2e.ts | 4 +--- .../src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc-ldml/test/test-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-compiler-e2e.ts index 41acd7c81d3..343f6cc7ac6 100644 --- a/developer/src/kmc-ldml/test/test-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-compiler-e2e.ts @@ -1,12 +1,10 @@ import 'mocha'; import {assert} from 'chai'; -import x_hextobin from '@keymanapp/hextobin'; +import hextobin from '@keymanapp/hextobin'; import { KMXBuilder } from '@keymanapp/common-types'; import {checkMessages, compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture} from './helpers/index.js'; import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js'; -const hextobin = (x_hextobin as any).default; - describe('compiler-tests', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts index 87d4d914c9e..133f4286c85 100644 --- a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts @@ -1,11 +1,9 @@ import 'mocha'; import {assert} from 'chai'; -import x_hextobin from '@keymanapp/hextobin'; +import hextobin from '@keymanapp/hextobin'; import { KvkFileWriter } from '@keymanapp/common-types'; import {checkMessages, compilerTestOptions, compileVisualKeyboard, makePathToFixture} from './helpers/index.js'; -const hextobin = (x_hextobin as any).default; - describe('visual-keyboard-compiler', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while From 2dd480c8e5c877b85a090c10facbe0cd2f650f22 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 4 Oct 2023 14:03:47 -0400 Subject: [PATCH 141/207] auto: increment master version to 17.0.187 --- HISTORY.md | 9 +++++++++ VERSION.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index afc72e7a17c..e22d0e994b0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Keyman Version History +## 17.0.186 alpha 2023-10-04 + +* feat(developer): show an INFO message when warnings have failed a build (#9652) +* chore(developer): reduce duplicate words warning to hint (#9653) +* feat(developer): issue hint if package includes keyboard source files (#9658) +* chore(developer): switch on code coverage reporting for kmc (#9662) +* fix(core): clean cached ICU in core (#9668) +* feat(developer): warn if .kps includes a .js which is not touch-capable (#9667) + ## 17.0.185 alpha 2023-10-03 * chore(web): Add non-printing characters to the OSK (#9547) diff --git a/VERSION.md b/VERSION.md index e7118d25e17..8eb52f8484b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.186 \ No newline at end of file +17.0.187 \ No newline at end of file From 2472a941cfc3558d830a77413696d98c32bf68d8 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Wed, 4 Oct 2023 18:34:20 -0500 Subject: [PATCH 142/207] =?UTF-8?q?feat(core):=20match=20any=20marker=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add a regex form that matches any marker For: #9119 --- common/web/types/src/kmx/kmx-plus.ts | 4 ++-- .../types/src/ldml-keyboard/pattern-parser.ts | 12 ++++++++--- .../test/ldml-keyboard/test-pattern-parser.ts | 12 +++++++++++ core/src/ldml/C9134_ldml_markers.md | 2 ++ .../unit/ldml/keyboards/k_210_marker-test.xml | 20 ++++++++++++------- .../unit/ldml/keyboards/k_210_marker.xml | 3 +++ developer/src/kmc-ldml/src/compiler/tran.ts | 4 ++-- 7 files changed, 43 insertions(+), 14 deletions(-) diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts index 1b653f2f5a0..ea36f7908ac 100644 --- a/common/web/types/src/kmx/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -293,8 +293,8 @@ export class Vars extends Section { return v[0]; } } - substituteMarkerString(s : string) : string { - return MarkerParser.toSentinelString(s, this.markers); + substituteMarkerString(s : string, forMatch? : boolean) : string { + return MarkerParser.toSentinelString(s, this.markers, forMatch); } }; diff --git a/common/web/types/src/ldml-keyboard/pattern-parser.ts b/common/web/types/src/ldml-keyboard/pattern-parser.ts index f6013248d26..b02440553e6 100644 --- a/common/web/types/src/ldml-keyboard/pattern-parser.ts +++ b/common/web/types/src/ldml-keyboard/pattern-parser.ts @@ -64,6 +64,9 @@ export class MarkerParser { public static readonly MAX_MARKER_INDEX = constants.marker_max_index; /** Max count of markers */ public static readonly MAX_MARKER_COUNT = constants.marker_max_count; + /** Expression that matches any marker */ + public static readonly ANY_MARKER_MATCH = + this.SENTINEL + this.MARKER_CODE + `[\\u0001-\\u${this.MAX_MARKER_INDEX.toString(16)}]`; /** * Pattern for matching a marker reference, OR the special marker \m{.} @@ -91,10 +94,13 @@ export class MarkerParser { } /** @returns all marker strings as sentinel values */ - public static toSentinelString(s: string, markers?: OrderedStringList) : string { + public static toSentinelString(s: string, markers?: OrderedStringList, forMatch?: boolean) : string { if (!s) return s; return s.replaceAll(this.REFERENCE, (sub, arg) => { if (arg === MarkerParser.ANY_MARKER_ID) { + if (forMatch) { + return this.ANY_MARKER_MATCH; + } return MarkerParser.markerOutput(MarkerParser.ANY_MARKER_INDEX); } if (!markers) { @@ -103,10 +109,10 @@ export class MarkerParser { const order = markers.getItemOrder(arg); if (order === -1) { throw RangeError(`Internal Error: Could not find marker \\m{${arg}}`); - } else if(order >= MarkerParser.MAX_MARKER_INDEX) { + } else if(order > MarkerParser.MAX_MARKER_INDEX) { throw RangeError(`Internal Error: marker \\m{${arg}} has out of range index ${order}`); } else { - return MarkerParser.markerOutput(order+1); + return MarkerParser.markerOutput(order + 1); } }); } diff --git a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts b/common/web/types/test/ldml-keyboard/test-pattern-parser.ts index e7f06a1c557..1ea96797a7b 100644 --- a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts +++ b/common/web/types/test/ldml-keyboard/test-pattern-parser.ts @@ -75,6 +75,7 @@ describe('Test of Pattern Parsers', () => { 'a': 0, 'b': 1, 'c': 2, + 'zz': MarkerParser.MAX_MARKER_INDEX - 1, // this is an ordering, so needs to be -1 'zzz': 0x2FFFFF, }; const o = m[item]; @@ -103,6 +104,17 @@ describe('Test of Pattern Parsers', () => { markers ) ); + // verify the matching behavior of these + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{a}`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`Q\\m{a}`, markers, false)), `Q\\m{a} did not match`); + assert.isFalse(new RegExp(MarkerParser.toSentinelString(`Q\\m{a}`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`Q\\m{b}`, markers, false)), `Q\\m{a} should not match Q\\m{b}`); + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`Q\\m{a}`, markers, false)), `Q\\m{.} did not match Q\\m{a}`); + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`Q\\m{zz}`, markers, false)), `Q\\m{.} did not match Q\\m{zz} (max marker)`); + assert.isFalse(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`\\m{a}`, markers, false)), `Q\\m{.} did not match \\m{a}`); }); it('should match some marker constants', () => { assert.equal(constants.uc_sentinel, KMXFile.UC_SENTINEL); diff --git a/core/src/ldml/C9134_ldml_markers.md b/core/src/ldml/C9134_ldml_markers.md index 065dd0ee2f3..788cd82b500 100644 --- a/core/src/ldml/C9134_ldml_markers.md +++ b/core/src/ldml/C9134_ldml_markers.md @@ -47,6 +47,8 @@ Note that this is different from other 0-based indices in KMX+. If there are thr ## Compiler (kmc) - `U+FFFF` needs to be illegal as a literal or escaped sequence. So `\u{FFFF}` is not allowed, for example, nor as a literal in the UTF-8 .xml stream. +- Matching `\m{abc}` (some marker) will turn into a match for `U+FFFF U+0008 U+XXXX` for that match. +- Matching `\m{.}` (_any_ marker) will turn into the special sequence `U+FFFF U+0008 [U+0001-U+D7FE]` where the latter is a range ### `vars` diff --git a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml index 3286ac11792..73083e180f5 100644 --- a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml +++ b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml @@ -3,7 +3,7 @@ - + - - @@ -41,6 +35,18 @@ + --> + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_210_marker.xml b/core/tests/unit/ldml/keyboards/k_210_marker.xml index 1635ce0a2a6..d411725f6d0 100644 --- a/core/tests/unit/ldml/keyboards/k_210_marker.xml +++ b/core/tests/unit/ldml/keyboards/k_210_marker.xml @@ -33,6 +33,9 @@ + + + diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts index 9f39e302ef3..f874273f6f6 100644 --- a/developer/src/kmc-ldml/src/compiler/tran.ts +++ b/developer/src/kmc-ldml/src/compiler/tran.ts @@ -142,8 +142,8 @@ export class TransformCompiler Date: Wed, 4 Oct 2023 18:45:45 -0500 Subject: [PATCH 143/207] =?UTF-8?q?feat(core):=20more=20marker=20?= =?UTF-8?q?=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reenable some tests For: #9119 --- .../unit/ldml/keyboards/k_210_marker-test.xml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml index 73083e180f5..24f104f4d71 100644 --- a/core/tests/unit/ldml/keyboards/k_210_marker-test.xml +++ b/core/tests/unit/ldml/keyboards/k_210_marker-test.xml @@ -3,7 +3,7 @@ - + - + + + + + + + + + + From 41145243a3ae17dfac9629751bf609b280d2a519 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Wed, 4 Oct 2023 18:54:16 -0500 Subject: [PATCH 144/207] =?UTF-8?q?feat(developer):=20test:=20more=20vars?= =?UTF-8?q?=20and=20markers=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add and re-enable some tests For: #9121 For: #9119 --- .../src/kmc-ldml/test/fixtures/sections/disp/maximal.xml | 6 +++++- .../src/kmc-ldml/test/fixtures/sections/keys/maximal.xml | 7 +++++-- developer/src/kmc-ldml/test/test-keys.ts | 8 ++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml b/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml index 1a698edddbb..18bc33ffd3c 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml @@ -9,7 +9,11 @@ - + + + + + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml index 6c9b983fbd6..7aaa99aa74c 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml @@ -15,10 +15,9 @@ - + - @@ -34,4 +33,8 @@ + + + + diff --git a/developer/src/kmc-ldml/test/test-keys.ts b/developer/src/kmc-ldml/test/test-keys.ts index fb36f9a96f6..0941169b4fd 100644 --- a/developer/src/kmc-ldml/test/test-keys.ts +++ b/developer/src/kmc-ldml/test/test-keys.ts @@ -57,7 +57,7 @@ describe('keys', function () { const [flick0_ne_sw] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('ne sw'.split(' '))); assert.ok(flick0_ne_sw); - assert.equal(flick0_ne_sw.to?.value, 'ê'); + assert.equal(flick0_ne_sw.to?.value, 'ê'); // via variable }, }, { @@ -129,11 +129,11 @@ describe('keys', function () { const MARKER_1 = MarkerParser.markerOutput(1); assert.equal(ww.to.value, MARKER_1); assert.equal(ww.longPressDefault.value, MARKER_1); - // TODO-LDML: assert.equal(ww.longPress[0].value.value, MARKER_1); - // TODO-LDML: assert.equal(ww.multiTap[0].value.value, MARKER_1); + assert.equal(ww.longPress[0].value.value, MARKER_1); + assert.equal(ww.multiTap[0].value.value, MARKER_1); const [flickw] = keys.flicks?.filter(({id}) => id.value === 'flickw'); assert.ok(flickw); - // TODO-LDML: assert.equal(flickw.flicks[0].to.value, MARKER_1); + assert.equal(flickw.flicks[0].to.value, MARKER_1); }, }, ]); From e04b41e89ebe7d261015cea58a01283720a23f55 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Wed, 4 Oct 2023 19:21:04 -0500 Subject: [PATCH 145/207] =?UTF-8?q?feat(developer):=20fix=20vars=20and=20m?= =?UTF-8?q?arkers=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - finish all of the developer LDML-TODOs around markers and variables For: #9121 For: #9119 --- common/web/types/src/kmx/kmx-plus.ts | 13 +++++++ developer/src/kmc-ldml/src/compiler/disp.ts | 9 ++--- developer/src/kmc-ldml/src/compiler/keys.ts | 34 ++++++++++--------- .../test/fixtures/sections/disp/maximal.xml | 2 +- .../test/fixtures/sections/keys/maximal.xml | 3 +- developer/src/kmc-ldml/test/test-keys.ts | 4 +++ 6 files changed, 43 insertions(+), 22 deletions(-) diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts index ea36f7908ac..6ffffc138f9 100644 --- a/common/web/types/src/kmx/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -234,6 +234,7 @@ export class Vars extends Section { }); } substituteStrings(str: string, sections: DependencySections): string { + if (!str) return str; return str.replaceAll(VariableParser.STRING_REFERENCE, (_entire, id) => { const val = this.findStringVariableValue(id); if (val === null) { @@ -513,6 +514,18 @@ export class List extends Section { } return this.allocList(strs, s.split(' ').map(unescapeString)); } + /** perform string variable, marker, and unescaping */ + allocListFromSubstitutedSpaces(s: string, sections: DependencySections): ListItem { + if(s === undefined || s === null) { + s = ''; + } + return this.allocList(sections.strs, s.split(' ').map(s => { + s = sections.vars.substituteStrings(s, sections); + s = sections.vars.substituteMarkerString(s); + s = unescapeString(s); + return s; + })); + } /** * Return a List object referring to the string list. * Note that a falsy list, or a list containing only an empty string diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts index 77c9609876a..a3d8ae36fc8 100644 --- a/developer/src/kmc-ldml/src/compiler/disp.ts +++ b/developer/src/kmc-ldml/src/compiler/disp.ts @@ -59,13 +59,14 @@ export class DispCompiler extends SectionCompiler { // displayOptions result.baseCharacter = sections.strs.allocAndUnescapeString(this.keyboard3.displays?.displayOptions?.baseCharacter); - // TODO-LDML: substitute variables! - // displays result.disps = this.keyboard3.displays?.display.map(display => ({ - to: sections.strs.allocAndUnescapeString(sections.vars.substituteMarkerString(display.to)), + to: sections.strs.allocAndUnescapeString( + sections.vars.substituteMarkerString( + sections.vars.substituteStrings(display.to, sections))), id: sections.strs.allocString(display.id), // not escaped, not substituted - display: sections.strs.allocAndUnescapeString(display.display), + display: sections.strs.allocAndUnescapeString( + sections.vars.substituteStrings(display.display, sections)), })) || []; // TODO-LDML: need coverage for the [] result.disps.sort((a: DispItem, b: DispItem) => { diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts index 8482de316c1..aad5e76f37a 100644 --- a/developer/src/kmc-ldml/src/compiler/keys.ts +++ b/developer/src/kmc-ldml/src/compiler/keys.ts @@ -130,7 +130,11 @@ export class KeysCompiler extends SectionCompiler { for (let lkflick of lkflicks.flick) { let flags = 0; - const to = sections.strs.allocAndUnescapeString(lkflick.to, true); + let cookedTo = lkflick.to; + // pull in string variables and markers + cookedTo = sections.vars.substituteStrings(cookedTo, sections); + cookedTo = sections.vars.substituteMarkerString(cookedTo); + const to = sections.strs.allocAndUnescapeString(cookedTo, true); if (!to.isOneChar) { flags |= constants.keys_flick_flags_extend; } @@ -141,7 +145,6 @@ export class KeysCompiler extends SectionCompiler { flicks.flicks.push({ directions, flags, - // TODO-LDML: markers,variables to, }); } @@ -168,24 +171,23 @@ export class KeysCompiler extends SectionCompiler { flags |= constants.keys_key_flags_notransform; } const id = sections.strs.allocString(key.id); - const longPress: ListItem = sections.list.allocListFromEscapedSpaces( - sections.strs, - // TODO-LDML: markers,variables - key.longPress + const longPress: ListItem = sections.list.allocListFromSubstitutedSpaces( + key.longPress, + sections, ); - const longPressDefault = sections.strs.allocAndUnescapeString( - // TODO-LDML: variables - sections.vars.substituteMarkerString(key.longPressDefault), - ); - const multiTap: ListItem = sections.list.allocListFromEscapedSpaces( - sections.strs, - // TODO-LDML: markers,variables - key.multiTap + let cookedLongPressDefault = key.longPressDefault; + cookedLongPressDefault = sections.vars.substituteStrings(cookedLongPressDefault, sections); + cookedLongPressDefault = sections.vars.substituteMarkerString(cookedLongPressDefault) + const longPressDefault = sections.strs.allocAndUnescapeString(cookedLongPressDefault); + + const multiTap: ListItem = sections.list.allocListFromSubstitutedSpaces( + key.multiTap, + sections, ); const keySwitch = sections.strs.allocString(key.switch); // 'switch' is a reserved word const toRaw = key.to; - // TODO-LDML: variables - let toCooked = sections.vars.substituteMarkerString(toRaw); + let toCooked = sections.vars.substituteStrings(toRaw, sections); + toCooked = sections.vars.substituteMarkerString(toCooked); const to = sections.strs.allocAndUnescapeString(toCooked, true); if (!to.isOneChar) { flags |= constants.keys_key_flags_extend; diff --git a/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml b/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml index 18bc33ffd3c..862c9310043 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/disp/maximal.xml @@ -9,7 +9,7 @@ - + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml index 7aaa99aa74c..40f0be95ba1 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/maximal.xml @@ -12,7 +12,7 @@ - + @@ -36,5 +36,6 @@ + diff --git a/developer/src/kmc-ldml/test/test-keys.ts b/developer/src/kmc-ldml/test/test-keys.ts index 0941169b4fd..5a085a0d0fb 100644 --- a/developer/src/kmc-ldml/test/test-keys.ts +++ b/developer/src/kmc-ldml/test/test-keys.ts @@ -38,6 +38,10 @@ describe('keys', function () { assert.equal(compilerTestCallbacks.messages.length, 0); assert.equal(keys.keys.length, 4); + const [w] = keys.keys.filter(({ id }) => id.value === 'w'); + assert.ok(w); + assert.equal(w.to.value, 'w', 'substituted key value'); + const [q] = keys.keys.filter(({ id }) => id.value === 'q'); assert.ok(q); assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); From 225877f57a5bfc0f310a32fe5e7cc34f26ec97cc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 13:54:40 +0700 Subject: [PATCH 146/207] feat(common): precompile json schemas to improve performance Fixes #9640. This ended up being somewhat more fragile than originally anticipated. For the `ajv` cli call to get the right versions, we needed to install ajv as a dev dependency in the top-level package.json as well as at the common/web/types level. Ajv does not do all that well with ESM yet, either, so we use esbuild to transform the compiled validators before building in typescript. --- common/web/types/.eslintrc.cjs | 2 + common/web/types/.gitignore | 3 +- common/web/types/build.sh | 21 +- common/web/types/package.json | 6 +- .../keyman-touch-layout-file-reader.ts | 9 +- common/web/types/src/kpj/kpj-file-reader.ts | 12 +- common/web/types/src/kvk/kvks-file-reader.ts | 9 +- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 9 +- common/web/types/src/main.ts | 3 +- common/web/types/src/osk/osk.ts | 9 +- common/web/types/src/schema-validators.ts | 23 ++ common/web/types/test/helpers/index.ts | 4 +- common/web/types/test/tsconfig.json | 2 +- common/web/types/tools/formats.cjs | 10 + common/web/types/tools/schema-bundler.js | 27 ++ common/web/types/tsconfig.json | 6 +- common/web/utils/package.json | 4 +- developer/src/kmc-keyboard-info/build.sh | 1 + developer/src/kmc-keyboard-info/package.json | 4 +- developer/src/kmc-keyboard-info/src/index.ts | 18 +- developer/src/kmc-ldml/package.json | 1 - developer/src/kmc-ldml/tsconfig.json | 1 - package-lock.json | 264 ++++++++++++------ package.json | 3 + tsconfig.esm-base.json | 1 + 25 files changed, 296 insertions(+), 156 deletions(-) create mode 100644 common/web/types/src/schema-validators.ts create mode 100644 common/web/types/tools/formats.cjs create mode 100644 common/web/types/tools/schema-bundler.js diff --git a/common/web/types/.eslintrc.cjs b/common/web/types/.eslintrc.cjs index 21618ea66ff..98db7f812e5 100644 --- a/common/web/types/.eslintrc.cjs +++ b/common/web/types/.eslintrc.cjs @@ -8,6 +8,8 @@ module.exports = { "coverage/*", "node_modules/*", "test/fixtures/*", + "tools/*", + "src/schemas/*" ], overrides: [ { diff --git a/common/web/types/.gitignore b/common/web/types/.gitignore index 35512aa7852..2f943a2f4e9 100644 --- a/common/web/types/.gitignore +++ b/common/web/types/.gitignore @@ -1 +1,2 @@ -src/schemas/ \ No newline at end of file +src/schemas/ +obj/ \ No newline at end of file diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 560ade06ed2..85c448024ed 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -40,16 +40,35 @@ function compile_schemas() { "$KEYMAN_ROOT/common/schemas/keyboard_info/keyboard_info.schema.json" ) + rm -rf "$THIS_SCRIPT_PATH/obj/schemas" + mkdir -p "$THIS_SCRIPT_PATH/obj/schemas" rm -rf "$THIS_SCRIPT_PATH/src/schemas" mkdir -p "$THIS_SCRIPT_PATH/src/schemas" cp "${schemas[@]}" "$THIS_SCRIPT_PATH/src/schemas/" # TODO: use https://github.com/tc39/proposal-json-modules instead of this once it stablises for schema in "${schemas[@]}"; do - local fn="$THIS_SCRIPT_PATH/src/schemas/$(basename "$schema" .json)" + local schema_base="$(basename "$schema" .json)" + local fn="$THIS_SCRIPT_PATH/src/schemas/$schema_base" + local out="$THIS_SCRIPT_PATH/obj/schemas/$schema_base.validator.cjs" + + # emit a .ts wrapper for the schema file + + builder_echo "Compiling schema $schema_base.json" echo 'export default ' > "$fn.ts" cat "$fn.json" >> "$fn.ts" + + # emit a compiled validator for the schema file + + # While would seem obvious to just run 'ajv' directly here, somewhere node + # is picking up the wrong path for the build and breaking the formats + # imports. So it is essential to use `npm run` at this point, even though it + # is painfully slower, at least until we figure out the path discrepancy. + npm run build:schema -- -c ./tools/formats.cjs -s "$fn.json" --strict-types false -o "$out" done + + # the validators now need to be compiled to esm + node tools/schema-bundler.js } function copy_cldr_imports() { diff --git a/common/web/types/package.json b/common/web/types/package.json index 83a403aeedc..fd59cffa97d 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -16,6 +16,7 @@ ], "scripts": { "build": "tsc -b", + "build:schema": "ajv compile", "lint": "eslint .", "test": "npm run lint && cd test && tsc -b && cd .. && c8 --skip-full --reporter=lcov --reporter=text mocha", "prepublishOnly": "npm run build" @@ -27,7 +28,6 @@ }, "dependencies": { "@keymanapp/keyman-version": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -39,6 +39,9 @@ "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/xml2js": "^0.4.5", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -74,6 +77,7 @@ "src/ldml-keyboard/unicodeset-parser-api.ts", "src/keyman-touch-layout/keyman-touch-layout-file-writer.ts", "src/osk/osk.ts", + "src/schemas/*", "test/" ] } diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts index f2dc0a5c7d9..bb5d8c2df17 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts @@ -1,7 +1,5 @@ -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { TouchLayoutFile } from "./keyman-touch-layout-file.js"; -import Schemas from '../../src/schemas.js'; +import SchemaValidators from '../schema-validators.js'; export class TouchLayoutFileReader { public read(source: Uint8Array): TouchLayoutFile { @@ -69,11 +67,10 @@ export class TouchLayoutFileReader { } public validate(source: TouchLayoutFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.touchLayoutClean, source)) + if(!SchemaValidators.touchLayoutClean(source)) /* c8 ignore next 3 */ { - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.touchLayoutClean).errors); } } diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 5472d5c8d24..cffa2069c64 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -1,11 +1,9 @@ import * as xml2js from 'xml2js'; import { KPJFile, KPJFileProject } from './kpj-file.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; import { CompilerCallbacks } from '../util/compiler-interfaces.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; export class KPJFileReader { constructor(private callbacks: CompilerCallbacks) { @@ -35,13 +33,11 @@ export class KPJFileReader { } public validate(source: KPJFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.kpj, source)) { - const ajvLegacy = new Ajv(); - if(!ajvLegacy.validate(Schemas.kpj90, source)) { + if(!SchemaValidators.kpj(source)) { + if(!SchemaValidators.kpj90(source)) { // If the legacy schema also does not validate, then we will only report // the errors against the modern schema - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.kpj).errors); } } } diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index c9808d0b934..70967df0538 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -1,12 +1,10 @@ import * as xml2js from 'xml2js'; import KVKSourceFile from './kvks-file.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { DEFAULT_KVK_FONT, VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; import { BUILDER_KVK_HEADER_VERSION, KVK_HEADER_IDENTIFIER_BYTES } from './kvk-file.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; export default class KVKSFileReader { @@ -85,9 +83,8 @@ export default class KVKSFileReader { } public validate(source: KVKSourceFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.kvks, source)) { - throw new Error(ajv.errorsText()); + if(!SchemaValidators.kvks(source)) { + throw new Error((SchemaValidators.kvks).errorsText()); } } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 3fdd9a74025..aaf46262c5d 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,13 +1,11 @@ import * as xml2js from 'xml2js'; import { LDMLKeyboardXMLSourceFile, LKImport } from './ldml-keyboard-xml.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { CompilerCallbacks } from '../util/compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; import { CommonTypesMessages } from '../util/common-events.js'; import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; interface NameAndProps { '$'?: any; // content @@ -207,9 +205,8 @@ export class LDMLKeyboardXMLSourceFileReader { * @returns true if valid, false if invalid */ public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.ldmlKeyboard3, source)) { - for (let err of ajv.errors) { + if(!SchemaValidators.ldmlKeyboard3(source)) { + for (let err of (SchemaValidators.ldmlKeyboard3).errors) { this.callbacks.reportMessage(CommonTypesMessages.Error_SchemaValidationError({ instancePath: err.instancePath, keyword: err.keyword, diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index a814903ab75..108a2d57c29 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -47,4 +47,5 @@ export * as KeymanFileTypes from './util/file-types.js'; export * as Osk from './osk/osk.js'; -export * as Schemas from './schemas.js'; \ No newline at end of file +export * as Schemas from './schemas.js'; +export * as SchemaValidators from './schema-validators.js'; \ No newline at end of file diff --git a/common/web/types/src/osk/osk.ts b/common/web/types/src/osk/osk.ts index 620712baeb2..9c7d1bf04c4 100644 --- a/common/web/types/src/osk/osk.ts +++ b/common/web/types/src/osk/osk.ts @@ -1,8 +1,6 @@ import { TouchLayoutFile, TouchLayoutFlick, TouchLayoutKey, TouchLayoutPlatform, TouchLayoutSubKey } from "src/keyman-touch-layout/keyman-touch-layout-file.js"; import { VisualKeyboard } from "../kvk/visual-keyboard.js"; -import { default as AjvModule } from 'ajv'; -import Schemas from "../schemas.js"; -const Ajv = AjvModule.default; // The actual expected Ajv type. +import SchemaValidators from "../schema-validators.js"; export interface StringRefUsage { filename: string; @@ -24,11 +22,10 @@ export interface StringResult { export type PuaMap = {[index:string]: string}; export function parseMapping(mapping: any) { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.displayMap, mapping)) + if(!SchemaValidators.displayMap(mapping)) /* c8 ignore next 3 */ { - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.displayMap).errorsText()); } let map: PuaMap = {}; diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts new file mode 100644 index 00000000000..340517b188d --- /dev/null +++ b/common/web/types/src/schema-validators.ts @@ -0,0 +1,23 @@ +import kpj from './schemas/kpj.schema.validator.mjs'; +import kpj90 from './schemas/kpj-9.0.schema.validator.mjs'; +import kvks from './schemas/kvks.schema.validator.mjs'; +import ldmlKeyboard3 from './schemas/ldml-keyboard3.schema.validator.mjs'; +import ldmlKeyboardTest3 from './schemas/ldml-keyboardtest3.schema.validator.mjs'; +import displayMap from './schemas/displaymap.schema.validator.mjs'; +import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator.mjs'; +import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; +import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; + +const SchemaValidators = { + kpj, + kpj90, + kvks, + ldmlKeyboard3, + ldmlKeyboardTest3, + displayMap, + touchLayoutClean, + touchLayout, + keyboard_info, +}; + +export default SchemaValidators; diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts index e92b9656bfa..9b2be1d6056 100644 --- a/common/web/types/test/helpers/index.ts +++ b/common/web/types/test/helpers/index.ts @@ -1,5 +1,5 @@ -import path from "path"; -import fs from "fs"; +import * as path from "path"; +import * as fs from "fs"; import { fileURLToPath } from "url"; /** diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json index c522129c99a..a59923bf9a6 100644 --- a/common/web/types/test/tsconfig.json +++ b/common/web/types/test/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "../build/test", "baseUrl": ".", "strictNullChecks": false, // TODO: get rid of this as some point - "allowSyntheticDefaultImports": true // for ajv + "allowSyntheticDefaultImports": true }, "include": [ "**/test-*.ts", diff --git a/common/web/types/tools/formats.cjs b/common/web/types/tools/formats.cjs new file mode 100644 index 00000000000..82eca97ab2f --- /dev/null +++ b/common/web/types/tools/formats.cjs @@ -0,0 +1,10 @@ +/* + * This somewhat peculiar function is used in `build.sh configure` when + * precompiling the validators and makes it possible to use the extended formats + * in ajv-formats. + */ +function formats(ajv) { + require("ajv-formats")(ajv); +} + +module.exports = formats; diff --git a/common/web/types/tools/schema-bundler.js b/common/web/types/tools/schema-bundler.js new file mode 100644 index 00000000000..995befaec1f --- /dev/null +++ b/common/web/types/tools/schema-bundler.js @@ -0,0 +1,27 @@ +/* + * Bundle schema validation files (from .cjs) and make them available as ES modules + */ + +import esbuild from 'esbuild'; + +await esbuild.build({ + entryPoints: [ + 'obj/schemas/kpj.schema.validator.cjs', + 'obj/schemas/kpj-9.0.schema.validator.cjs', + 'obj/schemas/kvks.schema.validator.cjs', + 'obj/schemas/ldml-keyboard3.schema.validator.cjs', + 'obj/schemas/ldml-keyboardtest3.schema.validator.cjs', + 'obj/schemas/displaymap.schema.validator.cjs', + 'obj/schemas/keyman-touch-layout.clean.spec.validator.cjs', + 'obj/schemas/keyman-touch-layout.spec.validator.cjs', + 'obj/schemas/keyboard_info.schema.validator.cjs', + ], + bundle: true, + format: 'esm', + target: 'es2022', + outdir: 'src/schemas/', + sourcemap: false, + + // We want a .mjs extension to force node into ESM module mode + outExtension: { '.js': '.mjs' }, +}); diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json index 5059030c0f8..ec2873ed5e0 100644 --- a/common/web/types/tsconfig.json +++ b/common/web/types/tsconfig.json @@ -5,11 +5,11 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", - "allowSyntheticDefaultImports": true, // for ajv - "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "src/schemas/*.mjs", // Import the validators ], "references": [ { "path": "../keyman-version" }, diff --git a/common/web/utils/package.json b/common/web/utils/package.json index 210ac4b6be5..3f5729ec6a8 100644 --- a/common/web/utils/package.json +++ b/common/web/utils/package.json @@ -23,13 +23,13 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "*", "@keymanapp/keyman-version": "*", + "@keymanapp/resources-gosh": "*", + "@types/node": "^14.0.5", "c8": "^7.12.0", "chai": "^4.3.4", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "@types/node": "^14.0.5", "typescript": "^4.9.5" }, "type": "module", diff --git a/developer/src/kmc-keyboard-info/build.sh b/developer/src/kmc-keyboard-info/build.sh index 094221c2e43..fb35c450197 100755 --- a/developer/src/kmc-keyboard-info/build.sh +++ b/developer/src/kmc-keyboard-info/build.sh @@ -11,6 +11,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe "Build Keyman kmc keyboard-info Compiler module" \ "@/common/web/types" \ + "@/developer/src/common/web/utils" \ "clean" \ "configure" \ "build" \ diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index a354c942e53..7ca8db1f626 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -25,10 +25,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "@keymanapp/kmc-package": "*", "@keymanapp/developer-utils": "*", - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1" + "@keymanapp/kmc-package": "*" }, "bundleDependencies": [ "@keymanapp/developer-utils" diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index c36e9b82fe5..a933e8771ae 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -11,12 +11,7 @@ import langtags from "./imports/langtags.js"; import { validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; -import AjvModule from 'ajv'; -import AjvFormatsModule from 'ajv-formats'; -const Ajv = AjvModule.default; // The actual expected Ajv type. -const ajvFormats = AjvFormatsModule.default; - -import { Schemas } from "@keymanapp/common-types"; +import { SchemaValidators } from "@keymanapp/common-types"; import { packageKeysExamplesToKeyboardInfo } from "./example-keys.js"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); @@ -290,17 +285,10 @@ export class KeyboardInfoCompiler { const jsonOutput = JSON.stringify(keyboard_info, null, 2); - // TODO: look at performance improvements by precompiling Ajv schemas on first use - const ajv = new Ajv({ logger: { - log: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_OutputValidation({message})), - warn: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Warn_OutputValidation({message})), - error: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_OutputValidation({message})), - }}); - ajvFormats.default(ajv); - if(!ajv.validate(Schemas.default.keyboard_info, keyboard_info)) { + if(!SchemaValidators.default.keyboard_info(keyboard_info)) { // This is an internal fatal error; we should not be capable of producing // invalid output, so it is best to throw and die - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.default.keyboard_info).errorsText()); } return new TextEncoder().encode(jsonOutput); diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index b140a5c552e..53542664c8b 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -29,7 +29,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" diff --git a/developer/src/kmc-ldml/tsconfig.json b/developer/src/kmc-ldml/tsconfig.json index c9ec649b40f..0306158ebd6 100644 --- a/developer/src/kmc-ldml/tsconfig.json +++ b/developer/src/kmc-ldml/tsconfig.json @@ -5,7 +5,6 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", - "allowSyntheticDefaultImports": true, // for ajv "paths": { // "@keymanapp/keyman-version": ["../../../common/web/keyman-version/keyman-version.mts"], diff --git a/package-lock.json b/package-lock.json index fbcf72c79f6..644d3991870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,9 @@ "devDependencies": { "@types/chai": "^4.3.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "chai": "^4.3.4", "esbuild": "^0.15.16", "eslint": "^8.39.0", @@ -378,7 +381,6 @@ "license": "MIT", "dependencies": { "@keymanapp/keyman-version": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -390,6 +392,9 @@ "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/xml2js": "^0.4.5", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -411,20 +416,6 @@ "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==", "dev": true }, - "common/web/types/node_modules/ajv": { - "version": "8.11.2", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "common/web/types/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -473,10 +464,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "common/web/types/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, "common/web/types/node_modules/minimatch": { "version": "3.0.4", "dev": true, @@ -1160,9 +1147,7 @@ "dependencies": { "@keymanapp/common-types": "*", "@keymanapp/developer-utils": "*", - "@keymanapp/kmc-package": "*", - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1" + "@keymanapp/kmc-package": "*" }, "devDependencies": { "@types/chai": "^4.3.5", @@ -1188,21 +1173,6 @@ "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==", "dev": true }, - "developer/src/kmc-keyboard-info/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "developer/src/kmc-keyboard-info/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1256,11 +1226,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "developer/src/kmc-keyboard-info/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "developer/src/kmc-keyboard-info/node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1754,7 +1719,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -1786,21 +1750,6 @@ "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==", "dev": true }, - "developer/src/kmc-ldml/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "developer/src/kmc-ldml/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1854,11 +1803,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "developer/src/kmc-ldml/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "developer/src/kmc-ldml/node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3033,6 +2977,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3050,6 +3010,12 @@ } } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/@eslint/js": { "version": "8.39.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", @@ -4377,14 +4343,14 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -4392,10 +4358,71 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "bin": { + "ajv": "dist/index.js" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/ajv-cli/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/ajv-cli/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/ajv-cli/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "dependencies": { "ajv": "^8.0.0" }, @@ -4408,26 +4435,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/ansi-colors": { "version": "4.1.1", "dev": true, @@ -6469,6 +6476,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6544,6 +6567,12 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6573,6 +6602,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -6813,6 +6855,7 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6831,6 +6874,24 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fast-json-patch/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8162,10 +8223,19 @@ "version": "3.0.1", "license": "MIT" }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + } + }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -10001,6 +10071,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, "engines": { "node": ">=6" } @@ -10166,7 +10237,9 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10681,6 +10754,12 @@ "node": "*" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -11306,6 +11385,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index ac8c23dec5d..01f3f387f0b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "devDependencies": { "@types/chai": "^4.3.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "chai": "^4.3.4", "esbuild": "^0.15.16", "eslint": "^8.39.0", diff --git a/tsconfig.esm-base.json b/tsconfig.esm-base.json index d17d505e5c2..170983b8a25 100644 --- a/tsconfig.esm-base.json +++ b/tsconfig.esm-base.json @@ -14,6 +14,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "noUnusedLocals": true, + "allowJs": true, "paths": { "@keymanapp/keyman-version": ["./common/web/keyman-version/keyman-version.mts"], From a314ba46a6a87d7e6c687a3181d3da04baa22420 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 14:42:33 +0700 Subject: [PATCH 147/207] fix(developer): server node addin paths --- developer/src/server/build-addins.inc.sh | 23 +++++++++++++++-------- developer/src/server/build.sh | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/developer/src/server/build-addins.inc.sh b/developer/src/server/build-addins.inc.sh index 3643ebffcdf..d48d44d1e74 100644 --- a/developer/src/server/build-addins.inc.sh +++ b/developer/src/server/build-addins.inc.sh @@ -29,8 +29,13 @@ do_build_addins() { echo "TRAYICON_TARGET=$TRAYICON_TARGET" echo "HIDECONSOLE_TARGET=$HIDECONSOLE_TARGET" - HIDECONSOLE_TARGET="$KEYMAN_ROOT/developer/src/server/src/win32/console/$HIDECONSOLE_TARGET" - TRAYICON_TARGET="$KEYMAN_ROOT/developer/src/server/src/win32/trayicon/$TRAYICON_TARGET" + local HIDECONSOLE_SRC_TARGET="$KEYMAN_ROOT/developer/src/server/src/win32/console/$HIDECONSOLE_TARGET" + local HIDECONSOLE_BIN_TARGET="$KEYMAN_ROOT/developer/src/server/build/src/win32/console/$HIDECONSOLE_TARGET" + local TRAYICON_SRC_TARGET="$KEYMAN_ROOT/developer/src/server/src/win32/trayicon/$TRAYICON_TARGET" + local TRAYICON_BIN_TARGET="$KEYMAN_ROOT/developer/src/server/build/src/win32/trayicon/$TRAYICON_TARGET" + + mkdir -p "$(dirname "$HIDECONSOLE_BIN_TARGET")" + mkdir -p "$(dirname "$TRAYICON_BIN_TARGET")" # # Build node-windows-trayicon @@ -39,7 +44,8 @@ do_build_addins() { pushd "$KEYMAN_ROOT/node_modules/node-windows-trayicon" rm -rf build npx node-gyp clean configure build --arch=$ARCH --silent - cp build/Release/addon.node "$TRAYICON_TARGET" + cp build/Release/addon.node "$TRAYICON_SRC_TARGET" + cp build/Release/addon.node "$TRAYICON_BIN_TARGET" popd # @@ -49,7 +55,8 @@ do_build_addins() { pushd "$KEYMAN_ROOT/node_modules/hetrodo-node-hide-console-window-napi" rm -rf build npx node-gyp clean configure build --arch=$ARCH --silent - cp build/Release/node-hide-console-window.node "$HIDECONSOLE_TARGET" + cp build/Release/node-hide-console-window.node "$HIDECONSOLE_SRC_TARGET" + cp build/Release/node-hide-console-window.node "$HIDECONSOLE_BIN_TARGET" popd # @@ -57,10 +64,10 @@ do_build_addins() { # if (( NODEX64 )); then - [[ $(isFileX64 "$TRAYICON_TARGET") == 1 ]] || builder_die "$TRAYICON_TARGET should be 64-bit" - [[ $(isFileX64 "$HIDECONSOLE_TARGET") == 1 ]] || builder_die "$HIDECONSOLE_TARGET should be 64-bit" + [[ $(isFileX64 "$TRAYICON_BIN_TARGET") == 1 ]] || builder_die "$TRAYICON_TARGET should be 64-bit" + [[ $(isFileX64 "$HIDECONSOLE_BIN_TARGET") == 1 ]] || builder_die "$HIDECONSOLE_TARGET should be 64-bit" else - [[ $(isFileX64 "$TRAYICON_TARGET") == 0 ]] || builder_die "$TRAYICON_TARGET should not be 64-bit" - [[ $(isFileX64 "$HIDECONSOLE_TARGET") == 0 ]] || builder_die "$HIDECONSOLE_TARGET should not be 64-bit" + [[ $(isFileX64 "$TRAYICON_BIN_TARGET") == 0 ]] || builder_die "$TRAYICON_TARGET should not be 64-bit" + [[ $(isFileX64 "$HIDECONSOLE_BIN_TARGET") == 0 ]] || builder_die "$HIDECONSOLE_TARGET should not be 64-bit" fi } diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 7e47d4f663d..51523c52a3e 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -32,7 +32,7 @@ builder_describe_outputs \ configure:server /node_modules \ configure:addins /node_modules \ build:server /developer/src/server/build/src/index.js \ - build:addins /developer/src/server/build/src/win32/console/index.js + build:addins /developer/src/server/build/src/win32/trayicon/addon.node builder_parse "$@" From 3f21f65c43124b779c98ed10bac1eb37593509d5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 14:46:14 +0700 Subject: [PATCH 148/207] chore(common): fixup sourcemap-path-remapper build settings --- common/tools/sourcemap-path-remapper/src/convert-source-map.d.ts | 1 + common/tools/sourcemap-path-remapper/tsconfig.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 common/tools/sourcemap-path-remapper/src/convert-source-map.d.ts diff --git a/common/tools/sourcemap-path-remapper/src/convert-source-map.d.ts b/common/tools/sourcemap-path-remapper/src/convert-source-map.d.ts new file mode 100644 index 00000000000..15a4da3c643 --- /dev/null +++ b/common/tools/sourcemap-path-remapper/src/convert-source-map.d.ts @@ -0,0 +1 @@ +declare module 'convert-source-map'; \ No newline at end of file diff --git a/common/tools/sourcemap-path-remapper/tsconfig.json b/common/tools/sourcemap-path-remapper/tsconfig.json index 6bf1a23b8d2..e207097f614 100644 --- a/common/tools/sourcemap-path-remapper/tsconfig.json +++ b/common/tools/sourcemap-path-remapper/tsconfig.json @@ -5,6 +5,7 @@ "allowJs": false, "allowSyntheticDefaultImports": true, "declaration": true, + "sourceMap": false, "inlineSourceMap": true, "inlineSources": true, "sourceRoot": "/common/tools/sourcemap-path-remapper/src", From 6cfcf08f3fad0cfdf389fea5da7e8dfdc989427e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 15:32:43 +0700 Subject: [PATCH 149/207] chore(web): reduce strictness on ts --- web/tsconfig.base.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/tsconfig.base.json b/web/tsconfig.base.json index fa60ca26e6d..ccb568c867c 100644 --- a/web/tsconfig.base.json +++ b/web/tsconfig.base.json @@ -1,14 +1,24 @@ { "extends": "../tsconfig.base.json", + // TODO: eliminate settings duplicated in ../tsconfig.base.json "compilerOptions": { // Primary settings - the version of ES6 we can target in TS, our downcompile target, // and our module-related settings. "allowSyntheticDefaultImports": true, + "lib": ["es6"], "module": "es6", "target": "es5", + // TODO: These override ../tsconfig.base.json settings, and so should be removed if possible, + // but existing code in web/ breaks some of these settinsg + "noImplicitThis": false, + "noImplicitReturns": false, + "noImplicitAny": false, + "strictFunctionTypes": false, + "noUnusedLocals": false, + // Other settings - declaration files, sourcemapping, and other miscellaneous bits. "allowJs": false, "declaration": true, From 7e7c81e7ee28c7a0fa7ef80f735877730f3a7607 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 15:48:53 +0700 Subject: [PATCH 150/207] chore(common): reduce strictness on ts for sentry-manager --- common/web/sentry-manager/src/tsconfig.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/web/sentry-manager/src/tsconfig.json b/common/web/sentry-manager/src/tsconfig.json index f42eaea73bd..1b438c93c4b 100644 --- a/common/web/sentry-manager/src/tsconfig.json +++ b/common/web/sentry-manager/src/tsconfig.json @@ -1,6 +1,16 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + + // TODO: These override /tsconfig.base.json settings, and so should be removed if possible, + // but existing code in web/ breaks some of these settinsg + "noImplicitThis": false, + "noImplicitReturns": false, + "noImplicitAny": false, + "strictFunctionTypes": false, + "noUnusedLocals": false, + + "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": "./", From 4c0cab2796dad1d02be58a7ca36fc9db6fee7976 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 5 Oct 2023 17:40:30 -0500 Subject: [PATCH 151/207] =?UTF-8?q?feat(common,developer):=20DRY=20out=20s?= =?UTF-8?q?trings=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor string preprocessing pipeline into an options bag --- common/web/types/src/kmx/element-string.ts | 6 +- common/web/types/src/kmx/kmx-plus.ts | 99 +++++++++++---------- common/web/types/src/kmx/string-list.ts | 7 +- developer/src/kmc-ldml/src/compiler/disp.ts | 16 ++-- developer/src/kmc-ldml/src/compiler/keys.ts | 62 ++++++++----- developer/src/kmc-ldml/src/compiler/tran.ts | 25 +++--- developer/src/kmc-ldml/src/compiler/vars.ts | 2 +- 7 files changed, 123 insertions(+), 94 deletions(-) diff --git a/common/web/types/src/kmx/element-string.ts b/common/web/types/src/kmx/element-string.ts index ed64a498ff8..816627c3d36 100644 --- a/common/web/types/src/kmx/element-string.ts +++ b/common/web/types/src/kmx/element-string.ts @@ -80,16 +80,16 @@ export class ElementString extends Array { throw Error(`Could not parse uset ${item.segment}`); } elem.uset = sections.uset.allocUset(uset, sections); - elem.value = sections.strs.allocString('', true); // no string + elem.value = sections.strs.allocString('', {singleOk: true}); // no string } else if (item.type === ElementType.codepoint || item.type === ElementType.escaped || item.type === ElementType.string) { // some kind of a string let str = item.segment; if (item.type === ElementType.escaped && !MATCH_HEX_ESCAPE.test(str)) { str = unescapeOneQuadString(str); // TODO-LDML: any other escape forms here? - elem.value = sections.strs.allocString(str, true); + elem.value = sections.strs.allocString(str, { singleOk: true }); } else { - elem.value = sections.strs.allocAndUnescapeString(str, true); + elem.value = sections.strs.allocString(str, { unescape: true, singleOk: true }); } // Now did we end up with one char or no? if (elem.value.isOneChar) { diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts index 6ffffc138f9..d28bcbfd3f9 100644 --- a/common/web/types/src/kmx/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -143,45 +143,67 @@ export class CharStrsItem extends StrsItem { } }; +/** class for string manipulation options. These are in order of the pipeline. */ +export interface StrsOptions { + /** apply string variables (requires sections) */ + stringVariables?: boolean; + /** apply markers (requires sections) */ + markers?: boolean; + /** unescape with unescapeString */ + unescape?: boolean; + /** string can be stored as a single CharStrsItem, not in strs table. */ + singleOk?: boolean; +}; + export class Strs extends Section { strings: StrsItem[] = [ new StrsItem('') ]; // C7043: The null string is always requierd /** * Allocate a StrsItem given the string, unescaping if necessary. * @param s escaped string - * @param singleOk if true, allocate a CharStrsItem (not in strs table) if single-char capable. - * @returns - */ - allocAndUnescapeString(s?: string, singleOk?: boolean): StrsItem { - return this.allocString(unescapeString(s), singleOk); - } - /** - * Allocate a StrsItem given the string. - * @param s string - * @param singleOk if true, allocate a CharStrsItem (not in strs table) if single-char capable. - * @returns + * @param opts options for allocation + * @param sections other sections, if needed + * @returns StrsItem */ - allocString(s?: string, singleOk?: boolean): StrsItem { - if(s === undefined || s === null) { - // undefined or null are always equivalent to empty string, see C7043 - s = ''; - } + allocString(s?: string, opts?: StrsOptions, sections?: DependencySections): StrsItem { + // Run the string processing pipeline + s = Strs.processString(s, opts, sections); - if(typeof s !== 'string') { - throw new Error('alloc_string: s must be a string, undefined, or null.'); - } - - // if it's a single char, don't push it into the list - if (singleOk && isOneChar(s)) { + // if it's a single char, don't push it into the strs table + if (opts?.singleOk && isOneChar(s)) { return new CharStrsItem(s); } + // default: look to see if the string is already present let result = this.strings.find(item => item.value === s); if(result === undefined) { + // only add if not already present result = new StrsItem(s); this.strings.push(result); } return result; } + + /** process everything according to opts */ + static processString(s: string, opts: StrsOptions, sections: DependencySections) { + s = s ?? ''; + // type check everything else + if (typeof s !== 'string') { + throw new Error('alloc_string: s must be a string, undefined, or null.'); + } + // substitute variables + if (opts?.stringVariables) { + s = sections.vars.substituteStrings(s, sections); + } + // substitute markers + if (opts?.markers) { + s = sections.vars.substituteMarkerString(s); + } + // unescape \u{…} + if (opts?.unescape) { + s = unescapeString(s); + } + return s; + } }; /** @@ -310,7 +332,7 @@ export class VarsItem extends Section { constructor(id: string, value: string, sections: DependencySections) { super(); this.id = sections.strs.allocString(id); - this.value = sections.strs.allocAndUnescapeString(value); + this.value = sections.strs.allocString(value, {unescape: true}); } valid() : boolean { @@ -500,31 +522,14 @@ export class List extends Section { * Allocate a list from a space-separated list of items. * Note that passing undefined or null or `''` will * end up being the same as the empty list `[]` - * @param strs Strs section for allocation * @param s space-separated list of items + * @param opts string options + * @param sections sections * @returns a List object */ - allocListFromSpaces(strs: Strs, s?: string): ListItem { + allocListFromSpaces(s: string, opts: StrsOptions, sections: DependencySections): ListItem { s = s ?? ''; - return this.allocList(strs, s.split(' ')); - } - allocListFromEscapedSpaces(strs: Strs, s?: string): ListItem { - if(s === undefined || s === null) { - s = ''; - } - return this.allocList(strs, s.split(' ').map(unescapeString)); - } - /** perform string variable, marker, and unescaping */ - allocListFromSubstitutedSpaces(s: string, sections: DependencySections): ListItem { - if(s === undefined || s === null) { - s = ''; - } - return this.allocList(sections.strs, s.split(' ').map(s => { - s = sections.vars.substituteStrings(s, sections); - s = sections.vars.substituteMarkerString(s); - s = unescapeString(s); - return s; - })); + return this.allocList(s.split(' '), opts, sections); } /** * Return a List object referring to the string list. @@ -534,7 +539,7 @@ export class List extends Section { * @param s string list to allocate * @returns */ - allocList(strs: Strs, s?: string[]): ListItem { + allocList(s: string[], opts: StrsOptions, sections: DependencySections): ListItem { // Special case the 'null' list for [] or [''] if (!s || (s.length === 1 && s[0] === '')) { return this.lists[0]; @@ -542,14 +547,14 @@ export class List extends Section { let result = this.lists.find(item => item.isEqual(s)); if(result === undefined) { // allocate a new ListItem - result = new ListItem(strs, s); + result = new ListItem(s, opts, sections); this.lists.push(result); } return result; } constructor(strs: Strs) { super(); - this.lists.push(new ListItem(strs, [])); // C7043: null element string + this.lists.push(new ListItem([], {}, { strs })); // C7043: null element string } lists: ListItem[] = []; }; diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/kmx/string-list.ts index f9f4b46e912..cbeb4e425dc 100644 --- a/common/web/types/src/kmx/string-list.ts +++ b/common/web/types/src/kmx/string-list.ts @@ -1,5 +1,5 @@ import { OrderedStringList } from 'src/ldml-keyboard/pattern-parser.js'; -import { Strs, StrsItem } from './kmx-plus.js'; +import { DependencySections, StrsItem, StrsOptions } from './kmx-plus.js'; /** * A single entry in a ListItem. @@ -31,14 +31,13 @@ export class ListItem extends Array implements OrderedStringList { * @param source array of strings * @returns */ - constructor(strs: Strs, source: Array) { + constructor(source: Array, opts: StrsOptions, sections: DependencySections) { super(); if(!source) { return; } - for (const str of source) { - let index = new ListIndex(strs.allocString(str)); + let index = new ListIndex(sections.strs.allocString(str, opts, sections)); this.push(index); } } diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts index a3d8ae36fc8..ade215b1fa7 100644 --- a/developer/src/kmc-ldml/src/compiler/disp.ts +++ b/developer/src/kmc-ldml/src/compiler/disp.ts @@ -57,16 +57,20 @@ export class DispCompiler extends SectionCompiler { let result = new Disp(); // displayOptions - result.baseCharacter = sections.strs.allocAndUnescapeString(this.keyboard3.displays?.displayOptions?.baseCharacter); + result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, {unescape: true}); // displays result.disps = this.keyboard3.displays?.display.map(display => ({ - to: sections.strs.allocAndUnescapeString( - sections.vars.substituteMarkerString( - sections.vars.substituteStrings(display.to, sections))), + to: sections.strs.allocString(display.to, { + stringVariables: true, + markers: true, + unescape: true, + }, sections), id: sections.strs.allocString(display.id), // not escaped, not substituted - display: sections.strs.allocAndUnescapeString( - sections.vars.substituteStrings(display.display, sections)), + display: sections.strs.allocString(display.display, { + stringVariables: true, + unescape: true, + }, sections), })) || []; // TODO-LDML: need coverage for the [] result.disps.sort((a: DispItem, b: DispItem) => { diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts index aad5e76f37a..a09ebbf2b23 100644 --- a/developer/src/kmc-ldml/src/compiler/keys.ts +++ b/developer/src/kmc-ldml/src/compiler/keys.ts @@ -130,18 +130,18 @@ export class KeysCompiler extends SectionCompiler { for (let lkflick of lkflicks.flick) { let flags = 0; - let cookedTo = lkflick.to; - // pull in string variables and markers - cookedTo = sections.vars.substituteStrings(cookedTo, sections); - cookedTo = sections.vars.substituteMarkerString(cookedTo); - const to = sections.strs.allocAndUnescapeString(cookedTo, true); + const to = sections.strs.allocString(lkflick.to, { + stringVariables: true, markers: true, unescape: true, singleOk: true + }, sections); if (!to.isOneChar) { flags |= constants.keys_flick_flags_extend; } let directions: ListItem = sections.list.allocListFromSpaces( - sections.strs, - lkflick.directions - ); + lkflick.directions, + { + stringVariables: true, markers: true, unescape: true + }, + sections); flicks.flicks.push({ directions, flags, @@ -171,24 +171,44 @@ export class KeysCompiler extends SectionCompiler { flags |= constants.keys_key_flags_notransform; } const id = sections.strs.allocString(key.id); - const longPress: ListItem = sections.list.allocListFromSubstitutedSpaces( - key.longPress, - sections, - ); - let cookedLongPressDefault = key.longPressDefault; - cookedLongPressDefault = sections.vars.substituteStrings(cookedLongPressDefault, sections); - cookedLongPressDefault = sections.vars.substituteMarkerString(cookedLongPressDefault) - const longPressDefault = sections.strs.allocAndUnescapeString(cookedLongPressDefault); - - const multiTap: ListItem = sections.list.allocListFromSubstitutedSpaces( + const longPress: ListItem = sections.list.allocListFromSpaces( + key.longPress, { + stringVariables: true, + markers: true, + unescape: true, + }, + sections); + + const longPressDefault = sections.strs.allocString(key.longPressDefault, + { + stringVariables: true, + markers: true, + unescape: true, + }, + sections); + + const multiTap: ListItem = sections.list.allocListFromSpaces( key.multiTap, - sections, - ); + { + stringVariables: true, + markers: true, + unescape: true, + }, + sections); const keySwitch = sections.strs.allocString(key.switch); // 'switch' is a reserved word + const toRaw = key.to; + let toCooked = sections.vars.substituteStrings(toRaw, sections); toCooked = sections.vars.substituteMarkerString(toCooked); - const to = sections.strs.allocAndUnescapeString(toCooked, true); + const to = sections.strs.allocString(key.to, + { + stringVariables: true, + markers: true, + unescape: true, + singleOk: true + }, + sections); if (!to.isOneChar) { flags |= constants.keys_key_flags_extend; } diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts index f874273f6f6..89a9d61f1cf 100644 --- a/developer/src/kmc-ldml/src/compiler/tran.ts +++ b/developer/src/kmc-ldml/src/compiler/tran.ts @@ -121,12 +121,11 @@ export class TransformCompiler m !== MarkerParser.ANY_MARKER_ID).sort(); - result.markers = sections.list.allocList(sections.strs, allMarkers); + result.markers = sections.list.allocList(allMarkers, {}, sections); return result.valid() ? result : null; } From b8b0e561e8063e4b8e5cb1b593290cb83931820e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 12:29:02 +1100 Subject: [PATCH 152/207] chore: Add comment to common/web/types/src/schema-validators.ts --- common/web/types/src/schema-validators.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts index 340517b188d..07bcc032f16 100644 --- a/common/web/types/src/schema-validators.ts +++ b/common/web/types/src/schema-validators.ts @@ -8,6 +8,8 @@ import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; +// How to use: https://ajv.js.org/standalone.html#using-the-validation-function-s +// See also existing uses (search for `SchemaValidators`) for examples. const SchemaValidators = { kpj, kpj90, From eeb755f76a2b65ff95d9c1a2eabeff154558f040 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 09:14:35 +0700 Subject: [PATCH 153/207] fix(windows): re-enable signature check Fixes #9692. Signature checking was skipped because we missed a ".virtual" to force nmake to build the test and test_i3633 targets. This opened up a small cascade of related formatting issues on Makefiles, and the fact that the test_i3633 (has there ever been a more poorly named project?) Makefile did not even work. Refactored significantly, added same tests to Developer Makefile, and also now verifying the .msi and installer executable. We can improve this further but I'd like to get this in to avoid further critical issues with code signing given the current broken signing configuration. --- .../windows/delphi/ext/sentry/test/Makefile | 3 +- common/windows/delphi/tools/Makefile | 6 +++- .../delphi/tools/verify_signatures/Makefile | 13 ++++++++ .../tools/verify_signatures}/sigcheck.bin | Bin .../verify_signatures/verify_signatures.dpr | 4 +-- .../verify_signatures/verify_signatures.dproj | 6 ++-- .../verify_signatures/verify_signatures.res | Bin developer/src/Makefile | 2 ++ developer/src/inst/download.in.mak | 3 ++ .../src/tools/verify_signatures/Makefile | 19 +++++++++++ windows/src/Defines.mak | 9 +++++ windows/src/Makefile | 2 +- windows/src/desktop/inst/download.in | 4 +++ windows/src/desktop/insthelp/Makefile | 4 +-- windows/src/desktop/kmconfig/Makefile | 2 +- windows/src/desktop/kmshell/Makefile | 2 +- windows/src/support/charident/Makefile | 2 +- windows/src/support/km_yim/Makefile | 5 +-- windows/src/test/Makefile | 14 ++++---- windows/src/test/test_i3633/Makefile | 31 ------------------ windows/src/test/verify_signatures/Makefile | 21 ++++++++++++ 21 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 common/windows/delphi/tools/verify_signatures/Makefile rename {windows/src/test/test_i3633 => common/windows/delphi/tools/verify_signatures}/sigcheck.bin (100%) rename windows/src/test/test_i3633/verify.dpr => common/windows/delphi/tools/verify_signatures/verify_signatures.dpr (94%) rename windows/src/test/test_i3633/verify.dproj => common/windows/delphi/tools/verify_signatures/verify_signatures.dproj (99%) rename windows/src/test/test_i3633/verify.res => common/windows/delphi/tools/verify_signatures/verify_signatures.res (100%) create mode 100644 developer/src/tools/verify_signatures/Makefile delete mode 100644 windows/src/test/test_i3633/Makefile create mode 100644 windows/src/test/verify_signatures/Makefile diff --git a/common/windows/delphi/ext/sentry/test/Makefile b/common/windows/delphi/ext/sentry/test/Makefile index 63fc664b011..b2e92bb8964 100644 --- a/common/windows/delphi/ext/sentry/test/Makefile +++ b/common/windows/delphi/ext/sentry/test/Makefile @@ -4,7 +4,8 @@ !include ..\..\..\Defines.mak -build: dirs # version.res manifest.res +build: dirs +# version.res manifest.res $(DELPHI_MSBUILD) SentryClientTest.dproj "/p:Platform=Win32" $(DELPHI_MSBUILD) SentryClientVclTest.dproj "/p:Platform=Win32" diff --git a/common/windows/delphi/tools/Makefile b/common/windows/delphi/tools/Makefile index a9c1151473a..2ea11ab2372 100644 --- a/common/windows/delphi/tools/Makefile +++ b/common/windows/delphi/tools/Makefile @@ -7,7 +7,7 @@ NOTARGET_SIGNCODE=yes !ifdef NODELPHI TARGETS=.virtual !else -TARGETS=build_standards_data buildunidata devtools sentrytool test-klog +TARGETS=build_standards_data buildunidata devtools sentrytool test-klog verify_signatures !endif CLEANS=clean-tools @@ -28,6 +28,10 @@ buildunidata: .virtual cd $(COMMON_ROOT)\tools\buildunidata $(MAKE) $(TARGET) +verify_signatures: .virtual + cd $(COMMON_ROOT)\tools\verify_signatures + $(MAKE) $(TARGET) + devtools: .virtual !ifdef NODELPHI echo Skipping devtools diff --git a/common/windows/delphi/tools/verify_signatures/Makefile b/common/windows/delphi/tools/verify_signatures/Makefile new file mode 100644 index 00000000000..0f67747665e --- /dev/null +++ b/common/windows/delphi/tools/verify_signatures/Makefile @@ -0,0 +1,13 @@ +# +# test for signatures and version information being correct in ?install directory or ?bin directory +# + +!include ..\..\Defines.mak + +build: + $(DELPHI_MSBUILD) verify_signatures.dproj + copy sigcheck.bin $(WIN32_TARGET_PATH)\sigcheck.exe + +clean: def-clean + +!include ..\..\Target.mak diff --git a/windows/src/test/test_i3633/sigcheck.bin b/common/windows/delphi/tools/verify_signatures/sigcheck.bin similarity index 100% rename from windows/src/test/test_i3633/sigcheck.bin rename to common/windows/delphi/tools/verify_signatures/sigcheck.bin diff --git a/windows/src/test/test_i3633/verify.dpr b/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr similarity index 94% rename from windows/src/test/test_i3633/verify.dpr rename to common/windows/delphi/tools/verify_signatures/verify_signatures.dpr index b3707d84617..fe77f887d7f 100644 --- a/windows/src/test/test_i3633/verify.dpr +++ b/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr @@ -1,4 +1,4 @@ -program verify; +program verify_signatures; {$APPTYPE CONSOLE} @@ -90,7 +90,7 @@ begin if (ParamStr(1) = '-?') or (ParamCount < 1) then begin - writeln('verify [-d] VERSION.md: Verify the output of sigcheck to ensure all executables are signed and have proper version.'); + writeln('verify_signatures [-d] VERSION.md: Verify the output of sigcheck to ensure all executables are signed and have proper version.'); writeln(' -d: Check the timestamp on the signature is less than 2 days old.'); writeln(' VERSION.md: path to the version to verify against'); Halt(2); diff --git a/windows/src/test/test_i3633/verify.dproj b/common/windows/delphi/tools/verify_signatures/verify_signatures.dproj similarity index 99% rename from windows/src/test/test_i3633/verify.dproj rename to common/windows/delphi/tools/verify_signatures/verify_signatures.dproj index e23aeb3a4d8..323353c79b9 100644 --- a/windows/src/test/test_i3633/verify.dproj +++ b/common/windows/delphi/tools/verify_signatures/verify_signatures.dproj @@ -3,7 +3,7 @@ {A6DCA558-8DD0-4FFF-8DAE-F305AB8D2AF4} 18.8 None - verify.dpr + verify_signatures.dpr True Debug Win32 @@ -45,7 +45,7 @@ true - verify + verify_signatures $(BDS)\bin\delphi_PROJECTICNS.icns $(BDS)\bin\delphi_PROJECTICON.ico bindcompfmx;fmx;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;xmlrtl;DbxCommonDriver;DBXMySQLDriver;dbxcds;soaprtl;bindengine;CustomIPTransport;dsnap;fmxase;inet;fmxobj;inetdbxpress;fmxdae;dbexpress;$(DCC_UsePackage) @@ -161,7 +161,7 @@ - verify.dpr + verify_signatures.dpr Microsoft Office 2000 Sample Automation Server Wrapper Components diff --git a/windows/src/test/test_i3633/verify.res b/common/windows/delphi/tools/verify_signatures/verify_signatures.res similarity index 100% rename from windows/src/test/test_i3633/verify.res rename to common/windows/delphi/tools/verify_signatures/verify_signatures.res diff --git a/developer/src/Makefile b/developer/src/Makefile index 0c6c81ece6b..6a1cf398e51 100644 --- a/developer/src/Makefile +++ b/developer/src/Makefile @@ -153,6 +153,8 @@ do-build-release: $(MAKE) build-release-dependencies $(MAKE) "SIGNCODE_BUILD=SIGNCODE_BUILD" build test $(MAKE) signcode + cd $(DEVELOPER_ROOT)\src\tools\verify_signatures + $(MAKE) test cd $(DEVELOPER_ROOT)\src\inst $(MAKE) diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index eeadd67ac0a..871f4b012c0 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -26,6 +26,9 @@ copykmdev: makeinstaller make-kmcomp-install-zip -mkdir $(DEVELOPER_ROOT)\release\$Version copy /Y $(DEVELOPER_ROOT)\src\inst\keymandeveloper.msi $(DEVELOPER_ROOT)\release\$Version\keymandeveloper.msi copy /Y $(DEVELOPER_ROOT)\src\inst\keymandeveloper-$Version.exe $(DEVELOPER_ROOT)\release\$Version\keymandeveloper-$Version.exe + $(SIGCHECK) $(DEVELOPER_ROOT)\release\$Version\* > sig1 + $(VERIFY_SIGNATURES) < sig1 + -del sig1 test-releaseexists: if exist $(DEVELOPER_ROOT)\release\$Version\keymandeveloper*.msi echo. & echo Release $Version already exists. Delete it or update VERSION.md and try again & exit 1 diff --git a/developer/src/tools/verify_signatures/Makefile b/developer/src/tools/verify_signatures/Makefile new file mode 100644 index 00000000000..003f6aa5d50 --- /dev/null +++ b/developer/src/tools/verify_signatures/Makefile @@ -0,0 +1,19 @@ +# +# test for signatures and version information being correct in bin folder +# + +!include ..\..\Defines.mak + +test: prereq + $(SIGCHECK) $(DEVELOPER_PROGRAM)\* > sig1 + $(VERIFY_SIGNATURES) < sig1 + +# prereq may not be needed? +prereq: + cd $(VERIFY_SIGNATURES_PATH) + $(MAKE) + +clean: def-clean + -del sig1 + +!include ..\..\Target.mak diff --git a/windows/src/Defines.mak b/windows/src/Defines.mak index bf8f77b9987..9506dfa6df0 100644 --- a/windows/src/Defines.mak +++ b/windows/src/Defines.mak @@ -322,3 +322,12 @@ SYMSTORE="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\symstore.exe" add /compress /f CLEAN=-del /S /Q + +# +# Signature checks +# + +VERIFY_SIGNATURES_PATH=$(KEYMAN_ROOT)\common\windows\delphi\tools\verify_signatures +# Note: hyphen prefix is important to ignore return value from sigcheck +SIGCHECK=-$(VERIFY_SIGNATURES_PATH)\$(WIN32_TARGET_PATH)\sigcheck -q -s -e -v -accepteula +VERIFY_SIGNATURES=$(VERIFY_SIGNATURES_PATH)\$(WIN32_TARGET_PATH)\verify_signatures -d $(KEYMAN_ROOT)\VERSION.md diff --git a/windows/src/Makefile b/windows/src/Makefile index 7dda8852632..e9048fcf71b 100644 --- a/windows/src/Makefile +++ b/windows/src/Makefile @@ -81,7 +81,7 @@ support: global $(MAKE) $(TARGET) cd $(ROOT)\src -test: +test: .virtual cd $(ROOT)\src\test $(MAKE) $(TARGET) cd $(ROOT)\src diff --git a/windows/src/desktop/inst/download.in b/windows/src/desktop/inst/download.in index 0bf062eeba4..17b832210cc 100644 --- a/windows/src/desktop/inst/download.in +++ b/windows/src/desktop/inst/download.in @@ -17,6 +17,10 @@ copyredist-desktop: copy /Y keymandesktop.exe $(ROOT)\release\$Version\keyman-$Version.exe copy /Y $(ROOT)\bin\desktop\setup.exe $(ROOT)\release\$Version\setup.exe + $(SIGCHECK) $(ROOT)\release\$Version\* > sig1 + $(VERIFY_SIGNATURES) < sig1 + -del sig1 + # Copy the unsigned setup.exe for use in bundling scenarios; zip it up for clarity $(WZZIP) $(ROOT)\release\$Version\setup-redist.zip $(ROOT)\bin\desktop\setup-redist.exe diff --git a/windows/src/desktop/insthelp/Makefile b/windows/src/desktop/insthelp/Makefile index b3d4dcdf070..b1ca957636e 100644 --- a/windows/src/desktop/insthelp/Makefile +++ b/windows/src/desktop/insthelp/Makefile @@ -11,9 +11,9 @@ build: version.res dirs $(COPY) $(WIN32_TARGET_PATH)\insthelp.exe $(ROOT)\bin\desktop\insthelp.exe test-manifest: - # test that (a) linked manifest exists and correct, and (b) has uiAccess=true +# test that (a) linked manifest exists and correct, and (b) has uiAccess=true @rem $(MT) -nologo -inputresource:$(PROGRAM)\desktop\insthelp.exe -validate_manifest - # TODO: Investigate why no manifest included? +# TODO: Investigate why no manifest included? clean: def-clean diff --git a/windows/src/desktop/kmconfig/Makefile b/windows/src/desktop/kmconfig/Makefile index d7f308f9d65..98028758335 100644 --- a/windows/src/desktop/kmconfig/Makefile +++ b/windows/src/desktop/kmconfig/Makefile @@ -21,7 +21,7 @@ wrap-symbols: $(SYMSTORE) $(DEBUGPATH)\desktop\kmconfig.dbg /t keyman-windows test-manifest: - # test that linked manifest exists and correct +# test that linked manifest exists and correct $(MT) -nologo -inputresource:$(PROGRAM)\desktop\kmconfig.exe -validate_manifest install: diff --git a/windows/src/desktop/kmshell/Makefile b/windows/src/desktop/kmshell/Makefile index caaa7da54e0..773942d0a1b 100644 --- a/windows/src/desktop/kmshell/Makefile +++ b/windows/src/desktop/kmshell/Makefile @@ -39,7 +39,7 @@ wrap-symbols: $(SYMSTORE) $(DEBUGPATH)\desktop\kmshell.dbg /t keyman-windows test-manifest: - # test that (a) linked manifest exists and correct, and (b) has uiAccess=true +# test that (a) linked manifest exists and correct, and (b) has uiAccess=true $(MT) -nologo -inputresource:$(PROGRAM)\desktop\kmshell.exe -validate_manifest install: .virtual diff --git a/windows/src/support/charident/Makefile b/windows/src/support/charident/Makefile index dd6721c5c0a..cff67416776 100644 --- a/windows/src/support/charident/Makefile +++ b/windows/src/support/charident/Makefile @@ -15,6 +15,6 @@ signcode: wrap-symbols: $(SYMSTORE) $(PROGRAM)\support\charident.exe /t keyman-windows - #TODO: $(SYMSTORE) $(DEBUGPATH)\support\charident.dbg /t keyman-windows +#TODO: $(SYMSTORE) $(DEBUGPATH)\support\charident.dbg /t keyman-windows !include ..\..\Target.mak diff --git a/windows/src/support/km_yim/Makefile b/windows/src/support/km_yim/Makefile index f0f93e6fe22..1aef5f80f02 100644 --- a/windows/src/support/km_yim/Makefile +++ b/windows/src/support/km_yim/Makefile @@ -4,12 +4,13 @@ !include ..\..\Defines.mak -build: # version.res +build: +# version.res $(DCC32) km_yim.dpr rem $(TDSPACK) $(PROGRAM)\desktop\km_yim.exe km_yim.tds rem $(TDS2DBG) $(PROGRAM)\desktop\km_yim.exe $(WZZIP) inst_km_yim.zip km_yim.exe - # $(WZSE) inst_km_yim -setup -t inst_km_yim.dialog.txt -st "Tavultesoft Keyman Desktop Yahoo Messenger Addin" -c km_yim.exe +# $(WZSE) inst_km_yim -setup -t inst_km_yim.dialog.txt -st "Tavultesoft Keyman Desktop Yahoo Messenger Addin" -c km_yim.exe clean: def-clean if exist inst_km_yim.zip del inst_km_yim.zip diff --git a/windows/src/test/Makefile b/windows/src/test/Makefile index 5df2892dfad..5e8dec85b53 100644 --- a/windows/src/test/Makefile +++ b/windows/src/test/Makefile @@ -4,12 +4,14 @@ # ---------------------------------------------------------------------- +# TODO: both test-manifest-exec and verify_signatures are really part of buildtools + !ifdef NODELPHI TARGETS=.virtual !else -TARGETS=test_i3633 +TARGETS=verify_signatures !endif -CLEAN=test_i3633 +CLEAN=verify_signatures test: test-manifest-exec $(MAKE) "TARGET=test" $(TARGETS) @@ -22,10 +24,10 @@ test-manifest-exec: $(MAKE) test-manifest cd test -# test_i3633: validate certificates and binary metadata on executables -# TODO: Move this to buildtools -test_i3633: - cd $(ROOT)\src\test\test_i3633 +# validate certificates and binary metadata on executables +# TODO: move to buildtools? +verify_signatures: .virtual + cd $(ROOT)\src\buildtools\verify_signatures $(MAKE) $(TARGET) # ---------------------------------------------------------------------- diff --git a/windows/src/test/test_i3633/Makefile b/windows/src/test/test_i3633/Makefile deleted file mode 100644 index 575d77e7e83..00000000000 --- a/windows/src/test/test_i3633/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -# -# test for signatures and version information being correct in ?install directory or ?bin directory -# - -!include ..\..\Defines.mak - -SIGCHECK=-$(WIN32_TARGET_PATH)\sigcheck -q -s -e -v -accepteula -VERIFY=$(WIN32_TARGET_PATH)\verify -d $(KEYMAN_ROOT)\VERSION.md - -test: test-bin - -test-bin: prereq - $(SIGCHECK) $(ROOT)\bin\desktop\* > sig1 - $(SIGCHECK) $(ROOT)\bin\developer\* >> sig1 - $(SIGCHECK) $(ROOT)\bin\engine\* >> sig1 - $(SIGCHECK) $(ROOT)\bin\inst\* >> sig1 - $(VERIFY) < sig1 - -test-install: prereq - - -prereq: - $(DELPHI_MSBUILD) verify.dproj "/p:Platform=Win32" - copy sigcheck.bin $(WIN32_TARGET_PATH)\sigcheck.exe - - -clean: def-clean - -del sig1 - -del sig2 - -!include ..\..\Target.mak diff --git a/windows/src/test/verify_signatures/Makefile b/windows/src/test/verify_signatures/Makefile new file mode 100644 index 00000000000..dfd1495992c --- /dev/null +++ b/windows/src/test/verify_signatures/Makefile @@ -0,0 +1,21 @@ +# +# test for signatures and version information being correct in bin folder +# + +!include ..\..\Defines.mak + +test: prereq + $(SIGCHECK) $(ROOT)\bin\desktop\* > sig1 + $(SIGCHECK) $(ROOT)\bin\engine\* >> sig1 + $(SIGCHECK) $(ROOT)\bin\inst\* >> sig1 + $(VERIFY_SIGNATURES) < sig1 + +# prereq may not be needed? +prereq: + cd $(VERIFY_SIGNATURES) + $(MAKE) + +clean: def-clean + -del sig1 + +!include ..\..\Target.mak From 9461a3b5832faafbcc1fa6348e89fd05f845d8eb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 09:45:22 +0700 Subject: [PATCH 154/207] chore(windows): fix path in Makefile --- windows/src/test/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/test/Makefile b/windows/src/test/Makefile index 5e8dec85b53..2087f3d8a00 100644 --- a/windows/src/test/Makefile +++ b/windows/src/test/Makefile @@ -27,7 +27,7 @@ test-manifest-exec: # validate certificates and binary metadata on executables # TODO: move to buildtools? verify_signatures: .virtual - cd $(ROOT)\src\buildtools\verify_signatures + cd $(ROOT)\src\test\verify_signatures $(MAKE) $(TARGET) # ---------------------------------------------------------------------- From 702b3a0582da7cad144b8126064aaa788642eff2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 10:11:08 +0700 Subject: [PATCH 155/207] chore(windows): fix typo --- windows/src/test/verify_signatures/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/test/verify_signatures/Makefile b/windows/src/test/verify_signatures/Makefile index dfd1495992c..607e5d22cf9 100644 --- a/windows/src/test/verify_signatures/Makefile +++ b/windows/src/test/verify_signatures/Makefile @@ -12,7 +12,7 @@ test: prereq # prereq may not be needed? prereq: - cd $(VERIFY_SIGNATURES) + cd $(VERIFY_SIGNATURES_PATH) $(MAKE) clean: def-clean From 3dc9ac245f5d713376bd9e893b2baf08cdec9508 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 10:22:02 +0700 Subject: [PATCH 156/207] chore(ios): renew certificate --- ios/exportAppStore.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/exportAppStore.plist b/ios/exportAppStore.plist index 6393f3c4cbf..48ef58f532d 100644 --- a/ios/exportAppStore.plist +++ b/ios/exportAppStore.plist @@ -7,9 +7,9 @@ teamID 3YE4W86L3G signingCertificate - 05473BF50CCD4B78B304656FB4D93FDC7EE7ACD0 + BB842A0081298BBA6D034C85451750B08FC9EE8A installerSigningCertificate - 05473BF50CCD4B78B304656FB4D93FDC7EE7ACD0 + BB842A0081298BBA6D034C85451750B08FC9EE8A provisioningProfiles Tavultesoft.Keyman From 7c2d3624cdfd790740a84b5f4cd790bc93ee38b1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 10:37:25 +0700 Subject: [PATCH 157/207] chore(developer): sign addons in server --- developer/src/server/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/developer/src/server/Makefile b/developer/src/server/Makefile index 0a580ea3d6a..a66eb449c11 100644 --- a/developer/src/server/Makefile +++ b/developer/src/server/Makefile @@ -14,7 +14,10 @@ clean: .virtual -del tsconfig.tsbuildinfo signcode: - @rem nothing to do + $(SIGNCODE) /d "Keyman" build\src\win32\console\node-hide-console-window.node + $(SIGNCODE) /d "Keyman" build\src\win32\console\node-hide-console-window.x64.node + $(SIGNCODE) /d "Keyman" build\src\win32\trayicon\addon.node + $(SIGNCODE) /d "Keyman" build\src\win32\trayicon\addon.x64.node wrap-symbols: @rem nothing to do From c970b1e2460b26970e22f3ab367b8e39e0328411 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 11:38:55 +0700 Subject: [PATCH 158/207] chore(developer): fix paths for addon signing --- developer/src/server/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/developer/src/server/Makefile b/developer/src/server/Makefile index a66eb449c11..214732e41db 100644 --- a/developer/src/server/Makefile +++ b/developer/src/server/Makefile @@ -14,10 +14,10 @@ clean: .virtual -del tsconfig.tsbuildinfo signcode: - $(SIGNCODE) /d "Keyman" build\src\win32\console\node-hide-console-window.node - $(SIGNCODE) /d "Keyman" build\src\win32\console\node-hide-console-window.x64.node - $(SIGNCODE) /d "Keyman" build\src\win32\trayicon\addon.node - $(SIGNCODE) /d "Keyman" build\src\win32\trayicon\addon.x64.node + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\server\dist\win32\console\node-hide-console-window.node + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\server\dist\win32\console\node-hide-console-window.x64.node + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\server\dist\win32\trayicon\addon.node + $(SIGNCODE) /d "Keyman Developer" $(DEVELOPER_PROGRAM)\server\dist\win32\trayicon\addon.x64.node wrap-symbols: @rem nothing to do From bcb2233cfd520bfe45230fa57251cbca1f9d2f77 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 12:56:35 +0700 Subject: [PATCH 159/207] chore: ignore types for imported schemas for now --- common/web/types/src/schema-validators.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts index 07bcc032f16..8b89ca7bfd9 100644 --- a/common/web/types/src/schema-validators.ts +++ b/common/web/types/src/schema-validators.ts @@ -1,11 +1,28 @@ +//@ts-expect-error import kpj from './schemas/kpj.schema.validator.mjs'; + +//@ts-expect-error import kpj90 from './schemas/kpj-9.0.schema.validator.mjs'; + +//@ts-expect-error import kvks from './schemas/kvks.schema.validator.mjs'; + +//@ts-expect-error import ldmlKeyboard3 from './schemas/ldml-keyboard3.schema.validator.mjs'; + +//@ts-expect-error import ldmlKeyboardTest3 from './schemas/ldml-keyboardtest3.schema.validator.mjs'; + +//@ts-expect-error import displayMap from './schemas/displaymap.schema.validator.mjs'; + +//@ts-expect-error import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator.mjs'; + +//@ts-expect-error import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; + +//@ts-expect-error import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; // How to use: https://ajv.js.org/standalone.html#using-the-validation-function-s From 928bd2a7cd972584378d993ba9e69744a43bfcff Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 14:48:28 +0700 Subject: [PATCH 160/207] chore(common): fixup tsconfig for common/web/utils --- developer/src/common/web/utils/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json index dbb068a7a22..b509561362d 100644 --- a/developer/src/common/web/utils/tsconfig.json +++ b/developer/src/common/web/utils/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../../tsconfig.esm-base.json", + "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "rootDir": ".", From 2e4dd105229436b75e52bca0ef4613f92b676a4c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 6 Oct 2023 16:48:57 +0200 Subject: [PATCH 161/207] fix(linux): Explicitly initialize GTK Usually this is done automatically. However, if e.g. no `DISPLAY` variable is set it'll fail. When we explicitly initialize GTK it still fails, but shows a more helpful warning instead of throwing a runtime error. Fixes #9705. --- linux/keyman-config/km-config | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index dfdbb7d4517..604e7d17cf7 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -2,6 +2,12 @@ import argparse import logging +import sys +import gi + +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk from keyman_config import __versionwithtag__, __pkgversion__, add_standard_arguments, initialize_logging, initialize_sentry from keyman_config.handle_install import download_and_install_package @@ -30,6 +36,8 @@ if __name__ == '__main__': args = parser.parse_args() + Gtk.init(sys.argv[1:]) + initialize_logging(args) initialize_sentry() From 25c20ee812a619391574a9633c8e21e243a65b9d Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 6 Oct 2023 11:58:59 -0500 Subject: [PATCH 162/207] =?UTF-8?q?feat(common):=20fix=20marker=20parsing?= =?UTF-8?q?=20=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cleaned up regex For: #9119 --- .../web/types/src/ldml-keyboard/pattern-parser.ts | 10 ++++++++-- .../test/ldml-keyboard/test-pattern-parser.ts | 14 +++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/web/types/src/ldml-keyboard/pattern-parser.ts b/common/web/types/src/ldml-keyboard/pattern-parser.ts index b02440553e6..b0d6fb07962 100644 --- a/common/web/types/src/ldml-keyboard/pattern-parser.ts +++ b/common/web/types/src/ldml-keyboard/pattern-parser.ts @@ -64,9 +64,15 @@ export class MarkerParser { public static readonly MAX_MARKER_INDEX = constants.marker_max_index; /** Max count of markers */ public static readonly MAX_MARKER_COUNT = constants.marker_max_count; + + private static anyMarkerMatch() : string { + const start = (`0000` + (this.MIN_MARKER_INDEX).toString(16)).slice(-4); + const end = (`0000` + (this.MAX_MARKER_INDEX).toString(16)).slice(-4); + return `${this.SENTINEL}${this.MARKER_CODE}[\\u${start}-\\u${end}]`; + } + /** Expression that matches any marker */ - public static readonly ANY_MARKER_MATCH = - this.SENTINEL + this.MARKER_CODE + `[\\u0001-\\u${this.MAX_MARKER_INDEX.toString(16)}]`; + public static readonly ANY_MARKER_MATCH = MarkerParser.anyMarkerMatch(); /** * Pattern for matching a marker reference, OR the special marker \m{.} diff --git a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts b/common/web/types/test/ldml-keyboard/test-pattern-parser.ts index 1ea96797a7b..0274f2a5c27 100644 --- a/common/web/types/test/ldml-keyboard/test-pattern-parser.ts +++ b/common/web/types/test/ldml-keyboard/test-pattern-parser.ts @@ -105,16 +105,20 @@ describe('Test of Pattern Parsers', () => { ) ); // verify the matching behavior of these - assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{a}`, markers, true), 'u') + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`^Q\\m{a}$`, markers, true), 'u') .test(MarkerParser.toSentinelString(`Q\\m{a}`, markers, false)), `Q\\m{a} did not match`); - assert.isFalse(new RegExp(MarkerParser.toSentinelString(`Q\\m{a}`, markers, true), 'u') + assert.isFalse(new RegExp(MarkerParser.toSentinelString(`^Q\\m{a}$`, markers, true), 'u') .test(MarkerParser.toSentinelString(`Q\\m{b}`, markers, false)), `Q\\m{a} should not match Q\\m{b}`); - assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`^Q\\m{.}$`, markers, true), 'u') .test(MarkerParser.toSentinelString(`Q\\m{a}`, markers, false)), `Q\\m{.} did not match Q\\m{a}`); - assert.isTrue(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`^Q\\m{.}$`, markers, true), 'u') .test(MarkerParser.toSentinelString(`Q\\m{zz}`, markers, false)), `Q\\m{.} did not match Q\\m{zz} (max marker)`); - assert.isFalse(new RegExp(MarkerParser.toSentinelString(`Q\\m{.}`, markers, true), 'u') + assert.isFalse(new RegExp(MarkerParser.toSentinelString(`^Q\\m{.}$`, markers, true), 'u') .test(MarkerParser.toSentinelString(`\\m{a}`, markers, false)), `Q\\m{.} did not match \\m{a}`); + assert.isTrue(new RegExp(MarkerParser.toSentinelString(`^\\m{.}$`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`\\m{a}`, markers, false)), `\\m{.} did not match \\m{a}`); + assert.isFalse(new RegExp(MarkerParser.toSentinelString(`^\\m{.}$`, markers, true), 'u') + .test(MarkerParser.toSentinelString(`\\m{a}\\m{b}`, markers, false)), `\\m{.} did not match \\m{a}\\m{b}`); }); it('should match some marker constants', () => { assert.equal(constants.uc_sentinel, KMXFile.UC_SENTINEL); From 982fdd0318a8fd4a10dda826bec652ced6841393 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 7 Oct 2023 05:49:25 +0700 Subject: [PATCH 163/207] fix(developer): skip resource check for node addons --- .../delphi/tools/verify_signatures/verify_signatures.dpr | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr b/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr index fe77f887d7f..49478ac3edf 100644 --- a/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr +++ b/common/windows/delphi/tools/verify_signatures/verify_signatures.dpr @@ -36,6 +36,7 @@ begin Delete(s,1,1); end; + // TODO: sign these files and move the check down to the EndsWith node test if str[0].ToLower.Contains('sentry.') or str[0].ToLower.Contains('crashpad_handler') or str[0].ToLower.Contains('keymanmc') then // We don't verify sentry.dll or sentry.x64.dll or crashpad_handler.exe because they're not our files // We don't verify keymanmc.dll because it has no version resources, as it is mc-generated @@ -62,6 +63,14 @@ begin end; end; + if str[0].ToLower.EndsWith('node') then + begin + // It's one of the node addons -- in developer server at time of writing + // we don't have a version resource but we have signed it, so treat it + // as valid + Exit(''); + end; + if str[3] <> 'SIL International' then begin Exit('File has wrong company name'); From b7c7a1c7b36b57080bc6c779546126f4c284a8a1 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 6 Oct 2023 18:11:37 -0500 Subject: [PATCH 164/207] =?UTF-8?q?feat(developer):=20fix=20vars=20and=20m?= =?UTF-8?q?arkers=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - workaround for debugger/test issue For: #9121 For: #9119 --- common/web/types/src/kmx/string-list.ts | 12 ++++++++++-- developer/src/kmc-ldml/test/test-vars.ts | 5 +++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/kmx/string-list.ts index cbeb4e425dc..88ab5bbcf7f 100644 --- a/common/web/types/src/kmx/string-list.ts +++ b/common/web/types/src/kmx/string-list.ts @@ -73,10 +73,18 @@ export class ListItem extends Array implements OrderedStringList { } /** for debugging, print as single string */ toString(): string { - return this.toStringArray().join(' '); + let ss = ''; + for (let i = 0 ;i < this.length; i++) { + if (ss) { + ss = ss + ' '; + } + ss = ss + (this[i].toString()); + } + return ss; + // return this.toStringArray().join(' '); } /** for debugging, map to string array */ toStringArray(): string[] { - return this.map(v => v.value.value); + return this.map(v => v.toString()); } }; diff --git a/developer/src/kmc-ldml/test/test-vars.ts b/developer/src/kmc-ldml/test/test-vars.ts index 5bdd5e65f01..4b053c30d23 100644 --- a/developer/src/kmc-ldml/test/test-vars.ts +++ b/developer/src/kmc-ldml/test/test-vars.ts @@ -203,8 +203,9 @@ describe('vars', function () { callback(sect) { const vars = sect; assert.ok(vars.markers); - assert.sameDeepOrderedMembers(vars.markers.toStringArray(), - ['m','x']); + // assert.sameDeepOrderedMembers(vars.markers.toStringArray(), + // ['m','x']); + assert.equal(vars.markers.toString(), 'm x'); }, }, { From 00484a0e0f39166dbec2cdff817a2aea62dc025a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 7 Oct 2023 06:46:10 +0700 Subject: [PATCH 165/207] chore: fixup js imports for schema validators --- common/web/types/src/schema-validators.ts | 17 ----------------- tsconfig.base.json | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts index 8b89ca7bfd9..07bcc032f16 100644 --- a/common/web/types/src/schema-validators.ts +++ b/common/web/types/src/schema-validators.ts @@ -1,28 +1,11 @@ -//@ts-expect-error import kpj from './schemas/kpj.schema.validator.mjs'; - -//@ts-expect-error import kpj90 from './schemas/kpj-9.0.schema.validator.mjs'; - -//@ts-expect-error import kvks from './schemas/kvks.schema.validator.mjs'; - -//@ts-expect-error import ldmlKeyboard3 from './schemas/ldml-keyboard3.schema.validator.mjs'; - -//@ts-expect-error import ldmlKeyboardTest3 from './schemas/ldml-keyboardtest3.schema.validator.mjs'; - -//@ts-expect-error import displayMap from './schemas/displaymap.schema.validator.mjs'; - -//@ts-expect-error import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator.mjs'; - -//@ts-expect-error import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; - -//@ts-expect-error import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; // How to use: https://ajv.js.org/standalone.html#using-the-validation-function-s diff --git a/tsconfig.base.json b/tsconfig.base.json index 2c56fd70794..7630ddbfc4a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,6 +12,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "noUnusedLocals": true, + "allowJs": true, "rootDir": ".", From 450e29cf2aee9a659af091f2cf841b240e801ef2 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Sat, 7 Oct 2023 08:48:18 -0500 Subject: [PATCH 166/207] =?UTF-8?q?feat(developer):=20fix=20vars=20and=20m?= =?UTF-8?q?arkers=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - odd, but seems to work this way For: #9121 For: #9119 --- common/web/types/src/kmx/string-list.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/kmx/string-list.ts index 88ab5bbcf7f..2e313dae0c7 100644 --- a/common/web/types/src/kmx/string-list.ts +++ b/common/web/types/src/kmx/string-list.ts @@ -27,8 +27,10 @@ export class ListItem extends Array implements OrderedStringList { /** * Construct a new list from an array of strings. * Use List. This is meant to be called by the List.allocString*() functions. - * @param strs the Strs section is needed to construct this object. * @param source array of strings + * @param opts string handling options + * @param sections the Strs section is needed to construct this object, and other sections may + * be needed depending on the options * @returns */ constructor(source: Array, opts: StrsOptions, sections: DependencySections) { @@ -71,20 +73,13 @@ export class ListItem extends Array implements OrderedStringList { return 0; } } - /** for debugging, print as single string */ + /** for debugging and tests, print as single string */ toString(): string { - let ss = ''; - for (let i = 0 ;i < this.length; i++) { - if (ss) { - ss = ss + ' '; - } - ss = ss + (this[i].toString()); - } - return ss; - // return this.toStringArray().join(' '); + return this.toStringArray().join(' '); } - /** for debugging, map to string array */ + /** for debugging and tests, map to string array */ toStringArray(): string[] { - return this.map(v => v.toString()); + // TODO-LDML: this crashes: return this.map(v => v.toString()); + return Array.from(this.values()).map(v => v.toString()); } }; From 12bb554b5a6680d28f0af2bfba2fb29aae9f699f Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Sat, 7 Oct 2023 09:11:27 -0500 Subject: [PATCH 167/207] =?UTF-8?q?feat(developer):=20fix=20vars=20and=20m?= =?UTF-8?q?arkers=20=F0=9F=99=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Figured it out. Problem was a custom Array constructor. - also updated ElementString class, which would have the same issue. For: #9121 For: #9119 --- common/web/types/src/kmx/element-string.ts | 11 ++++++----- common/web/types/src/kmx/kmx-plus.ts | 8 ++++---- common/web/types/src/kmx/string-list.ts | 11 ++++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/common/web/types/src/kmx/element-string.ts b/common/web/types/src/kmx/element-string.ts index 816627c3d36..ebf4d398e55 100644 --- a/common/web/types/src/kmx/element-string.ts +++ b/common/web/types/src/kmx/element-string.ts @@ -31,11 +31,11 @@ export class ElementString extends Array { * @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem. * If it is a string, will be interpreted per reorder element rules. */ - constructor(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) { - super(); - //TODO-LDML: full UnicodeSet and parsing + static fromStrings(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) : ElementString { + // the returned array + const array = new ElementString(); if(!source) { - return; + return array; } let items : ElementSegment[]; @@ -104,8 +104,9 @@ export class ElementString extends Array { (ElemElementFlags.type & typeFlag) | (tertiary_bases?.[i] == '1' /* TODO-LDML: or 'true'? */ ? ElemElementFlags.tertiary_base : 0) | (prebases?.[i] == '1' /* TODO-LDML: or 'true'? */ ? ElemElementFlags.prebase : 0); - this.push(elem); + array.push(elem); }; + return array; } isEqual(a: ElementString): boolean { if (a.length != this.length) { diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts index d28bcbfd3f9..1060e50fba8 100644 --- a/common/web/types/src/kmx/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -35,14 +35,14 @@ export class Elem extends Section { strings: ElementString[] = []; constructor(sections: DependencySections) { super(); - this.strings.push(new ElementString(sections, '')); // C7043: null element string + this.strings.push(ElementString.fromStrings(sections, '')); // C7043: null element string } /** * @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem. * If it is a string, will be interpreted per reorder element ruls. */ allocElementString(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString { - let s = new ElementString(sections, source, order, tertiary, tertiary_base, prebase); + let s = ElementString.fromStrings(sections, source, order, tertiary, tertiary_base, prebase); let result = this.strings.find(item => item.isEqual(s)); if(result === undefined) { result = s; @@ -547,14 +547,14 @@ export class List extends Section { let result = this.lists.find(item => item.isEqual(s)); if(result === undefined) { // allocate a new ListItem - result = new ListItem(s, opts, sections); + result = ListItem.fromStrings(s, opts, sections); this.lists.push(result); } return result; } constructor(strs: Strs) { super(); - this.lists.push(new ListItem([], {}, { strs })); // C7043: null element string + this.lists.push(ListItem.fromStrings([], {}, { strs })); // C7043: null element string } lists: ListItem[] = []; }; diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/kmx/string-list.ts index 2e313dae0c7..411c940acf1 100644 --- a/common/web/types/src/kmx/string-list.ts +++ b/common/web/types/src/kmx/string-list.ts @@ -33,15 +33,16 @@ export class ListItem extends Array implements OrderedStringList { * be needed depending on the options * @returns */ - constructor(source: Array, opts: StrsOptions, sections: DependencySections) { - super(); - if(!source) { - return; + static fromStrings(source: Array, opts: StrsOptions, sections: DependencySections) : ListItem { + const a = new ListItem(); + if (!source) { + return a; } for (const str of source) { let index = new ListIndex(sections.strs.allocString(str, opts, sections)); - this.push(index); + a.push(index); } + return a; } getItemOrder(item: string): number { return this.findIndex(({value}) => value.value === item); From 400444933cb51e972d7c9b95fbaa06b520d8feb6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sun, 8 Oct 2023 07:22:57 +0700 Subject: [PATCH 168/207] feat(developer): introduce WelcomeFile property to packages Fixes #9478. This adds a property WelcomeFile to .kps and kmp.json, which allows us to move away from the hardcoded welcome.htm filename in the future, and makes transform from Markdown (#9477) a simpler operation, and just generally starts the cleanup of the messiness of hard-coded filenames. The package compiler will fallback to injecting welcome.htm into this property if (a) welcome.htm is present, and (b) the property does not already have a value. This doesn't buy us much because we still need to support welcome.htm for existing legacy packages, but does mean that our .kmp package metadata will be more consistent for packages compiled with 17.0+ compilers. --- common/schemas/kps/kps.xsd | 1 + common/web/types/src/package/kmp-json-file.ts | 1 + common/web/types/src/package/kps-file.ts | 1 + .../windows/delphi/packages/PackageInfo.pas | 39 ++++++++ .../kmc-package/src/compiler/kmp-compiler.ts | 18 ++++ .../test/fixtures/khmer_angkor/ref/kmp.json | 3 +- .../khmer_angkor/source/khmer_angkor.kps | 1 + .../src/tike/child/UfrmPackageEditor.dfm | 99 +++++++++++-------- .../src/tike/child/UfrmPackageEditor.pas | 22 +++++ 9 files changed, 143 insertions(+), 42 deletions(-) diff --git a/common/schemas/kps/kps.xsd b/common/schemas/kps/kps.xsd index 013ffa34f93..292f0c8fc6a 100644 --- a/common/schemas/kps/kps.xsd +++ b/common/schemas/kps/kps.xsd @@ -32,6 +32,7 @@ + diff --git a/common/web/types/src/package/kmp-json-file.ts b/common/web/types/src/package/kmp-json-file.ts index 219cbc5fbf9..ae88b5ba3e9 100644 --- a/common/web/types/src/package/kmp-json-file.ts +++ b/common/web/types/src/package/kmp-json-file.ts @@ -18,6 +18,7 @@ export interface KmpJsonFileOptions { readmeFile?: string; graphicFile?: string; licenseFile?: string; + welcomeFile?: string; executeProgram?: string; msiFilename?: string; msiOptions?: string; diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index ebf6ad6e153..2154297d405 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -42,6 +42,7 @@ export interface KpsFileOptions { readMeFile?: string; graphicFile?: string; licenseFile?: string; + welcomeFile?: string; executeProgram?: string; msiFileName?: string; msiOptions?: string; diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index a5629e1bdf6..202fa9b4e7f 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -150,6 +150,7 @@ TPackageOptions = class(TPackageBaseObject) FReadmeFile: TPackageContentFile; FGraphicFile: TPackageContentFile; FLicenseFile: TPackageContentFile; + FWelcomeFile: TPackageContentFile; FLoadLegacy: Boolean; procedure SetReadmeFile(const Value: TPackageContentFile); procedure SetExecuteProgram(Value: WideString); @@ -162,6 +163,9 @@ TPackageOptions = class(TPackageBaseObject) procedure SetLicenseFile(const Value: TPackageContentFile); procedure LicenseRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); + procedure SetWelcomeFile(const Value: TPackageContentFile); + procedure WelcomeRemoved(Sender: TObject; + EventType: TPackageNotifyEventType; var FAllow: Boolean); public constructor Create(APackage: TPackage); override; destructor Destroy; override; @@ -178,6 +182,7 @@ TPackageOptions = class(TPackageBaseObject) property ReadmeFile: TPackageContentFile read FReadmeFile write SetReadmeFile; property GraphicFile: TPackageContentFile read FGraphicFile write SetGraphicFile; property LicenseFile: TPackageContentFile read FLicenseFile write SetLicenseFile; + property WelcomeFile: TPackageContentFile read FWelcomeFile write SetWelcomeFile; end; { Package Information } @@ -515,6 +520,7 @@ implementation SReadmeNotOwnedCorrectly = 'The readme file ''%s'' referred to is not part of the package.'; SGraphicNotOwnedCorrectly = 'The graphic file ''%s'' referred to is not part of the package.'; SLicenseNotOwnedCorrectly = 'The license file ''%s'' referred to is not part of the package.'; + SWelcomeNotOwnedCorrectly = 'The welcome file ''%s'' referred to is not part of the package.'; SFileNotOwnedCorrectly = 'The file ''%s'' referred to is not part of the package.'; SDisplayFontNotOwnedCorrectly = 'The display font file ''%s'' referred to is not part of the package.'; SOSKFontNotOwnedCorrectly = 'The OSK font file ''%s'' referred to is not part of the package.'; @@ -568,6 +574,7 @@ implementation SJSON_Options_ReadMeFile = 'readmeFile'; SJSON_Options_GraphicFile = 'graphicFile'; SJSON_Options_LicenseFile = 'licenseFile'; + SJSON_Options_WelcomeFile = 'welcomeFile'; SJSON_Registry = 'registry'; SJSON_Registry_Root = 'root'; @@ -665,6 +672,9 @@ procedure TPackageOptions.Assign(Source: TPackageOptions); if Assigned(Source.LicenseFile) then LicenseFile := Package.Files.FromFileName(Source.LicenseFile.FileName) else LicenseFile := nil; + if Assigned(Source.WelcomeFile) + then WelcomeFile := Package.Files.FromFileName(Source.WelcomeFile.FileName) + else WelcomeFile := nil; end; constructor TPackageOptions.Create(APackage: TPackage); @@ -679,6 +689,7 @@ destructor TPackageOptions.Destroy; ReadmeFile := nil; GraphicFile := nil; LicenseFile := nil; + WelcomeFile := nil; inherited Destroy; end; @@ -697,6 +708,11 @@ procedure TPackageOptions.LicenseRemoved(Sender: TObject; EventType: TPackageNot FLicenseFile := nil; end; +procedure TPackageOptions.WelcomeRemoved(Sender: TObject; EventType: TPackageNotifyEventType; var FAllow: Boolean); +begin + FWelcomeFile := nil; +end; + procedure TPackageOptions.LoadXML(ARoot: IXMLNode); begin FileVersion := XmlVarToStr(ARoot.ChildNodes['System'].ChildNodes['FileVersion'].NodeValue); @@ -704,9 +720,11 @@ procedure TPackageOptions.LoadXML(ARoot: IXMLNode); ReadmeFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ReadMeFile'].NodeValue)); GraphicFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue)); LicenseFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue)); + WelcomeFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['WelcomeFile'].NodeValue)); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); if Assigned(LicenseFile) then LicenseFile.AddNotifyObject(LicenseRemoved); + if Assigned(WelcomeFile) then WelcomeFile.AddNotifyObject(WelcomeRemoved); end; procedure TPackageOptions.SaveXML(ARoot: IXMLNode); @@ -719,6 +737,8 @@ procedure TPackageOptions.SaveXML(ARoot: IXMLNode); ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue := GraphicFile.RelativeFileName; if Assigned(LicenseFile) then ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue := LicenseFile.RelativeFileName; + if Assigned(WelcomeFile) then + ARoot.ChildNodes['Options'].ChildNodes['WelcomeFile'].NodeValue := WelcomeFile.RelativeFileName; end; procedure TPackageOptions.LoadIni(AIni: TIniFile); @@ -730,6 +750,7 @@ procedure TPackageOptions.LoadIni(AIni: TIniFile); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); // LicenseFile not supported in ini + // WelcomeFile not supported in ini end; procedure TPackageOptions.LoadJSON(ARoot: TJSONObject); @@ -744,9 +765,11 @@ procedure TPackageOptions.LoadJSON(ARoot: TJSONObject); ReadmeFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_ReadMeFile)); GraphicFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_GraphicFile)); LicenseFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_LicenseFile)); + WelcomeFile := Package.Files.FromFileName(GetJsonValueString(FOptions, SJSON_Options_WelcomeFile)); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); if Assigned(LicenseFile) then LicenseFile.AddNotifyObject(LicenseRemoved); + if Assigned(WelcomeFile) then WelcomeFile.AddNotifyObject(WelcomeRemoved); end; procedure TPackageOptions.SaveIni(AIni: TIniFile); @@ -758,6 +781,7 @@ procedure TPackageOptions.SaveIni(AIni: TIniFile); if Assigned(GraphicFile) then AIni.WriteString('Package', 'GraphicFile', GraphicFile.RelativeFileName); // licenseFile not supported in ini + // welcomeFile not supported in ini end; procedure TPackageOptions.SaveJSON(ARoot: TJSONObject); @@ -779,6 +803,8 @@ procedure TPackageOptions.SaveJSON(ARoot: TJSONObject); FOptions.AddPair(SJSON_Options_GraphicFile, GraphicFile.RelativeFileName); if Assigned(LicenseFile) then FOptions.AddPair(SJSON_Options_LicenseFile, LicenseFile.RelativeFileName); + if Assigned(WelcomeFile) then + FOptions.AddPair(SJSON_Options_WelcomeFile, WelcomeFile.RelativeFileName); end; procedure TPackageOptions.SetExecuteProgram(Value: WideString); @@ -819,6 +845,19 @@ procedure TPackageOptions.SetLicenseFile(const Value: TPackageContentFile); end; end; +procedure TPackageOptions.SetWelcomeFile(const Value: TPackageContentFile); +begin + if Assigned(FWelcomeFile) then FWelcomeFile.RemoveNotifyObject(WelcomeRemoved); + if not Assigned(Value) then + FWelcomeFile := nil + else + begin + if Value.Package <> Package then raise EPackageInfo.CreateFmt(SWelcomeNotOwnedCorrectly, [Value]); + FWelcomeFile := Value; + FWelcomeFile.AddNotifyObject(WelcomeRemoved); + end; +end; + procedure TPackageOptions.SetReadmeFile(const Value: TPackageContentFile); begin if Assigned(FReadmeFile) then FReadmeFile.RemoveNotifyObject(ReadmeRemoved); diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 3859cbac4dd..22fb02a38d4 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -16,6 +16,11 @@ import { markdownToHTML } from './markdown.js'; const KMP_JSON_FILENAME = 'kmp.json'; const KMP_INF_FILENAME = 'kmp.inf'; +// welcome.htm: this is a legacy filename, as of 17.0 the welcome +// (documentation) filename can be any file, but we will fallback to detecting +// this filename for existing keyboard packages. +const WELCOME_HTM_FILENAME = 'welcome.htm'; + export class KmpCompiler { constructor(private callbacks: CompilerCallbacks) { @@ -84,6 +89,7 @@ export class KmpCompiler { kmp.options.msiOptions = kps.options.msiOptions || undefined; kmp.options.readmeFile = kps.options.readMeFile || undefined; kmp.options.licenseFile = kps.options.licenseFile || undefined; + kmp.options.welcomeFile = kps.options.welcomeFile || undefined; } // @@ -373,6 +379,8 @@ export class KmpCompiler { return null; } + // TODO #9477: transform .md to .htm + // Remove path data from file references in options if(data.options.graphicFile) { @@ -384,6 +392,16 @@ export class KmpCompiler { if(data.options.licenseFile) { data.options.licenseFile = this.callbacks.path.basename(data.options.licenseFile); } + if(data.options.welcomeFile) { + data.options.welcomeFile = this.callbacks.path.basename(data.options.welcomeFile); + } else if(data.files.find(file => file.name == WELCOME_HTM_FILENAME)) { + // We will, for improved backward-compatibility with existing packages, add a + // reference to the file welcome.htm is it is present in the package. This allows + // newer tools to avoid knowing about welcome.htm, if we assume that they work with + // packages compiled with kmc-package (17.0+) and not kmcomp (5.x-16.x). + data.options.welcomeFile = WELCOME_HTM_FILENAME; + } + if(data.options.msiFilename) { data.options.msiFilename = this.callbacks.path.basename(data.options.msiFilename); } diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/ref/kmp.json b/developer/src/kmc-package/test/fixtures/khmer_angkor/ref/kmp.json index 5cb4d4029e4..1fd94b374ef 100644 --- a/developer/src/kmc-package/test/fixtures/khmer_angkor/ref/kmp.json +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/ref/kmp.json @@ -5,7 +5,8 @@ }, "options": { "readmeFile": "readme.htm", - "graphicFile": "splash.gif" + "graphicFile": "splash.gif", + "welcomeFile": "welcome.htm" }, "info": { "name": { diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps index 2272334352b..cb4628a0f56 100644 --- a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps @@ -8,6 +8,7 @@ readme.htm splash.gif + welcome.htm diff --git a/developer/src/tike/child/UfrmPackageEditor.dfm b/developer/src/tike/child/UfrmPackageEditor.dfm index 07567cfa6fd..911781c2986 100644 --- a/developer/src/tike/child/UfrmPackageEditor.dfm +++ b/developer/src/tike/child/UfrmPackageEditor.dfm @@ -184,7 +184,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 0 Width = 965 Height = 622 - ActivePage = pageKeyboards + ActivePage = pageDetails Align = alClient Images = modActionsMain.ilEditorPages MultiLine = True @@ -236,7 +236,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 546 Height = 32 AutoSize = False - Caption = + Caption = 'A typical package will need keyboards, fonts, and documentation.' + ' You shouldn'#39't typically add source files. Also, don'#39't add any s' + 'tandard Keyman files (such as keyman.exe) here.' @@ -839,7 +839,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblReadme: TLabel Left = 15 - Top = 120 + Top = 148 Width = 63 Height = 13 Caption = '&Readme file:' @@ -853,35 +853,35 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblStep2c: TLabel Left = 15 - Top = 173 + Top = 202 Width = 41 Height = 13 Caption = 'Version:' end object lblStep2d: TLabel Left = 15 - Top = 221 + Top = 254 Width = 54 Height = 13 Caption = 'Copyright:' end object lblStep2e: TLabel Left = 15 - Top = 253 + Top = 282 Width = 39 Height = 13 Caption = 'Author:' end object lblStep2f: TLabel - Left = 15 - Top = 277 + Left = 295 + Top = 282 Width = 77 Height = 13 Caption = 'E-mail address:' end object lblStep2g: TLabel Left = 15 - Top = 301 + Top = 309 Width = 49 Height = 13 Caption = 'Web Site:' @@ -927,7 +927,7 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblVersionHint: TLabel Left = 622 - Top = 175 + Top = 204 Width = 46 Height = 13 Anchors = [akTop, akRight] @@ -959,20 +959,28 @@ inherited frmPackageEditor: TfrmPackageEditor end object lblLicenseFile: TLabel Left = 15 - Top = 147 + Top = 175 Width = 59 Height = 13 Caption = '&License file:' FocusControl = cbLicense end + object lblWelcomeFile: TLabel + Left = 14 + Top = 118 + Width = 69 + Height = 13 + Caption = '&Welcome file:' + FocusControl = cbWelcomeFile + end object cbReadMe: TComboBox Left = 114 - Top = 115 + Top = 143 Width = 499 Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 1 + TabOrder = 2 OnClick = cbReadMeClick end object editInfoName: TEdit @@ -986,58 +994,57 @@ inherited frmPackageEditor: TfrmPackageEditor end object editInfoVersion: TEdit Left = 114 - Top = 170 + Top = 199 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 3 + TabOrder = 4 OnChange = editInfoVersionChange end object editInfoCopyright: TEdit Left = 114 - Top = 218 + Top = 251 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 5 + TabOrder = 6 Text = #169 OnChange = editInfoCopyrightChange end object editInfoAuthor: TEdit Left = 114 - Top = 250 - Width = 499 + Top = 279 + Width = 175 Height = 21 - Anchors = [akLeft, akTop, akRight] - TabOrder = 7 + TabOrder = 8 OnChange = editInfoAuthorChange end object editInfoEmail: TEdit - Left = 114 - Top = 274 - Width = 499 + Left = 377 + Top = 279 + Width = 236 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 8 + TabOrder = 9 OnChange = editInfoEmailChange end object editInfoWebSite: TEdit Left = 114 - Top = 298 + Top = 306 Width = 499 Height = 21 Anchors = [akLeft, akTop, akRight] - TabOrder = 9 + TabOrder = 10 OnChange = editInfoWebSiteChange end object cmdInsertCopyright: TButton Left = 620 - Top = 218 + Top = 251 Width = 49 Height = 21 Anchors = [akTop, akRight] Caption = '&Insert '#169 - TabOrder = 6 + TabOrder = 7 OnClick = cmdInsertCopyrightClick end object cbKMPImageFile: TComboBox @@ -1047,7 +1054,7 @@ inherited frmPackageEditor: TfrmPackageEditor Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 10 + TabOrder = 11 OnClick = cbKMPImageFileClick end object panKMPImageSample: TPanel @@ -1057,7 +1064,7 @@ inherited frmPackageEditor: TfrmPackageEditor Height = 251 Anchors = [akTop, akRight] BevelOuter = bvLowered - TabOrder = 11 + TabOrder = 12 object imgKMPSample: TImage Left = 1 Top = 0 @@ -1067,11 +1074,11 @@ inherited frmPackageEditor: TfrmPackageEditor end object chkFollowKeyboardVersion: TCheckBox Left = 114 - Top = 196 + Top = 225 Width = 237 Height = 17 Caption = 'Package version follows keyboard version' - TabOrder = 4 + TabOrder = 5 OnClick = chkFollowKeyboardVersionClick end object memoInfoDescription: TMemo @@ -1079,7 +1086,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 394 Width = 499 Height = 97 - TabOrder = 12 + TabOrder = 13 OnChange = memoInfoDescriptionChange end object gridRelatedPackages: TStringGrid @@ -1092,7 +1099,7 @@ inherited frmPackageEditor: TfrmPackageEditor FixedCols = 0 RowCount = 9 Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] - TabOrder = 13 + TabOrder = 14 OnDblClick = gridRelatedPackagesDblClick ColWidths = ( 78 @@ -1104,7 +1111,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 73 Height = 25 Caption = '&Add...' - TabOrder = 14 + TabOrder = 15 OnClick = cmdAddRelatedPackageClick end object cmdEditRelatedPackage: TButton @@ -1113,7 +1120,7 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 73 Height = 25 Caption = 'Ed&it...' - TabOrder = 15 + TabOrder = 16 OnClick = cmdEditRelatedPackageClick end object cmdRemoveRelatedPackage: TButton @@ -1122,19 +1129,29 @@ inherited frmPackageEditor: TfrmPackageEditor Width = 72 Height = 25 Caption = '&Remove' - TabOrder = 16 + TabOrder = 17 OnClick = cmdRemoveRelatedPackageClick end object cbLicense: TComboBox Left = 114 - Top = 143 + Top = 171 Width = 499 Height = 21 Style = csDropDownList Anchors = [akLeft, akTop, akRight] - TabOrder = 2 + TabOrder = 3 OnClick = cbLicenseClick end + object cbWelcomeFile: TComboBox + Left = 114 + Top = 115 + Width = 499 + Height = 21 + Style = csDropDownList + Anchors = [akLeft, akTop, akRight] + TabOrder = 1 + OnClick = cbWelcomeFileClick + end end end object pageShortcuts: TTabSheet @@ -1325,7 +1342,7 @@ inherited frmPackageEditor: TfrmPackageEditor Top = 48 Width = 551 Height = 13 - Caption = + Caption = 'Compiling the package takes all the files you have selected and ' + 'compresses them into a single package file.' end diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index 7d4faabe90c..39ab96b5f81 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -217,6 +217,8 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 lblWebDisplayFonts: TLabel; lblLicenseFile: TLabel; cbLicense: TComboBox; + lblWelcomeFile: TLabel; + cbWelcomeFile: TComboBox; procedure cmdCloseClick(Sender: TObject); procedure cmdAddFileClick(Sender: TObject); procedure cmdRemoveFileClick(Sender: TObject); @@ -293,6 +295,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure cmdKeyboardWebOSKFontsClick(Sender: TObject); procedure cmdKeyboardWebDisplayFontsClick(Sender: TObject); procedure cbLicenseClick(Sender: TObject); + procedure cbWelcomeFileClick(Sender: TObject); private pack: TKPSFile; FSetup: Integer; @@ -308,6 +311,7 @@ TfrmPackageEditor = class(TfrmTikeEditor) // I4689 procedure EnableDetailsTabControls; procedure EnableCompileTabControls; function DoAction(action: TProjectFileAction): Boolean; + procedure UpdateWelcomeFile; procedure UpdateReadme; procedure UpdateImageFiles; procedure GetStartMenuEntries(var AName, AProg, AParams: WideString); @@ -448,6 +452,7 @@ procedure TfrmPackageEditor.FormCreate(Sender: TObject); gridRelatedPackages.Cells[1, 0] := 'Relationship'; UpdateStartMenuPrograms; + UpdateWelcomeFile; UpdateReadme; UpdateLicense; UpdateImageFiles; @@ -768,6 +773,7 @@ procedure TfrmPackageEditor.AddFile(FileName: WideString); pack.Files.Add(f); lbFiles.ItemIndex := lbFiles.Items.AddObject(ExtractFileName(FileName), f); lbFilesClick(lbFiles); + UpdateWelcomeFile; UpdateReadme; UpdateLicense; UpdateImageFiles; @@ -814,6 +820,7 @@ procedure TfrmPackageEditor.cmdRemoveFileClick(Sender: TObject); lbFiles.Items.Delete(lbFiles.ItemIndex); if lbFiles.Items.Count > 0 then lbFiles.ItemIndex := 0; lbFilesClick(lbFiles); + UpdateWelcomeFile; UpdateReadme; UpdateLicense; UpdateImageFiles; @@ -942,6 +949,15 @@ procedure TfrmPackageEditor.lbFilesClick(Sender: TObject); - Options page - -------------------------------------------------------------------------------} +procedure TfrmPackageEditor.cbWelcomeFileClick(Sender: TObject); +begin + if FSetup > 0 then Exit; + if cbWelcomeFile.ItemIndex <= 0 + then pack.Options.WelcomeFile := nil + else pack.Options.WelcomeFile := cbWelcomeFile.Items.Objects[cbWelcomeFile.ItemIndex] as TPackageContentFile; + Modified := True; +end; + procedure TfrmPackageEditor.cbReadMeClick(Sender: TObject); begin if FSetup > 0 then Exit; @@ -1258,6 +1274,11 @@ procedure TfrmPackageEditor.UpdateStartMenuPrograms; cbStartMenuProgram.Items.Insert(3, '(About Product)'); end; +procedure TfrmPackageEditor.UpdateWelcomeFile; +begin + FillFileList(cbWelcomeFile, pack.Options.WelcomeFile); +end; + procedure TfrmPackageEditor.UpdateReadme; begin FillFileList(cbReadme, pack.Options.ReadmeFile); @@ -1297,6 +1318,7 @@ procedure TfrmPackageEditor.UpdateData; lbFiles.Items.AddObject(ExtractFileName(pack.Files[i].FileName), pack.Files[i]); UpdateOutPath; + UpdateWelcomeFile; UpdateReadme; UpdateLicense; UpdateImageFiles; From cc5b8da69b24e0ad82c2add1c016410c2d84e7df Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sun, 8 Oct 2023 19:01:14 +0700 Subject: [PATCH 169/207] refactor(developer): .keyboard_info examples should use keys as a string Fixes #9708. Matches the kmp.json format of keys string in the .keyboard_info schema and compiler, in order to reduce the number of formats we are working with. This same format may be used elsewhere in Keyman schemas in the future for sets of keys, for example, I hope we can use it in regression tests. --- common/schemas/keyboard_info/README.md | 2 +- .../keyboard_info/keyboard_info.schema.json | 18 +-------------- .../src/kmc-keyboard-info/src/example-keys.ts | 23 ------------------- developer/src/kmc-keyboard-info/src/index.ts | 8 ++++--- .../src/keyboard-info-file.ts | 20 +++++++++++----- .../build/khmer_angkor.keyboard_info | 8 +------ 6 files changed, 22 insertions(+), 57 deletions(-) delete mode 100644 developer/src/kmc-keyboard-info/src/example-keys.ts diff --git a/common/schemas/keyboard_info/README.md b/common/schemas/keyboard_info/README.md index d59febe3732..1c318ff593b 100644 --- a/common/schemas/keyboard_info/README.md +++ b/common/schemas/keyboard_info/README.md @@ -12,7 +12,7 @@ Documentation at https://help.keyman.com/developer/cloud/keyboard_info # .keyboard_info version history ## 2023-08-11 2.0 stable -* Removed legacyId, documentationFilename, documentationFileSize. Source vs distribution keyboard_info distinction is removed. +* Removed legacyId, documentationFilename, documentationFileSize. Source vs distribution keyboard_info distinction is removed. Example key sequences are simplified. ## 2019-09-06 1.0.6 stable * No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). diff --git a/common/schemas/keyboard_info/keyboard_info.schema.json b/common/schemas/keyboard_info/keyboard_info.schema.json index 54a8a3f5848..75d8e0329e8 100644 --- a/common/schemas/keyboard_info/keyboard_info.schema.json +++ b/common/schemas/keyboard_info/keyboard_info.schema.json @@ -75,10 +75,7 @@ "KeyboardExampleInfo": { "type": "object", "properties": { - "keys": { - "type": "array", - "items": { "$ref": "#/definitions/KeyboardExampleKeyInfo" } - }, + "keys": { "type": "string" }, "text": { "type": "string" }, "note": { "type": "string" } }, @@ -86,19 +83,6 @@ "additionalProperties": false }, - "KeyboardExampleKeyInfo": { - "type": "object", - "properties": { - "key": { "type": "string" }, - "modifiers": { - "type": "array", - "items": { "type": "string" } - } - }, - "required": ["key"], - "additionalProperties": false - }, - "KeyboardPlatformInfo": { "type": "object", "patternProperties": { diff --git a/developer/src/kmc-keyboard-info/src/example-keys.ts b/developer/src/kmc-keyboard-info/src/example-keys.ts deleted file mode 100644 index e9a8bb34ccd..00000000000 --- a/developer/src/kmc-keyboard-info/src/example-keys.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { KeyboardInfoFileExampleKey } from "./keyboard-info-file.js"; - -/** - * Converts a .kps or .kmp example keys string into an array of key objects - * matching the .keyboard_info example file format - * @param keysString - * @returns - */ -export function packageKeysExamplesToKeyboardInfo(keysString: string): KeyboardInfoFileExampleKey[] { - const items = keysString.trim().split(/ +/); - const result: KeyboardInfoFileExampleKey[] = []; - for(const item of items) { - const keyAndModifiers = item.split('+'); - if(keyAndModifiers.length > 0) { - const key: KeyboardInfoFileExampleKey = {key: keyAndModifiers.pop()} - if(keyAndModifiers.length) { - key.modifiers = [...keyAndModifiers]; - }; - result.push(key); - } - } - return result; -} \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index a933e8771ae..052c12dd63c 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -12,7 +12,6 @@ import { validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; import { SchemaValidators } from "@keymanapp/common-types"; -import { packageKeysExamplesToKeyboardInfo } from "./example-keys.js"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); const scriptNames = new Intl.DisplayNames(['en'], { type: "script" }); @@ -288,7 +287,10 @@ export class KeyboardInfoCompiler { if(!SchemaValidators.default.keyboard_info(keyboard_info)) { // This is an internal fatal error; we should not be capable of producing // invalid output, so it is best to throw and die - throw new Error((SchemaValidators.default.keyboard_info).errorsText()); + throw new Error(JSON.stringify({ + keyboard_info: keyboard_info, + error: SchemaValidators.default.keyboard_info.errors + }, null, 2)); } return new TextEncoder().encode(jsonOutput); @@ -390,7 +392,7 @@ export class KeyboardInfoCompiler { if(example.id == bcp47) { language.examples.push({ // we don't copy over example.id - keys: packageKeysExamplesToKeyboardInfo(example.keys), + keys: example.keys, note: example.note, text: example.text }); diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts index 60c25a13c0e..75e98e4a366 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-file.ts @@ -49,12 +49,20 @@ export interface KeyboardInfoFileLanguageFont { } export interface KeyboardInfoFileExample { - keys?: KeyboardInfoFileExampleKey[]; + /** + * A space-separated list of keys. + * - modifiers indicated with "+" + * - spacebar is "space" + * - plus key is "shift+=" or "plus" on US English (all other punctuation as per key cap). + * - Hardware modifiers are: "shift", "ctrl", "alt", "left-ctrl", + * "right-ctrl", "left-alt", "right-alt" + * - Key caps should generally be their character for desktop (Latin script + * case insensitive), or the actual key cap for touch + * - Caps Lock should be indicated with "caps-on", "caps-off" + * + * e.g. "shift+a b right-alt+c space plus z z z" represents something like: "Ab{AltGr+C} +zzz" + */ + keys?: string; text?: string; note?: string; } - -export interface KeyboardInfoFileExampleKey { - key: string; - modifiers?: string[]; -} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info index 98806bacecc..7d85bbfb6cf 100644 --- a/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info +++ b/developer/src/kmc-keyboard-info/test/fixtures/khmer_angkor/build/khmer_angkor.keyboard_info @@ -15,13 +15,7 @@ ] }, "examples": [{ - "keys": [ - { "key": "x" }, - { "key": "j" }, - { "key": "m" }, - { "key": "E" }, - { "key": "r" } - ], + "keys": "x j m E r", "text": "\u1781\u17D2\u1798\u17C2\u179A", "note": "Name of language" }], From 36a3a27bacf0be46a4446004d9e91bb14d3ed6d7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 05:19:43 +0700 Subject: [PATCH 170/207] fix(developer): non-minimal BCP 47 tags in kps should be a hint Fixes #9266. For historical reasons there are many non-minimal BCP 47 tags in package metadata in the repository (over 2500). Furthermore, earlier versions of kmcomp did not fail the build on package compilation warnings. This means it is better to reduce WARN_LanguageTagIsNotMinimal to HINT_LanguageTagIsNotMinimal, so that we can otherwise respect the 'treat warnings as errors' flag on the keyboard projects. We may be able to upgrade this to a warning again one day in the future. --- developer/src/kmc-package/src/compiler/messages.ts | 4 ++-- developer/src/kmc-package/src/compiler/package-validation.ts | 2 +- developer/src/kmc-package/test/test-messages.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index 9358882ad4f..b216246c5a9 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -88,9 +88,9 @@ export class CompilerMessages { `Language tag '${o.lang}' in ${o.resourceType} ${o.id} is invalid.`); static ERROR_LanguageTagIsNotValid = SevError | 0x0014; - static Warn_LanguageTagIsNotMinimal = (o: {resourceType: string, id:string, actual:string, expected:string}) => m(this.WARN_LanguageTagIsNotMinimal, + static Hint_LanguageTagIsNotMinimal = (o: {resourceType: string, id:string, actual:string, expected:string}) => m(this.HINT_LanguageTagIsNotMinimal, `Language tag '${o.actual}' in ${o.resourceType} ${o.id} is not minimal, and should be '${o.expected}'.`); - static WARN_LanguageTagIsNotMinimal = SevWarn | 0x0015; + static HINT_LanguageTagIsNotMinimal = SevHint | 0x0015; static Error_ModelMustHaveAtLeastOneLanguage = (o:{id:string}) => m(this.ERROR_ModelMustHaveAtLeastOneLanguage, `The lexical model ${o.id} must have at least one language specified.`); diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts index f0463e8f532..adbaf085bae 100644 --- a/developer/src/kmc-package/src/compiler/package-validation.ts +++ b/developer/src/kmc-package/src/compiler/package-validation.ts @@ -64,7 +64,7 @@ export class PackageValidation { const minimalTag = locale.minimize().toString(); if(minimalTag.toLowerCase() !== lang.id.toLowerCase()) { - this.callbacks.reportMessage(CompilerMessages.Warn_LanguageTagIsNotMinimal({resourceType, id, actual: lang.id, expected: minimalTag})); + this.callbacks.reportMessage(CompilerMessages.Hint_LanguageTagIsNotMinimal({resourceType, id, actual: lang.id, expected: minimalTag})); } if(minimalTags[minimalTag]) { diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index 019ee24852f..a9943e668ed 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -163,7 +163,7 @@ describe('CompilerMessages', function () { // WARN_LanguageTagIsNotMinimal it('should generate WARN_LanguageTagIsNotMinimal if keyboard has a non-minimal language tag', async function() { - testForMessage(this, ['invalid', 'warn_language_tag_is_not_minimal.kps'], CompilerMessages.WARN_LanguageTagIsNotMinimal); + testForMessage(this, ['invalid', 'warn_language_tag_is_not_minimal.kps'], CompilerMessages.HINT_LanguageTagIsNotMinimal); }); // ERROR_ModelMustHaveAtLeastOneLanguage @@ -218,7 +218,7 @@ describe('CompilerMessages', function () { it('should not generate HINT_JsKeyboardFileHasNoTouchTargets if keyboard has a touch target', async function() { testForMessage(this, ['khmer_angkor', 'source', 'khmer_angkor.kps'], null); }); - + // HINT_PackageContainsSourceFile it('should generate HINT_PackageContainsSourceFile if package contains a source file', async function() { From f76efc38cca9479376a60baeaa4d2d22ff183fb3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 05:43:29 +0700 Subject: [PATCH 171/207] chore(common): stop using xml2js.processors.firstCharLowerCase Fixes #9713. --- common/web/types/src/package/kps-file.ts | 124 +++++++++--------- .../kmc-package/src/compiler/kmp-compiler.ts | 116 ++++++++-------- .../src/compiler/package-version-validator.ts | 2 +- .../windows-package-installer-compiler.ts | 4 +- 4 files changed, 124 insertions(+), 122 deletions(-) diff --git a/common/web/types/src/package/kps-file.ts b/common/web/types/src/package/kps-file.ts index 2154297d405..7c7ba12c879 100644 --- a/common/web/types/src/package/kps-file.ts +++ b/common/web/types/src/package/kps-file.ts @@ -17,44 +17,44 @@ export interface KpsPackage { /** * -- the root element. */ - package: KpsFile; + Package: KpsFile; } export interface KpsFile { - system: KpsFileSystem; - options: KpsFileOptions; - info?: KpsFileInfo; - files?: KpsFileContentFiles; - keyboards?: KpsFileKeyboards; - lexicalModels?: KpsFileLexicalModels; - startMenu?: KpsFileStartMenu; - strings?: KpsFileStrings; - relatedPackages?: KpsFileRelatedPackages; + System: KpsFileSystem; + Options: KpsFileOptions; + Info?: KpsFileInfo; + Files?: KpsFileContentFiles; + Keyboards?: KpsFileKeyboards; + LexicalModels?: KpsFileLexicalModels; + StartMenu?: KpsFileStartMenu; + Strings?: KpsFileStrings; + RelatedPackages?: KpsFileRelatedPackages; } export interface KpsFileSystem { - keymanDeveloperVersion: string; - fileVersion: string; + KeymanDeveloperVersion: string; + FileVersion: string; } export interface KpsFileOptions { - followKeyboardVersion?: string; - readMeFile?: string; - graphicFile?: string; - licenseFile?: string; - welcomeFile?: string; - executeProgram?: string; - msiFileName?: string; - msiOptions?: string; + FollowKeyboardVersion?: string; + ReadMeFile?: string; + GraphicFile?: string; + LicenseFile?: string; + WelcomeFile?: string; + ExecuteProgram?: string; + MsiFileName?: string; + MsiOptions?: string; } export interface KpsFileInfo { - name?: KpsFileInfoItem; - copyright?: KpsFileInfoItem; - author?: KpsFileInfoItem; - webSite?: KpsFileInfoItem; - version?: KpsFileInfoItem; - description?: KpsFileInfoItem; + Name?: KpsFileInfoItem; + Copyright?: KpsFileInfoItem; + Author?: KpsFileInfoItem; + WebSite?: KpsFileInfoItem; + Version?: KpsFileInfoItem; + Description?: KpsFileInfoItem; } export interface KpsFileInfoItem { @@ -63,31 +63,31 @@ export interface KpsFileInfoItem { } export interface KpsFileContentFiles { - file: KpsFileContentFile[] | KpsFileContentFile; + File: KpsFileContentFile[] | KpsFileContentFile; } export interface KpsFileContentFile { - name: string; + Name: string; /** @deprecated */ - description: string; + Description: string; /** @deprecated */ - copyLocation: string; + CopyLocation: string; /** @deprecated */ - fileType: string; + FileType: string; } export interface KpsFileLexicalModel { - name: string; - iD: string; - languages: KpsFileLanguages; + Name: string; + ID: string; + Languages: KpsFileLanguages; } export interface KpsFileLexicalModels { - lexicalModel: KpsFileLexicalModel[] | KpsFileLexicalModel; + LexicalModel: KpsFileLexicalModel[] | KpsFileLexicalModel; } export interface KpsFileLanguages { - language: KpsFileLanguage[] | KpsFileLanguage; + Language: KpsFileLanguage[] | KpsFileLanguage; } export interface KpsFileLanguage { @@ -96,7 +96,7 @@ export interface KpsFileLanguage { } export interface KpsFileRelatedPackages { - relatedPackage: KpsFileRelatedPackage | KpsFileRelatedPackage[]; + RelatedPackage: KpsFileRelatedPackage | KpsFileRelatedPackage[]; } export interface KpsFileRelatedPackage { @@ -110,26 +110,26 @@ export interface KpsFileRelatedPackage { } export interface KpsFileKeyboard { - name: string; /// the descriptive name of the keyboard - iD: string; /// the keyboard identifier, equal to the basename of the keyboard file sans extension - version: string; - oSKFont?: string; - displayFont?: string; - rTL?: string; - languages?: KpsFileLanguages; - examples?: KpsFileLanguageExamples; + Name: string; /// the descriptive name of the keyboard + ID: string; /// the keyboard identifier, equal to the basename of the keyboard file sans extension + Version: string; + OSKFont?: string; + DisplayFont?: string; + RTL?: string; + Languages?: KpsFileLanguages; + Examples?: KpsFileLanguageExamples; /** * array of web font alternatives for OSK. should be same font data as oskFont */ - webOSKFonts?: KpsFileFonts; + WebOSKFonts?: KpsFileFonts; /** * array of web font alternatives for display. should be same font data as displayFont */ - webDisplayFonts?: KpsFileFonts; + WebDisplayFonts?: KpsFileFonts; } export interface KpsFileFonts { - font: KpsFileFont[] | KpsFileFont; + Font: KpsFileFont[] | KpsFileFont; } export interface KpsFileFont { @@ -139,40 +139,40 @@ export interface KpsFileFont { } export interface KpsFileKeyboards { - keyboard: KpsFileKeyboard[] | KpsFileKeyboard; + Keyboard: KpsFileKeyboard[] | KpsFileKeyboard; } export interface KpsFileStartMenu { - folder?: string; - addUninstallEntry?: string; - items?: KpsFileStartMenuItems; + Folder?: string; + AddUninstallEntry?: string; + Items?: KpsFileStartMenuItems; } export interface KpsFileStartMenuItem { - name: string; - fileName: string; - arguments?: string; - icon?: string; - location?: string; + Name: string; + FileName: string; + Arguments?: string; + Icon?: string; + Location?: string; } export interface KpsFileStartMenuItems { - item: KpsFileStartMenuItem[] | KpsFileStartMenuItem; + Item: KpsFileStartMenuItem[] | KpsFileStartMenuItem; } export interface KpsFileStrings { - string: KpsFileString[] | KpsFileString; + String: KpsFileString[] | KpsFileString; } export interface KpsFileString { $: { - name: string; - value: string; + Name: string; + Value: string; } } export interface KpsFileLanguageExamples { - example: KpsFileLanguageExample | KpsFileLanguageExample[]; + Example: KpsFileLanguageExample | KpsFileLanguageExample[]; } /** diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 22fb02a38d4..e4e85140d08 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -43,7 +43,6 @@ export class KmpCompiler { const kpsPackage = (() => { let a: KpsFile.KpsPackage; let parser = new xml2js.Parser({ - tagNameProcessors: [xml2js.processors.firstCharLowerCase], explicitArray: false }); // TODO: add unit test for xml errors parsing .kps file @@ -51,7 +50,7 @@ export class KmpCompiler { return a; })(); - const kps: KpsFile.KpsFile = kpsPackage.package; + const kps: KpsFile.KpsFile = kpsPackage.Package; return kps; } @@ -82,32 +81,32 @@ export class KmpCompiler { // Fill in additional fields // - if(kps.options) { - kmp.options.executeProgram = kps.options?.executeProgram || undefined; - kmp.options.graphicFile = kps.options.graphicFile || undefined; - kmp.options.msiFilename = kps.options.msiFileName || undefined; - kmp.options.msiOptions = kps.options.msiOptions || undefined; - kmp.options.readmeFile = kps.options.readMeFile || undefined; - kmp.options.licenseFile = kps.options.licenseFile || undefined; - kmp.options.welcomeFile = kps.options.welcomeFile || undefined; + if(kps.Options) { + kmp.options.executeProgram = kps.Options?.ExecuteProgram || undefined; + kmp.options.graphicFile = kps.Options.GraphicFile || undefined; + kmp.options.msiFilename = kps.Options.MsiFileName || undefined; + kmp.options.msiOptions = kps.Options.MsiOptions || undefined; + kmp.options.readmeFile = kps.Options.ReadMeFile || undefined; + kmp.options.licenseFile = kps.Options.LicenseFile || undefined; + kmp.options.welcomeFile = kps.Options.WelcomeFile || undefined; } // // Add basic metadata // - if(kps.info) { - kmp.info = this.kpsInfoToKmpInfo(kps.info); + if(kps.Info) { + kmp.info = this.kpsInfoToKmpInfo(kps.Info); } // // Add related package metadata // - if(kps.relatedPackages) { + if(kps.RelatedPackages) { // Note: 'relationship' field is required for kmp.json but optional for .kps, only // two values are supported -- deprecates or related. - kmp.relatedPackages = (this.arrayWrap(kps.relatedPackages.relatedPackage) as KpsFile.KpsFileRelatedPackage[]).map(p => + kmp.relatedPackages = (this.arrayWrap(kps.RelatedPackages.RelatedPackage) as KpsFile.KpsFileRelatedPackage[]).map(p => ({id: p.$.ID, relationship: p.$.Relationship == 'deprecates' ? 'deprecates' : 'related'}) ); } @@ -116,12 +115,12 @@ export class KmpCompiler { // Add file metadata // - if(kps.files && kps.files.file) { - kmp.files = this.arrayWrap(kps.files.file).map((file: KpsFile.KpsFileContentFile) => { + if(kps.Files && kps.Files.File) { + kmp.files = this.arrayWrap(kps.Files.File).map((file: KpsFile.KpsFileContentFile) => { return { - name: file.name.trim(), - description: file.description.trim(), - copyLocation: parseInt(file.copyLocation, 10) || undefined + name: file.Name.trim(), + description: file.Description.trim(), + copyLocation: parseInt(file.CopyLocation, 10) || undefined // note: we don't emit fileType as that is not permitted in kmp.json }; }); @@ -130,7 +129,7 @@ export class KmpCompiler { // Keyboard packages also include a legacy kmp.inf file (this will be removed, // one day) - if(kps.keyboards && kps.keyboards.keyboard) { + if(kps.Keyboards && kps.Keyboards.Keyboard) { kmp.files.push({ name: KMP_INF_FILENAME, description: "Package information" @@ -147,29 +146,29 @@ export class KmpCompiler { // Add keyboard metadata // - if(kps.keyboards && kps.keyboards.keyboard) { - kmp.keyboards = this.arrayWrap(kps.keyboards.keyboard).map((keyboard: KpsFile.KpsFileKeyboard) => ({ - displayFont: keyboard.displayFont ? this.callbacks.path.basename(keyboard.displayFont) : undefined, - oskFont: keyboard.oSKFont ? this.callbacks.path.basename(keyboard.oSKFont) : undefined, - name:keyboard.name.trim(), - id:keyboard.iD.trim(), - version:keyboard.version.trim(), - rtl:keyboard.rTL == 'True' ? true : undefined, - languages: keyboard.languages ? - this.kpsLanguagesToKmpLanguages(this.arrayWrap(keyboard.languages.language) as KpsFile.KpsFileLanguage[]) : + if(kps.Keyboards && kps.Keyboards.Keyboard) { + kmp.keyboards = this.arrayWrap(kps.Keyboards.Keyboard).map((keyboard: KpsFile.KpsFileKeyboard) => ({ + displayFont: keyboard.DisplayFont ? this.callbacks.path.basename(keyboard.DisplayFont) : undefined, + oskFont: keyboard.OSKFont ? this.callbacks.path.basename(keyboard.OSKFont) : undefined, + name:keyboard.Name.trim(), + id:keyboard.ID.trim(), + version:keyboard.Version.trim(), + rtl:keyboard.RTL == 'True' ? true : undefined, + languages: keyboard.Languages ? + this.kpsLanguagesToKmpLanguages(this.arrayWrap(keyboard.Languages.Language) as KpsFile.KpsFileLanguage[]) : [], - examples: keyboard.examples ? - (this.arrayWrap(keyboard.examples.example) as KpsFile.KpsFileLanguageExample[]).map( + examples: keyboard.Examples ? + (this.arrayWrap(keyboard.Examples.Example) as KpsFile.KpsFileLanguageExample[]).map( e => ({id: e.$.ID, keys: e.$.Keys, text: e.$.Text, note: e.$.Note}) ) as KmpJsonFile.KmpJsonFileExample[] : undefined, - webDisplayFonts: keyboard.webDisplayFonts ? - (this.arrayWrap(keyboard.webDisplayFonts.font) as KpsFile.KpsFileFont[]).map( + webDisplayFonts: keyboard.WebDisplayFonts ? + (this.arrayWrap(keyboard.WebDisplayFonts.Font) as KpsFile.KpsFileFont[]).map( e => (this.callbacks.path.basename(e.$.Filename)) ) : undefined, - webOskFonts: keyboard.webOSKFonts ? - (this.arrayWrap(keyboard.webOSKFonts.font) as KpsFile.KpsFileFont[]).map( + webOskFonts: keyboard.WebOSKFonts ? + (this.arrayWrap(keyboard.WebOSKFonts.Font) as KpsFile.KpsFileFont[]).map( e => (this.callbacks.path.basename(e.$.Filename)) ) : undefined, @@ -180,12 +179,12 @@ export class KmpCompiler { // Add lexical-model metadata // - if(kps.lexicalModels && kps.lexicalModels.lexicalModel) { - kmp.lexicalModels = this.arrayWrap(kps.lexicalModels.lexicalModel).map((model: KpsFile.KpsFileLexicalModel) => ({ - name:model.name.trim(), - id:model.iD.trim(), - languages: model.languages ? - this.kpsLanguagesToKmpLanguages(this.arrayWrap(model.languages.language) as KpsFile.KpsFileLanguage[]) : [] + if(kps.LexicalModels && kps.LexicalModels.LexicalModel) { + kmp.lexicalModels = this.arrayWrap(kps.LexicalModels.LexicalModel).map((model: KpsFile.KpsFileLexicalModel) => ({ + name:model.Name.trim(), + id:model.ID.trim(), + languages: model.Languages ? + this.kpsLanguagesToKmpLanguages(this.arrayWrap(model.Languages.Language) as KpsFile.KpsFileLanguage[]) : [] })); } @@ -210,7 +209,7 @@ export class KmpCompiler { return null; } - if(kps.keyboards && kps.keyboards.keyboard) { + if(kps.Keyboards && kps.Keyboards.Keyboard) { kmp.system.fileVersion = versionValidator.getMinKeymanVersion(metadata); } else { kmp.system.fileVersion = MIN_LM_FILEVERSION_KMP_JSON; @@ -234,21 +233,24 @@ export class KmpCompiler { // Add Windows Start Menu metadata // - if(kps.startMenu && (kps.startMenu.folder || kps.startMenu.items)) { + if(kps.StartMenu && (kps.StartMenu.Folder || kps.StartMenu.Items)) { kmp.startMenu = {}; - if(kps.startMenu.addUninstallEntry === '') kmp.startMenu.addUninstallEntry = true; - if(kps.startMenu.folder) kmp.startMenu.folder = kps.startMenu.folder; - if(kps.startMenu.items && kps.startMenu.items.item) { - kmp.startMenu.items = this.arrayWrap(kps.startMenu.items.item); + if(kps.StartMenu.AddUninstallEntry === '') kmp.startMenu.addUninstallEntry = true; + if(kps.StartMenu.Folder) kmp.startMenu.folder = kps.StartMenu.Folder; + if(kps.StartMenu.Items && kps.StartMenu.Items.Item) { + kmp.startMenu.items = this.arrayWrap(kps.StartMenu.Items.Item).map((item: KpsFile.KpsFileStartMenuItem) => ({ + filename: item.FileName, + name: item.Name, + arguments: item.Arguments, + icon: item.Icon, + location: item.Location + })); // Remove default values for(let item of kmp.startMenu.items) { if(item.icon == '') delete item.icon; if(item.location == 'psmelStartMenu') delete item.location; if(item.arguments == '') delete item.arguments; - // Horrific case change between .kps and kmp.json: - item.filename = (item).fileName; - delete (item).fileName; } } else { kmp.startMenu.items = []; @@ -266,12 +268,12 @@ export class KmpCompiler { let kmpInfo: KmpJsonFile.KmpJsonFileInfo = {}; const keys: [(keyof KpsFile.KpsFileInfo), (keyof KmpJsonFile.KmpJsonFileInfo), boolean][] = [ - ['author','author',false], - ['copyright','copyright',false], - ['name','name',false], - ['version','version',false], - ['webSite','website',false], - ['description','description',true], + ['Author','author',false], + ['Copyright','copyright',false], + ['Name','name',false], + ['Version','version',false], + ['WebSite','website',false], + ['Description','description',true], ]; for (let [src,dst,isMarkdown] of keys) { diff --git a/developer/src/kmc-package/src/compiler/package-version-validator.ts b/developer/src/kmc-package/src/compiler/package-version-validator.ts index 8f71418be2f..b53ee564e8e 100644 --- a/developer/src/kmc-package/src/compiler/package-version-validator.ts +++ b/developer/src/kmc-package/src/compiler/package-version-validator.ts @@ -35,7 +35,7 @@ export class PackageVersionValidator { * @returns */ public validateAndUpdateVersions(kps: KpsFile.KpsFile, kmp: KmpJsonFile.KmpJsonFile, keyboardMetadata: KeyboardMetadataCollection) { - const followKeyboardVersion = kps.options?.followKeyboardVersion !== undefined; + const followKeyboardVersion = kps.Options?.FollowKeyboardVersion !== undefined; if(followKeyboardVersion) { if(!this.checkFollowKeyboardVersion(kmp)) { diff --git a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts index d2f93994bbe..8f920535bbf 100644 --- a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts +++ b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts @@ -123,11 +123,11 @@ License=${this.callbacks.path.basename(sources.licenseFilename)} setupInf += `\n[Packages]\n`; setupInf += kmpFilename + '\n'; // TODO: multiple packages? - const strings = !kps.strings?.string ? [] : (Array.isArray(kps.strings.string) ? kps.strings.string : [kps.strings.string]); + const strings = !kps.Strings?.String ? [] : (Array.isArray(kps.Strings.String) ? kps.Strings.String : [kps.Strings.String]); if (strings.length) { setupInf += `\n[Strings]\n`; for (const str of strings) { - setupInf += `${str.$?.name}=${str.$?.value}\n`; + setupInf += `${str.$?.Name}=${str.$?.Value}\n`; } } From 0f5e88192c7b4f4a3976c325ab9100a1114b81e2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 06:06:34 +0700 Subject: [PATCH 172/207] chore(developer): Update kmc-package xml2js dependency I missed adding this as a dependency for @keymanapp/kmc-package. because node has a very lenient dependency search, it found xml2js from other @keymanapp/common-types, so never caused errors locally. --- developer/src/kmc-ldml/package.json | 4 +--- developer/src/kmc-model-info/package.json | 1 - developer/src/kmc-model/package.json | 4 +--- developer/src/kmc-package/package.json | 3 ++- package-lock.json | 12 ++++-------- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index 53542664c8b..88173c2902e 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -30,8 +30,7 @@ "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", - "semver": "^7.5.2", - "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + "semver": "^7.5.2" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -39,7 +38,6 @@ "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "@types/semver": "^7.3.12", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index cddc61af63a..a953062acb3 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -42,7 +42,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index d15bc21b976..faed0049d4a 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -34,15 +34,13 @@ "@keymanapp/common-types": "*", "@keymanapp/keyman-version": "*", "@keymanapp/models-types": "*", - "typescript": "^4.9.5", - "xml2js": "^0.4.19" + "typescript": "^4.9.5" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index 8a9882acb1d..a5f9f3e7936 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -32,7 +32,8 @@ "dependencies": { "@keymanapp/common-types": "*", "jszip": "^3.7.0", - "marked": "^7.0.0" + "marked": "^7.0.0", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", diff --git a/package-lock.json b/package-lock.json index b6f15bd5b03..ac003038c96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1713,8 +1713,7 @@ "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", - "semver": "^7.5.2", - "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + "semver": "^7.5.2" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -1722,7 +1721,6 @@ "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "@types/semver": "^7.3.12", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -1954,15 +1952,13 @@ "@keymanapp/common-types": "*", "@keymanapp/keyman-version": "*", "@keymanapp/models-types": "*", - "typescript": "^4.9.5", - "xml2js": "^0.4.19" + "typescript": "^4.9.5" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -1986,7 +1982,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -2265,7 +2260,8 @@ "dependencies": { "@keymanapp/common-types": "*", "jszip": "^3.7.0", - "marked": "^7.0.0" + "marked": "^7.0.0", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", From 52291b1ccb8592a7e38ecf332f2a1d6666bbbc0e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 06:34:32 +0700 Subject: [PATCH 173/207] chore(developer): remove unused xml2js references --- developer/src/kmc-analyze/package.json | 1 - developer/src/kmc-kmn/package.json | 1 - developer/src/kmc/package.json | 1 - package-lock.json | 3 --- 4 files changed, 6 deletions(-) diff --git a/developer/src/kmc-analyze/package.json b/developer/src/kmc-analyze/package.json index d60afa08488..807f70f0cea 100644 --- a/developer/src/kmc-analyze/package.json +++ b/developer/src/kmc-analyze/package.json @@ -30,7 +30,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", diff --git a/developer/src/kmc-kmn/package.json b/developer/src/kmc-kmn/package.json index cdd1f982196..5802c859055 100644 --- a/developer/src/kmc-kmn/package.json +++ b/developer/src/kmc-kmn/package.json @@ -38,7 +38,6 @@ "@types/semver": "^7.3.12", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.9", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index 892acd93d73..7f9e0a9053a 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -58,7 +58,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "esbuild": "^0.15.8", diff --git a/package-lock.json b/package-lock.json index ac003038c96..49b34fdc35d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -862,7 +862,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "esbuild": "^0.15.8", @@ -884,7 +883,6 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -1379,7 +1377,6 @@ "@types/semver": "^7.3.12", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.9", - "@types/xml2js": "^0.4.5", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", From 35814bc29f05068f656d22ee33bd28ee125d39dc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 07:45:34 +0700 Subject: [PATCH 174/207] chore(developer): add --for-publishing to kmc The new --for-publishing flag allows us to enforce additional requirements for keyboards and lexical models that are to be published to the keymanapp repositories. This flag will always be switched on in the repository builds. This flag overrides the 'skipMetadata' option which is a project-level option. There is a bit of a delicate balance of requirements here: 1. We want to be able to automatically verify keyboards and package licenses if they are in the repository. 2. We do not want to force license checks on privately built keyboards and models. 3. We want to provide pathways for users to check locally before submitting to the repository. 4. If possible, we want to be able to build the .keyboard_info and .model_info files locally, but this should be up to the user for local builds. For now, the only additional check that this flag provides is to verify that the license is MIT, which was an unchecked requirement for the repositories in the past. Future checks can be added for file layout, additional required metadata files. Anticipate adding this as a tool to Keyman Developer IDE -- a 'pre-publish' check -- in the future. --- developer/src/kmc-keyboard-info/src/index.ts | 23 ++++++++++++++----- .../test/test-keyboard-info-compiler.ts | 1 + .../kmc-model-info/src/model-info-compiler.ts | 3 +++ .../test/test-model-info-compiler.ts | 1 + developer/src/kmc/src/commands/build.ts | 8 ++++--- .../buildClasses/BuildKeyboardInfo.ts | 8 ++++--- .../commands/buildClasses/BuildModelInfo.ts | 8 ++++--- .../src/commands/buildClasses/BuildProject.ts | 11 +++++---- .../kmc/src/util/extendedCompilerOptions.ts | 10 ++++++++ 9 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 developer/src/kmc/src/util/extendedCompilerOptions.ts diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index 052c12dd63c..611e7ea9a97 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -56,6 +56,9 @@ export interface KeyboardInfoSources { /** Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' */ lastCommitDate?: string; + + /** Return an error if project does not meet requirements of keyboards repository */ + forPublishing: boolean; }; export class KeyboardInfoCompiler { @@ -121,15 +124,23 @@ export class KeyboardInfoCompiler { // License - if(!kmpJsonData.options?.licenseFile) { - this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_NoLicenseFound()); - return null; - } + if(sources.forPublishing) { + // We will only verify the license if asked to do so, so that all keyboard + // projects can be built even if license is not present. Keyboards + // repository will always verify license + if(!kmpJsonData.options?.licenseFile) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_NoLicenseFound()); + return null; + } - if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, kmpJsonData.options.licenseFile))) { - return null; + if(!this.isLicenseMIT(this.callbacks.resolveFilename(sources.kpsFilename, kmpJsonData.options.licenseFile))) { + return null; + } } + // Even if license is not verified, we set the .keyboard_info license to + // 'mit' to meet the schema requirements. The .keyboard_info file is only + // used by the keyboards repository, so this is a fair assumption to make. keyboard_info.license = 'mit'; // isRTL diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts index 0e718991849..a0fdb14ca21 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler.ts @@ -26,6 +26,7 @@ describe('keyboard-info-compiler', function () { sourcePath: 'release/k/khmer_angkor', kpsFilename, jsFilename: jsFilename, + forPublishing: true, }); } catch(e) { callbacks.printMessages(); diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index 63644b9ef09..8a7101e86fe 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -33,6 +33,9 @@ export class ModelInfoSources { /** Last modification date for files in the project folder 'YYYY-MM-DDThh:mm:ssZ' */ lastCommitDate?: string; + + /** Return an error if project does not meet requirements of lexical-models repository */ + forPublishing: boolean; }; /* c8 ignore stop */ diff --git a/developer/src/kmc-model-info/test/test-model-info-compiler.ts b/developer/src/kmc-model-info/test/test-model-info-compiler.ts index aa0c2ed65b7..d74a2f5c60a 100644 --- a/developer/src/kmc-model-info/test/test-model-info-compiler.ts +++ b/developer/src/kmc-model-info/test/test-model-info-compiler.ts @@ -29,6 +29,7 @@ describe('model-info-compiler', function () { modelFileName, sourcePath: 'release/sil/sil.cmo.bw', kpsFilename, + forPublishing: true, }); if(data == null) { callbacks.printMessages(); diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 9cea460ec88..0d091680b7c 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -11,10 +11,9 @@ import { expandFileLists } from '../util/fileLists.js'; import { isProject } from '../util/projectLoader.js'; import { buildTestData } from './buildTestData/index.js'; import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js'; -//import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js'; +import { ExtendedCompilerOptions } from 'src/util/extendedCompilerOptions.js'; - -function commandOptionsToCompilerOptions(options: any): CompilerOptions { +function commandOptionsToCompilerOptions(options: any): ExtendedCompilerOptions { // We don't want to rename command line options to match the precise // properties that we have in CompilerOptions, but nor do we want to rename // CompilerOptions properties... @@ -29,6 +28,8 @@ function commandOptionsToCompilerOptions(options: any): CompilerOptions { saveDebug: options.debug, compilerWarningsAsErrors: options.compilerWarningsAsErrors, warnDeprecatedCode: options.warnDeprecatedCode, + // ExtendedOptions + forPublishing: options.forPublishing, } } @@ -50,6 +51,7 @@ export function declareBuild(program: Command) { buildCommand.command('file [infile...]', {isDefault: true}) .description(`Compile one or more source files or projects ('file' subcommand is default).`) + .option('--for-publishing', 'Verify that project meets @keymanapp repository requirements') .addHelpText('after', ` Supported file types: * folder: Keyman project in folder diff --git a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts index 0af8343b2da..8d8d66938f1 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildKeyboardInfo.ts @@ -1,18 +1,19 @@ import * as fs from 'fs'; import { BuildActivity } from './BuildActivity.js'; -import { CompilerCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanFileTypes } from '@keymanapp/common-types'; +import { CompilerCallbacks, KeymanDeveloperProject, KeymanFileTypes } from '@keymanapp/common-types'; import { KeyboardInfoCompiler } from '@keymanapp/kmc-keyboard-info'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; import { getLastGitCommitDate } from '../../util/getLastGitCommitDate.js'; +import { ExtendedCompilerOptions } from 'src/util/extendedCompilerOptions.js'; export class BuildKeyboardInfo extends BuildActivity { public get name(): string { return 'Keyboard metadata'; } public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.Project; } public get compiledExtension(): KeymanFileTypes.Binary { return KeymanFileTypes.Binary.KeyboardInfo; } public get description(): string { return 'Build a keyboard metadata file'; } - public async build(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: ExtendedCompilerOptions): Promise { if(!KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.Project)) { // Even if the project file does not exist, we use its name as our reference // in order to avoid ambiguity @@ -41,7 +42,8 @@ export class BuildKeyboardInfo extends BuildActivity { kpsFilename: project.resolveInputFilePath(kps), jsFilename: fs.existsSync(jsFilename) ? jsFilename : undefined, sourcePath: calculateSourcePath(infile), - lastCommitDate + lastCommitDate, + forPublishing: !!options.forPublishing, }); if(data == null) { diff --git a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts index 490543c0e6a..bddfcc38db7 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildModelInfo.ts @@ -1,12 +1,13 @@ import * as fs from 'fs'; import { BuildActivity } from './BuildActivity.js'; -import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types'; +import { CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types'; import { ModelInfoCompiler } from '@keymanapp/kmc-model-info'; import { KmpCompiler } from '@keymanapp/kmc-package'; import { loadProject } from '../../util/projectLoader.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { calculateSourcePath } from '../../util/calculateSourcePath.js'; import { getLastGitCommitDate } from '../../util/getLastGitCommitDate.js'; +import { ExtendedCompilerOptions } from 'src/util/extendedCompilerOptions.js'; export class BuildModelInfo extends BuildActivity { public get name(): string { return 'Lexical model metadata'; } @@ -24,7 +25,7 @@ export class BuildModelInfo extends BuildActivity { * @param options * @returns */ - public async build(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: ExtendedCompilerOptions): Promise { if(!KeymanFileTypes.filenameIs(infile, KeymanFileTypes.Source.Project)) { // Even if the project file does not exist, we use its name as our reference // in order to avoid ambiguity @@ -65,7 +66,8 @@ export class BuildModelInfo extends BuildActivity { modelFileName: project.resolveOutputFilePath(model, KeymanFileTypes.Source.Model, KeymanFileTypes.Binary.Model), kmpFileName: project.resolveOutputFilePath(kps, KeymanFileTypes.Source.Package, KeymanFileTypes.Binary.Package), kpsFilename: project.resolveInputFilePath(kps), - lastCommitDate + lastCommitDate, + forPublishing: !!options.forPublishing, }); if(data == null) { diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index db74ccec066..de0e55dfc16 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -1,17 +1,18 @@ import * as path from 'path'; import * as fs from 'fs'; -import { CompilerCallbacks, CompilerFileCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanFileTypes } from '@keymanapp/common-types'; +import { CompilerCallbacks, CompilerFileCallbacks, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanFileTypes } from '@keymanapp/common-types'; import { BuildActivity } from './BuildActivity.js'; import { buildActivities, buildKeyboardInfoActivity, buildModelInfoActivity } from './buildActivities.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { loadProject } from '../../util/projectLoader.js'; +import { ExtendedCompilerOptions } from 'src/util/extendedCompilerOptions.js'; export class BuildProject extends BuildActivity { public get name(): string { return 'Project'; } public get sourceExtension(): KeymanFileTypes.Source { return KeymanFileTypes.Source.Project; } public get compiledExtension(): KeymanFileTypes.Binary { return null; } public get description(): string { return 'Build a keyboard or lexical model project'; } - public async build(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: ExtendedCompilerOptions): Promise { let builder = new ProjectBuilder(infile, callbacks, options); return builder.run(); } @@ -20,10 +21,10 @@ export class BuildProject extends BuildActivity { class ProjectBuilder { callbacks: CompilerCallbacks; infile: string; - options: CompilerOptions; + options: ExtendedCompilerOptions; project: KeymanDeveloperProject; - constructor(infile: string, callbacks: CompilerCallbacks, options: CompilerOptions) { + constructor(infile: string, callbacks: CompilerCallbacks, options: ExtendedCompilerOptions) { this.infile = path.resolve(infile); this.callbacks = new CompilerFileCallbacks(infile, options, callbacks); this.options = options; @@ -53,7 +54,7 @@ class ProjectBuilder { } // Build project metadata - if(!this.project.options.skipMetadataFiles) { + if(this.options.forPublishing || !this.project.options.skipMetadataFiles) { if(!await (this.buildProjectTargets( this.project.isKeyboardProject() ? buildKeyboardInfoActivity diff --git a/developer/src/kmc/src/util/extendedCompilerOptions.ts b/developer/src/kmc/src/util/extendedCompilerOptions.ts new file mode 100644 index 00000000000..8d2903e53cb --- /dev/null +++ b/developer/src/kmc/src/util/extendedCompilerOptions.ts @@ -0,0 +1,10 @@ +import { CompilerOptions } from '@keymanapp/common-types'; + +export interface ExtendedCompilerOptions extends CompilerOptions { + /** + * Verify that the project meets the requirements of the keymanapp/keyboards + * or keymanapp/lexical-models repository, e.g. verify that project license is + * MIT + */ + forPublishing?: boolean; +}; From 7544b332160d91a411649786eea2d9664c7c54e7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 12:58:19 +0700 Subject: [PATCH 175/207] fix(developer): reduce WARN_PackageShouldNotRepeatLanguages to HINT Part of #9266. Also cleans up filenames for hint_language_tag_is_not_minimal, and fixes a bug in transformKpsToKmpObject where a null object could be passed on to another function in case of failure. --- .../kmc-package/src/compiler/kmp-compiler.ts | 3 +++ .../src/kmc-package/src/compiler/messages.ts | 4 ++-- .../src/compiler/package-validation.ts | 2 +- ...ps => hint_language_tag_is_not_minimal.kps} | 2 +- ...nt_package_should_not_repeat_languages.kps} | 2 +- ...kage_should_not_repeat_languages.model.kps} | 2 +- .../src/kmc-package/test/test-messages.ts | 18 +++++++++--------- 7 files changed, 18 insertions(+), 15 deletions(-) rename developer/src/kmc-package/test/fixtures/invalid/{warn_language_tag_is_not_minimal.kps => hint_language_tag_is_not_minimal.kps} (95%) rename developer/src/kmc-package/test/fixtures/invalid/{warn_package_should_not_repeat_languages.kps => hint_package_should_not_repeat_languages.kps} (94%) rename developer/src/kmc-package/test/fixtures/invalid/{keyman.en.warn_package_should_not_repeat_languages.model.kps => keyman.en.hint_package_should_not_repeat_languages.model.kps} (95%) diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 22fb02a38d4..839a6efda15 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -28,6 +28,9 @@ export class KmpCompiler { public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { const kps = this.loadKpsFile(kpsFilename); + if(!kps) { + return null; + } return this.transformKpsFileToKmpObject(kpsFilename, kps); } diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index b216246c5a9..2eeaa6e0618 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -50,9 +50,9 @@ export class CompilerMessages { `The package contains both lexical models and keyboards, which is not permitted.`); static ERROR_PackageCannotContainBothModelsAndKeyboards = SevError | 0x000B; - static Warn_PackageShouldNotRepeatLanguages = (o:{resourceType: string, id: string, minimalTag: string, firstTag: string, secondTag: string}) => m(this.WARN_PackageShouldNotRepeatLanguages, + static Hint_PackageShouldNotRepeatLanguages = (o:{resourceType: string, id: string, minimalTag: string, firstTag: string, secondTag: string}) => m(this.HINT_PackageShouldNotRepeatLanguages, `Two language tags in ${o.resourceType} ${o.id}, '${o.firstTag}' and '${o.secondTag}', reduce to the same minimal tag '${o.minimalTag}'.`); - static WARN_PackageShouldNotRepeatLanguages = SevWarn | 0x000C; + static HINT_PackageShouldNotRepeatLanguages = SevHint | 0x000C; static Warn_PackageNameDoesNotFollowLexicalModelConventions = (o:{filename: string}) => m(this.WARN_PackageNameDoesNotFollowLexicalModelConventions, `The package file ${o.filename} does not follow the recommended model filename conventions. The name should be all lower case, `+ diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts index adbaf085bae..3fe787d054a 100644 --- a/developer/src/kmc-package/src/compiler/package-validation.ts +++ b/developer/src/kmc-package/src/compiler/package-validation.ts @@ -68,7 +68,7 @@ export class PackageValidation { } if(minimalTags[minimalTag]) { - this.callbacks.reportMessage(CompilerMessages.Warn_PackageShouldNotRepeatLanguages({resourceType, id, minimalTag, firstTag: lang.id, secondTag: minimalTags[minimalTag]})); + this.callbacks.reportMessage(CompilerMessages.Hint_PackageShouldNotRepeatLanguages({resourceType, id, minimalTag, firstTag: lang.id, secondTag: minimalTags[minimalTag]})); } else { minimalTags[minimalTag] = lang.id; diff --git a/developer/src/kmc-package/test/fixtures/invalid/warn_language_tag_is_not_minimal.kps b/developer/src/kmc-package/test/fixtures/invalid/hint_language_tag_is_not_minimal.kps similarity index 95% rename from developer/src/kmc-package/test/fixtures/invalid/warn_language_tag_is_not_minimal.kps rename to developer/src/kmc-package/test/fixtures/invalid/hint_language_tag_is_not_minimal.kps index cc757d6431c..d29ae419231 100644 --- a/developer/src/kmc-package/test/fixtures/invalid/warn_language_tag_is_not_minimal.kps +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_language_tag_is_not_minimal.kps @@ -24,7 +24,7 @@ basic 1.0 - + Central Khmer (Khmer, Cambodia) diff --git a/developer/src/kmc-package/test/fixtures/invalid/warn_package_should_not_repeat_languages.kps b/developer/src/kmc-package/test/fixtures/invalid/hint_package_should_not_repeat_languages.kps similarity index 94% rename from developer/src/kmc-package/test/fixtures/invalid/warn_package_should_not_repeat_languages.kps rename to developer/src/kmc-package/test/fixtures/invalid/hint_package_should_not_repeat_languages.kps index ef8fcfb89df..e67d60f7c29 100644 --- a/developer/src/kmc-package/test/fixtures/invalid/warn_package_should_not_repeat_languages.kps +++ b/developer/src/kmc-package/test/fixtures/invalid/hint_package_should_not_repeat_languages.kps @@ -24,7 +24,7 @@ basic 1.0 - + Central Khmer (Khmer, Cambodia) Khmer diff --git a/developer/src/kmc-package/test/fixtures/invalid/keyman.en.warn_package_should_not_repeat_languages.model.kps b/developer/src/kmc-package/test/fixtures/invalid/keyman.en.hint_package_should_not_repeat_languages.model.kps similarity index 95% rename from developer/src/kmc-package/test/fixtures/invalid/keyman.en.warn_package_should_not_repeat_languages.model.kps rename to developer/src/kmc-package/test/fixtures/invalid/keyman.en.hint_package_should_not_repeat_languages.model.kps index 0fdb2703214..1e8b849b2ad 100644 --- a/developer/src/kmc-package/test/fixtures/invalid/keyman.en.warn_package_should_not_repeat_languages.model.kps +++ b/developer/src/kmc-package/test/fixtures/invalid/keyman.en.hint_package_should_not_repeat_languages.model.kps @@ -24,7 +24,7 @@ example.qaa.sencoten North Straits Salish - + Salish Again SENĆOŦEN diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index a9943e668ed..f9649e9c090 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -95,16 +95,16 @@ describe('CompilerMessages', function () { testForMessage(this, ['invalid', 'error_package_cannot_contain_both_models_and_keyboards.kps'], CompilerMessages.ERROR_PackageCannotContainBothModelsAndKeyboards); }); - // WARN_PackageShouldNotRepeatLanguages (models) + // HINT_PackageShouldNotRepeatLanguages (models) - it('should generate WARN_PackageShouldNotRepeatLanguages if model has same language repeated', async function() { - testForMessage(this, ['invalid', 'keyman.en.warn_package_should_not_repeat_languages.model.kps'], CompilerMessages.WARN_PackageShouldNotRepeatLanguages); + it('should generate HINT_PackageShouldNotRepeatLanguages if model has same language repeated', async function() { + testForMessage(this, ['invalid', 'keyman.en.hint_package_should_not_repeat_languages.model.kps'], CompilerMessages.HINT_PackageShouldNotRepeatLanguages); }); - // WARN_PackageShouldNotRepeatLanguages (keyboards) + // HINT_PackageShouldNotRepeatLanguages (keyboards) - it('should generate WARN_PackageShouldNotRepeatLanguages if keyboard has same language repeated', async function() { - testForMessage(this, ['invalid', 'warn_package_should_not_repeat_languages.kps'], CompilerMessages.WARN_PackageShouldNotRepeatLanguages); + it('should generate HINT_PackageShouldNotRepeatLanguages if keyboard has same language repeated', async function() { + testForMessage(this, ['invalid', 'hint_package_should_not_repeat_languages.kps'], CompilerMessages.HINT_PackageShouldNotRepeatLanguages); }); // WARN_PackageNameDoesNotFollowLexicalModelConventions @@ -160,10 +160,10 @@ describe('CompilerMessages', function () { testForMessage(this, ['invalid', 'error_language_tag_is_not_valid.kps'], CompilerMessages.ERROR_LanguageTagIsNotValid); }); - // WARN_LanguageTagIsNotMinimal + // HINT_LanguageTagIsNotMinimal - it('should generate WARN_LanguageTagIsNotMinimal if keyboard has a non-minimal language tag', async function() { - testForMessage(this, ['invalid', 'warn_language_tag_is_not_minimal.kps'], CompilerMessages.HINT_LanguageTagIsNotMinimal); + it('should generate HINT_LanguageTagIsNotMinimal if keyboard has a non-minimal language tag', async function() { + testForMessage(this, ['invalid', 'hint_language_tag_is_not_minimal.kps'], CompilerMessages.HINT_LanguageTagIsNotMinimal); }); // ERROR_ModelMustHaveAtLeastOneLanguage From 4ef18f5e29ad21fef15e975992089cb98092de62 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 14:21:01 +0700 Subject: [PATCH 176/207] chore(core): rename keyboardprocessor.h to keyman_core_api.h Fixes #9721. Also tweaks some documentation references to keyboardprocessor, but does not touch those relating to debian/control. --- core/doc/hotdoc.json | 2 +- core/doc/introspection.schema | 2 +- core/doc/markdown_files/index.md | 2 +- core/doc/meson.build | 2 +- .../{keyboardprocessor.h => keyman_core_api.h} | 10 +++++----- ...rdprocessor_bits.h => keyman_core_api_bits.h} | 0 ...ocessor_consts.h => keyman_core_api_consts.h} | 0 ...processor_debug.h => keyman_core_api_debug.h} | 4 ++-- ...version.h.in => keyman_core_api_version.h.in} | 0 ...processor_vkeys.h => keyman_core_api_vkeys.h} | 2 +- core/include/keyman/meson.build | 16 ++++++++-------- core/src/context.hpp | 2 +- core/src/debug.hpp | 4 ++-- core/src/debuglog.h | 2 +- core/src/keyboard.hpp | 2 +- core/src/km_kbp_context_api.cpp | 2 +- core/src/km_kbp_debug_api.cpp | 2 +- core/src/km_kbp_keyboard_api.cpp | 2 +- core/src/km_kbp_options_api.cpp | 2 +- core/src/km_kbp_processevent_api.cpp | 2 +- core/src/km_kbp_state_api.cpp | 4 ++-- core/src/kmx/kmx_base.h | 4 ++-- core/src/kmx/kmx_capslock.cpp | 2 +- core/src/kmx/kmx_environment.cpp | 2 +- core/src/kmx/kmx_options.h | 2 +- core/src/kmx/kmx_processevent.cpp | 2 +- core/src/kmx/kmx_processevent.h | 2 +- core/src/kmx/kmx_processor.cpp | 2 +- core/src/kmx/kmx_processor.hpp | 2 +- core/src/ldml/ldml_processor.hpp | 2 +- core/src/ldml/ldml_vkeys.hpp | 2 +- core/src/mock/mock_processor.hpp | 2 +- core/src/option.hpp | 2 +- core/src/path.hpp | 2 +- core/src/processor.hpp | 4 ++-- core/src/state.cpp | 2 +- core/src/state.hpp | 2 +- core/src/version.rc | 2 +- core/tests/unit/kmnkbd/action_items.hpp | 2 +- core/tests/unit/kmnkbd/context_api.cpp | 2 +- core/tests/unit/kmnkbd/debug_api.cpp | 6 +++--- core/tests/unit/kmnkbd/debug_items.hpp | 4 ++-- core/tests/unit/kmnkbd/keyboard_api.cpp | 2 +- core/tests/unit/kmnkbd/options_api.cpp | 2 +- core/tests/unit/kmnkbd/state_api.cpp | 6 +++--- core/tests/unit/ldml/ldml_test_utils.hpp | 2 +- .../src/tike/main/Keyman.System.KeymanCore.pas | 6 +++--- .../tike/main/Keyman.System.KeymanCoreDebug.pas | 6 +++--- docs/settings/linux/settings.json | 2 +- linux/debian/tests/test-build | 4 ++-- linux/ibus-keyman/src/engine.c | 4 ++-- linux/ibus-keyman/src/keycodes.h | 2 +- linux/ibus-keyman/src/keymanutil.c | 2 +- linux/ibus-keyman/src/keymanutil.h | 2 +- linux/ibus-keyman/tests/testfixture.cpp | 2 +- windows/src/engine/keyman32/keymanengine.h | 8 ++++---- 56 files changed, 83 insertions(+), 83 deletions(-) rename core/include/keyman/{keyboardprocessor.h => keyman_core_api.h} (99%) rename core/include/keyman/{keyboardprocessor_bits.h => keyman_core_api_bits.h} (100%) rename core/include/keyman/{keyboardprocessor_consts.h => keyman_core_api_consts.h} (100%) rename core/include/keyman/{keyboardprocessor_debug.h => keyman_core_api_debug.h} (98%) rename core/include/keyman/{keyboardprocessor_version.h.in => keyman_core_api_version.h.in} (100%) rename core/include/keyman/{keyboardprocessor_vkeys.h => keyman_core_api_vkeys.h} (99%) diff --git a/core/doc/hotdoc.json b/core/doc/hotdoc.json index 4ece50d0f52..946c62beac0 100644 --- a/core/doc/hotdoc.json +++ b/core/doc/hotdoc.json @@ -4,7 +4,7 @@ "sitemap": "@doc_dir@/sitemap.txt", "index": "@doc_dir@/markdown_files/index.md", "c_sources": [ - "@include_dir@/keyman/keyboardprocessor.h" + "@include_dir@/keyman/keyman_core_api.h" ], "c_include_directories": [ "@include_dir@", diff --git a/core/doc/introspection.schema b/core/doc/introspection.schema index ff10c7c9695..3b7684e0e9d 100644 --- a/core/doc/introspection.schema +++ b/core/doc/introspection.schema @@ -47,7 +47,7 @@ } }, "properties": { - "$schema": { "const": "keyman/keyboardprocessor/doc/introspection.schema" }, + "$schema": { "const": "keyman/core/doc/introspection.schema" }, "keyboard": { "$ref": "#/definitions/keyboard" }, "options": { "type": "object", diff --git a/core/doc/markdown_files/index.md b/core/doc/markdown_files/index.md index 98870c356dc..bc196a0a0cb 100644 --- a/core/doc/markdown_files/index.md +++ b/core/doc/markdown_files/index.md @@ -46,7 +46,7 @@ A virtual key board event and modifier map recevied from the Platform layer to b processed with the state object for this Client application. - __Virtual Key:__ A code based on the US English layout, with values matching the Windows -virtual key codes. See `keyboardprocessor_vkeys.h` for definitions. +virtual key codes. See `keyman_core_api_vkeys.h` for definitions. - __Modifier Key:__ The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may have other names (e.g. Alt is called Option on macOS); other platform-specific diff --git a/core/doc/meson.build b/core/doc/meson.build index 01fcb5c98ad..6039e2e2dda 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -18,7 +18,7 @@ if hotdoc.found() output: 'hotdoc.json', configuration: cfg) deps = files( - '../include/keyman/keyboardprocessor.h', + '../include/keyman/keyman_core_api.h', '../src/jsonpp.hpp', '../src/utfcodec.hpp' ) diff --git a/core/include/keyman/keyboardprocessor.h b/core/include/keyman/keyman_core_api.h similarity index 99% rename from core/include/keyman/keyboardprocessor.h rename to core/include/keyman/keyman_core_api.h index 429a8ae6cbf..b32be630731 100644 --- a/core/include/keyman/keyboardprocessor.h +++ b/core/include/keyman/keyman_core_api.h @@ -57,7 +57,7 @@ A virtual key event and modifier map received from the Platform layer to be processed with the state object for this Client application. - __Virtual Key:__ A code based on the US English layout, with values matching the Windows -virtual key codes. See `keyboardprocessor_vkeys.h` for definitions. +virtual key codes. See `keyman_core_api_vkeys.h` for definitions. - __Modifier Key:__ The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may have other names (e.g. Alt is called Option on macOS); other platform-specific @@ -100,9 +100,9 @@ nothing in that event. */ #include #include -#include -#include -#include +#include +#include +#include #if defined(__cplusplus) extern "C" @@ -989,7 +989,7 @@ used in IMX callbacks called during `km_kbp_process_event`. In the event the `state` or `action_items` pointer are null. ##### Parameters: - __state__: A pointer to the opaque `km_kbp_state` object to be queried. -- __action_items__: The action items to be added to the keyboardprocessor +- __action_items__: The action items to be added to the core queue. Must be terminated with a `KM_KBP_IT_END` entry. ```c diff --git a/core/include/keyman/keyboardprocessor_bits.h b/core/include/keyman/keyman_core_api_bits.h similarity index 100% rename from core/include/keyman/keyboardprocessor_bits.h rename to core/include/keyman/keyman_core_api_bits.h diff --git a/core/include/keyman/keyboardprocessor_consts.h b/core/include/keyman/keyman_core_api_consts.h similarity index 100% rename from core/include/keyman/keyboardprocessor_consts.h rename to core/include/keyman/keyman_core_api_consts.h diff --git a/core/include/keyman/keyboardprocessor_debug.h b/core/include/keyman/keyman_core_api_debug.h similarity index 98% rename from core/include/keyman/keyboardprocessor_debug.h rename to core/include/keyman/keyman_core_api_debug.h index 380b8ee3012..f038e2cd819 100644 --- a/core/include/keyman/keyboardprocessor_debug.h +++ b/core/include/keyman/keyman_core_api_debug.h @@ -15,8 +15,8 @@ #include #include -#include -#include +#include +#include #if defined(__cplusplus) extern "C" diff --git a/core/include/keyman/keyboardprocessor_version.h.in b/core/include/keyman/keyman_core_api_version.h.in similarity index 100% rename from core/include/keyman/keyboardprocessor_version.h.in rename to core/include/keyman/keyman_core_api_version.h.in diff --git a/core/include/keyman/keyboardprocessor_vkeys.h b/core/include/keyman/keyman_core_api_vkeys.h similarity index 99% rename from core/include/keyman/keyboardprocessor_vkeys.h rename to core/include/keyman/keyman_core_api_vkeys.h index e8d8122bda2..a1e580df28b 100644 --- a/core/include/keyman/keyboardprocessor_vkeys.h +++ b/core/include/keyman/keyman_core_api_vkeys.h @@ -7,7 +7,7 @@ Create Date: 17 Oct 2018 Authors: Tim Eves (TSE) History: 17 Oct 2018 - TSE - Moved & refactored km_kbp_modifier_state - from keyboardprocessor.h. + from keyman_core_api.h. - Added VKey and mask definitions. 6 Oct 2018 - TSE - Move into keyman folder. diff --git a/core/include/keyman/meson.build b/core/include/keyman/meson.build index 7da255502de..6be28e49799 100644 --- a/core/include/keyman/meson.build +++ b/core/include/keyman/meson.build @@ -19,16 +19,16 @@ cfg.set('patchver', project_ver[2]) configure_file( configuration: cfg, - input: 'keyboardprocessor_version.h.in', - output: 'keyboardprocessor_version.h', + input: 'keyman_core_api_version.h.in', + output: 'keyman_core_api_version.h', ) -install_headers('keyboardprocessor.h', - 'keyboardprocessor_bits.h', - 'keyboardprocessor_consts.h', - 'keyboardprocessor_debug.h', - 'keyboardprocessor_vkeys.h', - join_paths(meson.current_build_dir(), 'keyboardprocessor_version.h'), +install_headers('keyman_core_api.h', + 'keyman_core_api_bits.h', + 'keyman_core_api_consts.h.h', + 'keyman_core_api_debug.h.h', + 'keyman_core_api_vkeys.h', + join_paths(meson.current_build_dir(), 'keyman_core_api_version.h'), '../../../common/include/km_types.h', '../../../common/include/kmx_file.h', subdir: 'keyman') diff --git a/core/src/context.hpp b/core/src/context.hpp index b344e0657cd..03d4aeb2d79 100644 --- a/core/src/context.hpp +++ b/core/src/context.hpp @@ -9,7 +9,7 @@ #pragma once #include #include -#include +#include // Forward declarations class json; diff --git a/core/src/debug.hpp b/core/src/debug.hpp index 8069cfbc532..a910c03a288 100644 --- a/core/src/debug.hpp +++ b/core/src/debug.hpp @@ -9,8 +9,8 @@ #include #include -#include -#include +#include +#include namespace km { namespace kbp diff --git a/core/src/debuglog.h b/core/src/debuglog.h index 16fba464285..1ecbaca6124 100644 --- a/core/src/debuglog.h +++ b/core/src/debuglog.h @@ -1,6 +1,6 @@ /* Debugging */ -#include +#include namespace km { namespace kbp { diff --git a/core/src/keyboard.hpp b/core/src/keyboard.hpp index 57f8128c781..6c8b93263c4 100644 --- a/core/src/keyboard.hpp +++ b/core/src/keyboard.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "option.hpp" #include "path.hpp" diff --git a/core/src/km_kbp_context_api.cpp b/core/src/km_kbp_context_api.cpp index a9111f66c45..a0845618b4f 100644 --- a/core/src/km_kbp_context_api.cpp +++ b/core/src/km_kbp_context_api.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include "context.hpp" #include "jsonpp.hpp" diff --git a/core/src/km_kbp_debug_api.cpp b/core/src/km_kbp_debug_api.cpp index 0871df17ae5..ff7ad03044b 100644 --- a/core/src/km_kbp_debug_api.cpp +++ b/core/src/km_kbp_debug_api.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "processor.hpp" #include "state.hpp" diff --git a/core/src/km_kbp_keyboard_api.cpp b/core/src/km_kbp_keyboard_api.cpp index 13f3cafc45c..b12ae3a9cb8 100644 --- a/core/src/km_kbp_keyboard_api.cpp +++ b/core/src/km_kbp_keyboard_api.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "keyboard.hpp" #include "processor.hpp" #include "kmx/kmx_processor.hpp" diff --git a/core/src/km_kbp_options_api.cpp b/core/src/km_kbp_options_api.cpp index 7dc48790987..0f522d6ffeb 100644 --- a/core/src/km_kbp_options_api.cpp +++ b/core/src/km_kbp_options_api.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "processor.hpp" #include "jsonpp.hpp" diff --git a/core/src/km_kbp_processevent_api.cpp b/core/src/km_kbp_processevent_api.cpp index 69eba06a41c..8e818ea42d2 100644 --- a/core/src/km_kbp_processevent_api.cpp +++ b/core/src/km_kbp_processevent_api.cpp @@ -8,7 +8,7 @@ History: 17 Oct 2018 - TSE - Initial implementation. */ -#include +#include #include "processor.hpp" #include "state.hpp" diff --git a/core/src/km_kbp_state_api.cpp b/core/src/km_kbp_state_api.cpp index b966688950f..168288423df 100644 --- a/core/src/km_kbp_state_api.cpp +++ b/core/src/km_kbp_state_api.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include "jsonpp.hpp" #include "processor.hpp" @@ -212,7 +212,7 @@ km_kbp_status km_kbp_state_to_json(km_kbp_state const *state, { // Pretty print the document. jo << json::object - << "$schema" << "keyman/keyboardprocessor/doc/introspection.schema" + << "$schema" << "keyman/core/doc/introspection.schema" << "keyboard" << state->processor().keyboard() // << "options" << state->options() TODO: Fix << "context" << state->context() diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index 7fc335cfc46..77cf53c209b 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/core/src/kmx/kmx_capslock.cpp b/core/src/kmx/kmx_capslock.cpp index e125ccd4cf1..ca324778830 100644 --- a/core/src/kmx/kmx_capslock.cpp +++ b/core/src/kmx/kmx_capslock.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include #include using namespace km::kbp; diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index 4d3d3c54903..b77971a4036 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -3,7 +3,7 @@ Authors: mcdurdin */ #include -#include +#include #include #include diff --git a/core/src/kmx/kmx_options.h b/core/src/kmx/kmx_options.h index 76af7dc4057..500dc7474ea 100644 --- a/core/src/kmx/kmx_options.h +++ b/core/src/kmx/kmx_options.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "option.hpp" #include "kmx_base.h" diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index c6fec0e468f..94015ef2c8b 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -4,7 +4,7 @@ */ #include "kmx_processevent.h" #include "state.hpp" -#include +#include using namespace km::kbp; using namespace kmx; diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index 5dfdfc71903..13b6f0d6bb3 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "debuglog.h" #include "kmx_base.h" #include "kmx_file.h" diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 30b3ce7691f..3bf136f1a20 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -1,4 +1,4 @@ -#include +#include #include "state.hpp" #include "kmx/kmx_processor.hpp" #include diff --git a/core/src/kmx/kmx_processor.hpp b/core/src/kmx/kmx_processor.hpp index c58743aff88..e279eb8f62b 100644 --- a/core/src/kmx/kmx_processor.hpp +++ b/core/src/kmx/kmx_processor.hpp @@ -9,7 +9,7 @@ #pragma once #include -#include +#include #include "kmx/kmx_processevent.h" #include "keyboard.hpp" #include "processor.hpp" diff --git a/core/src/ldml/ldml_processor.hpp b/core/src/ldml/ldml_processor.hpp index 09c743d4d16..d2016e5c9f8 100644 --- a/core/src/ldml/ldml_processor.hpp +++ b/core/src/ldml/ldml_processor.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include "processor.hpp" #include "option.hpp" #include "ldml_vkeys.hpp" diff --git a/core/src/ldml/ldml_vkeys.hpp b/core/src/ldml/ldml_vkeys.hpp index bfab902b0fa..0b632f670c2 100644 --- a/core/src/ldml/ldml_vkeys.hpp +++ b/core/src/ldml/ldml_vkeys.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include namespace km { namespace kbp { diff --git a/core/src/mock/mock_processor.hpp b/core/src/mock/mock_processor.hpp index e5a72cba4a9..5e8857fb46b 100644 --- a/core/src/mock/mock_processor.hpp +++ b/core/src/mock/mock_processor.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include "processor.hpp" #include "option.hpp" diff --git a/core/src/option.hpp b/core/src/option.hpp index 70627bfb8f2..979cc80b961 100644 --- a/core/src/option.hpp +++ b/core/src/option.hpp @@ -12,7 +12,7 @@ #include -#include +#include // Forward declarations class json; diff --git a/core/src/path.hpp b/core/src/path.hpp index 9bb8145d741..ec08c064f45 100644 --- a/core/src/path.hpp +++ b/core/src/path.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include "jsonpp.hpp" #include "utfcodec.hpp" diff --git a/core/src/processor.hpp b/core/src/processor.hpp index 39edb120d2d..7fc6a543181 100644 --- a/core/src/processor.hpp +++ b/core/src/processor.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "keyboard.hpp" @@ -96,7 +96,7 @@ namespace kbp ) = 0; /** - * Returns the keyboardprocessor context as an array of + * Returns the core context as an array of * km_kbp_context_items. Caller is responsible for freeing * the memory * @return km_kbp_context_item* diff --git a/core/src/state.cpp b/core/src/state.cpp index d0e07573dd5..e873af8287e 100644 --- a/core/src/state.cpp +++ b/core/src/state.cpp @@ -7,7 +7,7 @@ #include "state.hpp" #include "processor.hpp" -#include +#include using namespace km::kbp; diff --git a/core/src/state.hpp b/core/src/state.hpp index c11f503cbe6..0b5b2ad6ca2 100644 --- a/core/src/state.hpp +++ b/core/src/state.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "context.hpp" #include "option.hpp" diff --git a/core/src/version.rc b/core/src/version.rc index cd852f01546..fddda79e286 100644 --- a/core/src/version.rc +++ b/core/src/version.rc @@ -1,5 +1,5 @@ #include -#include "include/keyman/keyboardprocessor_version.h" +#include "include/keyman/keyman_core_api_version.h" // Encoding: UTF8 (compile with rc /c65001) // Use rc /n to append null to all strings diff --git a/core/tests/unit/kmnkbd/action_items.hpp b/core/tests/unit/kmnkbd/action_items.hpp index d7eabf20d31..da485589ef1 100644 --- a/core/tests/unit/kmnkbd/action_items.hpp +++ b/core/tests/unit/kmnkbd/action_items.hpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/core/tests/unit/kmnkbd/context_api.cpp b/core/tests/unit/kmnkbd/context_api.cpp index 728a21924c5..c8a6fc51eda 100644 --- a/core/tests/unit/kmnkbd/context_api.cpp +++ b/core/tests/unit/kmnkbd/context_api.cpp @@ -10,7 +10,7 @@ mutation functions. */ #include -#include +#include #include "context.hpp" #include "utfcodec.hpp" diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.cpp index b4cfae6ab39..57f63666971 100644 --- a/core/tests/unit/kmnkbd/debug_api.cpp +++ b/core/tests/unit/kmnkbd/debug_api.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "path.hpp" #include "state.hpp" #include "kmx/kmx_base.h" @@ -462,14 +462,14 @@ int error_args() { int print_sizeof() { std::cout << std::endl; - std::cout << "keyboardprocessor.h:" << std::endl; + std::cout << "keyman_core_api.h:" << std::endl; std::cout << "sizeof(km_kbp_context_item): " << sizeof(km_kbp_context_item) << std::endl; std::cout << "sizeof(km_kbp_action_item): " << sizeof(km_kbp_action_item) << std::endl; std::cout << "sizeof(km_kbp_option_item): " << sizeof(km_kbp_option_item) << std::endl; std::cout << "sizeof(km_kbp_keyboard_attrs): " << sizeof(km_kbp_keyboard_attrs) << std::endl; std::cout << "sizeof(km_kbp_attr): " << sizeof(km_kbp_attr) << std::endl; std::cout << std::endl; - std::cout << "keyboardprocessor_debug.h:" << std::endl; + std::cout << "keyman_core_api_debug.h.h:" << std::endl; std::cout << "sizeof(km_kbp_state_debug_item): " << sizeof(km_kbp_state_debug_item) << std::endl; std::cout << "sizeof(km_kbp_state_debug_key_info): " << sizeof(km_kbp_state_debug_key_info) << std::endl; std::cout << "sizeof(km_kbp_state_debug_kmx_info): " << sizeof(km_kbp_state_debug_kmx_info) << std::endl; diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp index 95703005c45..3ac2ff1f68d 100644 --- a/core/tests/unit/kmnkbd/debug_items.hpp +++ b/core/tests/unit/kmnkbd/debug_items.hpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include "kmx/kmx_base.h" #include "kmx/kmx_xstring.h" diff --git a/core/tests/unit/kmnkbd/keyboard_api.cpp b/core/tests/unit/kmnkbd/keyboard_api.cpp index cefba0d47c5..48e36197ce1 100644 --- a/core/tests/unit/kmnkbd/keyboard_api.cpp +++ b/core/tests/unit/kmnkbd/keyboard_api.cpp @@ -6,7 +6,7 @@ */ #include -#include +#include #include "path.hpp" //#include "keyboard.hpp" diff --git a/core/tests/unit/kmnkbd/options_api.cpp b/core/tests/unit/kmnkbd/options_api.cpp index 5fa64cffc68..9612195847e 100644 --- a/core/tests/unit/kmnkbd/options_api.cpp +++ b/core/tests/unit/kmnkbd/options_api.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include "option.hpp" #include "state.hpp" diff --git a/core/tests/unit/kmnkbd/state_api.cpp b/core/tests/unit/kmnkbd/state_api.cpp index 3ae446541c0..866ebe49a42 100644 --- a/core/tests/unit/kmnkbd/state_api.cpp +++ b/core/tests/unit/kmnkbd/state_api.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include "path.hpp" #include "state.hpp" @@ -49,7 +49,7 @@ namespace constexpr char const *doc1_expected = u8"\ {\n\ - \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\ + \"$schema\" : \"keyman/core/doc/introspection.schema\",\n\ \"keyboard\" : {\n\ \"id\" : \"dummy\",\n\ \"folder\" : \"\",\n\ @@ -75,7 +75,7 @@ constexpr char const *doc1_expected = u8"\ constexpr char const *doc2_expected = u8"\ {\n\ - \"$schema\" : \"keyman/keyboardprocessor/doc/introspection.schema\",\n\ + \"$schema\" : \"keyman/core/doc/introspection.schema\",\n\ \"keyboard\" : {\n\ \"id\" : \"dummy\",\n\ \"folder\" : \"\",\n\ diff --git a/core/tests/unit/ldml/ldml_test_utils.hpp b/core/tests/unit/ldml/ldml_test_utils.hpp index 8b19edf8d33..146afef3899 100644 --- a/core/tests/unit/ldml/ldml_test_utils.hpp +++ b/core/tests/unit/ldml/ldml_test_utils.hpp @@ -13,7 +13,7 @@ #include // for char to vk mapping tables #include // for surrogate pair macros #include -#include +#include namespace km { namespace tests { diff --git a/developer/src/tike/main/Keyman.System.KeymanCore.pas b/developer/src/tike/main/Keyman.System.KeymanCore.pas index 29a2341b182..159b3e66934 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCore.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCore.pas @@ -1,7 +1,7 @@ { * Keyman is copyright (C) SIL International. MIT License. * - * Interface for Keyman Core, matches keyboardprocessor.h + * Interface for Keyman Core, matches keyman_core_api.h } unit Keyman.System.KeymanCore; @@ -321,7 +321,7 @@ function KM_KBP_EVENT_FLAG_TOUCH = 1; // set if the event is touch, otherwise hardware -// keyboardprocessor_vkeys.h +// keyman_core_api_vkeys.h const // enum km_kbp_modifier_state - matches Keyman32 shift states @@ -620,7 +620,7 @@ function KM_KBP_VKEY__FE = $FE; KM_KBP_VKEY__FF = $FF; -// keyboardprocessor_consts.h +// keyman_core_api_consts.h.h const // Defined environment options for KMX processor KM_KBP_KMX_ENV_PLATFORM = 'platform'; diff --git a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas index a1f63e3b38e..bca7395088f 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas @@ -1,7 +1,7 @@ { * Keyman is copyright (C) SIL International. MIT License. * - * Debug interface for Keyman Core, matches keyboardprocessor_debug.h + * Debug interface for Keyman Core, matches keyman_core_api_debug.h.h } unit Keyman.System.KeymanCoreDebug; @@ -181,7 +181,7 @@ initialization // Assertions generated by debug_api.cpp for win32 x86. // Usage: debug-api.exe --print-sizeof - //keyboardprocessor.h: + //keyman_core_api.h: Assert(sizeof(km_kbp_context_item) = 8); Assert(sizeof(km_kbp_context_item) = 8); Assert(sizeof(km_kbp_action_item) = 12); @@ -189,7 +189,7 @@ initialization Assert(sizeof(km_kbp_keyboard_attrs) = 16); Assert(sizeof(km_kbp_attr) = 16); - //keyboardprocessor_debug.h: + //keyman_core_api_debug.h.h: Assert(sizeof(km_kbp_state_debug_item) = 432); Assert(sizeof(km_kbp_state_debug_key_info) = 6); Assert(sizeof(km_kbp_state_debug_kmx_info) = 416); diff --git a/docs/settings/linux/settings.json b/docs/settings/linux/settings.json index 891507dd2ec..f17739ece29 100644 --- a/docs/settings/linux/settings.json +++ b/docs/settings/linux/settings.json @@ -16,7 +16,7 @@ "python.pythonPath": "/usr/bin/python3", "files.associations": { "*.tcc": "cpp", - "keyboardprocessor.h": "c", + "keyman_core_api.h": "c", "engine.h": "c", "keyman-service.h": "c", "keymanutil.h": "c", diff --git a/linux/debian/tests/test-build b/linux/debian/tests/test-build index db0a343849c..9fd3b6f077c 100644 --- a/linux/debian/tests/test-build +++ b/linux/debian/tests/test-build @@ -11,7 +11,7 @@ cd "$WORKDIR" # Test all include files are available cat < keymantest.c -#include +#include km_kbp_context* c; EOF @@ -21,7 +21,7 @@ echo "build 1: OK" # Test pkg-config file - include without path cat < keymantest.c -#include +#include km_kbp_context* c; EOF diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 49b9161f9a3..2fb17c4ff73 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -28,8 +28,8 @@ #include #include -#include -#include +#include +#include #include "config.h" #include "keymanutil.h" diff --git a/linux/ibus-keyman/src/keycodes.h b/linux/ibus-keyman/src/keycodes.h index 2735da48cb2..b563f11ad2c 100644 --- a/linux/ibus-keyman/src/keycodes.h +++ b/linux/ibus-keyman/src/keycodes.h @@ -1,7 +1,7 @@ #ifndef __KEYCODES_H__ #define __KEYCODES_H__ -#include +#include // from android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMScanCodeMap.java // uses kernel keycodes which are (X11 keycode - 8) diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c index 7ae92e2a2d9..bcb92ee4d9e 100644 --- a/linux/ibus-keyman/src/keymanutil.c +++ b/linux/ibus-keyman/src/keymanutil.c @@ -55,7 +55,7 @@ #include #include #include -#include +#include #include "bcp47util.h" #include "kmpdetails.h" diff --git a/linux/ibus-keyman/src/keymanutil.h b/linux/ibus-keyman/src/keymanutil.h index 4b358c02ead..e76fc6253c5 100644 --- a/linux/ibus-keyman/src/keymanutil.h +++ b/linux/ibus-keyman/src/keymanutil.h @@ -55,7 +55,7 @@ #include #include -#include +#include // Number of default Keyboard processor environment options for: "platform", "baseLayout", and "baseLayoutAlt" #define KEYMAN_ENVIRONMENT_OPTIONS 3 diff --git a/linux/ibus-keyman/tests/testfixture.cpp b/linux/ibus-keyman/tests/testfixture.cpp index 84aab933c83..6092407b1b7 100644 --- a/linux/ibus-keyman/tests/testfixture.cpp +++ b/linux/ibus-keyman/tests/testfixture.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index f40aeaf3728..1bb8762ed7f 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -18,11 +18,11 @@ #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 1 #endif -// For keyboardprocessor_bits.h +// For keyman_core_api_bits.h #ifndef KMN_KBP_STATIC #define KMN_KBP_STATIC #endif -// For keyboardprocessor_bits.h +// For keyman_core_api_bits.h #ifndef _WIN32 #define _WIN32 1 #endif @@ -39,8 +39,8 @@ #include #include #include "../../../../common/windows/cpp/include/legacy_kmx_file.h" -#include -#include +#include +#include /***************************************************************************/ From 906a0a2ed7a8bead56fa0e682a6b0b32fdcd5a53 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 14:41:01 +0700 Subject: [PATCH 177/207] chore: fix typo --- core/include/keyman/meson.build | 2 +- core/src/kmx/kmx_capslock.cpp | 2 +- core/src/kmx/kmx_environment.cpp | 2 +- core/src/kmx/kmx_processevent.cpp | 2 +- core/src/state.cpp | 2 +- developer/src/tike/main/Keyman.System.KeymanCore.pas | 2 +- linux/ibus-keyman/src/engine.c | 2 +- windows/src/engine/keyman32/keymanengine.h | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/include/keyman/meson.build b/core/include/keyman/meson.build index 6be28e49799..b807ac69a25 100644 --- a/core/include/keyman/meson.build +++ b/core/include/keyman/meson.build @@ -25,7 +25,7 @@ configure_file( install_headers('keyman_core_api.h', 'keyman_core_api_bits.h', - 'keyman_core_api_consts.h.h', + 'keyman_core_api_consts.h', 'keyman_core_api_debug.h.h', 'keyman_core_api_vkeys.h', join_paths(meson.current_build_dir(), 'keyman_core_api_version.h'), diff --git a/core/src/kmx/kmx_capslock.cpp b/core/src/kmx/kmx_capslock.cpp index ca324778830..f14745bf326 100644 --- a/core/src/kmx/kmx_capslock.cpp +++ b/core/src/kmx/kmx_capslock.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include #include using namespace km::kbp; diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index b77971a4036..eb5691c29d4 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -3,7 +3,7 @@ Authors: mcdurdin */ #include -#include +#include #include #include diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index 94015ef2c8b..2a4439f23f7 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -4,7 +4,7 @@ */ #include "kmx_processevent.h" #include "state.hpp" -#include +#include using namespace km::kbp; using namespace kmx; diff --git a/core/src/state.cpp b/core/src/state.cpp index e873af8287e..b8b0a44f8e4 100644 --- a/core/src/state.cpp +++ b/core/src/state.cpp @@ -7,7 +7,7 @@ #include "state.hpp" #include "processor.hpp" -#include +#include using namespace km::kbp; diff --git a/developer/src/tike/main/Keyman.System.KeymanCore.pas b/developer/src/tike/main/Keyman.System.KeymanCore.pas index 159b3e66934..fb0410acc14 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCore.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCore.pas @@ -620,7 +620,7 @@ function KM_KBP_VKEY__FE = $FE; KM_KBP_VKEY__FF = $FF; -// keyman_core_api_consts.h.h +// keyman_core_api_consts.h const // Defined environment options for KMX processor KM_KBP_KMX_ENV_PLATFORM = 'platform'; diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 2fb17c4ff73..889f78ee294 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -29,7 +29,7 @@ #include #include -#include +#include #include "config.h" #include "keymanutil.h" diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index 1bb8762ed7f..d6e0370a71c 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -40,7 +40,7 @@ #include #include "../../../../common/windows/cpp/include/legacy_kmx_file.h" #include -#include +#include /***************************************************************************/ From 4e5cbc3b9680be3c697881ec19351f613f5fd9b1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 14:51:51 +0700 Subject: [PATCH 178/207] chore: fix typo --- core/include/keyman/meson.build | 2 +- core/src/debug.hpp | 2 +- core/tests/unit/kmnkbd/debug_api.cpp | 2 +- core/tests/unit/kmnkbd/debug_items.hpp | 2 +- developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/include/keyman/meson.build b/core/include/keyman/meson.build index b807ac69a25..8ef7d1a9f26 100644 --- a/core/include/keyman/meson.build +++ b/core/include/keyman/meson.build @@ -26,7 +26,7 @@ configure_file( install_headers('keyman_core_api.h', 'keyman_core_api_bits.h', 'keyman_core_api_consts.h', - 'keyman_core_api_debug.h.h', + 'keyman_core_api_debug.h', 'keyman_core_api_vkeys.h', join_paths(meson.current_build_dir(), 'keyman_core_api_version.h'), '../../../common/include/km_types.h', diff --git a/core/src/debug.hpp b/core/src/debug.hpp index a910c03a288..cee5e37c521 100644 --- a/core/src/debug.hpp +++ b/core/src/debug.hpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace km { namespace kbp diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.cpp index 57f63666971..ae59fd35aca 100644 --- a/core/tests/unit/kmnkbd/debug_api.cpp +++ b/core/tests/unit/kmnkbd/debug_api.cpp @@ -469,7 +469,7 @@ int print_sizeof() { std::cout << "sizeof(km_kbp_keyboard_attrs): " << sizeof(km_kbp_keyboard_attrs) << std::endl; std::cout << "sizeof(km_kbp_attr): " << sizeof(km_kbp_attr) << std::endl; std::cout << std::endl; - std::cout << "keyman_core_api_debug.h.h:" << std::endl; + std::cout << "keyman_core_api_debug.h:" << std::endl; std::cout << "sizeof(km_kbp_state_debug_item): " << sizeof(km_kbp_state_debug_item) << std::endl; std::cout << "sizeof(km_kbp_state_debug_key_info): " << sizeof(km_kbp_state_debug_key_info) << std::endl; std::cout << "sizeof(km_kbp_state_debug_kmx_info): " << sizeof(km_kbp_state_debug_kmx_info) << std::endl; diff --git a/core/tests/unit/kmnkbd/debug_items.hpp b/core/tests/unit/kmnkbd/debug_items.hpp index 3ac2ff1f68d..880ceaaf736 100644 --- a/core/tests/unit/kmnkbd/debug_items.hpp +++ b/core/tests/unit/kmnkbd/debug_items.hpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include "kmx/kmx_base.h" #include "kmx/kmx_xstring.h" diff --git a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas index bca7395088f..9b190c82824 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas @@ -1,7 +1,7 @@ { * Keyman is copyright (C) SIL International. MIT License. * - * Debug interface for Keyman Core, matches keyman_core_api_debug.h.h + * Debug interface for Keyman Core, matches keyman_core_api_debug.h } unit Keyman.System.KeymanCoreDebug; @@ -189,7 +189,7 @@ initialization Assert(sizeof(km_kbp_keyboard_attrs) = 16); Assert(sizeof(km_kbp_attr) = 16); - //keyman_core_api_debug.h.h: + //keyman_core_api_debug.h: Assert(sizeof(km_kbp_state_debug_item) = 432); Assert(sizeof(km_kbp_state_debug_key_info) = 6); Assert(sizeof(km_kbp_state_debug_kmx_info) = 416); From 22362d435f68275f4ae85105f6efcf83a2fedbb3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 14:32:48 +0700 Subject: [PATCH 179/207] chore: rename pkm_kbp_ to pkm_core_ --- developer/src/tike/child/UfrmDebug.pas | 2 +- .../debug/Keyman.System.Debug.DebugCore.pas | 24 ++-- .../debug/Keyman.System.Debug.DebugEvent.pas | 26 ++-- .../tike/main/Keyman.System.KeymanCore.pas | 116 +++++++++--------- .../main/Keyman.System.KeymanCoreDebug.pas | 18 +-- .../Keyman.System.VisualKeyboardImportKMX.pas | 2 +- 6 files changed, 94 insertions(+), 94 deletions(-) diff --git a/developer/src/tike/child/UfrmDebug.pas b/developer/src/tike/child/UfrmDebug.pas index 615cfebc809..3a07d98bf4d 100644 --- a/developer/src/tike/child/UfrmDebug.pas +++ b/developer/src/tike/child/UfrmDebug.pas @@ -371,7 +371,7 @@ procedure TfrmDebug.memoMessage(Sender: TObject; var Message: TMessage; function TfrmDebug.SetKeyEventContext: Boolean; var - context: pkm_kbp_context; + context: pkm_core_context; context_items: TArray; n, i: Integer; ch: Char; diff --git a/developer/src/tike/debug/Keyman.System.Debug.DebugCore.pas b/developer/src/tike/debug/Keyman.System.Debug.DebugCore.pas index 6b0a5b5fd5d..c1107900a14 100644 --- a/developer/src/tike/debug/Keyman.System.Debug.DebugCore.pas +++ b/developer/src/tike/debug/Keyman.System.Debug.DebugCore.pas @@ -18,8 +18,8 @@ EDebugCore = class(Exception); TDebugCore = class private - FKeyboard: pkm_kbp_keyboard; - FState: pkm_kbp_state; + FKeyboard: pkm_core_keyboard; + FState: pkm_core_state; class var KeymanCoreLoaded: Boolean; class procedure InitKeymanCore; static; function GetKMXPlatform: string; @@ -30,8 +30,8 @@ TDebugCore = class function GetOption(const name: string): string; procedure SetOption(const name, value: string); property KMXPlatform: string read GetKMXPlatform write SetKMXPlatform; - property Keyboard: pkm_kbp_keyboard read FKeyboard; - property State: pkm_kbp_state read FState; + property Keyboard: pkm_core_keyboard read FKeyboard; + property State: pkm_core_state read FState; end; implementation @@ -98,13 +98,13 @@ class procedure TDebugCore.InitKeymanCore; function TDebugCore.GetKMXPlatform: string; var - p: pkm_kbp_cp; + p: pkm_core_cp; status: km_kbp_status; begin status := km_kbp_state_option_lookup( FState, KM_KBP_OPT_ENVIRONMENT, - pkm_kbp_cp(PWideChar(KM_KBP_KMX_ENV_PLATFORM)), + pkm_core_cp(PWideChar(KM_KBP_KMX_ENV_PLATFORM)), p ); if status <> KM_KBP_STATUS_OK then @@ -117,8 +117,8 @@ procedure TDebugCore.SetKMXPlatform(const Value: string); options: array[0..1] of km_kbp_option_item; status: km_kbp_status; begin - options[0].key := pkm_kbp_cp(PWideChar(KM_KBP_KMX_ENV_PLATFORM)); - options[0].value := pkm_kbp_cp(PWideChar(Value)); + options[0].key := pkm_core_cp(PWideChar(KM_KBP_KMX_ENV_PLATFORM)); + options[0].value := pkm_core_cp(PWideChar(Value)); options[0].scope := KM_KBP_OPT_ENVIRONMENT; options[1] := KM_KBP_OPTIONS_END; status := km_kbp_state_options_update(FState, @options[0]); @@ -128,13 +128,13 @@ procedure TDebugCore.SetKMXPlatform(const Value: string); function TDebugCore.GetOption(const name: string): string; var - p: pkm_kbp_cp; + p: pkm_core_cp; status: km_kbp_status; begin status := km_kbp_state_option_lookup( FState, KM_KBP_OPT_KEYBOARD, - pkm_kbp_cp(PWideChar(name)), + pkm_core_cp(PWideChar(name)), p ); if status <> KM_KBP_STATUS_OK then @@ -147,8 +147,8 @@ procedure TDebugCore.SetOption(const name, value: string); options: array[0..1] of km_kbp_option_item; status: km_kbp_status; begin - options[0].key := pkm_kbp_cp(PWideChar(Name)); - options[0].value := pkm_kbp_cp(PWideChar(Value)); + options[0].key := pkm_core_cp(PWideChar(Name)); + options[0].value := pkm_core_cp(PWideChar(Value)); options[0].scope := KM_KBP_OPT_KEYBOARD; options[1] := KM_KBP_OPTIONS_END; status := km_kbp_state_options_update(FState, @options[0]); diff --git a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas index 6a7e0b3f0ed..5f8f6a2bf08 100644 --- a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas +++ b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas @@ -47,7 +47,7 @@ TDebugEventRuleData = class Context: WideString; StoreOffsets: array[0..20] of Word; //TKeymanStoreEx; nStores: Integer; - procedure FillStoreList(event: pkm_kbp_state_debug_item; KeyboardMemory: PChar); + procedure FillStoreList(event: pkm_core_state_debug_item; KeyboardMemory: PChar); end; TDebugEventType = (etAction, etRuleMatch); @@ -73,23 +73,23 @@ TDebugEventList = class(TObjectList) expected_value: uintptr_t); procedure Action_EmitKeystroke(const key: Word); procedure Action_Marker(marker: uintptr_t); - function AddActionItem(key: Word; action: pkm_kbp_action_item): Boolean; + function AddActionItem(key: Word; action: pkm_core_action_item): Boolean; procedure AddDebugItem( - debug: pkm_kbp_state_debug_item; + debug: pkm_core_state_debug_item; debugkeyboard: TDebugKeyboard; vk: uint16_t; modifier_state: uint16_t ); public function AddStateItems( - state: pkm_kbp_state; + state: pkm_core_state; vk: uint16_t; modifier_state: uint16_t; debugkeyboard: TDebugKeyboard ): Boolean; overload; function AddStateItems( - state: pkm_kbp_state; + state: pkm_core_state; vk: uint16_t; modifier_state: uint16_t ): Boolean; overload; @@ -133,7 +133,7 @@ procedure TDebugEvent.SetEventType(const Value: TDebugEventType); end; end; -function TDebugEventList.AddActionItem(key: Word; action: pkm_kbp_action_item): Boolean; +function TDebugEventList.AddActionItem(key: Word; action: pkm_core_action_item): Boolean; begin Result := True; case action._type of @@ -220,7 +220,7 @@ procedure TDebugEventList.Action_Marker(marker: uintptr_t); end; procedure TDebugEventList.AddDebugItem( - debug: pkm_kbp_state_debug_item; + debug: pkm_core_state_debug_item; debugkeyboard: TDebugKeyboard; vk: uint16_t; modifier_state: uint16_t); @@ -312,12 +312,12 @@ procedure TDebugEventList.AddDebugItem( end; function TDebugEventList.AddStateItems( - state: pkm_kbp_state; + state: pkm_core_state; vk: uint16_t; modifier_state: uint16_t ): Boolean; var - action: pkm_kbp_action_item; + action: pkm_core_action_item; begin Result := True; action := km_kbp_state_action_items(state, nil); @@ -329,14 +329,14 @@ function TDebugEventList.AddStateItems( end; function TDebugEventList.AddStateItems( - state: pkm_kbp_state; + state: pkm_core_state; vk: uint16_t; modifier_state: uint16_t; debugkeyboard: TDebugKeyboard ): Boolean; var - action: pkm_kbp_action_item; - debug: pkm_kbp_state_debug_item; + action: pkm_core_action_item; + debug: pkm_core_state_debug_item; action_index: Integer; begin Result := True; @@ -374,7 +374,7 @@ function TDebugEventList.AddStateItems( { TDebugEventRuleData } -procedure TDebugEventRuleData.FillStoreList(event: pkm_kbp_state_debug_item; KeyboardMemory: PChar); +procedure TDebugEventRuleData.FillStoreList(event: pkm_core_state_debug_item; KeyboardMemory: PChar); function StoreOffset(kfh: PKeyboardFileHeader; i: Word): PChar; begin Result := KeyboardMemory; diff --git a/developer/src/tike/main/Keyman.System.KeymanCore.pas b/developer/src/tike/main/Keyman.System.KeymanCore.pas index fb0410acc14..2cf5505c312 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCore.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCore.pas @@ -21,22 +21,22 @@ interface km_kbp_usv = uint32_t; // UTF-32 km_kbp_cp = WideChar; - pkm_kbp_cp = ^km_kbp_cp; + pkm_core_cp = ^km_kbp_cp; km_kbp_context = record end; - pkm_kbp_context = ^km_kbp_context; + pkm_core_context = ^km_kbp_context; km_kbp_keyboard = record end; - pkm_kbp_keyboard = ^km_kbp_keyboard; + pkm_core_keyboard = ^km_kbp_keyboard; km_kbp_state = record end; - pkm_kbp_state = ^km_kbp_state; + pkm_core_state = ^km_kbp_state; km_kbp_options = record end; - pkm_kbp_options = ^km_kbp_options; + pkm_core_options = ^km_kbp_options; km_kbp_path_name = PWideChar; // on Windows - pkm_kbp_path_name = ^km_kbp_path_name; + pkm_core_path_name = ^km_kbp_path_name; type km_kbp_status = ( @@ -69,7 +69,7 @@ km_kbp_context_item = record 2: (marker: uint32_t); end; - pkm_kbp_context_item = ^km_kbp_context_item; + pkm_core_context_item = ^km_kbp_context_item; const KM_KBP_CONTEXT_ITEM_END: km_kbp_context_item = ( @@ -82,23 +82,23 @@ km_kbp_context_item = record kmnkbp0 = 'kmnkbp0-0.dll'; function km_kbp_context_items_from_utf16( - const text: pkm_kbp_cp; - var out_ptr: pkm_kbp_context_item + const text: pkm_core_cp; + var out_ptr: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_context_items_from_utf8( const text: PAnsiChar; - var out_ptr: pkm_kbp_context_item + var out_ptr: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_context_items_to_utf16( - const item: pkm_kbp_context_item; - buf: pkm_kbp_cp; + const item: pkm_core_context_item; + buf: pkm_core_cp; var buf_size: integer ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_context_items_to_utf8( - const item: pkm_kbp_context_item; + const item: pkm_core_context_item; buf: pansichar; var buf_size: integer ): km_kbp_status; cdecl; external kmnkbp0 delayed; @@ -108,28 +108,28 @@ procedure km_kbp_context_items_dispose( ); cdecl; external kmnkbp0 delayed; function km_kbp_context_set( - context: pkm_kbp_context; - context_items: pkm_kbp_context_item + context: pkm_core_context; + context_items: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_context_get( - context: pkm_kbp_context; - var context_items: pkm_kbp_context_item + context: pkm_core_context; + var context_items: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; procedure km_kbp_context_clear( - context: pkm_kbp_context + context: pkm_core_context ); cdecl; external kmnkbp0 delayed; function km_kbp_context_append( - context: pkm_kbp_context; - context_items: pkm_kbp_context_item + context: pkm_core_context; + context_items: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_context_shrink( - context: pkm_kbp_context; + context: pkm_core_context; num: Integer; - prefix: pkm_kbp_context_item + prefix: pkm_core_context_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; @@ -143,12 +143,12 @@ function km_kbp_context_shrink( ); km_kbp_option_item = record - key: pkm_kbp_cp; - value: pkm_kbp_cp; + key: pkm_core_cp; + value: pkm_core_cp; scope: km_kbp_option_scope; end; - pkm_kbp_option_item = ^km_kbp_option_item; + pkm_core_option_item = ^km_kbp_option_item; const KM_KBP_OPTIONS_END: km_kbp_option_item = (key: nil; value: nil; scope: KM_KBP_OPT_UNKNOWN); @@ -193,91 +193,91 @@ km_kbp_action_item = record {$ENDIF} case Integer of 0: (marker: uintptr_t); - 1: (option: pkm_kbp_option_item); + 1: (option: pkm_core_option_item); 2: (character: km_kbp_usv); 3: (backspace: km_kbp_backspace_item); 4: (capsLock: uint8_t); // CAPSLOCK type, 1 to turn on, 0 to turn off end; - pkm_kbp_action_item = ^km_kbp_action_item; + pkm_core_action_item = ^km_kbp_action_item; // These types are used only for debugging convenience type km_kbp_action_item_array = array[0..100] of km_kbp_action_item; - pkm_kbp_action_item_array = ^km_kbp_action_item_array; + pkm_core_action_item_array = ^km_kbp_action_item_array; function km_kbp_options_list_size( - opts: pkm_kbp_option_item + opts: pkm_core_option_item ): Integer; cdecl; external kmnkbp0 delayed; function km_kbp_state_option_lookup( - state: pkm_kbp_state; + state: pkm_core_state; scope: km_kbp_option_scope; - key: pkm_kbp_cp; - var value: pkm_kbp_cp + key: pkm_core_cp; + var value: pkm_core_cp ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_state_options_update( - state: pkm_kbp_state; - new_opts: pkm_kbp_option_item + state: pkm_core_state; + new_opts: pkm_core_option_item ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_state_options_to_json( - state: pkm_kbp_state; + state: pkm_core_state; buf: PAnsiChar; var space: Integer ): km_kbp_status; cdecl; external kmnkbp0 delayed; type km_kbp_keyboard_attrs = record - version_string: pkm_kbp_cp; - id: pkm_kbp_cp; + version_string: pkm_core_cp; + id: pkm_core_cp; folder_path: km_kbp_path_name; - default_optons: pkm_kbp_option_item + default_optons: pkm_core_option_item end; - pkm_kbp_keyboard_attrs = ^km_kbp_keyboard_attrs; + pkm_core_keyboard_attrs = ^km_kbp_keyboard_attrs; function km_kbp_keyboard_load( kb_path: km_kbp_path_name; - var keyboard: pkm_kbp_keyboard + var keyboard: pkm_core_keyboard ): km_kbp_status; cdecl; external kmnkbp0 delayed; procedure km_kbp_keyboard_dispose( - keyboard: pkm_kbp_keyboard + keyboard: pkm_core_keyboard ); cdecl; external kmnkbp0 delayed; function km_kbp_keyboard_get_attrs( - keyboard: pkm_kbp_keyboard; - var out: pkm_kbp_keyboard_attrs + keyboard: pkm_core_keyboard; + var out: pkm_core_keyboard_attrs ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_state_create( - keyboard: pkm_kbp_keyboard; - env: pkm_kbp_option_item; - var out: pkm_kbp_state + keyboard: pkm_core_keyboard; + env: pkm_core_option_item; + var out: pkm_core_state ): km_kbp_status; cdecl; external kmnkbp0 delayed; function km_kbp_state_clone( - state: pkm_kbp_state; - var out: pkm_kbp_state + state: pkm_core_state; + var out: pkm_core_state ): km_kbp_status; cdecl; external kmnkbp0 delayed; procedure km_kbp_state_dispose( - state: pkm_kbp_state + state: pkm_core_state ); cdecl; external kmnkbp0 delayed; function km_kbp_state_context( - state: pkm_kbp_state -): pkm_kbp_context; cdecl; external kmnkbp0 delayed; + state: pkm_core_state +): pkm_core_context; cdecl; external kmnkbp0 delayed; function km_kbp_state_action_items( - state: pkm_kbp_state; + state: pkm_core_state; num_items: pinteger -): pkm_kbp_action_item; cdecl; external kmnkbp0 delayed; +): pkm_core_action_item; cdecl; external kmnkbp0 delayed; function km_kbp_state_to_json( - state: pkm_kbp_state; + state: pkm_core_state; buf: PAnsiChar; space: pinteger ): km_kbp_status; cdecl; external kmnkbp0 delayed; @@ -293,7 +293,7 @@ km_kbp_attr = record vendor: pansichar // Implementor of the processor. end; - pkm_kbp_attr = ^km_kbp_attr; + pkm_core_attr = ^km_kbp_attr; km_kbp_tech_value = ( KM_KBP_TECH_UNSPECIFIED = 0, @@ -304,12 +304,12 @@ km_kbp_attr = record ); function km_kbp_get_engine_attrs( - state: pkm_kbp_state -): pkm_kbp_attr; cdecl; external kmnkbp0 delayed; + state: pkm_core_state +): pkm_core_attr; cdecl; external kmnkbp0 delayed; function km_kbp_process_event( - state: pkm_kbp_state; + state: pkm_core_state; vk: km_kbp_virtual_key; modifier_state: uint16_t; is_key_down: uint8_t; diff --git a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas index 9b190c82824..4ff3a7bcad4 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCoreDebug.pas @@ -50,14 +50,14 @@ km_kbp_state_debug_key_info = record character: char16_t; end; -pkm_kbp_state_debug_key_info = ^km_kbp_state_debug_key_info; +pkm_core_state_debug_key_info = ^km_kbp_state_debug_key_info; km_kbp_state_debug_kmx_option_info = record store: Pointer; // LPSTORE value: array[0..DEBUG_MAX_CONTEXT-1] of km_kbp_cp; // value to be saved into the store end; -pkm_kbp_state_debug_kmx_option_info = ^km_kbp_state_debug_kmx_option_info; +pkm_core_state_debug_kmx_option_info = ^km_kbp_state_debug_kmx_option_info; /// /// KMX processor data for each event. kmx_base.h defines the types that are @@ -83,7 +83,7 @@ km_kbp_state_debug_kmx_info = record option: km_kbp_state_debug_kmx_option_info; end; -pkm_kbp_state_debug_kmx_info = ^km_kbp_state_debug_kmx_info; +pkm_core_state_debug_kmx_info = ^km_kbp_state_debug_kmx_info; /// /// A single debug event. @@ -98,7 +98,7 @@ km_kbp_state_debug_item = record kmx_info: km_kbp_state_debug_kmx_info; end; -pkm_kbp_state_debug_item = ^km_kbp_state_debug_item; +pkm_core_state_debug_item = ^km_kbp_state_debug_item; /// /// A single debug event. @@ -109,7 +109,7 @@ km_kbp_state_debug_item = record // These types are used only for debugging convenience type km_kbp_state_debug_item_array = array[0..100] of km_kbp_state_debug_item; - pkm_kbp_state_debug_item_array = ^km_kbp_state_debug_item_array; + pkm_core_state_debug_item_array = ^km_kbp_state_debug_item_array; const KM_KBP_DEBUG_BEGIN = 0; @@ -145,7 +145,7 @@ km_kbp_state_debug_item = record /// @returns KM_KBP_STATUS_OK on success /// function km_kbp_state_debug_set( - state: pkm_kbp_state; + state: pkm_core_state; value: integer ): km_kbp_status; cdecl; external kmnkbp0 delayed; @@ -157,7 +157,7 @@ function km_kbp_state_debug_set( /// @returns 1 if debugging is enabled, 0 otherwise /// function km_kbp_state_debug_get( - state: pkm_kbp_state + state: pkm_core_state ): uint8_t; cdecl; external kmnkbp0 delayed; /// @@ -171,9 +171,9 @@ function km_kbp_state_debug_get( /// with last entry guaranteed to be KM_KBP_DEBUG_END. /// function km_kbp_state_debug_items( - state: pkm_kbp_state; + state: pkm_core_state; num_items: PCardinal -): pkm_kbp_state_debug_item; cdecl; external kmnkbp0 delayed; +): pkm_core_state_debug_item; cdecl; external kmnkbp0 delayed; implementation diff --git a/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas b/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas index e36216e1559..8fc1f8aecd6 100644 --- a/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas +++ b/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas @@ -106,7 +106,7 @@ procedure TVisualKeyboardImportKMX.ImportKey(vk: TVKKey); var data: string; i: Integer; - context: pkm_kbp_context; + context: pkm_core_context; begin context := km_kbp_state_context(FCore.State); km_kbp_context_clear(context); From ba5f18defd16cbcf3527252248614343fc85bc89 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 9 Oct 2023 14:35:58 +0700 Subject: [PATCH 180/207] chore: rename km_kbp_ to km_core_ --- HISTORY.md | 6 +- common/include/km_types.h | 12 +- core/doc/markdown_files/index.md | 2 +- core/include/keyman/keyman_core_api.h | 424 +++++++++--------- core/include/keyman/keyman_core_api_bits.h | 4 +- core/include/keyman/keyman_core_api_debug.h | 32 +- .../keyman/keyman_core_api_version.h.in | 6 +- core/include/keyman/keyman_core_api_vkeys.h | 6 +- core/src/context.hpp | 16 +- core/src/debug.hpp | 14 +- core/src/keyboard.cpp | 2 +- core/src/keyboard.hpp | 6 +- core/src/km_kbp_context_api.cpp | 62 +-- core/src/km_kbp_debug_api.cpp | 16 +- core/src/km_kbp_keyboard_api.cpp | 34 +- core/src/km_kbp_options_api.cpp | 22 +- core/src/km_kbp_processevent_api.cpp | 22 +- core/src/km_kbp_state_api.cpp | 48 +- core/src/kmx/kmx_debugger.cpp | 8 +- core/src/kmx/kmx_debugger.h | 6 +- core/src/kmx/kmx_environment.cpp | 2 +- core/src/kmx/kmx_environment.h | 4 +- core/src/kmx/kmx_file.cpp | 4 +- core/src/kmx/kmx_plus.cpp | 4 +- core/src/kmx/kmx_plus.h | 6 +- core/src/kmx/kmx_processevent.cpp | 10 +- core/src/kmx/kmx_processevent.h | 10 +- core/src/kmx/kmx_processor.cpp | 64 +-- core/src/kmx/kmx_processor.hpp | 38 +- core/src/kmx/kmx_xstring.cpp | 28 +- core/src/kmx/kmx_xstring.h | 40 +- core/src/ldml/ldml_processor.cpp | 46 +- core/src/ldml/ldml_processor.hpp | 38 +- core/src/ldml/ldml_transforms.cpp | 6 +- core/src/ldml/ldml_transforms.hpp | 8 +- core/src/ldml/ldml_vkeys.cpp | 4 +- core/src/ldml/ldml_vkeys.hpp | 6 +- core/src/meson.build | 24 +- core/src/mock/mock_processor.cpp | 40 +- core/src/mock/mock_processor.hpp | 30 +- core/src/option.cpp | 6 +- core/src/option.hpp | 14 +- core/src/path.hpp | 4 +- core/src/processor.hpp | 46 +- core/src/state.cpp | 14 +- core/src/state.hpp | 46 +- .../tests/kmx_test_source/kmx_test_source.cpp | 20 +- .../tests/kmx_test_source/kmx_test_source.hpp | 6 +- core/tests/unit/kmnkbd/action_items.hpp | 14 +- core/tests/unit/kmnkbd/context_api.cpp | 108 ++--- core/tests/unit/kmnkbd/debug_api.cpp | 300 ++++++------- core/tests/unit/kmnkbd/debug_items.hpp | 12 +- core/tests/unit/kmnkbd/keyboard_api.cpp | 22 +- core/tests/unit/kmnkbd/options_api.cpp | 40 +- core/tests/unit/kmnkbd/state_api.cpp | 78 ++-- core/tests/unit/kmx/kmx.cpp | 70 +-- core/tests/unit/kmx/kmx_external_event.cpp | 22 +- core/tests/unit/kmx/kmx_imx.cpp | 114 ++--- core/tests/unit/kmx/kmx_key_list.cpp | 32 +- core/tests/unit/kmx/kmx_key_list.kmn | 2 +- core/tests/unit/kmx/meson.build | 2 +- core/tests/unit/ldml/ldml.cpp | 72 +-- core/tests/unit/ldml/ldml_test_source.cpp | 28 +- core/tests/unit/ldml/ldml_test_source.hpp | 6 +- core/tests/unit/ldml/ldml_test_utils.cpp | 2 +- core/tests/unit/ldml/ldml_test_utils.hpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/tike/child/UfrmDebug.pas | 14 +- .../debug/Keyman.System.Debug.DebugCore.pas | 32 +- .../debug/Keyman.System.Debug.DebugEvent.pas | 12 +- .../tike/main/Keyman.System.KeymanCore.pas | 182 ++++---- .../main/Keyman.System.KeymanCoreDebug.pas | 66 +-- .../Keyman.System.VisualKeyboardImportKMX.pas | 6 +- linux/debian/libkmnkbp0-0.symbols | 78 ++-- linux/debian/tests/test-build | 4 +- linux/ibus-keyman/src/engine.c | 108 ++--- linux/ibus-keyman/src/keycodes.h | 2 +- linux/ibus-keyman/src/keymanutil.c | 6 +- linux/ibus-keyman/src/keymanutil.h | 2 +- .../src/engine/keyman32/CoreEnvironment.cpp | 10 +- windows/src/engine/keyman32/K32_load.cpp | 18 +- windows/src/engine/keyman32/appint/appint.cpp | 36 +- windows/src/engine/keyman32/appint/appint.h | 10 +- windows/src/engine/keyman32/calldll.cpp | 54 +-- windows/src/engine/keyman32/calldll.h | 2 +- .../src/engine/keyman32/keyboardoptions.cpp | 28 +- windows/src/engine/keyman32/keyboardoptions.h | 2 +- windows/src/engine/keyman32/keyman32.cpp | 8 +- windows/src/engine/keyman32/keymanengine.h | 18 +- .../src/engine/keyman32/kmhook_getmessage.cpp | 2 +- windows/src/engine/keyman32/kmprocess.cpp | 12 +- .../src/engine/keyman32/kmprocessactions.cpp | 28 +- .../src/engine/keyman32/preservedkeymap.cpp | 22 +- .../src/engine/keyman32/selectkeyboard.cpp | 4 +- .../keyboardoptionstests.cpp | 24 +- .../kmprocessactionstests.cpp | 48 +- 96 files changed, 1541 insertions(+), 1541 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index e22d0e994b0..37f73a8ec51 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -926,7 +926,7 @@ * chore: git tag with release@semver (#8035) * fix(linux): Properly set context after changing IP (#8026) * chore(linux): use faster zero-length string check (#8037) -* chore(linux): log failures to `km_kbp_context_clear(context)` (#8036) +* chore(linux): log failures to `km_core_context_clear(context)` (#8036) * chore(linux): Update recommended extension (#8038) ## 17.0.30 alpha 2023-01-17 @@ -1600,14 +1600,14 @@ ## 16.0.60 alpha 2022-09-10 * fix(web): enhanced timer for prediction algorithm (#7037) -* chore(core): fixup km_kbp_event docs (#7253) +* chore(core): fixup km_core_event docs (#7253) * fix(windows) Update unit tests for tsf bkspace (#7254) * fix(android): Standardize language ID in language picker menu (#7239) ## 16.0.59 alpha 2022-09-09 * chore: use keyman.com instead of keyman-staging.com (#7233) -* feat(core): add `km_kbp_event` API endpoint (#7223) +* feat(core): add `km_core_event` API endpoint (#7223) * fix(windows): Delete both code units when deleting surrogate pairs in TSF-aware apps (#7243) ## 16.0.58 alpha 2022-09-08 diff --git a/common/include/km_types.h b/common/include/km_types.h index d81f2672384..aa7fb2e5182 100644 --- a/common/include/km_types.h +++ b/common/include/km_types.h @@ -28,14 +28,14 @@ typedef uint8_t KMX_BYTE; typedef uint16_t KMX_WORD; #if defined(__cplusplus) -typedef char16_t km_kbp_cp; -typedef char32_t km_kbp_usv; +typedef char16_t km_core_cp; +typedef char32_t km_core_usv; #else -typedef uint16_t km_kbp_cp; // code point -typedef uint32_t km_kbp_usv; // Unicode Scalar Value +typedef uint16_t km_core_cp; // code point +typedef uint32_t km_core_usv; // Unicode Scalar Value #endif -typedef km_kbp_cp KMX_WCHAR; // wc, 16-bit UNICODE character +typedef km_core_cp KMX_WCHAR; // wc, 16-bit UNICODE character typedef KMX_WCHAR* PKMX_WCHAR; typedef char KMX_CHAR; @@ -60,7 +60,7 @@ typedef KMX_DWORD* PKMX_DWORD; #ifdef USE_CHAR16_T #define lpuch(x) u ## x -typedef km_kbp_cp KMX_UCHAR; +typedef km_core_cp KMX_UCHAR; #else #define lpuch(x) L ## x typedef wchar_t KMX_UCHAR; diff --git a/core/doc/markdown_files/index.md b/core/doc/markdown_files/index.md index bc196a0a0cb..d389857c5f6 100644 --- a/core/doc/markdown_files/index.md +++ b/core/doc/markdown_files/index.md @@ -58,4 +58,4 @@ Caps Lock. ### Namespace -All calls, types and enums are prefixed with the namespace identifier `km_kbp_` +All calls, types and enums are prefixed with the namespace identifier `km_core_` diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index b32be630731..bbb194d4315 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -67,12 +67,12 @@ Caps Lock. ## API ### Namespace -All calls, types and enums are prefixed with the namespace identifier `km_kbp_` +All calls, types and enums are prefixed with the namespace identifier `km_core_` ### API idioms Almost all calls marshalling variable length aggregate data in or out of an API object take the form: -> km_kbp_status *fn_name*(object_ref, buffer_ptr, size_ptr) +> km_core_status *fn_name*(object_ref, buffer_ptr, size_ptr) where the buffer is nullable and all other arguments are required (will result in an `KM_KBP_STATUS_INVALID_ARGUMENT` status being returned if nulled). When @@ -81,7 +81,7 @@ buffer in the variable pointed to by `size_ptr`. Calls which result in the allocation of resources, regardless of resulting ownership, are of the form: -> km_kbp_status *fn_name*(object_ref, out_ptr) +> km_core_status *fn_name*(object_ref, out_ptr) where `out_ptr` is a valid pointer to a caller allocated variable to hold the resulting ouput. This is often a reference to a created object. All arguments @@ -110,24 +110,24 @@ extern "C" #endif // Basic types // -typedef uint16_t km_kbp_virtual_key; // A virtual key code. -typedef uint32_t km_kbp_status; // Status return code. +typedef uint16_t km_core_virtual_key; // A virtual key code. +typedef uint32_t km_core_status; // Status return code. // Opaque object types. // -typedef struct km_kbp_context km_kbp_context; -typedef struct km_kbp_keyboard km_kbp_keyboard; -typedef struct km_kbp_state km_kbp_state; -typedef struct km_kbp_options km_kbp_options; +typedef struct km_core_context km_core_context; +typedef struct km_core_keyboard km_core_keyboard; +typedef struct km_core_state km_core_state; +typedef struct km_core_options km_core_options; // Forward declarations // -typedef struct km_kbp_option_item km_kbp_option_item; +typedef struct km_core_option_item km_core_option_item; // Callback function used to to access Input Method eXtension library functions // from Keyman Core // -typedef uint8_t (*km_kbp_keyboard_imx_platform)(km_kbp_state*, uint32_t, void*); +typedef uint8_t (*km_core_keyboard_imx_platform)(km_core_state*, uint32_t, void*); /*``` ### Error Handling @@ -137,7 +137,7 @@ value is an error). Any functions that can fail will always return a status value and all results are returned via outparams passed to the function. ```c */ -enum km_kbp_status_codes { +enum km_core_status_codes { KM_KBP_STATUS_OK = 0, KM_KBP_STATUS_NO_MEM = 1, KM_KBP_STATUS_IO_ERROR = 2, @@ -176,7 +176,7 @@ Contexts are always owned by their state. They may be set to a list of context_items or interrogated for their current list of context items. ```c */ -enum km_kbp_context_type { +enum km_core_context_type { KM_KBP_CT_END, KM_KBP_CT_CHAR, KM_KBP_CT_MARKER @@ -186,17 +186,17 @@ typedef struct { uint8_t type; uint8_t _reserved[3]; union { - km_kbp_usv character; + km_core_usv character; uint32_t marker; }; -} km_kbp_context_item; +} km_core_context_item; #define KM_KBP_CONTEXT_ITEM_END {KM_KBP_CT_END, {0,}, {0,}} /* ``` -### `km_kbp_context_items_from_utf16` +### `km_core_context_items_from_utf16` ##### Description: -Convert a UTF16 encoded Unicode string into an array of `km_kbp_context_item` +Convert a UTF16 encoded Unicode string into an array of `km_core_context_item` structures. Allocates memory as needed. ##### Return status: - `KM_KBP_STATUS_OK`: On success. @@ -208,23 +208,23 @@ structures. Allocates memory as needed. ##### Parameters: - __text__: a pointer to a null terminated array of utf16 encoded data. - __out_ptr__: a pointer to the result variable: - A pointer to the start of the `km_kbp_context_item` array containing the + A pointer to the start of the `km_core_context_item` array containing the representation of the input string. Terminated with a type of `KM_KBP_CT_END`. Must be disposed of with - `km_kbp_context_items_dispose`. + `km_core_context_items_dispose`. ```c */ KMN_API -km_kbp_status -km_kbp_context_items_from_utf16(km_kbp_cp const *text, - km_kbp_context_item **out_ptr); +km_core_status +km_core_context_items_from_utf16(km_core_cp const *text, + km_core_context_item **out_ptr); /* ``` -### `km_kbp_context_items_from_utf8` +### `km_core_context_items_from_utf8` ##### Description: -Convert an UTF8 encoded Unicode string into an array of `km_kbp_context_item` +Convert an UTF8 encoded Unicode string into an array of `km_core_context_item` structures. Allocates memory as needed. ##### Status: - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -235,20 +235,20 @@ decoded. ##### Parameters: - __text__: a pointer to a null terminated array of utf8 encoded data. - __out_ptr__: a pointer to the result variable: - A pointer to the start of the `km_kbp_context_item` array containing the + A pointer to the start of the `km_core_context_item` array containing the representation of the input string. Terminated with a type of `KM_KBP_CT_END`. ```c */ KMN_API -km_kbp_status -km_kbp_context_items_from_utf8(char const *text, - km_kbp_context_item **out_ptr); +km_core_status +km_core_context_items_from_utf8(char const *text, + km_core_context_item **out_ptr); /* ``` -### `km_kbp_context_items_to_utf16` +### `km_core_context_items_to_utf16` ##### Description: Convert a context item array into a UTF-16 encoded string placing it into the supplied buffer of specified size, and return the number of code units @@ -262,7 +262,7 @@ context during the conversion. `buf_size` will contain the space required. The contents of the buffer are undefined. ##### Parameters: -- __context_items__: A pointer to the start of an array `km_kbp_context_item`. +- __context_items__: A pointer to the start of an array `km_core_context_item`. Must be terminated with a type of `KM_KBP_CT_END`. - __buf__: A pointer to the buffer to place the UTF-16 string into. May be null to request size calculation. @@ -273,14 +273,14 @@ context during the conversion. ```c */ KMN_API -km_kbp_status -km_kbp_context_items_to_utf16(km_kbp_context_item const *item, - km_kbp_cp *buf, +km_core_status +km_core_context_items_to_utf16(km_core_context_item const *item, + km_core_cp *buf, size_t *buf_size); /* ``` -### `km_kbp_context_items_to_utf8` +### `km_core_context_items_to_utf8` ##### Description: Convert a context item array into a UTF-8 encoded string placing it into the supplied buffer of specified size, and return the number of code units @@ -294,7 +294,7 @@ context during the conversion. `buf_size` will contain the space required. The contents of the buffer are undefined. ##### Parameters: -- __context_items__: A pointer to the start of an array `km_kbp_context_item`. +- __context_items__: A pointer to the start of an array `km_core_context_item`. Must be terminated with a type of `KM_KBP_CT_END`. - __buf__: A pointer to the buffer to place the UTF-8 string into. May be null to request size calculation. @@ -305,33 +305,33 @@ context during the conversion. ```c */ KMN_API -km_kbp_status -km_kbp_context_items_to_utf8(km_kbp_context_item const *item, +km_core_status +km_core_context_items_to_utf8(km_core_context_item const *item, char *buf, size_t *buf_size); /* ``` -### `km_kbp_context_items_dispose` +### `km_core_context_items_dispose` ##### Description: -Free the allocated memory belonging to a `km_kbp_context_item` array previously -returned by `km_kbp_context_items_from_utf16` or `km_kbp_context_get` +Free the allocated memory belonging to a `km_core_context_item` array previously +returned by `km_core_context_items_from_utf16` or `km_core_context_get` ##### Parameters: -- __context_items__: A pointer to the start of the `km_kbp_context_item` array +- __context_items__: A pointer to the start of the `km_core_context_item` array to be disposed of. ```c */ KMN_API void -km_kbp_context_items_dispose(km_kbp_context_item *context_items); +km_core_context_items_dispose(km_core_context_item *context_items); /* ``` -### `km_kbp_context_set` +### `km_core_context_set` ##### Description: Replace the contents of the current context with a new sequence of -`km_kbp_context_item` entries. +`km_core_context_item` entries. ##### Return status: - `KM_KBP_STATUS_OK`: On success. - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -339,45 +339,45 @@ Replace the contents of the current context with a new sequence of grow the context buffer internally. ##### Parameters: - __context__: A pointer to an opaque context object -- __context_items__: A pointer to the start of the `km_kbp_context_item` +- __context_items__: A pointer to the start of the `km_core_context_item` array containing the new context. It must be terminated with an item of type `KM_KBP_CT_END`. ```c */ KMN_API -km_kbp_status -km_kbp_context_set(km_kbp_context *context, - km_kbp_context_item const *context_items); +km_core_status +km_core_context_set(km_core_context *context, + km_core_context_item const *context_items); /* ``` -### `km_kbp_context_get` +### `km_core_context_get` ##### Description: Copies all items in the context into a new array and returns the new array. -This must be disposed of by caller using `km_kbp_context_items_dispose`. +This must be disposed of by caller using `km_core_context_items_dispose`. ##### Return status: - `KM_KBP_STATUS_OK`: On success. - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. - `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated for the output buffer. ##### Parameters: -- __context_items__: A pointer to the start of an array `km_kbp_context_item`. +- __context_items__: A pointer to the start of an array `km_core_context_item`. - __out__: a pointer to the result variable: - A pointer to the start of the `km_kbp_context_item` array containing a + A pointer to the start of the `km_core_context_item` array containing a copy of the context. Terminated with a type of `KM_KBP_CT_END`. Must be - disposed of with `km_kbp_context_items_dispose`. + disposed of with `km_core_context_items_dispose`. ```c */ KMN_API -km_kbp_status -km_kbp_context_get(km_kbp_context const *context_items, - km_kbp_context_item **out); +km_core_status +km_core_context_get(km_core_context const *context_items, + km_core_context_item **out); /* ``` -### `km_kbp_context_clear` +### `km_core_context_clear` ##### Description: Removes all context_items from the internal array. If `context` is null, has no effect. @@ -388,11 +388,11 @@ null, has no effect. */ KMN_API void -km_kbp_context_clear(km_kbp_context *); +km_core_context_clear(km_core_context *); /* ``` -### `km_kbp_context_length` +### `km_core_context_length` ##### Description: Return the number of items in the context. ##### Return: @@ -405,11 +405,11 @@ pointer. */ KMN_API size_t -km_kbp_context_length(km_kbp_context *); +km_core_context_length(km_core_context *); /* ``` -### `km_kbp_context_append` +### `km_core_context_append` ##### Description: Add more items to the end (insertion point) of the context. If these exceed the maximum context length the same number of items will be dropped from the @@ -422,18 +422,18 @@ beginning of the context. ##### Parameters: - __context__: A pointer to an opaque context object. - __context_items__: A pointer to the start of the `KM_KBP_CT_END` terminated - array of `km_kbp_context_item` to append. + array of `km_core_context_item` to append. ```c */ KMN_API -km_kbp_status -km_kbp_context_append(km_kbp_context *context, - km_kbp_context_item const *context_items); +km_core_status +km_core_context_append(km_core_context *context, + km_core_context_item const *context_items); /* ``` -### `km_kbp_context_shrink` +### `km_core_context_shrink` ##### Description: Remove a specified number of items from the end of the context, optionally add up to the same number of the supplied items to the front of the context. @@ -446,34 +446,34 @@ add up to the same number of the supplied items to the front of the context. - __context__: A pointer to an opaque context object. - __num__: The number of items to remove from the end of context. - __context_items__: Pointer to the start of the `KM_KBP_CT_END` terminated - array of `km_kbp_context_item` to add to the front. Up to `num` items will + array of `km_core_context_item` to add to the front. Up to `num` items will be prepended. This may be null if not required. ```c */ KMN_API -km_kbp_status -km_kbp_context_shrink(km_kbp_context *context, +km_core_status +km_core_context_shrink(km_core_context *context, size_t num, - km_kbp_context_item const *prefix); + km_core_context_item const *prefix); /* ``` -### `km_kbp_context_item_list_size` +### `km_core_context_item_list_size` ##### Description: -Return the length of a terminated `km_kbp_context_item` array. +Return the length of a terminated `km_core_context_item` array. ##### Return: The number of items in the list, not including terminating item, or 0 if `context_items` is null. ##### Parameters: - __context_items__: A pointer to a `KM_KBP_CT_END` terminated array of - `km_kbp_context_item` values. + `km_core_context_item` values. ```c */ KMN_API size_t -km_kbp_context_item_list_size(km_kbp_context_item const *context_items); +km_core_context_item_list_size(km_core_context_item const *context_items); /* ``` @@ -486,11 +486,11 @@ other actions. */ typedef struct { - uint8_t expected_type; // km_kbp_backspace_type + uint8_t expected_type; // km_core_backspace_type uintptr_t expected_value; // used mainly in unit tests -} km_kbp_backspace_item; +} km_core_backspace_item; -enum km_kbp_backspace_type { +enum km_core_backspace_type { KM_KBP_BT_UNKNOWN = 0, // Used at beginning of context; user-initiated backspace KM_KBP_BT_CHAR = 1, // Deleting a character prior to insertion point KM_KBP_BT_MARKER = 2, // Deleting a marker prior to insertion point @@ -502,14 +502,14 @@ typedef struct { uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; union { uintptr_t marker; // MARKER type - km_kbp_option_item const * option; // OPT types - km_kbp_usv character; // CHAR type + km_core_option_item const * option; // OPT types + km_core_usv character; // CHAR type uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off - km_kbp_backspace_item backspace; // BACKSPACE type + km_core_backspace_item backspace; // BACKSPACE type }; -} km_kbp_action_item; +} km_core_action_item; -enum km_kbp_action_type { +enum km_core_action_type { KM_KBP_IT_END = 0, // Marks end of action items list. KM_KBP_IT_CHAR = 1, // A Unicode character has been generated. KM_KBP_IT_MARKER = 2, // Correlates to kmn's "deadkey" markers. @@ -533,9 +533,9 @@ enum km_kbp_action_type { A state’s default options are set from the keyboard at creation time and the environment. The Platform layer is then is expected to apply any persisted options it is maintaining. Options are passed into and out of API functions as -simple C arrays of `km_kbp_option_item` terminated with a `KM_KBP_OPTIONS_END` +simple C arrays of `km_core_option_item` terminated with a `KM_KBP_OPTIONS_END` sentinel value. A state's options are exposed and manipulatable via the -`km_kbp_options` API. All option values are of type C string. +`km_core_options` API. All option values are of type C string. During processing when the Platform layer finds a PERSIST action type it should store the updated option in the appropriate place, based on its scope. @@ -545,16 +545,16 @@ value. ```c */ -enum km_kbp_option_scope { +enum km_core_option_scope { KM_KBP_OPT_UNKNOWN = 0, KM_KBP_OPT_KEYBOARD = 1, KM_KBP_OPT_ENVIRONMENT = 2, KM_KBP_OPT_MAX_SCOPES }; -struct km_kbp_option_item { - km_kbp_cp const * key; - km_kbp_cp const * value; +struct km_core_option_item { + km_core_cp const * key; + km_core_cp const * value; uint8_t scope; // Scope which an option belongs to. }; @@ -563,26 +563,26 @@ struct km_kbp_option_item { /* ``` -### `km_kbp_options_list_size` +### `km_core_options_list_size` ##### Description: -Return the length of a terminated `km_kbp_option_item` array (options +Return the length of a terminated `km_core_option_item` array (options list). ##### Return: The number of items in the list, not including terminating item, or 0 if `opts` is null. ##### Parameters: - __opts__: A pointer to a `KM_KBP_OPTIONS_END` terminated array of - `km_kbp_option_item` values. + `km_core_option_item` values. ```c */ KMN_API size_t -km_kbp_options_list_size(km_kbp_option_item const *opts); +km_core_options_list_size(km_core_option_item const *opts); /* ``` -### `km_kbp_state_option_lookup` +### `km_core_state_option_lookup` ##### Description: Lookup an option based on its key, in an options list. ##### Return status: @@ -593,7 +593,7 @@ Lookup an option based on its key, in an options list. ##### Parameters: - __state__: An opaque pointer to a state object. - __scope__: Which key-value store to interrogate. -- __key__: A UTF-16 string that matches the key in the target `km_kbp_option_item`. +- __key__: A UTF-16 string that matches the key in the target `km_core_option_item`. - __value__: A pointer to the result variable: A pointer to a UTF-16 string value owned by the state or keyboard object at the time of the call. This pointer is only valid *until* the next call to any @@ -601,17 +601,17 @@ Lookup an option based on its key, in an options list. ```c */ KMN_API -km_kbp_status -km_kbp_state_option_lookup(km_kbp_state const *state, +km_core_status +km_core_state_option_lookup(km_core_state const *state, uint8_t scope, - km_kbp_cp const *key, - km_kbp_cp const **value); + km_core_cp const *key, + km_core_cp const **value); /* ``` -### `km_kbp_state_options_update` +### `km_core_state_options_update` ##### Description: -Adds or updates one or more options from a list of `km_kbp_option_item`s. +Adds or updates one or more options from a list of `km_core_option_item`s. ##### Return status: - `KM_KBP_STATUS_OK`: On success. - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -619,21 +619,21 @@ Adds or updates one or more options from a list of `km_kbp_option_item`s. - `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. ##### Parameters: - __state__: An opaque pointer to a state object. -- __new_opts__: An array of `km_kbp_option_item` objects to update or add. Must be +- __new_opts__: An array of `km_core_option_item` objects to update or add. Must be terminated with `KM_KBP_OPTIONS_END`. ```c */ KMN_API -km_kbp_status -km_kbp_state_options_update(km_kbp_state *state, - km_kbp_option_item const *new_opts); +km_core_status +km_core_state_options_update(km_core_state *state, + km_core_option_item const *new_opts); /* ``` -### `km_kbp_state_options_to_json` +### `km_core_state_options_to_json` ##### Description: -Export the contents of a `km_kbp_options` array to a JSON formatted document and +Export the contents of a `km_core_options` array to a JSON formatted document and place it in the supplied buffer, reporting how much space was used. If null is passed as the buffer the number of bytes required is returned in `space`. If there is insufficent space to hold the document the contents of the buffer is @@ -653,8 +653,8 @@ null. On return it will hold how many bytes were used. ```c */ KMN_API -km_kbp_status -km_kbp_state_options_to_json(km_kbp_state const *state, +km_core_status +km_core_state_options_to_json(km_core_state const *state, char *buf, size_t *space); @@ -669,31 +669,31 @@ of state objects. ```c */ typedef struct { - km_kbp_cp const * version_string; // Processor specific version string. - km_kbp_cp const * id; // Keyman keyboard ID string. - km_kbp_path_name folder_path; // Path to the unpacked folder containing + km_core_cp const * version_string; // Processor specific version string. + km_core_cp const * id; // Keyman keyboard ID string. + km_core_path_name folder_path; // Path to the unpacked folder containing // the keyboard and associated resources. - km_kbp_option_item const * default_options; -} km_kbp_keyboard_attrs; + km_core_option_item const * default_options; +} km_core_keyboard_attrs; typedef struct { - km_kbp_virtual_key key; + km_core_virtual_key key; uint32_t modifier_flag; -} km_kbp_keyboard_key; +} km_core_keyboard_key; #define KM_KBP_KEYBOARD_KEY_LIST_END { 0, 0 } typedef struct { - km_kbp_cp const * library_name; - km_kbp_cp const * function_name; + km_core_cp const * library_name; + km_core_cp const * function_name; uint32_t imx_id; // unique identifier used to call this function -} km_kbp_keyboard_imx; +} km_core_keyboard_imx; #define KM_KBP_KEYBOARD_IMX_END { 0, 0, 0 } /* ``` -### `km_kbp_keyboard_load` +### `km_core_keyboard_load` ##### Description: Parse and load keyboard from the supplied path and a pointer to the loaded keyboard into the out paramter. @@ -711,21 +711,21 @@ into the out paramter. contains a valid path to the keyboard file. - __keyboard__: A pointer to result variable: A pointer to the opaque keyboard object returned by the Processor. This - memory must be freed with a call to `km_kbp_keyboard_dispose`. + memory must be freed with a call to `km_core_keyboard_dispose`. ```c */ KMN_API -km_kbp_status -km_kbp_keyboard_load(km_kbp_path_name kb_path, - km_kbp_keyboard **keyboard); +km_core_status +km_core_keyboard_load(km_core_path_name kb_path, + km_core_keyboard **keyboard); /* ``` -### `km_kbp_keyboard_dispose` +### `km_core_keyboard_dispose` ##### Description: Free the allocated memory belonging to an opaque keyboard object previously -returned by `km_kbp_keyboard_load`. +returned by `km_core_keyboard_load`. ##### Parameters: - __keyboard__: A pointer to the opaque keyboard object to be disposed of. @@ -734,11 +734,11 @@ returned by `km_kbp_keyboard_load`. */ KMN_API void -km_kbp_keyboard_dispose(km_kbp_keyboard *keyboard); +km_core_keyboard_dispose(km_core_keyboard *keyboard); /* ``` -### `km_kbp_keyboard_get_attrs` +### `km_core_keyboard_get_attrs` ##### Description: Returns the const internal attributes of the keyboard. This structure is valid for the lifetime of the opaque keyboard object. Do not modify the returned data. @@ -748,18 +748,18 @@ for the lifetime of the opaque keyboard object. Do not modify the returned data. ##### Parameters: - __keyboard__: A pointer to the opaque keyboard object to be queried. - __out__: A pointer to the result: - A pointer to a `km_kbp_keyboard_attrs` structure. + A pointer to a `km_core_keyboard_attrs` structure. ```c */ KMN_API -km_kbp_status -km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, - km_kbp_keyboard_attrs const **out); +km_core_status +km_core_keyboard_get_attrs(km_core_keyboard const *keyboard, + km_core_keyboard_attrs const **out); /* ``` -### `km_kbp_keyboard_get_key_list` +### `km_core_keyboard_get_key_list` ##### Description: Returns the unordered full set of modifier+virtual keys that are handled by the keyboard. The matching dispose call needs to be called to free the memory. @@ -768,23 +768,23 @@ keyboard. The matching dispose call needs to be called to free the memory. - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. ##### Parameters: - __keyboard__: A pointer to the opaque keyboard object to be queried. -- __out__: A pointer to an array of `km_kbp_keyboard_key` structures, +- __out__: A pointer to an array of `km_core_keyboard_key` structures, terminated by `KM_KBP_KEYBOARD_KEY_LIST_END`. ```c */ KMN_API -km_kbp_status -km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, - km_kbp_keyboard_key **out); +km_core_status +km_core_keyboard_get_key_list(km_core_keyboard const *keyboard, + km_core_keyboard_key **out); /** ``` -### `km_kbp_keyboard_key_list_dispose` +### `km_core_keyboard_key_list_dispose` ##### Description: Free the allocated memory belonging to a keyboard key list previously -returned by `km_kbp_keyboard_get_key_list`. +returned by `km_core_keyboard_get_key_list`. ##### Parameters: - __key_list__: A pointer to the keyboard key list to be disposed of. @@ -792,46 +792,46 @@ returned by `km_kbp_keyboard_get_key_list`. ```c */ KMN_API -void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list); +void km_core_keyboard_key_list_dispose(km_core_keyboard_key *key_list); /** - * km_kbp_keyboard_get_imx_list: + * km_core_keyboard_get_imx_list: * * Returns: the list of IMX libraries and function names that are referenced by * the keyboard.The matching dispose call needs to be called to free the memory. */ KMN_API -km_kbp_status km_kbp_keyboard_get_imx_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_imx **imx_list); +km_core_status km_core_keyboard_get_imx_list(km_core_keyboard const *keyboard, km_core_keyboard_imx **imx_list); /** - * km_kbp_keyboard_imx_list_dispose: + * km_core_keyboard_imx_list_dispose: * * Disposes of the IMX list * * Returns: -- */ KMN_API -void km_kbp_keyboard_imx_list_dispose(km_kbp_keyboard_imx *imx_list); +void km_core_keyboard_imx_list_dispose(km_core_keyboard_imx *imx_list); /** - * km_kbp_state_imx_register_callback: + * km_core_state_imx_register_callback: * * Register the IMX callback endpoint for the client. * * Returns: -- */ KMN_API -void km_kbp_state_imx_register_callback(km_kbp_state *state, km_kbp_keyboard_imx_platform imx_callback, void *callback_object); +void km_core_state_imx_register_callback(km_core_state *state, km_core_keyboard_imx_platform imx_callback, void *callback_object); /** - * km_kbp_state_imx_deregister_callback: + * km_core_state_imx_deregister_callback: * * De-register IMX callback endpoint for the client. * * Returns: -- */ KMN_API -void km_kbp_state_imx_deregister_callback(km_kbp_state *state); +void km_core_state_imx_deregister_callback(km_core_state *state); /* ``` @@ -844,7 +844,7 @@ and dynamic options ("option stores" in kmn format). /* ``` -### `km_kbp_state_create` +### `km_core_state_create` ##### Description: Create a keyboard processor state object, maintaining state for the keyboard in the environment passed. @@ -858,24 +858,24 @@ the environment passed. - __keyboard__: A pointer to the opaque keyboard object this object will hold state for. - __env__: -The array of `km_kbp_option_item` key/value pairs used to initialise the +The array of `km_core_option_item` key/value pairs used to initialise the environment, terminated by `KM_KBP_OPTIONS_END`. - __out__: A pointer to result variable: A pointer to the opaque state object returned by the Processor, initalised to maintain state for `keyboard`. -This must be disposed of by a call to `km_kbp_state_dispose`. +This must be disposed of by a call to `km_core_state_dispose`. ```c */ KMN_API -km_kbp_status -km_kbp_state_create(km_kbp_keyboard *keyboard, - km_kbp_option_item const *env, - km_kbp_state **out); +km_core_status +km_core_state_create(km_core_keyboard *keyboard, + km_core_option_item const *env, + km_core_state **out); /* ``` -### `km_kbp_state_clone` +### `km_core_state_clone` ##### Description: Clone an existing opaque state object. ##### Return status: @@ -890,22 +890,22 @@ A pointer to the opaque statea object to be cloned. - __out__: A pointer to result variable: A pointer to the opaque state object returned by the Processor, cloned from the existing object `state`. This -must be disposed of by a call to `km_kbp_state_dispose`. +must be disposed of by a call to `km_core_state_dispose`. ```c */ KMN_API -km_kbp_status -km_kbp_state_clone(km_kbp_state const *state, - km_kbp_state **out); +km_core_status +km_core_state_clone(km_core_state const *state, + km_core_state **out); /* ``` -### `km_kbp_state_dispose` +### `km_core_state_dispose` ##### Description: -Free the allocated resources belonging to a `km_kbp_state` object previously -returned by `km_kbp_state_create` or `km_kbp_state_clone`. After this all -pointers previously returned by any km_kbp_state family of calls will become +Free the allocated resources belonging to a `km_core_state` object previously +returned by `km_core_state_create` or `km_core_state_clone`. After this all +pointers previously returned by any km_core_state family of calls will become invalid. ##### Parameters: - __state__: A pointer to the opaque state object to be disposed. @@ -914,11 +914,11 @@ invalid. */ KMN_API void -km_kbp_state_dispose(km_kbp_state *state); +km_core_state_dispose(km_core_state *state); /* ``` -### `km_kbp_state_context` +### `km_core_state_context` ##### Description: Get access to the state object's context. ##### Return: @@ -930,8 +930,8 @@ of the state object. If null is passed in, then null is returned. ```c */ KMN_API -km_kbp_context * -km_kbp_state_context(km_kbp_state *state); +km_core_context * +km_core_state_context(km_core_state *state); /* @@ -942,29 +942,29 @@ Get access to the state object's keyboard processor's intermediate context. This is used during an IMX callback, part way through processing a keystroke. ##### Return: A pointer to an context item array. Must be disposed of by a call -to `km_kbp_context_items_dispose`. +to `km_core_context_items_dispose`. ##### Parameters: - __state__: A pointer to the opaque state object to be queried. ```c */ KMN_API -km_kbp_status -kbp_state_get_intermediate_context(km_kbp_state *state, km_kbp_context_item ** context_items); +km_core_status +kbp_state_get_intermediate_context(km_core_state *state, km_core_context_item ** context_items); /* ``` -### `km_kbp_state_action_items` +### `km_core_state_action_items` ##### Description: Get the list of action items generated by the last call to -`km_kbp_process_event`. +`km_core_process_event`. ##### Return: -A pointer to a `km_kbp_action_item` list, of `*num_items` in length. This data +A pointer to a `km_core_action_item` list, of `*num_items` in length. This data becomes invalid when the state object is destroyed, or after a call to -`km_kbp_process_event`. Do not modify the contents of this data. The returned +`km_core_process_event`. Do not modify the contents of this data. The returned array is terminated with a `KM_KBP_IT_END` entry. ##### Parameters: -- __state__: A pointer to the opaque `km_kbp_state` object to be queried. +- __state__: A pointer to the opaque `km_core_state` object to be queried. - __num_items__: A pointer to a result variable: The number of items in the action item list including the `KM_KBP_IT_END` terminator. May be null if not that @@ -973,37 +973,37 @@ information is required. ```c */ KMN_API -km_kbp_action_item const * -km_kbp_state_action_items(km_kbp_state const *state, +km_core_action_item const * +km_core_state_action_items(km_core_state const *state, size_t *num_items); /* ``` -### `km_kbp_state_queue_action_items` +### `km_core_state_queue_action_items` ##### Description: Queue actions for the current keyboard processor state; normally -used in IMX callbacks called during `km_kbp_process_event`. +used in IMX callbacks called during `km_core_process_event`. ##### Return: - `KM_KBP_STATUS_OK`: On success. - `KM_KBP_STATUS_INVALID_ARGUMENT`: In the event the `state` or `action_items` pointer are null. ##### Parameters: -- __state__: A pointer to the opaque `km_kbp_state` object to be queried. +- __state__: A pointer to the opaque `km_core_state` object to be queried. - __action_items__: The action items to be added to the core queue. Must be terminated with a `KM_KBP_IT_END` entry. ```c */ KMN_API -km_kbp_status -km_kbp_state_queue_action_items(km_kbp_state *state, - km_kbp_action_item const *action_items); +km_core_status +km_core_state_queue_action_items(km_core_state *state, + km_core_action_item const *action_items); /* ``` ### `km_kpb_state_to_json` ##### Description: -Export the internal state of a `km_kbp_state` object to a JSON format document +Export the internal state of a `km_core_state` object to a JSON format document and place it in the supplied buffer, reporting how much space was used. If null is passed as the buffer the number of bytes required is returned. If there is insufficent space to hold the document, the contents of the buffer is undefined. @@ -1027,8 +1027,8 @@ null. On return it will hold how many bytes were used. ```c */ KMN_API -km_kbp_status -km_kbp_state_to_json(km_kbp_state const *state, +km_core_status +km_core_state_to_json(km_core_state const *state, char *buf, size_t *space); @@ -1045,9 +1045,9 @@ typedef struct { uint16_t technology; // A bit field specifiying which Keyboard // technologies the engine supports. char const *vendor; // Implementor of the processor. -} km_kbp_attr; +} km_core_attr; -enum km_kbp_tech_value { +enum km_core_tech_value { KM_KBP_TECH_UNSPECIFIED = 0, KM_KBP_TECH_MOCK = 1 << 0, KM_KBP_TECH_KMX = 1 << 1, @@ -1055,34 +1055,34 @@ enum km_kbp_tech_value { }; /** - * km_kbp_event_flags: + * km_core_event_flags: * - * Bit flags to be used with the event_flags parameter of km_kbp_process_event + * Bit flags to be used with the event_flags parameter of km_core_process_event */ -enum km_kbp_event_flags { +enum km_core_event_flags { KM_KBP_EVENT_FLAG_DEFAULT = 0, // default value: hardware KM_KBP_EVENT_FLAG_TOUCH = 1, // set if the event is touch, otherwise hardware }; /* ``` -### `km_kbp_get_engine_attrs` +### `km_core_get_engine_attrs` ##### Description: Get access processors attributes describing version and technology implemented. ##### Return: -A pointer to a `km_kbp_attr` structure. Do not modify the contents of this +A pointer to a `km_core_attr` structure. Do not modify the contents of this structure. ##### Parameters: -- __state__: An opaque pointer to an `km_kbp_state`. +- __state__: An opaque pointer to an `km_core_state`. ```c */ KMN_API -km_kbp_attr const * -km_kbp_get_engine_attrs(km_kbp_state const *state); +km_core_attr const * +km_core_get_engine_attrs(km_core_state const *state); /* ``` -### `km_kbp_process_event` +### `km_core_process_event` ##### Description: Run the keyboard on an opaque state object with the provided virtual key and modifer key state. Updates the state object as appropriate and fills out its action list. @@ -1102,26 +1102,26 @@ state is passed. - __vk__: A virtual key to be processed. - __modifier_state__: The combinations of modifier keys set at the time key `vk` was pressed, bitmask -from the `km_kbp_modifier_state` enum. -- __event_flags__: Event level flags, see km_kbp_event_flags +from the `km_core_modifier_state` enum. +- __event_flags__: Event level flags, see km_core_event_flags ```c */ KMN_API -km_kbp_status -km_kbp_process_event(km_kbp_state *state, - km_kbp_virtual_key vk, +km_core_status +km_core_process_event(km_core_state *state, + km_core_virtual_key vk, uint16_t modifier_state, uint8_t is_key_down, uint16_t event_flags); /* ``` -### `km_kbp_process_queued_actions` +### `km_core_process_queued_actions` ##### Description: Process the keyboard processors queued actions for the opaque state object. Updates the state object as appropriate and fills out its action list. -The client can add actions externally via the `km_kbp_state_queue_action_items` and +The client can add actions externally via the `km_core_state_queue_action_items` and then request the processing of the actions with this method. The state action list will be cleared at the start of this call; options and context in @@ -1139,12 +1139,12 @@ In the event the `state` pointer is null ```c */ KMN_API -km_kbp_status -km_kbp_process_queued_actions(km_kbp_state *state); +km_core_status +km_core_process_queued_actions(km_core_state *state); /* ``` -### `km_kbp_event` +### `km_core_event` ##### Description: Tell the keyboard processor that an external event has occurred, such as a keyboard being activated through the language switching UI. @@ -1163,20 +1163,20 @@ the state may also be modified. ##### Parameters: - __state__: A pointer to the opaque state object. -- __event__: The event to be processed, from km_kbp_event_code enumeration +- __event__: The event to be processed, from km_core_event_code enumeration - __data__: Additional event-specific data. Currently unused, must be nullptr. ```c */ KMN_API -km_kbp_status -km_kbp_event( - km_kbp_state *state, +km_core_status +km_core_event( + km_core_state *state, uint32_t event, void* data ); -enum km_kbp_event_code { +enum km_core_event_code { // A keyboard has been activated by the user. The processor may use this // event, for example, to switch caps lock state or provide other UX. KM_KBP_EVENT_KEYBOARD_ACTIVATED = 1, diff --git a/core/include/keyman/keyman_core_api_bits.h b/core/include/keyman/keyman_core_api_bits.h index 03892a62afb..be2886d0583 100644 --- a/core/include/keyman/keyman_core_api_bits.h +++ b/core/include/keyman/keyman_core_api_bits.h @@ -27,7 +27,7 @@ #endif #if defined _WIN32 || defined __CYGWIN__ - typedef wchar_t const * km_kbp_path_name; + typedef wchar_t const * km_core_path_name; #define _KM_KBP_PATH_SEPARATOR (L'\\') #define _KM_KBP_EXT_SEPARATOR (L'.') #if defined __GNUC__ // These three will be redefined for Windows @@ -43,7 +43,7 @@ #define _kmn_import_flag dllimport #define _kmn_static_flag #else - typedef char const * km_kbp_path_name; + typedef char const * km_core_path_name; #define _KM_KBP_PATH_SEPARATOR ('/') #define _KM_KBP_EXT_SEPARATOR ('.') #endif diff --git a/core/include/keyman/keyman_core_api_debug.h b/core/include/keyman/keyman_core_api_debug.h index f038e2cd819..527da030dfc 100644 --- a/core/include/keyman/keyman_core_api_debug.h +++ b/core/include/keyman/keyman_core_api_debug.h @@ -24,7 +24,7 @@ extern "C" #endif /** - * The maximum size of context in km_kbp_cp units for a single debug + * The maximum size of context in km_core_cp units for a single debug * event. This is taken from MAXCONTEXT in keyman32 (Windows) and is purely * a convenience value. We can increase it if there is a demonstrated need. */ @@ -56,7 +56,7 @@ typedef struct { uint16_t vk; uint16_t modifier_state; char16_t character; -} km_kbp_state_debug_key_info; +} km_core_state_debug_key_info; /** * Option event data. @@ -65,8 +65,8 @@ typedef struct { */ typedef struct { void *store; // LPSTORE - km_kbp_cp value[DEBUG_MAX_CONTEXT]; // value to be saved into the store -} km_kbp_state_debug_kmx_option_info; + km_core_cp value[DEBUG_MAX_CONTEXT]; // value to be saved into the store +} km_core_state_debug_kmx_option_info; /** * KMX processor data for each event. kmx_base.h defines the types that are @@ -80,7 +80,7 @@ typedef struct { */ typedef struct { - km_kbp_cp context[DEBUG_MAX_CONTEXT]; // The context matched by the rule (? may not need this?) // TODO: rename to context_matched + km_core_cp context[DEBUG_MAX_CONTEXT]; // The context matched by the rule (? may not need this?) // TODO: rename to context_matched void *group; // LPGROUP void *rule; // LPKEY uint16_t store_offsets[DEBUG_STORE_OFFSETS_SIZE]; // pairs--store, char position, terminated by 0xFFFF // TODO use a better structure here @@ -89,8 +89,8 @@ typedef struct { /// the debugger; the debugger uses this to determine when to /// execute the actions when single-stepping. uint16_t first_action; - km_kbp_state_debug_kmx_option_info option; -} km_kbp_state_debug_kmx_info; + km_core_state_debug_kmx_option_info option; +} km_core_state_debug_kmx_info; /** * A single debug event. @@ -98,14 +98,14 @@ typedef struct { typedef struct { uint32_t type; // 32 bits is better optimized than 8 bits uint32_t flags; - km_kbp_state_debug_key_info key_info; - km_kbp_state_debug_kmx_info kmx_info; -} km_kbp_state_debug_item; + km_core_state_debug_key_info key_info; + km_core_state_debug_kmx_info kmx_info; +} km_core_state_debug_item; /** * A single debug event. */ -enum km_kbp_debug_type { +enum km_core_debug_type { KM_KBP_DEBUG_BEGIN = 0, //KM_KBP_DEBUG_BEGIN_ANSI = 1, // not supported; instead rewrite ansi keyboards to Unicode with mcompile KM_KBP_DEBUG_GROUP_ENTER = 2, @@ -140,8 +140,8 @@ enum km_kbp_debug_type { * @returns KM_KBP_STATUS_OK on success */ KMN_API -km_kbp_status -km_kbp_state_debug_set(km_kbp_state *state, int value); +km_core_status +km_core_state_debug_set(km_core_state *state, int value); /** * Get current debug tracing status @@ -152,7 +152,7 @@ km_kbp_state_debug_set(km_kbp_state *state, int value); */ KMN_API uint8_t -km_kbp_state_debug_get(km_kbp_state const *state); +km_core_state_debug_get(km_core_state const *state); /** * Read current debug trace log @@ -165,8 +165,8 @@ km_kbp_state_debug_get(km_kbp_state const *state); * with last entry guaranteed to be KM_KBP_DEBUG_END. */ KMN_API -km_kbp_state_debug_item const * -km_kbp_state_debug_items(km_kbp_state const *state, size_t *num_items); +km_core_state_debug_item const * +km_core_state_debug_items(km_core_state const *state, size_t *num_items); #if defined(__cplusplus) } // extern "C" diff --git a/core/include/keyman/keyman_core_api_version.h.in b/core/include/keyman/keyman_core_api_version.h.in index d434ae4e485..0f8c2934302 100644 --- a/core/include/keyman/keyman_core_api_version.h.in +++ b/core/include/keyman/keyman_core_api_version.h.in @@ -1,12 +1,12 @@ -#define km_kbp_version_stringify(x) km_kbp_version_to_string(x) -#define km_kbp_version_to_string(x) #x +#define km_core_version_stringify(x) km_core_version_to_string(x) +#define km_core_version_to_string(x) #x // Product versioning #define KM_KBP_VERSION_MAJOR @majorver@ #define KM_KBP_VERSION_MINOR @minorver@ #define KM_KBP_VERSION_PATCH @patchver@ -#define KM_KBP_VERSION_STRING km_kbp_version_stringify(@majorver@ ## . ## @minorver@ ## . ## @patchver@ ## .0) +#define KM_KBP_VERSION_STRING km_core_version_stringify(@majorver@ ## . ## @minorver@ ## . ## @patchver@ ## .0) // API versioning diff --git a/core/include/keyman/keyman_core_api_vkeys.h b/core/include/keyman/keyman_core_api_vkeys.h index a1e580df28b..0e7ba5206eb 100644 --- a/core/include/keyman/keyman_core_api_vkeys.h +++ b/core/include/keyman/keyman_core_api_vkeys.h @@ -6,7 +6,7 @@ maintain readability of the primary API header. Create Date: 17 Oct 2018 Authors: Tim Eves (TSE) - History: 17 Oct 2018 - TSE - Moved & refactored km_kbp_modifier_state + History: 17 Oct 2018 - TSE - Moved & refactored km_core_modifier_state from keyman_core_api.h. - Added VKey and mask definitions. 6 Oct 2018 - TSE - Move into keyman folder. @@ -15,7 +15,7 @@ #pragma once -enum km_kbp_modifier_state { +enum km_core_modifier_state { KM_KBP_MODIFIER_LCTRL = 1 << 0, KM_KBP_MODIFIER_RCTRL = 1 << 1, KM_KBP_MODIFIER_LALT = 1 << 2, @@ -40,7 +40,7 @@ enum km_kbp_modifier_state { */ }; -enum km_kbp_modifier_mask { +enum km_core_modifier_mask { KM_KBP_MODIFIER_MASK_ALL = 0x7f, KM_KBP_MODIFIER_MASK_ALT_GR_SIM = KM_KBP_MODIFIER_LCTRL|KM_KBP_MODIFIER_LALT, KM_KBP_MODIFIER_MASK_CHIRAL = 0x1f, diff --git a/core/src/context.hpp b/core/src/context.hpp index 03d4aeb2d79..9fcd46614ba 100644 --- a/core/src/context.hpp +++ b/core/src/context.hpp @@ -3,7 +3,7 @@ Description: Internal context class and adaptor class for the API. Create Date: 2 Oct 2018 Authors: Tim Eves (TSE) - History: 2 Oct 2018 - TSE - Refactored out of km_kbp_context_api.cpp + History: 2 Oct 2018 - TSE - Refactored out of km_core_context_api.cpp */ #pragma once @@ -20,32 +20,32 @@ namespace kbp // This will likely be replaced with a class implementing a more space // efficient data structure such as a ring buffer or bounded queue. -class context: public std::list +class context: public std::list { public: - void push_character(km_kbp_usv); + void push_character(km_core_usv); void push_marker(uint32_t); }; inline -void context::push_character(km_kbp_usv usv) { - emplace_back(km_kbp_context_item { KM_KBP_CT_CHAR, {0,}, {usv} }); +void context::push_character(km_core_usv usv) { + emplace_back(km_core_context_item { KM_KBP_CT_CHAR, {0,}, {usv} }); } inline void context::push_marker(uint32_t marker) { - emplace_back(km_kbp_context_item { KM_KBP_CT_MARKER, {0,}, {marker} }); + emplace_back(km_core_context_item { KM_KBP_CT_MARKER, {0,}, {marker} }); } } // namespace kbp } // namespace km json & operator << (json &, km::kbp::context const &); -json & operator << (json &, km_kbp_context_item const &); +json & operator << (json &, km_core_context_item const &); -struct km_kbp_context : public km::kbp::context +struct km_core_context : public km::kbp::context { }; diff --git a/core/src/debug.hpp b/core/src/debug.hpp index cee5e37c521..c34eaf38e0b 100644 --- a/core/src/debug.hpp +++ b/core/src/debug.hpp @@ -16,13 +16,13 @@ namespace km { namespace kbp { -class debug_items : public std::vector +class debug_items : public std::vector { private: bool _is_enabled; public: template debug_items(Args&&... args); - void push_begin(km_kbp_state_debug_key_info *key_info, uint32_t flags); + void push_begin(km_core_state_debug_key_info *key_info, uint32_t flags); void push_end(uint16_t first_action, uint32_t flags); void assert_push_entry(); bool is_enabled() const noexcept; @@ -31,10 +31,10 @@ class debug_items : public std::vector template debug_items::debug_items(Args&&... args) -: std::vector(std::forward(args)...) +: std::vector(std::forward(args)...) { // Ensure the debug_items list is terminated in case the client calls - // km_kbp_state_debug_items before they call process_event. + // km_core_state_debug_items before they call process_event. _is_enabled = false; push_end(0, 0); } @@ -45,15 +45,15 @@ void debug_items::assert_push_entry() { } inline -void debug_items::push_begin(km_kbp_state_debug_key_info *key_info, uint32_t flags) { +void debug_items::push_begin(km_core_state_debug_key_info *key_info, uint32_t flags) { assert_push_entry(); - emplace_back(km_kbp_state_debug_item{ KM_KBP_DEBUG_BEGIN, flags, {*key_info, }, { }}); + emplace_back(km_core_state_debug_item{ KM_KBP_DEBUG_BEGIN, flags, {*key_info, }, { }}); } inline void debug_items::push_end(uint16_t first_action, uint32_t flags) { assert_push_entry(); - emplace_back(km_kbp_state_debug_item{ KM_KBP_DEBUG_END, flags, { }, { u"", nullptr, nullptr, { }, first_action, {} } }); + emplace_back(km_core_state_debug_item{ KM_KBP_DEBUG_END, flags, { }, { u"", nullptr, nullptr, { }, first_action, {} } }); } inline diff --git a/core/src/keyboard.cpp b/core/src/keyboard.cpp index 977da597d31..47693b9b664 100644 --- a/core/src/keyboard.cpp +++ b/core/src/keyboard.cpp @@ -3,7 +3,7 @@ Description: Internal keyboard class and adaptor class for the API. Create Date: 2 Oct 2018 Authors: Tim Eves (TSE) - History: 7 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp + History: 7 Oct 2018 - TSE - Refactored out of km_core_keyboard_api.cpp */ #include "keyboard.hpp" #include "jsonpp.hpp" diff --git a/core/src/keyboard.hpp b/core/src/keyboard.hpp index 6c8b93263c4..97329b7e761 100644 --- a/core/src/keyboard.hpp +++ b/core/src/keyboard.hpp @@ -3,7 +3,7 @@ Description: Internal keyboard class and adaptor class for the API. Create Date: 2 Oct 2018 Authors: Tim Eves (TSE) - History: 2 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp + History: 2 Oct 2018 - TSE - Refactored out of km_core_keyboard_api.cpp */ #pragma once @@ -22,7 +22,7 @@ class json; namespace km { namespace kbp { - class keyboard_attributes : public km_kbp_keyboard_attrs + class keyboard_attributes : public km_core_keyboard_attrs { std::u16string _keyboard_id; std::u16string _version_string; @@ -36,7 +36,7 @@ namespace kbp using path_type = decltype(_folder_path); keyboard_attributes() - : km_kbp_keyboard_attrs {nullptr, nullptr, nullptr, nullptr} {} + : km_core_keyboard_attrs {nullptr, nullptr, nullptr, nullptr} {} keyboard_attributes(keyboard_attributes const &) = delete; keyboard_attributes(keyboard_attributes &&); diff --git a/core/src/km_kbp_context_api.cpp b/core/src/km_kbp_context_api.cpp index a0845618b4f..3347ecdcdba 100644 --- a/core/src/km_kbp_context_api.cpp +++ b/core/src/km_kbp_context_api.cpp @@ -20,9 +20,9 @@ namespace { template - km_kbp_status + km_core_status _context_items_from(typename U::codeunit_t const *text, - km_kbp_context_item **out_ptr) + km_core_context_item **out_ptr) { assert(text); assert(out_ptr); if (!text || !out_ptr) return KM_KBP_STATUS_INVALID_ARGUMENT; @@ -30,17 +30,17 @@ namespace { *out_ptr = nullptr; try { - std::vector res; + std::vector res; for (auto i = typename U::const_iterator(text); *i; ++i) { if(i.error()) return KM_KBP_STATUS_INVALID_UTF; - res.emplace_back(km_kbp_context_item {KM_KBP_CT_CHAR, {0,}, {*i}}); + res.emplace_back(km_core_context_item {KM_KBP_CT_CHAR, {0,}, {*i}}); } // Terminate the context_items array. - res.emplace_back(km_kbp_context_item KM_KBP_CONTEXT_ITEM_END); + res.emplace_back(km_core_context_item KM_KBP_CONTEXT_ITEM_END); - *out_ptr = new km_kbp_context_item[res.size()]; + *out_ptr = new km_core_context_item[res.size()]; std::move(res.begin(), res.end(), *out_ptr); } catch (std::bad_alloc &) @@ -52,7 +52,7 @@ namespace { } template - km_kbp_status _context_items_to(km_kbp_context_item const *ci, + km_core_status _context_items_to(km_core_context_item const *ci, typename U::codeunit_t *buf, size_t * sz_ptr) { @@ -107,23 +107,23 @@ namespace { } } -km_kbp_status -km_kbp_context_items_from_utf16(km_kbp_cp const *text, - km_kbp_context_item **out_ptr) +km_core_status +km_core_context_items_from_utf16(km_core_cp const *text, + km_core_context_item **out_ptr) { return _context_items_from(reinterpret_cast(text), out_ptr); } -km_kbp_status -km_kbp_context_items_from_utf8(char const *text, - km_kbp_context_item **out_ptr) +km_core_status +km_core_context_items_from_utf8(char const *text, + km_core_context_item **out_ptr) { return _context_items_from(reinterpret_cast(text), out_ptr); } -km_kbp_status km_kbp_context_items_to_utf8(km_kbp_context_item const *ci, +km_core_status km_core_context_items_to_utf8(km_core_context_item const *ci, char *buf, size_t * sz_ptr) { return _context_items_to(ci, @@ -132,8 +132,8 @@ km_kbp_status km_kbp_context_items_to_utf8(km_kbp_context_item const *ci, } -km_kbp_status km_kbp_context_items_to_utf16(km_kbp_context_item const *ci, - km_kbp_cp *buf, size_t * sz_ptr) +km_core_status km_core_context_items_to_utf16(km_core_context_item const *ci, + km_core_cp *buf, size_t * sz_ptr) { return _context_items_to(ci, reinterpret_cast(buf), @@ -141,28 +141,28 @@ km_kbp_status km_kbp_context_items_to_utf16(km_kbp_context_item const *ci, } -void km_kbp_context_items_dispose(km_kbp_context_item *ci) +void km_core_context_items_dispose(km_core_context_item *ci) { delete [] ci; } -km_kbp_status km_kbp_context_set(km_kbp_context *ctxt, km_kbp_context_item const *ci) +km_core_status km_core_context_set(km_core_context *ctxt, km_core_context_item const *ci) { - km_kbp_context_clear(ctxt); - return km_kbp_context_append(ctxt, ci); + km_core_context_clear(ctxt); + return km_core_context_append(ctxt, ci); } -km_kbp_status km_kbp_context_get(km_kbp_context const *ctxt, - km_kbp_context_item **out_ptr) +km_core_status km_core_context_get(km_core_context const *ctxt, + km_core_context_item **out_ptr) { assert(ctxt); assert(out_ptr); if (!ctxt || !out_ptr) return KM_KBP_STATUS_INVALID_ARGUMENT; try { - *out_ptr = new km_kbp_context_item[ctxt->size() + 1]; + *out_ptr = new km_core_context_item[ctxt->size() + 1]; } catch (std::bad_alloc &) { @@ -175,7 +175,7 @@ km_kbp_status km_kbp_context_get(km_kbp_context const *ctxt, } -void km_kbp_context_clear(km_kbp_context *ctxt) +void km_core_context_clear(km_core_context *ctxt) { assert(ctxt); if (ctxt) @@ -185,15 +185,15 @@ void km_kbp_context_clear(km_kbp_context *ctxt) } -size_t km_kbp_context_length(km_kbp_context *ctxt) +size_t km_core_context_length(km_core_context *ctxt) { assert(ctxt); return ctxt ? ctxt->size() : 0; } -km_kbp_status km_kbp_context_append(km_kbp_context *ctxt, - km_kbp_context_item const *ci) +km_core_status km_core_context_append(km_core_context *ctxt, + km_core_context_item const *ci) { assert(ctxt); assert(ci); if (!ctxt || !ci) return KM_KBP_STATUS_INVALID_ARGUMENT; @@ -212,8 +212,8 @@ km_kbp_status km_kbp_context_append(km_kbp_context *ctxt, } -km_kbp_status km_kbp_context_shrink(km_kbp_context *ctxt, size_t num, - km_kbp_context_item const * ci) +km_core_status km_core_context_shrink(km_core_context *ctxt, size_t num, + km_core_context_item const * ci) { assert(ctxt); if (!ctxt) return KM_KBP_STATUS_INVALID_ARGUMENT; @@ -239,7 +239,7 @@ km_kbp_status km_kbp_context_shrink(km_kbp_context *ctxt, size_t num, } size_t -km_kbp_context_item_list_size(km_kbp_context_item const *context_items) +km_core_context_item_list_size(km_core_context_item const *context_items) { assert(context_items); if (!context_items) return 0; @@ -258,7 +258,7 @@ json & operator << (json & j, km::kbp::context const & ctxt) { return j << json::close; } -json & operator << (json & j, km_kbp_context_item const & i) +json & operator << (json & j, km_core_context_item const & i) { utf8::codeunit_t cps[7] = {0,}; // 6 bytes for maximal UTF-8 char (e.g. U+10FFFF) + nul terminator int8_t l = 4; diff --git a/core/src/km_kbp_debug_api.cpp b/core/src/km_kbp_debug_api.cpp index ff7ad03044b..34d894d402f 100644 --- a/core/src/km_kbp_debug_api.cpp +++ b/core/src/km_kbp_debug_api.cpp @@ -15,9 +15,9 @@ using namespace km::kbp; -km_kbp_status -km_kbp_state_debug_set( - km_kbp_state *state, +km_core_status +km_core_state_debug_set( + km_core_state *state, int value ) { assert(state); @@ -29,8 +29,8 @@ km_kbp_state_debug_set( } uint8_t -km_kbp_state_debug_get( - km_kbp_state const *state +km_core_state_debug_get( + km_core_state const *state ) { assert(state); if(!state) { @@ -39,9 +39,9 @@ km_kbp_state_debug_get( return state->debug_items().is_enabled() ? 1 : 0; } -km_kbp_state_debug_item const * -km_kbp_state_debug_items( - km_kbp_state const *state, +km_core_state_debug_item const * +km_core_state_debug_items( + km_core_state const *state, size_t *num_items ) { assert(state && state->debug_items().size() > 0); diff --git a/core/src/km_kbp_keyboard_api.cpp b/core/src/km_kbp_keyboard_api.cpp index b12ae3a9cb8..71cfc01f0de 100644 --- a/core/src/km_kbp_keyboard_api.cpp +++ b/core/src/km_kbp_keyboard_api.cpp @@ -43,8 +43,8 @@ namespace } -km_kbp_status -km_kbp_keyboard_load(km_kbp_path_name kb_path, km_kbp_keyboard **keyboard) +km_core_status +km_core_keyboard_load(km_core_path_name kb_path, km_core_keyboard **keyboard) { assert(keyboard); if (!keyboard) @@ -53,12 +53,12 @@ km_kbp_keyboard_load(km_kbp_path_name kb_path, km_kbp_keyboard **keyboard) try { abstract_processor *kp = processor_factory(kb_path); - km_kbp_status status = kp->validate(); + km_core_status status = kp->validate(); if (status != KM_KBP_STATUS_OK) { delete kp; return status; } - *keyboard = static_cast(kp); + *keyboard = static_cast(kp); } catch (std::bad_alloc &) { @@ -68,14 +68,14 @@ km_kbp_keyboard_load(km_kbp_path_name kb_path, km_kbp_keyboard **keyboard) } void -km_kbp_keyboard_dispose(km_kbp_keyboard *keyboard) +km_core_keyboard_dispose(km_core_keyboard *keyboard) { delete keyboard; } -km_kbp_status -km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, - km_kbp_keyboard_attrs const **out) +km_core_status +km_core_keyboard_get_attrs(km_core_keyboard const *keyboard, + km_core_keyboard_attrs const **out) { assert(keyboard); assert(out); if (!keyboard || !out) @@ -85,9 +85,9 @@ km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, return KM_KBP_STATUS_OK; } -km_kbp_status -km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, - km_kbp_keyboard_key **out) +km_core_status +km_core_keyboard_get_key_list(km_core_keyboard const *keyboard, + km_core_keyboard_key **out) { assert(keyboard); assert(out); if (!keyboard || !out) @@ -97,14 +97,14 @@ km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, return KM_KBP_STATUS_OK; } -void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list) +void km_core_keyboard_key_list_dispose(km_core_keyboard_key *key_list) { delete[] key_list; } -km_kbp_status km_kbp_keyboard_get_imx_list( - km_kbp_keyboard const *keyboard, - km_kbp_keyboard_imx** imx_list +km_core_status km_core_keyboard_get_imx_list( + km_core_keyboard const *keyboard, + km_core_keyboard_imx** imx_list ) { assert(keyboard); assert(imx_list); if (!keyboard || !imx_list) { @@ -115,13 +115,13 @@ km_kbp_status km_kbp_keyboard_get_imx_list( return KM_KBP_STATUS_OK; } -void km_kbp_keyboard_imx_list_dispose(km_kbp_keyboard_imx *imx_list) +void km_core_keyboard_imx_list_dispose(km_core_keyboard_imx *imx_list) { if(!imx_list) { return; } - km_kbp_keyboard_imx *imx_rule_it = imx_list; + km_core_keyboard_imx *imx_rule_it = imx_list; for (; imx_rule_it->library_name; ++imx_rule_it) { delete [] imx_rule_it->library_name; // from u16dup delete [] imx_rule_it->function_name; // from u16dup diff --git a/core/src/km_kbp_options_api.cpp b/core/src/km_kbp_options_api.cpp index 0f522d6ffeb..27b7ac97846 100644 --- a/core/src/km_kbp_options_api.cpp +++ b/core/src/km_kbp_options_api.cpp @@ -19,7 +19,7 @@ size_t -km_kbp_options_list_size(km_kbp_option_item const *opts) +km_core_options_list_size(km_core_option_item const *opts) { assert(opts); if (!opts) return 0; @@ -33,10 +33,10 @@ km_kbp_options_list_size(km_kbp_option_item const *opts) } -km_kbp_status -km_kbp_state_option_lookup(km_kbp_state const *state, - uint8_t scope, km_kbp_cp const *key, - km_kbp_cp const **value_out) +km_core_status +km_core_state_option_lookup(km_core_state const *state, + uint8_t scope, km_core_cp const *key, + km_core_cp const **value_out) { assert(state); assert(key); assert(value_out); if (!state || !key || !value_out) return KM_KBP_STATUS_INVALID_ARGUMENT; @@ -46,15 +46,15 @@ km_kbp_state_option_lookup(km_kbp_state const *state, auto & processor = state->processor(); - *value_out = processor.lookup_option(km_kbp_option_scope(scope), key); + *value_out = processor.lookup_option(km_core_option_scope(scope), key); if (!*value_out) return KM_KBP_STATUS_KEY_ERROR; return KM_KBP_STATUS_OK; } -km_kbp_status -km_kbp_state_options_update(km_kbp_state *state, km_kbp_option_item const *opt) +km_core_status +km_core_state_options_update(km_core_state *state, km_core_option_item const *opt) { assert(state); assert(opt); if (!state|| !opt) return KM_KBP_STATUS_INVALID_ARGUMENT; @@ -69,7 +69,7 @@ km_kbp_state_options_update(km_kbp_state *state, km_kbp_option_item const *opt) return KM_KBP_STATUS_INVALID_ARGUMENT; if (processor.update_option( - km_kbp_option_scope(opt->scope), + km_core_option_scope(opt->scope), opt->key, opt->value).empty()) return KM_KBP_STATUS_KEY_ERROR; @@ -85,8 +85,8 @@ km_kbp_state_options_update(km_kbp_state *state, km_kbp_option_item const *opt) // This function doesn't need to use the json pretty printer for such a simple // list of key:value pairs but it's a good introduction to it. -km_kbp_status -km_kbp_state_options_to_json(km_kbp_state const *state, char *buf, size_t *space) +km_core_status +km_core_state_options_to_json(km_core_state const *state, char *buf, size_t *space) { assert(state); assert(space); if (!state || !space) diff --git a/core/src/km_kbp_processevent_api.cpp b/core/src/km_kbp_processevent_api.cpp index 8e818ea42d2..9a0ccb14e30 100644 --- a/core/src/km_kbp_processevent_api.cpp +++ b/core/src/km_kbp_processevent_api.cpp @@ -12,9 +12,9 @@ #include "processor.hpp" #include "state.hpp" -km_kbp_status -km_kbp_event( - km_kbp_state *state, +km_core_status +km_core_event( + km_core_state *state, uint32_t event, void* data ) { @@ -39,9 +39,9 @@ km_kbp_event( return state->processor().external_event(state, event, data); } -km_kbp_status -km_kbp_process_event(km_kbp_state *state, - km_kbp_virtual_key vk, +km_core_status +km_core_process_event(km_core_state *state, + km_core_virtual_key vk, uint16_t modifier_state, uint8_t is_key_down, uint16_t event_flags) { @@ -52,9 +52,9 @@ km_kbp_process_event(km_kbp_state *state, return state->processor().process_event(state, vk, modifier_state, is_key_down, event_flags); } -km_kbp_status -km_kbp_process_queued_actions( - km_kbp_state *state +km_core_status +km_core_process_queued_actions( + km_core_state *state ) { assert(state != nullptr); if(state == nullptr) { @@ -63,8 +63,8 @@ km_kbp_process_queued_actions( return state->processor().process_queued_actions(state); } -km_kbp_attr const * -km_kbp_get_engine_attrs(km_kbp_state const *state) +km_core_attr const * +km_core_get_engine_attrs(km_core_state const *state) { assert(state != nullptr); if(state == nullptr) { diff --git a/core/src/km_kbp_state_api.cpp b/core/src/km_kbp_state_api.cpp index 168288423df..7fe6e1caa48 100644 --- a/core/src/km_kbp_state_api.cpp +++ b/core/src/km_kbp_state_api.cpp @@ -23,9 +23,9 @@ using namespace km::kbp; // Forward declarations class context; -km_kbp_status km_kbp_state_create(km_kbp_keyboard * keyboard, - km_kbp_option_item const *env, - km_kbp_state ** out) +km_core_status km_core_state_create(km_core_keyboard * keyboard, + km_core_option_item const *env, + km_core_state ** out) { assert(keyboard); assert(env); assert(out); if (!keyboard || !env || !out) @@ -33,7 +33,7 @@ km_kbp_status km_kbp_state_create(km_kbp_keyboard * keyboard, try { - *out = new km_kbp_state(static_cast(*keyboard), env); + *out = new km_core_state(static_cast(*keyboard), env); } catch (std::bad_alloc &) { @@ -43,35 +43,35 @@ km_kbp_status km_kbp_state_create(km_kbp_keyboard * keyboard, } -km_kbp_status km_kbp_state_clone(km_kbp_state const *state, - km_kbp_state ** out) +km_core_status km_core_state_clone(km_core_state const *state, + km_core_state ** out) { assert(state); assert(out); if (!state || !out) return KM_KBP_STATUS_INVALID_ARGUMENT; - *out = new km_kbp_state(*state); + *out = new km_core_state(*state); return KM_KBP_STATUS_OK; } -void km_kbp_state_dispose(km_kbp_state *state) +void km_core_state_dispose(km_core_state *state) { delete state; } -km_kbp_context *km_kbp_state_context(km_kbp_state *state) +km_core_context *km_core_state_context(km_core_state *state) { assert(state); if (!state) return nullptr; - return static_cast(&state->context()); + return static_cast(&state->context()); } -km_kbp_status kbp_state_get_intermediate_context( - km_kbp_state *state, - km_kbp_context_item ** context_items +km_core_status kbp_state_get_intermediate_context( + km_core_state *state, + km_core_context_item ** context_items ) { assert(state); assert(context_items); @@ -84,7 +84,7 @@ km_kbp_status kbp_state_get_intermediate_context( return KM_KBP_STATUS_OK; } -km_kbp_action_item const * km_kbp_state_action_items(km_kbp_state const *state, +km_core_action_item const * km_core_state_action_items(km_core_state const *state, size_t *num_items) { assert(state && state->actions().size() > 0); @@ -99,9 +99,9 @@ km_kbp_action_item const * km_kbp_state_action_items(km_kbp_state const *state, return state->actions().data(); } -km_kbp_status km_kbp_state_queue_action_items( - km_kbp_state *state, - km_kbp_action_item const *action_items +km_core_status km_core_state_queue_action_items( + km_core_state *state, + km_core_action_item const *action_items ) { assert(state); assert(action_items); @@ -146,7 +146,7 @@ namespace { } -json & operator << (json & j, km_kbp_action_item const &act) +json & operator << (json & j, km_core_action_item const &act) { j << json::flat << json::object; if (act.type >= KM_KBP_IT_MAX_TYPE_ID) @@ -165,7 +165,7 @@ json & operator << (json & j, km_kbp_action_item const &act) break; case KM_KBP_IT_CHAR: case KM_KBP_IT_MARKER: - j << km_kbp_context_item {act.type, {0,}, {act.character}}; // TODO: is act.type correct here? it may map okay but this is bad practice to mix constants across types. Similarly using act.character instead of act.type + j << km_core_context_item {act.type, {0,}, {act.character}}; // TODO: is act.type correct here? it may map okay but this is bad practice to mix constants across types. Similarly using act.character instead of act.type break; case KM_KBP_IT_PERSIST_OPT: j << json::object @@ -197,7 +197,7 @@ json & operator << (json & j, actions const & acts) } -km_kbp_status km_kbp_state_to_json(km_kbp_state const *state, +km_core_status km_core_state_to_json(km_core_state const *state, char *buf, size_t *space) { @@ -239,9 +239,9 @@ km_kbp_status km_kbp_state_to_json(km_kbp_state const *state, } -void km_kbp_state_imx_register_callback( - km_kbp_state *state, - km_kbp_keyboard_imx_platform imx_callback, +void km_core_state_imx_register_callback( + km_core_state *state, + km_core_keyboard_imx_platform imx_callback, void *callback_object ) { assert(state); @@ -251,7 +251,7 @@ void km_kbp_state_imx_register_callback( state->imx_register_callback(imx_callback, callback_object); } -void km_kbp_state_imx_deregister_callback(km_kbp_state *state) +void km_core_state_imx_deregister_callback(km_core_state *state) { assert(state); if (!state) { diff --git a/core/src/kmx/kmx_debugger.cpp b/core/src/kmx/kmx_debugger.cpp index c217086acdb..a5126f13ab3 100644 --- a/core/src/kmx/kmx_debugger.cpp +++ b/core/src/kmx/kmx_debugger.cpp @@ -21,7 +21,7 @@ void KMX_DebugItems::push_item( PKMX_WORD index_stack ) { _items->assert_push_entry(); - km_kbp_state_debug_item item = {type, flags, {}, {}}; + km_core_state_debug_item item = {type, flags, {}, {}}; item.kmx_info.rule = key; if(item.kmx_info.rule && index_stack) { this->fill_store_offsets(&item.kmx_info, index_stack); @@ -43,7 +43,7 @@ void KMX_DebugItems::push_set_option( KMX_WCHAR const * value ) { _items->assert_push_entry(); - km_kbp_state_debug_item item = {KM_KBP_DEBUG_SET_OPTION, 0, {}, {}}; + km_core_state_debug_item item = {KM_KBP_DEBUG_SET_OPTION, 0, {}, {}}; item.kmx_info.first_action = first_action; item.kmx_info.rule = nullptr; item.kmx_info.group = nullptr; @@ -57,11 +57,11 @@ void KMX_DebugItems::push_set_option( _items->emplace_back(item); } -void KMX_DebugItems::fill_store_offsets(km_kbp_state_debug_kmx_info *info, PKMX_WORD index_stack) { +void KMX_DebugItems::fill_store_offsets(km_core_state_debug_kmx_info *info, PKMX_WORD index_stack) { int i, n; - km_kbp_cp *p; + km_core_cp *p; // TODO turn this into a struct rather than interwoven values for(i = n = 0, p = static_cast(info->rule)->dpContext; p && *p; p = incxstr(p), i++) { diff --git a/core/src/kmx/kmx_debugger.h b/core/src/kmx/kmx_debugger.h index 20006e291c7..915e4837a58 100644 --- a/core/src/kmx/kmx_debugger.h +++ b/core/src/kmx/kmx_debugger.h @@ -31,12 +31,12 @@ class KMX_DebugItems PKMX_WORD index_stack = nullptr ); void fill_store_offsets( - km_kbp_state_debug_kmx_info *info, + km_core_state_debug_kmx_info *info, PKMX_WORD index_stack ); public: KMX_DebugItems(debug_items *items); - void push_begin(km_kbp_state_debug_key_info *key_info, uint32_t flags); + void push_begin(km_core_state_debug_key_info *key_info, uint32_t flags); void push_end(uint16_t first_action, uint32_t flags); void push_group_enter(uint16_t first_action, LPGROUP group); void push_group_exit(uint16_t first_action, uint32_t flags, LPGROUP group); @@ -73,7 +73,7 @@ KMX_DebugItems::KMX_DebugItems(debug_items *items) { inline void KMX_DebugItems::push_begin( - km_kbp_state_debug_key_info *key_info, + km_core_state_debug_key_info *key_info, uint32_t flags ) { // As the vector always starts with a single KM_KBP_DEBUG_END entry, diff --git a/core/src/kmx/kmx_environment.cpp b/core/src/kmx/kmx_environment.cpp index eb5691c29d4..b3069b0958c 100644 --- a/core/src/kmx/kmx_environment.cpp +++ b/core/src/kmx/kmx_environment.cpp @@ -11,7 +11,7 @@ using namespace km::kbp; using namespace kmx; namespace { - km_kbp_cp const + km_core_cp const *DEFAULT_PLATFORM = u"windows hardware desktop native", *DEFAULT_BASELAYOUT = u"kbdus.dll", *DEFAULT_BASELAYOUTALT = u"en-US", diff --git a/core/src/kmx/kmx_environment.h b/core/src/kmx/kmx_environment.h index 4d934e0d104..d15b3c587bc 100644 --- a/core/src/kmx/kmx_environment.h +++ b/core/src/kmx/kmx_environment.h @@ -15,8 +15,8 @@ class KMX_Environment { std::u16string _platform; void InitOption( std::vector